MST

星途 面试题库

面试题:JavaScript 中闭包与并发实现的基础理解

请简要阐述 JavaScript 闭包的概念,并说明在并发场景下,闭包可能会带来哪些问题,如何避免这些问题?
43.9万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

1. JavaScript闭包概念

闭包是指函数能够记住并访问其词法作用域,即使函数在其原始作用域之外执行。简单来说,当一个内部函数在其外部函数返回后仍然可以访问外部函数的变量时,就形成了闭包。例如:

function outer() {
    let outerVar = 10;
    function inner() {
        console.log(outerVar);
    }
    return inner;
}
let closureFunc = outer();
closureFunc(); // 输出 10

在上述代码中,inner函数形成了闭包,它可以访问outer函数中的outerVar变量,即使outer函数已经执行完毕。

2. 并发场景下闭包可能带来的问题

  • 内存泄漏:由于闭包会持有外部函数作用域的引用,如果闭包长时间存在,外部函数作用域中的变量不会被垃圾回收机制回收,导致内存占用不断增加,最终可能引发内存泄漏。
  • 数据竞争:在并发环境下(如使用setTimeoutPromise等异步操作),多个闭包可能会同时访问和修改共享的外部变量,从而导致数据竞争问题,使得程序出现不可预测的行为。例如:
function createClosures() {
    let arr = [];
    for (let i = 0; i < 3; i++) {
        arr.push(() => console.log(i));
    }
    return arr;
}
let closures = createClosures();
closures.forEach(closure => closure()); 
// 预期输出 0 1 2,实际可能输出 3 3 3(在旧版JavaScript引擎,因为var声明变量作用域问题导致闭包共享同一变量)

3. 避免问题的方法

  • 避免不必要的闭包:如果不需要在函数外部访问内部函数,或者不需要内部函数访问外部函数的变量,就不要使用闭包,这样可以减少内存占用和潜在的问题。
  • 及时释放引用:当闭包不再需要时,手动将对闭包的引用设置为null,以便垃圾回收机制能够回收相关的内存。例如:
let closureFunc = outer();
closureFunc(); 
closureFunc = null; // 释放对闭包的引用
  • 使用块级作用域:在ES6中,使用letconst声明变量具有块级作用域,可以避免闭包共享同一变量导致的数据竞争问题。如上述createClosures函数中使用let声明i,就可以按预期输出0 1 2
  • 使用不可变数据结构:在并发场景下,尽量使用不可变数据结构,避免直接修改共享变量,这样可以有效避免数据竞争问题。例如使用Object.freeze冻结对象,使其不可变。