javascript 高级系列之闭包(closure)

来源:互联网 发布:中国出口印度的数据 编辑:程序博客网 时间:2024/05/21 00:56

写在正文前:

写这篇文的时候,我也在思考,如何才能讲清楚闭包(closure)的概念,所以这两天我也一直没有写文,就怕写的不好,给初学者带来不好的理解;
如果这篇文有地方存在不足的,希望大家看的地方就指正一下,这样也是共同交流,共同进步;

进入正文 :

官方给的解释:

一个拥有许多变量和绑定了这些变量的环境的表达式

当初看到上面的解释的时候,我确实没有看懂上面意思,这太书面化了;

但从上面给出的定义,我们可以获取到两点信息:

  1. 变量
  2. 环境(其实就是作用域)

变量:从作用域来看:分为 全局变量局部变量
作用域:能够形成作用的, 一是整个js,全局作用域,一是花括弧{},当然像if for while这些条件语句形成的环境是不能造成作用域的;

通俗来说

  1. 从变量看:函数能够访问到函数外部的局部变量可以称为闭包;
  2. 从内存看:如果函数内部变量执行完之后不被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实战等等,获得不同的视频教程,大量视频教程等你来拿;


原创不易,总结不易,手打不易,转载时请注明出处,谢谢

原创粉丝点击