代码改变世界

js 闭包

2018-01-31 18:56  ZengGW  阅读(324)  评论(0编辑  收藏  举报

  前言:经常看到很多面试题里有闭包的问题,包括一些程序里边也看到过闭包的写法,一直不知道闭包到底是个什么东西?有什么样的作用?为什么要使用闭包?接下来我就把这几天在网上看到的各位大神们写的关于闭包的文章的一种总结吧(鉴于自己觉得看起来有点懂的文章,说实话看了好多文章,还是似懂非懂,汗~ v ~)

一、什么是闭包

  1>.维基百科的定义:在计算机科学中,闭包(英文:Closure),又称词法闭包或者函数闭包,是引用了自由变量的函数。

    重点关键词/语句:词法闭包、函数闭包、自由变量。

  2>.阮一峰老师的关于js闭包的概念:闭包就是能够读取其他函数内部的变量(其他函数的内部变量也就是咱们说的局部变量);由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

    重点关键词/语句:局部变量、函数内部的函数、函数内部与外部连接起来

  3>.ECMAScript 中闭包的定义:词法表示包括不被计算的变量的函数,换句话说,函数可以使用函数之外定义的变量

    重点关键词/语句:函数之外定义的变量(这里说的函数之外的变量包含了全局变量、函数对应的父级变量(可能是全局变量,也可能是局部变量))

  其他的我就不一一列举了,大致我看的就这么几种...

二、闭包的用途(作用)

  我相信这也是很多人最关心的问题,闭包这个东西到底有什么用?用它有什么好处?用在什么地方?接下来就来看看

  1>.阮一峰老师的关于闭包用途的描述:闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中

  2>.网上其他描述1:闭包的最主要使用场景就是设计私有的方法和变量(闭包在做框架封装的时候体现的更明显,有些方法和属性只是运算逻辑过程中的使用,因此就可使用闭包的方式,只提供方法和属性的获取)

  分析1: 

function A(num)
{
    var count = 0;
    for(i = 0; i < num; i++)
    {
        count += i;
    }

    console.log(count); // res - 3
}

A(3);
console.log(i); // res - 3

   上边的代码中,我们经常这么使用,那么这个时候会造成的一个问题就是这个i变量,一旦这个函数执行了,那么这个i变量就会变成一个全局的变量(重点:在javascript中,函数内部声明变量使用var声明的变量是局部变量,作用域只在该函数内,而不使用var声明的变量,在函数执行之后就会升级成为全局变量,注意了,升级为全局变量的前提是该函数执行了,该函数不执行的话,你输出该变量会提示未定义Undefined),这样的话就会造成对全局环境的一个污染,为什么会这样呢?原因在于javascript中没有块级作用域,不像其他的语言,i的作用域和生命周期只在for循环中,一旦循环结束,i也会被自动销毁掉;接下来我使用闭包的方式改写一下上边的代码:

function A(num) {
    //核心代码
    (function(){
        for(var i = 0; i<num; i++) {
            console.log(i); // 0 1 2
        }
    })();
    //核心代码结束
    console.log(i)//underfined
}

A(3);
console.log(i); //underfined

  改代码我使用了一个匿名函数把循环放到匿名函数内,这其实就是一个闭包了,那么可以看到i变量在匿名函数执行完毕后输出,会提示underfined,在A函数外边全局环境中输出也是提示underfined,在这里使用闭包的方式就避免了过多的全局变量和函数,造成全局环境的混乱(要是多人合作开发的话,那情况可想而知......)。

  3>.闭包可以模仿块级作用域:在比如c++、java这些语言中有块级作用域(i只在for循环内有效,一旦for循环结束i就会被自动销毁掉),而javascript中没有,所以闭包可以很好的实现出块级作用域的作用,可以保证全局环境的干净,不会出现变量冲突等一些问题;

 

  分析2(验证"闭包用途的描述:可以读取函数内部的变量、变量的值始终保持在内存中"):

    其实这个读取函数内部的变量上边的代码中已经验证了,函数内部的子函数可以读取父级函数的局部变量,父级的局部变量相对于子函数就是自由变量  

function B()
{
    var count = 0;
    return function () {
        console.log(++count);
    }
}

var m = B();
m(); // 1
m(); // 2
m(); // 3

  上边的代码我是直接返回出了一个匿名函数,那么打眼一看有没有发现什么呢?没错,那就是变量的值始终保持在了m这个对象中(自己语言的描述,不标准勿怪),执行一次加一,匿名函数引用了count变量,所以B执行后count不会被释放,利用这一点,我们可以把比较重要或者计算耗费很大的值存在count中,只需要第一次计算赋值后,就可以通过m函数引用count的值,不必重复计算,同时也不容易被修改(因为是局部变量,B函数外部是无法做修改的,做到了将变量持久的保存在内存中(本人自己语言描述,勿怪......))

  总结:闭包可以将变量长期的保存在内存中,做到持久化

  分析3(验证:闭包就是将函数内部和函数外部连接起来的一座桥梁,也就是说函数外部操作函数内部的变量-局部变量):

    首先我们都知道javascript中是没有私有变量这一说的,不像php代码可以使用private来声明私有变量,那么在javascript中,哪种变量才是具有私有性质的变量呢?那就是咱们常说的局部变量,why?其实大家仔细想一下就明白了,在函数内部声明的变量是局部变量,作用域只在该函数内部,在执行该函数的时候,函数内部的变量会初始化被创建并赋值,一旦函数执行完毕,那么该函数内部声明的变量也会被销毁,而在函数外部是不能访问到函数内部的变量的,所以这个函数内部的变量相对于整个全局环境来说就是一个私有变量,是该函数私有的变量(作用域),那么怎么能做到函数外部能操作"别人私有的东西"呢?这就是接下来我们要验证的闭包的另一个用途:

function Person()
{
    var defaultName = 'closure';
    i = 2; //i在Person函数执行后相当于全局变量
    this.getName = function () {
        return defaultName;
    };

    this.setName = function (value) {
        defaultName = value;
    };
}

var result = new Person(); // 这时result就是这个Person函数的实例对象
console.log(result.getName()); //closure
result.setName('zgw2014');
console.log(result.getName()); //zgw2014
console.log(result.defaultName); //undefined(局部变量在外部是不能直接访问的,通过函数返回函数本身对象访问也不行)
console.log(i); //2

  上边的代码就实现了闭包成为了将函数内部和函数外部连接起来的一个桥梁,这样就做到了在函数外部访问和操作函数内部的私人变量(局部变量)的目的

  总结:闭包可以实现操作函数内部的私有变量,让函数内外进行连接

  暂时就写这么多,以后再更新.......

引用借鉴:

  https://segmentfault.com/a/1190000008681174

  http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

  https://github.com/lin-xin/blog/issues/8

  https://github.com/mqyqingfeng/Blog/issues/9

       https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

非常感谢贡献这么多好文章的作者们......