JavaScript闭包

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

闭包的局部变量可以在函数执行结束后仍然被函数外的代码访问。这意味着函数必须返回一个指向闭包的“引用”,或将这个”引用”赋值给某个外部变量,才能保证闭包中局部变量被外部代码访问。

在ECMAScript中,函数对象中定义的 内部函数(inner function) 是可以直接访问外部函数的局部变量,创建闭包的常见方式,就是在一个函数内部创建另一个函数。

function welcome (name) {
     var text = 'Hello ' + name;   // local variable
     // 每次调用时,产生闭包,并返回内部函数对象给调用者
     return function () {
         console.log(text);
     }
}
var sayHello = welcome( "Viewer" );
sayHello()   // 通过闭包访问到了局部变量text

代码的执行结果是:Hello Viewer,因为 sayHello() 函数在 welcome() 函数执行完毕后,仍然可以访问到定义在其内部的局部变量 text ,这就是闭包的效果。

在ECMAscript的脚本的函数运行时,每个函数关联都有一个执行上下文场景(Execution Context) ,这个执行上下文场景中包含三个部分:

  • 文法环境(The LexicalEnvironment)
    • 环境记录(Enviroment Recode)
    • 外部引用(指针)
  • 变量环境(The VariableEnvironment)
    • 局部变量
    • 参数变量
  • this绑定

外部引用指向了外部函数对象的上下文执行场景。全局的上下文场景中此引用值为 NULL。这样的数据结构就构成了一个单向的链表,每个引用都指向外层的上下文场景。

例如上面我们例子的闭包模型应该是这样,sayHello 函数在最下层,上层是函数 welcome,最外层是全局场景。如下图:

因此当sayHello被调用的时候,sayHello会通过上下文场景找到局部变量text的值,因此在屏幕的对话框中显示出”Hello Viewer”。

变量环境(The VariableEnvironment)和文法环境的作用基本相似。

 

闭包实例

前面的内容大致说明了Javascript闭包是什么,闭包在Javascript如何实现,下面通过针对一些例子来深入了解闭包。

例子1:闭包中局部变量是引用而非复制

function say667() {
    var num = 666;
    var sayAlert = function() {
        console.log(num);
    }
    num++;
    return sayAlert;
}
  
var sayAlert = say667();
sayAlert()    //667

 

例子2:多个函数绑定同一个闭包,因为他们定义在同一个函数内。

function setupSomeGlobals () {
    var num = 666;

    // 存储一些函数的引用作为全局变量
    gAlertNumber = function() {
        console.log(num);
    }

    gIncreaseNumber = function() {
        num++;
    }

    gSetNumber = function(x) {
        num = x;
    }
}

setupSomeGlobals();     // 为三个全局变量赋值
gAlertNumber();         //666

gIncreaseNumber();
gAlertNumber();         // 667

gSetNumber(12);         
gAlertNumber();         //12

 

例子3:当在一个循环中赋值函数时,这些函数将绑定同样的闭包

<script type="text/javascript">
  window.onload = function(){
    var lists = document.getElementsByTagName("li");
    for(var i=0,l=lists.length; i < l; i++){
      lists[i].onclick = function(){
        var t = i;
        return function(){
          console.log(t+1)
        }
      }()
    }
  }
</script>

<body>
<h1>当在一个循环中赋值函数时,这些函数将绑定同样的闭包</h1>
<ul>
  <li id="a1">aa</li>
  <li id="a2">aa</li>
  <li id="a3">aa</li>
</ul>
<body>

 

 

例子4:外部函数所有局部变量都在闭包内,即使这个变量声明在内部函数定义之后。

function sayAlice() {
    var sayAlert = function() {
        console.log(alice);
    }

    var alice = 'Hello Alice';
    return sayAlert;
}
var helloAlice = sayAlice();
helloAlice();

 

例子5:每次函数调用的时候创建一个新的闭包。

function newClosure(someNum, someRef) {
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
        '\nanArray ' + anArray.toString() +
        '\nref.someVar ' + ref.someVar);
    }
}
closure1 = newClosure(40,{someVar:'closure 1'});
closure2 = newClosure(1000,{someVar:'closure 2'});
  
closure1(5); // num:45 anArray[1,2,3,45] ref:'someVar closure1'
closure2(-10);// num:990 anArray[1,2,3,990] ref:'someVar closure2'

 

 

由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存,过度使用闭包会导致占用内存,所以只有在必要的时候再使用闭包。

 

参考资料:http://yuiblog.com/blog/2006/11/27/video-crockford-advjs/

              http://coolshell.cn/articles/6731.html

 

posted @ 2015-11-17 22:50  周福林  阅读(256)  评论(1编辑  收藏  举报