美文网首页js基础
闭包1(基础)

闭包1(基础)

作者: blueblue_c41a | 来源:发表于2018-02-05 09:38 被阅读4次

    (什么是闭包?闭包的作用?闭包的缺陷?)

    (闭包的几种可能的应用场景)

    (闭包与内存泄漏,有关闭包的面试题)

    推荐学习网站:


    一、什么是闭包?
    • Closures (闭包)就是指有权访问另一个函数作用域中的变量的函数。
    • 闭包是一个函数和声明该函数的词法环境的组合。从理论角度来说,所有函数都是闭包。
    • 要使用闭包,只需要简单地将一个函数定义在另一个函数内部,并将它暴露出来。要暴露一个函数,可以将它返回或者传给其他函数
    • 内部函数将能够访问到外部函数作用域中的变量,即使外部函数已经执行完毕。

    二、产生一个闭包

    创建闭包最常见方式,就是在一个函数内部创建另一个函数

    // closure 就是一个闭包
    function func(){
      var a = 1,b = 2;  
      function closure(){
        return a+b;
      }
      return closure;
    }
    

    闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。


    三、闭包的注意事项

    1、函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。

    function makeAdder(x) {
      return function(y) {
        return x + y;
      };
    }
    
    var add5 = makeAdder(5);
    var add10 = makeAdder(10);
    
    console.log(add5(2));  // 7
    console.log(add10(2)); // 12
    
    // 释放对闭包的引用
    add5 = null;
    add10 = null;
    

    add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。最后通过 null 释放了 add5 和 add10 对闭包的引用

    在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
    如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

    2、 闭包只能取得包含函数中任何变量的最后一个值,这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量。

    function test(){
      var arr = [];
      for(var i = 0;i < 10;i++){
        arr[i] = function(){
          return i;
        };
      }
      for(var a = 0;a < 10;a++){
        console.log(arr[a]());
      }
    }
    test(); // 连续打印 10 个 10
    
    // 函数1作用域——for作用域
    // 执行完for之后,在for作用域中i的值为10 
    for(var i = 0; i < 10; i++) { // 函数1作用域
            // 我在函数1作用域中
            arr[i] = function() {// 函数2作用域
              // 我在函数2作用域中
              return i;
            };
    }
    for(var a = 0;a < 10;a++){
    //  程序执行for的时候访问闭包function(){return i}词法作用域向上查找,
    //  找到for(var i=0;i<10;i++)的词法作用域i=10,因此每次都是10
        console.log(arr[a]());
    }
    // 毫无疑问,执行到这里的时候,i是10
    // 函数2作用域中没有,向上去函数1作用域中找
    console.log(i); 
    
    // 函数1作用域
    

    对于上面的情况,如果我们改变代码如下:

    function test(){
      var arr = [];
      for(let i = 0;i < 10;i++){  // 仅在这里作出了改动
        arr[i] = function(){
          return i;
        };
      }
      for(var a = 0;a < 10;a++){
        console.log(arr[a]());
      }
    }
    test(); // 打印 0 到 9
    
    //当你换成let的时候,读取i的时候,在当前作用域(块3)中没有找到
    // 向上一个作用域(块2)寻找,在块2中发现i,于是拿到值
    // 块1作用域
    for(let i = 0; i < 10; i++) { // 块2作用域
        // 我在块2作用域中
        // 在这里每次循环的块作用域都被劫持了,并且每次迭代i都被重新声明
        // 即在此时,每层迭代会生成一个块作用域,并且变量i的值被定义为上次结算的值,
        console.log(i); // 毫无疑问,这里的i从0依次增加到10  
        arr[i] = function() { // 块3作用域
          // 我在块3作用域中
          return i;
        };
    }
    for(var a = 0;a < 10;a++){
        //当再次访问闭包的时候访问的是劫持的块作用域,因此 i正确显示
        console.log(arr[a]());
    }
    // 块1作用域
    

    3、函数名与函数功能(或者称函数值)是分割开的,不要认为函数在哪里,其内部的this就指向哪里。匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。

    var name = "The Window";
    
    var obj = {
      name: "My Object",
    
      getName: function(){
        return function(){
          return this.name;
        };
      }
    };
    
    console.log(obj.getName()());  // The Window
    

    obj.getName()()实际上是在全局作用域中调用了匿名函数,this指向了window。

    var name = "The Window";
    
    var obj = {
      name: "My Object",
    
      getName: function(){
        var that = this;
        return function(){
          return that.name;
        };
      }
    };
    
    console.log(obj.getName()());  // My Object
    

    四、闭包的应用
    1、面向对象编程,实现对象数据的私有化,实例独立访问成员变量之间互不影响
    • 私有变量 :任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量

    • 私有变量包括函数的参数、局部变量和函数内定义的其他函数。

    • 特权方法:把有权访问私有变量的公有方法称为特权方法(privileged method)。

    function Animal(){
      
      // 私有变量
      var series = "哺乳动物";
      function run(){
        console.log("Run!!!");
      }
      
      // 特权方法
      this.getSeries = function(){
        return series;
      };
    }
    
    • 匿名函数最大的用途是创建闭包
    • 使用闭包模拟私有方法, 可以限制对代码的访问,减少全局变量的使用和污染,提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分
    var objEvent = objEvent || {};
    (function(){ 
        var addEvent = function(){ 
          // some code
        };
        function removeEvent(){
          // some code
        }
    
        objEvent.addEvent = addEvent;
        objEvent.removeEvent = removeEvent;
    })();
    

    在这段代码中函数 addEvent 和 removeEvent 都是局部变量,但我们可以通过全局变量 objEvent 使用它,这就大大减少了全局变量的使用,增强了网页的安全性。

    var countNumber = (function(){
      var num = 0;
      return function(){
        return ++num;
      };
    })();
    
    2、 匿名自执行函数

    创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。

    • 匿名函数:顾名思义,就是没有方法名的函数
    • 匿名函数调用方式:使用()将匿名函数括起来,然后后面再加一对小括号(包含参数列表 )
    // 将匿名函数赋值给一个变量,通过变量引用进行函数调用
    (function(a,b)
        {
           console.log('匿名函数加圆括号:'+(a+b));
        }(1,2));
    
    var noName= function(a,b){
        console.log('匿名函数赋值变量:'+(a+b));
    }(1,4);
    
    3、 缓存(未整理验证)

    :设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。
    闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
    (1)

     /*
        * 闭包实现缓存
        * 属性:有个键--值   --->所以可以将缓存数据存放在一个对象中
        * 缓存存储方法   setCache
        * 缓存的获取方法  getCache
    */
        function  configCache(){
            var  obj={};//设置一个内部的对象 用来存储缓存数据;这个属性是私有的
            //对外暴露两个公共的方法
            return {
                setCache:function(k,v){//设置缓存
                    obj[k]=v;
                },
                getCache:function(k){//获取缓存
                    return obj[k];
                }
            };
        }
        var conf = configCache();
        console.log(conf);
        conf.setCache(1,'sdwqecwqv');
        console.log(conf.getCache(1));//sdwqecwqv
       // 注意下面这种情况,两次configCache()会产生不同的执行环境
        configCache().setCache(1,'sdwqecwqv');
        console.log(configCache().getCache(1));//undefined
    

    (2)

    function doSometingToGetData () {
        //可以在这里向后台取数据,也可以是其他操作,目的就是获取你需要缓存的数据并返回
        return data;
    }
    
    var a = (function b () { 
        var _cache = {};
        return {
            setCache: function (name) {
                _cache[name] = doSometingToGetData(name);
            },
            getCache: function (name) {
                return _cache[name];
            }
        }
    });
     
    var c = function () {
        a.setCache('daihere');
        var data = a.getCache('daihere');
    }
    

    (3)

    var a = (function b () {
        var _cache = {};
        return {
            doSometing: function (name) {
                if (name in cache) {
                    var data = _cache[name];//获取缓存数据
                    doSometingByData(data);
                }else {
                    _cache[name] = doSometingToGetData(name);
                }
            },
             
        };
    })();
     
    function doSometingToGetData () {
        //可以在这里向后台取数据,也可以是其他操作,目的就是获取你需要缓存的数据并返回
        return data;
    }
    
    4、实现封装

    闭包可以封装形成‘私有变量‘,如:实现计算乘积:

    const mult = function() {
       var a = 1;
       for (let i = 0; i < arguments.length; i++) {
          a = a * arguments[i];
       }
       return a;
    }
    mult(1,2,3,4) // 24
    

    五、闭包的缺陷
    • 局部变量本来在函数退出时被销毁,然而闭包中不是这样,局部变量生命周期被延长,闭包将使这些数据无法及时销毁,常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露

    • 如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗

    六、面试题
    例1:
    function fun(n,o){
      console.log(o);
      return {
        fun: function(m){
          return fun(m,n);
        }
      };
    }
    
    var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);    // ?,?,?,?
    
    var b = fun(0).fun(1).fun(2).fun(3);  // ?,?,?,?
    
    var c = fun(0).fun(1);  c.fun(2);  c.fun(3);  // ?,?,?,?
    
    a: undefined,0,0,0
    b: undefined, 0, 1, 2
    c: undefined, 0,1,1
    
    例2:
    for (var i = 1; i <= 5; i++) {
    
      setTimeout( function timer() {
    
          console.log(i);
    
      }, 1000 );
    
    }
    

    上面代码输出什么?如何改动使它依次输出1、2、3、4、5

    // 上面代码返回6、6、6、6、6
    // 方法一
    for (let i = 1; i <= 5; i++) {
    
      setTimeout( function timer() {
    
          console.log(i);
    
      }, 1000 );
    
    }
    // 方法二
    for (var i = 1; i <= 5; i++) {
    
        (function(i){
    
            setTimeout( function timer() {
    
                  console.log(i);
    
              },  1000 );
    
        })(i);
    
    }
    

    相关文章

      网友评论

        本文标题:闭包1(基础)

        本文链接:https://www.haomeiwen.com/subject/vboqzxtx.html