1 前言
如果能够阅读我的上一篇从 JS 执行机制理解变量提升等特性,相信能够有更多的思考~
闭包的用处还是很多的,俺就是在学习 React
的时候了解到 hook
也用到了 JavaScript 的闭包机制,所以前来总结学习。
对于闭包,了解一直不深刻,就记得是:在函数 A 里面返回一个函数 B,这样然后在别处调用,这样就能通过函数 B,访问到函数 A里面的变量,即使函数 A 已经执行完毕销毁了,函数 B 仍然能够访问那些变量。
var local = '小林别闹';
function foo() {
var local = 1;
function bar() {
local++;
return local;
}
return bar;
}
var f1 = foo();
console.log(f1());//2
console.log(f1());//3
console.log(f1());//4
var f2 = foo();//新定义的函数,和上面那个是隔开的的,所以下面输出的是2,而不是4
console.log(f2());//2
console.log('local', local);//小林别闹 没有闭包作为接口,只能访问到全局变量local
复制代码
执行结果如图:
1.1 目的
这篇文章主要解决两个问题:
- 加深对于闭包的理解
- 理解闭包与垃圾回收机制的关系
正常来说,当 foo
函数执行完毕之后,其作用域是会被销毁的,然后垃圾回收器会释放那段内存空间。而闭包却很神奇的将 foo
的作用域存活了下来, fn1
依然持有该作用域的引用,这个引用就是闭包。
2 垃圾清除机制
仔细观察文章开头的代码:你难道没有疑惑嘛?俺有。为什么 f1
和 f2
取得的变量是独立,互不干扰的呢,为什么 f1
重复执行,返回的是 2,3,4….,而不是2呢?函数执行完毕不是会销毁的嘛?
首先解决第一个问题: f1
和 f2
是 foo
函数分别执行的结果,函数有函数作用域,就相当于local
变量在两个作用域分别声明,当然互相独立,互不干扰了。
第二个问题:其实这和我们的垃圾回收机制的引用计数相关了,如果一个变量的引用不为0,那么他不会被垃圾回收机制回收,引用,就是被调用。这里 f1
引用了 foo
函数,f1
执行的时候自然就用的是同一个 local
.
3 看一个经典的闭包
for (var i = 1; i <= 10; i++) {
console.log(i)//打印1-10
setTimeout(function () {
console.log(i);//打印10个11
}, 1000);
}
for (var i = 1; i <= 10; i++) {
(function () {
var j = i;
setTimeout(function () {
console.log(j);
}, 1000);
})();
}//打印1-10
for (let i = 1; i <= 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}//打印1-10
复制代码
3.1 循环1
第一个for
循环里面,上边能按照我们的想法直接打印出1-10,但是在定时器里面,就是打印的10个11
原因:setTimeout
是异步的,根据浏览器的事件循环机制,会等到同步操作全部执行完毕之后,才会执行。但是这个时候 i
已经自增到11了,至于为啥是11不是10,可以去了解一下前置++和后置++的区别
3.2 循环2
第二个 for
循环,我们借助了立即执行函数,立即执行函数是同步的,同时函数也拥有作用域,声明一个变量 j
来保存每次迭代的 i
,是不是有点 this
和 _this
那味。一个小细节, j
需要用 let
或者 var
声明,不然就是全局变量。
3.3 循环3
第三个 for
循环,我们借助 let
和块级作用域。用 var
就相当于在for循环外面定义了一个var i=1
,10次输出共用一个 i
,用 let
的话,i
只在自己的作用域内有效,一次++循环过去了,就换了个作用域,自己就不起作用了,当然i++
就会有一个新的i对应这个作用域。i
是 let
声明的,当前的 i
只对本轮循环有效,每一次循环的 i
都是一个新的变量,JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i
时,就是在上一轮的基础上进行的
4 闭包的优劣
优势:就是闭包的特性,能够在全局访问到局部的作用域,这样可以设置私有的变量和方法
劣势:闭包会使得函数中的变量都被保存在内存中,内存消耗很大。
因为知识储备原因,不能像很多大佬内样写的透彻,欢迎大佬指点!