1、引言
一直以来,我都在深思JavaScript闭包究竟是什么,也看过不少博文和书籍,但是始终不得其理,决定这次自己去理解闭包,追寻本质,我尽量用简练,推断式的论点循序渐进,意在理解本质。
2、本质分析
我们先不谈概念,因为十分生涩,先抛出几个论点:
1)闭包离不开作用域
我们深入想一想,JavaScript的作用域有几种?
《1》全局作用域(至始至终都会存在)
《2》块级作用域(es6语法,超出本节内容,有兴趣可以自行了解let关键字)
《3》函数作用域
2)闭包离不开函数作用域
JavaScript的作用域是静态的,也就是说,函数调用时 ,变量赋值会根据创建函数的作用域链去查找,思考一下下面两种特殊的函数使用情况:
<1>返回值为函数时
var a=1
var fn=function(){
var a=2;
return function func (){
console.log(a)
}}
var f1=fn()
f1() // 2
2>以函数当参数
var a=1;
function func(){
console.log(a)
}
(function(f){
var a =2;
f()
})(func) //1
通过上面代码,得出两个论点:
《1》自由变量的概念
存在于函数体,但又不是函数(func)的执行上下文编译期的属性(参数,argument,变量、函数表达式声明[默认值为undefined],函数声明(赋值),this(赋值))的变量(a)。
《2》自由变量的取值
自由变量(a)的取值取决于函数体(func)创建时所在的作用域决定的
4)形成闭包的条件
《1》自由变量
《2》存在自由变量的函数
《3》储存闭包的变量(这个可能理解不准确,但是目前为止,这些情况都适用,学习代码,就要辩证地看待问题)
5)怎么证明一直存在呢?请看代码
<1>返回函数
var func=function(){
var a=1;return function(){
console.log(a++)
}
}var f1=func()
f1() //1
f1() //2
<2>参数为函数
var a=1;
functionfunc(){
console.log(a++)
}
(function(f){f()})(func); //1
(function(f){f()})(func); //2
5)抽象定义
函数体,自由变量,自由变量所在的作用域链,存储闭包的变量 ,还有JavaScript静态作用域,作用域链,执行上下文栈,函数作用域这些概念形成了闭包。
6)通俗定义
通过函数调用,函数作用域内的自由变量变成不可销毁的变量,始终存在于内存中,直到浏览器垃圾回收机制或者手动将存储闭包的变量设为null,还能清除。
7)闭包的共享性与独立性
<1>共享性
var func=function( ){
var a=1;
return{
add:function(){console.log(a++)},
reduce:function(){console.log(a--)}
}
}var f1=func();
f1.add() //1
f1.add() //2
f1.reduce() //3
f1.reduce() //2
通过上面的代码,可以得出一个重要论点:
《1》同一个闭包引用的自由变量是共享的
特别提示:f1是闭包所在的环境
<2>独立性
var func = function(type){
var a=1;
function add(){console.log(a++)}
function reduce(){console.log(a--)}
if(type==1){
return add
}else{
return reduce
}
}
var f1=func(1)
var f2=func(0)
f1() // 1
f1() //2
f1() //3f2() //1
通过上述代码:能够得出一个论点:
《1》 f1 f2不同变量 导致不同闭包(请深刻理解,我认为这是闭包的精髓也是难点)
《2》for循环里,事件绑定里的闭包其实都是大同小异,理解闭包的共享性和独立性,一切有关闭包的问题将不复存在,如果你还不懂闭包,只能证明是其他知识点有盲区。
8)思考一下闭包有什么好处?
<1>相比全局变量,代码更加优雅,
<2>私有性较好
9)思考一下闭包有什么缺点?
如果你认真看了我的文章,并且真的懂了,应该很容易得出论点:
《1》额外增加了存储闭包的变量
《2》因为内存不能及时释放,造成内存泄露
对储存闭包的变量及时赋值为null,以便及时释放内存
10)闭包的难点
闭包最让人难以捉摸的地方就是,判断哪个变量是储存闭包状态,请看下面一段代码:
<1>很多博文或者书籍会介绍一个onclick for循环 加闭包的经典知识,下面是原理类似的代码:
var arr = [{},{},{},{}]
for(var i =0;i<arr.length;i++){
arr[i].onClickMock=function(){
console.log(i)
}
}
arr.forEach(function(item){
item.onClickMock() //4、4、4、4 调用闭包 取值i
})
<2>i是储存闭包的变量,也是自由变量,所以arr[0]到arr[3]是共享闭包的i
<3>解决办法
for(let i =0;i<arr.length;i++){ // es6语法 let 块级作用域每次循环创建一个{}作用域
//.... 其他部分都一样
}
for(var i=0;i<arr.length;i++){
(function(k){ //通过函数创建一个私有作用域
arr[k].onClickMock=function(){
console.log(k) // 函数参数k储存自由变量的地方
} // arr[0] arr[1] arr[2] arr[3]都是不同的闭包
})(i)
}
// 下面是不通过闭包解决问题
for(var i=0;i<arr.length;i++){
var arr[i].i=i;
arr[i].onClickMock=function(){
console.log(i)
}
}