外观
作用域和闭包是什么?
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() // 2outer 的调用已经结束,但 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提升的是声明,赋值留在原来的位置。