你不知道的JS上-作用域闭包
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 —— 这就是闭包的效果。
函数 bar( ) 的词法作用域能够访问 foo( ) 的内部作用域, 这个例子中, 它在自己定义的词法作用域以外的地方被执行。闭包使得函数可以继续访问定义时的词法作用域。
简单理解:不在定义函数的作用域下调用的,并且使用了外部作用域的变量就产生了闭包。
垃圾回收相关解释:
在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用,所以很自然地会考虑对其进行回收。
而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是 bar() 本身在使用。
拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。
经典题目
for (var i=1; i<=5; i++) {
setTimeout(function timer() {
console.log( i );
}, i*1000 );
} //会以每秒一次的频率输出五次 6
输出6是Event Loop的原因,而行为同语义所暗示的不一致是因为:
我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个 i 的副本。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的, 但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。
因此在循环的过程中每个迭代都需要一个闭包作用域。(仅仅封闭是不够的,需要将需要的变量存进去)
解决方案:
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout(function timer() {
console.log( j );
}, j*1000 );
})( i );
}
for (let i=1; i<=5; i++) {
setTimeout(function timer() {
console.log( i );
}, i*1000 );
}
使用ES6块级作用域-let 声明,可以用来劫持块作用域,并且在这个块作用域中声明一个变量,本质上这是将一个块转换成一个可以被关闭的作用域。
for 循环头部的 let 声明还会有一个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
模块
模块模式需要具备两个必要条件:
- 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。