闭包(closure)

● 闭包基础

● 闭包作用

● 闭包经典例子

● 闭包应用

● 闭包缺点

● 参考资料

1、闭包基础

     作用域和作用域链    匿名函数    this的理解与应用 闭包定义


1.1 作用域和作用域链

(1)作用域(Scope):作用域就是变量与函数的可访问范围。在JavaScript中,变量的作用域有全局作用域(全局变量)和局部作用(局部变量)域两种。

以下一个例子说明函数的作用域:

function outFun() {

    var outName = "outName";

    var outNum = "outNum";

    function innerFun() {

        var innerName = "innerName";

        var innerNum = "innerNum";

        alert(outName); // outName

        alert(outNum); // outNum

        alert(innerName); // innerName

        alert(innerNum); // innerNum

    }

    innerFun();

    alert(outName); // outName

    alert(outNum); // outNum

    alert(innerName); // undefined

    alert(innerNum); // undefined

}
outFun();

说明:内部函数(子级函数)innerFun()可以访问外部函数(父级函数)outFun的变量outName、outNum,父级函数不能访问子级函数的变量innerName、innerNum。作用域关乎函数、对象、变量的可访问范围。

全局作用域(Global Scope)(全局变量):任何地方都能访问的变量或者对象具有全局作用域。例如:

1)父级函数和父级函数外面定义的变量拥有全局作用域
    var a= 1;
    function f1() {
        var b= 2;
        function f2() {
            var c= 3;
            return a+b+c;
        }
        alert(a); // 1
        alert(b); // 2
        alert(c); // 3
        alert(f2()) ; // 6
    }
    f1();

说明:父级函数f1可以访问其内部定义的c变量和其外部定义的a、b变量,子级函数f2则可以访问所有变量。其中 a是全局变量,在web页面中全局变量属于 window 对象,全局变量可应用于页面上的所有脚本。所以a变量可以通过window.a获取,f1也可以通过window.f1()获取,一般情况下window对象可以省略不写。

2)变量声明如果不使用 var关键字,那么它就是一个全局变量,即便它在函数内定义
    function f1() {
        var a = "a" ;
        n = " n ";
        function f2() {
            alert(a)
        }
      return f2;
    }
  f1(); // "a"
  alert(n); // "n" 外部可以直接访问

说明:变量n没有用var关键字定义,那么它就是个全局变量,在函数外可以引用。

3)所有window对象的属性拥有全局作用域
一般情况下,全局变量属于 window 对象,全局变量可应用于页面上的所有脚本。window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等。

局部作用域(Local Scope)(局部变量):在固定的代码片段内访问,一般在函数内部,也称为函数作用域。

例如,上面的第一个例子中,在函数f1内部定义的变量b,只能在函数内部使用,在函数外部或脚本代码是不可用的。

全局变量和局部变量即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。

变量生命周期:全局变量的作用域是全局性的,即在整个JavaScript程序中,全局变量处处都在,不会自动在内存中清除。而函数内部生命的局部变量的作用域是局部性的,只有在函数内部起作用,当函数运行过程中对局部变量引用结束之后,局部变量就会在内存中清掉。

(2)作用域链(Scope Chain):创建函数的同时,它的作用域也被创建了。作用域中包含可访问的数据对象的集合,称为作用域链,它决定了哪些数据可以被函数访问。

function Add(num1, num2) {

     var sum = num1 + num2;

     return sum;

}

在函数Add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量。函数Add的作用域在运行时用到:

var total = Add(5, 7);

闭包(closure)_第1张图片
部分全局对象

执行此函数时会创建一个称为“运行期上下文( execution context )”的内部对象,它定义了函数执行时的环境,并被初始化为当前运行函数的[[Scope]]所包含的对象。

根据局部变量、命名参数、参数集合以及this等在函数中的出现顺序,它们被复制到“运行期上下文”的作用域链中,它们共同组成了一个新的对象,叫“活动对象( activation object )”:

闭包(closure)_第2张图片
活动对象与部分全局对象

在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,即从活动对象开始搜索,查找同名的标识符,若找到就使用这个标识符对应的变量,若没找到继续搜索作用域链中的下一个对象。如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。

(3)作用域链和代码优化

从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。如上图所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。

如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用, 例如:

function changeColor() {

    document.getElementById("button").onclick = function () {

        document.getElementById("button").style.backgroundColor = "red";

    }

  }

两次全局变量document,查找该变量必须遍历整个作用域链,直到最后在全局对象中才能找到。

function changeColor() {

    var doc = document;

    doc.getElementById("button").onclick = function () {

        doc.getElementById("button").style.backgroundColor = "red";

    }

}

这段代码比较简单,重写后不会显示出巨大的性能提升,但如果程序中有大量的全局变量被从反复访问,那么重写后的代码性能会有显著改善.

1.2 匿名函数

(1)匿名函数:没有给函数命名的函数。

(2)函数的定义有三种:

1)最常见的一种

function f1(x){

    return x;

}

2)使用了Function构造函数,把参数列表和函数体都作为字符串。不建议使用:

var f1 = new Function() { 'x', 'return x;' } ;

3 ) 通过匿名函数赋值,不推荐通过匿名函数赋值

var f1 = function(x) { return x; }

(3)匿名函数的创建有两重方法:

1)没有函数名:

  function(){};

2)通过两个括号实现:

( function (x, y) {

    return x+y;

})(2, 3);

其中,第一个括号是对匿名函数的定义;第二个括号是对匿名函数的调用。

(4)匿名函数的作用

1)创建闭包,构建命名空间,以减少全局变量的使用

var allObj =  { };

(function(){

    var addEvent =  function() {

        function removeEvent() {

            allObj.addEvent = addEvent ;

            allObj.removeEvent= removeEvent ;

        }

    }

})( );

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

2)通过两个括号的匿名函数赋值,比较实用

var addFun = (function(x, y){

    return x+y;

})(2, 3);

创建了一个addFun函数,并通过匿名函数将其初始化为5。

3)递增效果,闭包能使函数的内部变量保存在内存中。

var outer = null ;

(function(){

    var a = 1;

    function inner() {

          a +=1;

          alert( a );

    }

    outer = inner;

});

outer(); //2
outer(); //3
outer(); //4

我们要想使用此段代码:oEvent.addEvent(document.getElementById('box') , 'click' , function(){});

1.3 this理解与应用


在函数执行时,this 总是指向调用该函数的对象。要判断 this 的指向,其实就是判断 this 所在的函数属于谁。以下列出this出现的集中场景:

(1)有对象就指向调用对象

var object = {

    value: 123,

    getValue: function(){

        return this.value; // this指向object

   }

}

object.getValue(); // 调用getValue的对象为object.

(2)没有调用对象就指向全局对象

var myObj = {

    value: 123,

    getValue: function() {

          var foo = function(){

                    console.log( this.value ); // undefined 指向window

           }

          foo(); //没有调用对象

          return this.value; // this指向object

     }

}

console.log(myObj.getValue()); //123 调用getValue的对象为object.

(3)用new构造就指向新对象

js 中,我们通过 new 关键词来调用构造函数,此时 this 会绑定在该新对象上。

var SomeClass =function() {

    this.value = 100;

}

var myCreate =new SomeClass();

console.log(myCreate.value); // 输出100

(4) 通过 apply 或 call 或 bind 来改变 this 的所指

// apply 和 call 调用以及 bind 绑定: 指向绑定的对象

// apply 方法接受两个参数:第一个是函数运行的作用域, 另外一个是一个参数数组(arguments)。

// call 方法第一个参数的意义与 apply() 方法相同, 只是其他的参数需要一个个列举出来。

// 简单来说, call 的方式更接近我们平时调用函数, 而 apply 需要我们传递 Array 形式的数组给它。 它们是可以互相转换的。

var myObject = { value: 100 };

var foo =function() {

    console.log(this);

};

foo();// 全局变量 this指向window

foo.apply(myObject);// { value: 100 }

foo.call(myObject);// { value: 100 }

var newFoo = foo.bind(myObject);

newFoo();// { value: 100 }

1.4 闭包(closure)定义


(1)闭包:简而言之,闭包就是函数中的函数。

function f1(){

    var a = 666;

    function f2() {

        alert(a);

    }

}

(2)闭包特点:

1)子级f2()可以向上访问父级f1()的所有变量,而父级f1()不能向下访问子级f2()的变量,即外部不能访问内部变量,内部可以访问外部变量,这就是链式作用域。

2)子级函数可以引用父级函数的定义的变量,但该变量是最终的结果。


       


  •    


  •    


  •    




var lists = document.getElementsByTagName('li');

    for(var i = 0 , len = lists.length ; i < len ; i++){

        lists[ i ].onmouseover = function(){

            alert(i);

        };

    }


说明:当鼠标移过每一个非自执行的匿名函数function(){ alert(i); })时,会首先在内部查找是否定义了i,结果是没有定义;因此它会进一步向上查找,查找结果是已经定义了,并且i的值是4(父级循环结束后的i值,因为匿名函数没有自执行);所以,最终每次弹出的都是4。

(3)匿名函数本身是个闭包,可以在函数外部对函数内部的变量进行操作。

2、闭包作用


(1)通过闭包,可以读取函数内部的变量,并且闭包能让函数的内部变量(局部变量)始终保存在内存中,即使是在父级函数关闭(运行结束)的情况下。

function f1(){

    var n=666;

    add = function( ) { n+=1 } ; //全局变量

    function f2( ) {

         alert (n);

    }

    return f2;

}

var result = f1( );

result( ); // 666 读取函数内部变量

add(); // 读取函数内部变量

result(); // 667 函数的内部变量始终保存在内存中

上面函数中,result实际上就是闭包f2函数,它一共运行了两次,第一次是666,第二次是667。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

3、闭包经典例子


例1:

var name = "The Window";

    var object = {

        name : "My Object",

        getNameFunc : function(){

               return funtion(){

                       return this.name; // this指向window

              }

      }

alert(object.getNameFunc()()); // The Window

例2:

var name = "The Window";

var object = {

    name : "My Object",

    getNameFunc : function(){

        var that = this; // this指向object

        return function(){

            return that.name; // that指向object

        };

    }

};

alert(object.getNameFunc()()); //My Object

4、闭包应用


4.1、循环与闭包

var k = document.getElementsByTagName("li");

for(var i = 0; i < k.length; i ++) {

    (function(i){

        k[i].onclick = function() {

            alert(i);

        }

    })(i)

}

5、闭包缺点


1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。


参考资料


JavaScript中的匿名函数及函数的闭包  http://www.cnblogs.com/rainman/archive/2009/05/04/1448899.html#m0 

JavaScript开发进阶:理解JavaScript作用域和作用域链  http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html 

javascript中局部变量和全局变量的区别详解 http://www.jb51.net/article/61442.htm

hJavaScript中作用域、闭包与this指针  http://blog.csdn.net/junbo_song/article/details/52261247 

hJavaScript闭包详情  http://www.cnblogs.com/gbin1/p/4092427.html 

理解Javascript的闭包 http://coolshell.cn/articles/6731.html 

学习javascript的闭包 http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html 

JavaScript闭包 http://www.runoob.com/js/js-function-closures.html

JavaScript内存泄露  http://www.cnblogs.com/rainman/archive/2009/03/07/1405624.html

在函数执行时,this 总是指向调用该函数的对象。要判断 this 的指向,其实就是判断 this 所在的函数属于谁。

你可能感兴趣的:(闭包(closure))