闭包浅谈
该文章阅读需要8分钟,更多文章请点击本人博客halu886
当函数可以记住并访问所在的词法作用域,即使是在当前作用域外执行,这时就产生了闭包. —–<<你不知道的JavaScript(上卷)>>
闭包是基于词法作用域书写代码时所自然产生的结果.
本质
function foo(){
var a = 'test';
function bar(){
console.log(a);
}
bar()//test;
}
由词法作用域概念我们可以将这段代码理解为foo()中存在一个作用域,bar()中存在一个词法作用域
foo作用域包含bar作用域,bar作用域被foo作用域包含.
且根据词法作用域可知对于变量的检索可以由当前作用域向上层作用域进行遍历直至顶层作用域.
function foo(){
var a = 'test';
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz();//test
bar函数所含有的作用域引用了foo函数的作用域,且当执行foo()时,bar函数作为一个返回值返回,baz变量引用着词法作用域,当bar在全局作用域下执行时,bar指向的作用域既能访问foo指向的作用域,还能指向全局作用域.
按照JavaScript引擎执行逻辑作用域在执行完后且没有被引用后就会被垃圾回收机制回收,但是当foo()方法执行完,baz依旧引用bar方法的作用域,那么foo()执行完后依旧存在引用,foo作用域将不会被回收.
var fn;
function foo(){
var a = 2;
function bar(){
console.log(a);
}
fn = bar;
}
function baz(){
fn();
}
foo()
baz()//2
只要作用域内部的子作用域被作用域外引用并执行就构成了闭包
“回调===闭包”
在jquery,定时器,触发事件等等中大量的第三方API存在回调函数,比方说:
function fn(message){
setTimeout(function time(){ //回调===闭包
console.log(message);
},1000)
}
fu('test')//test
在大部分(几乎所有)的第三方API中的回调都属于闭包(在作用外执行方法)
循环和闭包
在关于大部分闭包的思考中,都会提到循环和传入定时器中闭包所产生的问题
for(var i=0;i<5;++i){
setTimeout(function(){
console.log(i);
},1000);
}
//5
//5
//5
//5
//5
首先我们得先理解定时器中回调函数执行的时刻是在for循环结束后,那么向事件循环中设置的五个匿名函数是for循环遍历完后才执行的。
根据之前我们梳理的概念,匿名函数所指向的作用域能够访问上一级的作用域,此时上一级的作用域的i变量指向常量5.
那么问题来了?
如何达到我们想要的效果,每次输出植入定时器是循环索引?
方法1:
//作用域a
for(var i=0;i<5;++i){
(function(){
//作用域b
var j = i;
setTimeout(function(){
//作用域c
console.log(j);
},1000);
})();
}
//0
//1
//2
//3
//4
这里我们用的思路是嵌套一层作用域进行保存索引i。遍历五次后,多生成五个作用域b,里面分别保存各次遍历时的索引i(定义一个新变量j指向索引常量).
当回调执行时先在作用域c寻找变量j,没有得到时,再去上一层作用域b,找到了后停止向上遍历,输出j。
方法2:
//作用域a
for(let i = 0;i<5;++i){//es6语法let关键字
//作用域b
setTimeout(function(){
//作用域c
console.log(j);
},1000);
}
//0
//1
//2
//3
//4
这里运用了es6中的let关键字,生成块级作用域本质上和方法一同理,这里不再赘述.
以上就是对闭包的总结,大部分js中模式都风骚的运用了闭包的特性。更多知识建议阅读《你不知道的JavaScript》
坚持原创技术分享,您的支持是我前进的动力!