一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 7 天,点击查看活动详情。
作用域闭包
什么是闭包?
当函数可以记住并访问所在的词法作用域时,就产生了闭包
- 函数;
- 记住并访问;
- 词法作用域
function foo() {
var a = 2;
function bar() {
console.log(a);
}
bar();
}
foo();
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar();
}
var baz = foo();
baz(); // 2;
- 函数bar()的词法作用域能够访问foo()函数的词法作用域,这个是没问题的。
- 然后我们将bar()函数本身当作一个值类型进行传递,比如这个例子中的函数对象本身
- bar()可以被正常执行,但是在这个例子中,
它在自己定义的词法作用域以外的地方执行 - 在foo()执行完后,通常是要被销毁的,因为js引擎里面有垃圾回收机制用来释放不再使用的内存空间
- 但是闭包,就是可以阻止foo()的作用域被销毁,事实上foo()的内部作用域依然存在,没有被回收
- 因为bar()函数还在使用这个内部作用域
- 因此foo()的作用域会一直存活,以供bar()在之后的任何时间使用。
bar()依然保持对该作用域的引用,这个引用就叫作闭包
这个函数在定义的词法作用域以外的地方被调用,闭包使得函数可以继续访问定义时的词法作用域。
使用传递参数的方式产生的闭包举例:
function foo() {
var a = 2;
function baz() {
console.log(a);
}
bar(baz);
}
function bar(fn) {
fn();
}
把内部函数baz传递给bar,当调用这个内部函数的时候,它涵盖的foo()的作用域的闭包就可以看到了,因为它可以访问a
间接传递函数产生的闭包:
var fn;
function foo() {
var a = 2;
function baz() {
console.log(a);
}
fn = baz;
}
function bar(fn) {
fn();
}
foo();
bar(); // 2;
无论通过哪种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用的引用,无论在何处执行这个函数都会使用闭包。
再次理解闭包
常见的闭包:
function wait(message) {
setTimeout( function timer() {
console.log(message);
},1000);
}
wait('hello,jingda');
- 将一个内部函数timer传递给setTimeout,timer具有涵盖wait作用域的闭包,因此还保留对变量的引用。
- wait执行1000毫秒之后,它的内部作用域并不会消失,timer函数依然会保有wait作用域的闭包
- 在引擎的内部,内置函数setTimeout有一个对参数的音乐,这个参数加上为fn,引擎会调用这个函数,在这个例子就是内部的timer函数,而词法作用域在这个过程一直都是保持完整的。这个就是闭包。
本质上,如果将函数当作第一级的值类型到处传递,会看到闭包在这些函数中的应用。
在定时器,事件监听器,ajax请求,跨窗口通信,web workers,或者其他异步同步的任务中,只要使用了回调函数,实际上就是在使用闭包。
循环和闭包
for(var i = 1;i <= 5;i++) {
setTimeout( function timer() {
console.log(i);
},i * 1000);
}
结果是:每秒一次输出五次6;
- 首先是为什么输出6,这个循环的终止条件是i>5,条件成立的第一个值就是6
- 输出显示的是循环结束时的最终值。
延迟函数的回调会在循环介绍时才执行。- 循环的五个函数在各个迭代中是分别定义的,但是他们都封闭在了一个共享的全局作用域中,因此,只有一个i
- 如果将延迟函数的回调重新定义5次,完全不使用循环,和这段代码是一样的效果。
- 我们需要更多的作用域。
- 立即执行函数
下面这样直接加是不起作用的哦,还是一样的效果: 此时的立即执行函数作用域里面是空的,没有实质的变量。
for(var i = 1;i <= 5;i++) {
(function () {
setTimeout( function timer() {
console.log(i);
},i * 1000);
})();
}
立即执行函数,需要有自己的变量去存储i的值:
for(var i = 1;i <= 5;i++) {
(function (j) {
setTimeout( function timer() {
console.log(j);
},j * 1000);
})(i);
}
闭包和块作用域
for(var i = 1;i <= 5;i++) {
let j = i;
setTimeout( function timer() {
console.log(j);
},j * 1000);
}
for(let i = 1;i <= 5;i++) {
setTimeout( function timer() {
console.log(i);
},i * 1000);
}
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包;