Skip to content

作用域和闭包是什么? ​

JavaScript 用词法作用域:函数能访问哪些变量,由它写在哪决定,不由它在哪被调用决定。闭包就是函数继续引用定义时的外层变量;只要这个函数还活着,那些变量就不会被回收。

原理 ​

查找变量时从当前作用域向外,直到全局。作用域链在函数创建时就确定了。

var 是函数作用域,声明会提升,提升后的初值是 undefined。let 和 const 是块级作用域。从进入块到声明语句之间是暂时性死区,提前访问会抛 ReferenceError。const 不能重新绑定,对象里的属性仍然可以改。

js
function outer() {
  let count = 0
  return function () {
    count += 1
    return count
  }
}

const inc = outer()
inc() // 1
inc() // 2

outer 的调用已经结束,但 inc 还握着 count 所在的词法环境,所以 count 还在。

循环里的差别:

js
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}
// 3 3 3    var 只有一个 i,回调执行时循环已结束

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}
// 0 1 2    每次迭代有自己的 i

追问 ​

闭包会造成内存泄漏吗?

闭包本身只是延长了变量的寿命。泄漏是指这块数据已经不需要了,引用却还在,例如全局缓存、没移除的事件监听、没清掉的定时器。内部函数不再被引用之后,外层变量可以被回收。

为什么 let 能在循环里各自记住 i?

for (let i ...) 会为每次迭代创建新的词法环境,并把上一轮的值拷进去。var 没有块级环境,回调都读同一个绑定。

易错点 ​

  • 「函数套函数」不等于闭包。闭包的关键是内部函数引用了外层变量,并且比外层调用活得更久。
  • const obj = {} 之后仍然可以改 obj.a。冻结要用 Object.freeze。
  • var 提升的是声明,赋值留在原来的位置。

相关 ​