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