关于Js中的闭包

在说闭包之前,先回忆之前我们了解的javascript中的变量和作用域链。上一篇博客《轻松搞定–作用域,变量、函数提升,作用域链》http://blog.csdn.net/diligentkong/article/details/76832038
变量的作用域有两种:全局变量和局部变量。当某个函数被调用时,会创建一个执行环境及相应的作用域链。
每个执行环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个作用域。
例如:

   var num = 10;
    //outer的作用域---》全局作用域
    function outer(){
        var s = 15;
        //inner的作用---》outer的作用域---》全局作用域
        function inner(){
            var t = "内部";
        }
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上述例子中,我们知道inner可以访问上级的数据,但是上级的数据却不能访问(下级)inner内部的数据。 如果我们非要想访问内部的数据肿么办呢!这个就是闭包要解决的问题。
来看,什么是闭包

闭包

简单来说,闭包是一个具有封闭的对外不公开的,包裹结构或空间。

在JavaScript中函数可以构成闭包。

闭包是指有权访另一个函数作用域中的变量的函数

  • 闭包创建的常见方式在一个函数内部创建另一个函数。
  • 闭包的原理就是作用域访问原则,解决外部作用域无法直接访问内部作用域中的变量的问题。

看个例子解释一下:

function fc() {
       var num = 5;  
       function func() {
           num++;
           return num;    
        }
       return func;
   }


   var f = fc();
   var res = f();
  console.log(res);
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

函数内的数据不能直接在函数外被访问,是因为作用域的关系,上级作用域不能直接访问下级作用域中的数据。

但是如果反过来,下级作用域可以直接访问上级作用域中的数据。那么如果在函数fc内定义一个函数,那么在这个内部函数中是可以直接访问fc中的num的。

闭包的场景:

1.使用闭包可以在JavaScript中模拟块级作用域;

JavaScript中没有块级作用域的概念,
如下:

function outer(count){

        for (var i =0;i<count;i++) {
            console.log(i);
        }

      console.log(i); //5
   }
outer(5);
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

对于有块级作用域的语言来说,for语句初始变量的表达式所定义的变量,只会存在于循环的环境之中。而对于JavaScript来说,由for语句创建的变量i即使在for循环执行结束之后,也依旧会存在于循环外部的执行环境中。
所以最后还是会输出i=5.

匿名函数可以用来模仿块级作用域并避免这个问题。

  • 块级作用域(通常称为私有作用域)的匿名函数的语法如下:
(function(){
//这里是块级作用域或者私有作用域
})();
  
  
  • 1
  • 2
  • 3

上述代码定义并立即调用了一个匿名函数。 将函数声明包含在一对圆括号中(),表示它实际上是一个函数表达式。而紧随其后的另一对圆括号()会立即调用这个函数。

若不理解,回顾知识:

1. 函数声明:

   function fnName(){}使用function关键字声明一个函数,再指定一个函数名,叫函数声明。

   函数声明的调用方式: fnName();

  
  
  • 1
  • 2
  • 3
  • 4

2. 函数表达式:

`var fnName = function(){};` 使用function关键字声明一个函数但未给函数命名,最后将匿名函数赋值给一个变量,叫函数表达式。

函数表达式的调用方式:

fnName()也可以  var fnName=function(console.log(3)){}(); //3

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3. 匿名函数:

  function(){}使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数.。
  
  
  • 1

函数表达式后面加括号()表示立即执行该函数,函数声明不可以。
要将函数声明转化为函数表达式,这要在函数声明中添加一对括号()就可以了.

  • 匿名函数的执行方式:
 (function(){})()
 (function(){}()) 

这种形式也叫立即执行函数,声明即执行函数表达式。
  
  
  • 1
  • 2
  • 3
  • 4

概念搞清楚之后来看例子:

       重写函数:

function outer(count){
      (function() {
        for (var i =0;i<count;i++) {
            console.log(i);
        }
      })();
      console.log(i); //报错 i is not defined
   }
outer(5);
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

无论在什么地方,只要临时需要一些变量,就可以使用私有作用域(也就是使用匿名函数的方式)。
               重写outer()中,在for循环外部插入了一个私有作用域。在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。
               这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用只要函数执行完毕,就可以立即销毁其作用域链。

闭包可以用于在对象中创建私有变量。

               严格来讲,JavaScript中没有私有成员的概念,所有对象属性都是公有的。不过,倒是有私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。但如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而利用这一点,就可以创建用于访问私有变量的公有方法。
       私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

如下:

function  MyObject(){
    //私有变量和私有函数
    var privateVar = 10;
    function privateFun(){
        return false;
    }
    //特权方法
    this.publicMethod = function(){
        privateVar ++;
        return privateFun;
    }
 }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

       特权方法this.publicMethod()作为闭包有权访问构造函数中定义的所有变量和函数。

       利用私有和特权成员,可以隐藏那些不应该被直接修改的数据,例如:

function Person(name){
    this.getName = function(){
        return name;
    };
    this.setName = function(value){
        name = value;
    };
 }
 var person = new Person("Jack");

 console.log(person.getName());

 person.setName("Rose");
 console.log(person.getName());
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

       getName(),setName()这两个方法都可以在构造函数外部使用,而且都有权访问私有变量name。但在Person构造函数外部,没有任何办法访问name、由于这两个方法是构造函数内部定义的,它们作为闭包能够通过作用域链访问name。

       构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题。

静态私有变量

       通过私有作用域(块级作用域)中定义私有变量或函数,同样可以创建特权方法,基本模式

(function(){
    //私有变量和私有函数
    var privateVar = 10;
    function privateFun(){
        return false;
    }
    //构造函数
    MyObject = function(){

    }; //这是全局变量。
    //公有/特权方法
    MyObject.prototype.publicMethod = function(){
        privateVar ++;
        return privateFun;
    }
   })();
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

       这个模式与在构造函数中定义特权方法的主要区别在于: 私有变量和函数是由实例共享的。由于特权方法是在原型上定义的,因此所有的实例都使用同一个函数。而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用。

模块模式

       前面的模式是用于自定义类型创建私有变量和特权方法的,而道格拉斯所说的模块模式则是为单例创建私有变量和特权方法。所谓单例,指的是只有一个实例的对象。
Javascript是以对象字面量的方式来创建单利对象的。

var singleton ={
    name:value,
    method:function(){
        //代码
    }
  };
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

模块模式通过为单例添加私有变量和特权方法能够使其得到增强。

var singleton = function(){
    //私有变量和私有函数
    var privateVar = 10;

    function privateFun (){
        return false;
    }
    //特权方法和属性
    return {
        publicProperty: true,
        publicMethod: function(){
            privateVar ++;
            return privateFun;
        }
    }
  }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

       在Web应用程序中,经常使用一个单例来管理应用程序级的信息。
       如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就使用模块模式。以这种模式创建的每个单例都是Object的实例,单例通常作为全局对象存在的。

增强的模块模式

 var singleton = function(){
    //私有变量和私有函数
    var privateVar = 10;

    function privateFun (){
        return false;
    }
    //创建对象
    var object == new CustomType();

    //添加特权方法和属性

        object.publicProperty: true,
        object.publicMethod = function(){
            privateVar ++;
            return privateFun;
        };
        //返回这个对象
        return object;

  }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

       这种增强的模块模式适合那些单例必须式某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。

猜你喜欢

转载自blog.csdn.net/gentleman_hua/article/details/82967741