You don't konw JavaScript => 闭包
amandakelake opened this issue · comments
function foo() {
var a = 2;
function bar() {
console.log(a); // 2
}
bar();
}
foo();
这段代码,严格意义上来说并不属于闭包
虽然bar劫持了foo作用域中的a变量,但是它在foo执行时也同时执行了,并没有把foo的作用域告诉foo之外的兄弟们。
当foo执行完毕后,JS的自动垃圾回收机制,会把a变量回收,因为已经没有其他的函数或者什么地方保持对a的引用了,说白了,就是没有把bar所引用的函数对象当做返回值返回
其他地方的引用
JavaScript拥有自动的垃圾回收机制,关于垃圾回收机制,有一个重要的行为,那就是,
当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存
。
再来看一段代码
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果。
看到了吗,foo()执行后,返回值(内部的bar()函数)赋值给了变量baz,这个时候,变量baz就保持了对foo内部的a变量的引用,按照上面说的垃圾回收机制,foo的作用域就没办法被销毁了,因为a卡在内存中,也就说闭包的存在,阻止了foo的内部作用域被回收这一过程
其他地方的引用
函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放,闭包的存在,会阻止这一过程。
到这里,我自己的理解就是:当一个函数所定义的内部作用域,可以在外部被访问到,就产生了闭包
再看书里面的定义,这些话就好理解多了
bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。
无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用 域的引用,无论在何处执行这个函数都会使用闭包
再来看一段代码
var fn;
function foo() {
var a = 2;
function baz() {
console.log(a);
}
fn = baz; // 将 baz 分配给全局变量
}
function bar() {
fn();// 妈妈快看呀,这就是闭包!
}
foo();
bar(); //2
上面把内部函数baz传递了出来,全局变量fn保持了对baz的引用,当执行bar()的时候,间接调用了baz,也就是调用了foo中的a变量,闭包就形成了
举一反三来想,我们平时使用回调函数的时候,不正是将内部函数传递到所在的词法作用域以外么?
说明了什么?
说明调用回调函数的过程,就是使用了闭包呀,开心
什么定时器、事件监听、网络请求、异步操作、跨窗口通信、web worker、service worker等等,不都是在使用闭包么
现在回到一道经典的循环题
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
稍微有点基础的都知道,会输出五个6
因为循环结束时,i = 6
这里循环时的每个i都共享同一个全局作用域,同时因为setTimeout是延迟执行的,所以输出全是最后的那个i
那么每次的时长间隔又是多少呢?
有同学可能会以为
先是1s后输出6,然后间隔2s后再输出一个6,然后3s、4s、5s
我告诉你,这样是错的
每次的i是不是不一样
对,是不一样
但是,这个时延,其实是相对与开始执行这个for循环时开始,并不是相对于上一个循环开始,这里要好好区分一下
也就是说从开始执行for循环时,1s后输出6,2s后输出6,……
所以每次输出的间隔都是1s
这样说,应该明白了吧
然后,下一个问题
怎么依次输出1,2,3,4,5呢?
先加个IIFE试一下
for (var i = 1; i <= 5; i++) {
(function() {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})();
}
答案还是5个6,为什么呢?
如果作用域是空的,那么仅仅将它们进行封闭是不够的。仔细看一下,我们的 IIFE 只是一 个什么都没有的空作用域。它需要包含一点实质内容才能为我们所用。
它需要有自己的变量,用来在每个迭代中储存 i 的值:
那就把i传进去吧
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
这次终于对了
大兄弟,闭包在哪呢?前面不是说IIFE跟闭包不太像么
IIFE是在函数本身所定义时的作用域内(),并不是在作用域之外被执行的
尽管 IIFE 本身并不是观察闭包的恰当例子,但它的确创建了闭包,并且也是最常用来创建 可以被封闭起来的闭包的工具。因此 IIFE 的确同闭包息息相关,即使本身并不会真的使用 闭包。
那就用个闭包
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);
}
闭包写到这里,基本概念就已经写完了
书里还有关于模块的高级用法,就留给大伙(包括我自己)去慢慢研读吧。