You Don’t Know JS — scope&closure ch5

闭包

closure is all around you in JavaScript, you just have to recognize and embrace it

闭包是依赖词法作用域编写代码而产生的结果。

当函数在其被编写时的词法作用域之外被调用,这个函数仍然拥有对其被定义时的词法作用域的引用,这个引用称为闭包。

闭包就是当一个函数即使是在它词法作用域之外被调用时,也可以记住并访问它的词法作用域。

for (var i=1; i<5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000);
}

我们期待这段代码逐次打印1, 2, 3, 4, 5一次一个,一秒一个

但是实际上打印出6,6,6,6,6一次一个,一秒一个

time out的callback都在循环结束之后执行,此时i已经为6,所以每次都打印6.

我们需要在每个循环上都持有一个i的拷贝,每次循环都有一个持有i拷贝的闭包。如下

for (var i=1; i<5; i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);
}, j*1000);
})();
}

或者稍微变形一下

for (var i=1; i<=5; i++) {
(function(j){
setTimeout(function timer(){
console.log(j);
}, j*1000);
})(i);
}

块作用域与闭包

使用let的块级作用域可以方便的实现给每个循环一个封闭的i

for (let i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000);
}

Modules

模块也是利用了闭包的力量。模块模式有两个必要条件:

  1. 必须有一个外部的外围函数,而且它必须至少被调用过一次(每次创建一个新的模块实例)。
  2. 外围的函数必须至少返回一个内部函数,这样,这个内部函数才拥有对私有作用域的闭包,并且可以访问和修改私有状态

ES6为模块添加了语法支持,通过模块系统加载时,ES6将一个文件视为一个独立的模块。每个模块可以导入其他的模块或者特定的API成员,也可以导出他们自己的共有API成员。

基于函数的模块并不是一个可以被静态识别的模式。他们的API语义直到运行的时候才被考虑。

相比之下ES6的模块API是静态的,编译器知道一切,也可以在编译期间检查一个指向被导入模块的成员的引用是否实际存在。

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.