写在正文前:
写这篇文的时候,我也在思考,如何才能讲清楚闭包(closure)的概念,所以这两天我也一直没有写文,就怕写的不好,给初学者带来不好的理解;
如果这篇文有地方存在不足的,希望大家看的地方就指正一下,这样也是共同交流,共同进步;
进入正文 :
官方给的解释:
一个拥有许多变量和绑定了这些变量的环境的表达式
当初看到上面的解释的时候,我确实没有看懂上面意思,这太书面化了;
但从上面给出的定义,我们可以获取到两点信息:
- 变量
- 环境(其实就是作用域)
变量:从作用域来看:分为 全局变量 与 局部变量;
作用域:能够形成作用的, 一是整个js,全局作用域,一是花括弧{},当然像if for while这些条件语句形成的环境是不能造成作用域的;
通俗来说
- 从变量看:函数能够访问到函数外部的局部变量可以称为闭包;
从内存看:如果函数内部变量执行完之后不被GC回收即可称为闭包;
这里要额外说一下闭包的出现:为了避免出现定义全局变量,这样做有两个原因,一是因为全局变量不会被GC回收,一是防止全局变量造成变量污染;
为了能使函数能够访问到外部的局部变量;
其实闭包的出现确实有些矛盾之处,因为他出现的部分原因是为了解决全局变量不能被GC回收这样一个问题,但是最后发现很大一部分的闭包函数内部的变量也不能被GC回收;
所以我们使用闭包的时候一定要小心,不要过度的使用闭包,不然会使得IE的某些浏览器出现内存泄漏,造成过多的内存开销,最后造成浏览器崩溃的现象,如果必须使大量使用闭包,请手动删除不能被回收的变量;
接下来结合例子说明什么是闭包,看下面例子;
function parentFun(){
var a = 10;
function childFun(){
var b = 20;
var c = a + b;
console.log(c);
}
childFun();
}
parentFun() // 30;
上面的例子是闭包吗?
很多人可能会说不是,因为从内存角度来看,变量a,在函数childFun执行完之后,就被销毁回收了,这能使函数parentFun形成闭包吗?
我的回答是能形成闭包,从变量作用域的变化来看,a是属于函数childFun外部函数定义的局部变量, 但是childFun却能访问并且使用到他,这就能使函数parentFun成为闭包;
当然观察角度不一样得到的结果就不一样,所以上面的例子要看你怎么去理解了,接下来看一个列子:
function parentFun(){
var a = 10;
var b = 20;
return function (){
b++;
var c = a + b;
console.log(c);
}
}
var oParent = parentFun();
oParent(); // 31;
上面的例子是闭包,相信所有人都会说是,因为这是最简单的典型闭包;
稍稍解释一下上面的例子,我们知道直接写函数名的时候只是重复写了一下函数而已,像上面的例子如果这样处理下:
console.log(parentFun) // ???
//其实肯定是打印出函数本身;
function parentFun(){
var a = 10;
var b = 20;
return function (){
b++;
var c = a + b;
console.log(c);
}
}
函数的调用在于后面的括号(),这才是对函数的调用,例如:parentFun();
如果写成下面这样相信更容易理解一点:
(function(){
var a = 10;
var b = 20;
return function (){
b++;
var c = a + b;
console.log(c);
}
})()
这是一个立即执行匿名函数,很明显函数结尾最后有个括弧,这就是对函数的调用了;
再来看第二个例子,函数内部返回如果是一个函数的话,那么返回的就是函数的本身,所以上面的例子调用的时候
var oParent = parentFun();
//相当于下面这样;
var oParent = function(){
b++;
var c = a + b;
console.log(c);
}
oParent(); // 31;
接下来改成这样;
var oParent = parentFun();
oParent(); // 31;
oParent(); // 32;
oParent(); // 33;
我们发现每调用一次,c的值就就大了1,这是因为b并没有被回收,他被保存在了内存之中,这就符合了局部变量不能被GC回收这一概念,而且a与b都是函数childFun外部变量,两者条件都满足当然可以称为闭包;
看下面另一个例子;
function init(){
this.a = 10;
this.b = 20;
}
init.prototype.sub = function(){
return this.c = this.a + this.b;
}
var oInit = new init();
console.log(oInit.sub()); // 30;
这种写法是闭包吗?当然是,函数外部的局部变量被调用了;
function init(){
this.a = 10;
this.b = 20;
}
function sub(){
init.call(this);
return this.c = this.a + this.b;
}
var aSub = new sub();
console.log(aSub.c);
利用call方法继承了函数init中的变量,满足了函数调用了外部的局部变量;所以自然是闭包;
最后看一个例子:
var obj = {
firstName : 'li',
rFun : function(lastName){
var that = this;
return {
lastname : lastName,
setName : function(){
return that.name = that.firstName + this.lastname;
}
}
}
}
var name = obj.rFun('shimin').setName()
console.log(name) // lishimin;
对于了setName对应的函数来看,其中使用到了局部变量firstName和lastname;所以算的上是闭包;
上面说了那么多闭包,那闭包到底用来干什么的呢?
其实上面的例子很明显:
- 因为闭包内部调用存在变量不能被GC回收,所以可以用来作为缓存;
- 可以用来做封装(最后一个例子)
- 可以用来做继承(例三与例四)
-
可能不了解闭包,开发起来好像没有什么影响,业务上来说,好像都没有阻碍我们开发。
是这样的,但是我们开发中的确用到了大量的闭包,但是如果想要写高级的函数,如果不了解闭包,其实很难很好的去写成的,所以了解这一概念还是很有必要的。
好了,关于闭包就写到这里了,感谢大家的阅读,如有不正确的地方,烦请指正,感谢;
微信搜索关注公众号 【大前端js】,回复vue教程,react教程,webpack实战等等,获得不同的视频教程,大量视频教程等你来拿;
原创不易,总结不易,手打不易,转载时请注明出处,谢谢