(收藏自 技术狂)javascript
前言:仍是一篇入门文章。Javascript中有几个很是重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C++的程序员来讲是一个新的语言特性。本文将以例子入手来介绍Javascript闭包的语言特性,并结合一点 ECMAScript语言规范来使读者能够更深刻的理解闭包。html
注:本文是入门文章,例子素材整理于网络,若是你是高手,欢迎针对文章提出技术性建议和意见。本文讨论的是Javascript,不想作语言对比,若是您对Javascript天生不适,请自行绕道。java
闭包是什么?闭包是Closure,这是静态语言所不具备的一个新特性。可是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是:程序员
上面的第二定义是第一个补充说明,抽取第一个定义的主谓宾——闭包是函数的‘局部变量’集合。只是这个局部变量是能够在函数返回后被访问。(这个不是官方定义,可是这个定义应该更有利于你理解闭包)web
作为局部变量均可以被函数内的代码访问,这个和静态语言是没有差异。闭包的差异在于局部变变量能够在函数执行结束后仍然被函数外的代码访问。这意味 着函数必须返回一个指向闭包的“引用”,或将这个”引用”赋值给某个外部变量,才能保证闭包中局部变量被外部代码访问。固然包含这个引用的实体应该是一个 对象,由于在Javascript中除了基本类型剩下的就都是对象了。惋惜的是,ECMAScript并无提供相关的成员和方法来访问闭包中的局部变 量。可是在ECMAScript中,函数对象中定义的内部函数(inner function)是能够直接访问外部函数的局部变量,经过这种机制,咱们就能够以以下的方式完成对闭包的访问了。shell
function greeting(name) { var text = 'Hello ' + name; // local variable // 每次调用时,产生闭包,并返回内部函数对象给调用者 return function() { alert(text); } } var sayHello=greeting("Closure"); sayHello() // 经过闭包访问到了局部变量text
上述代码的执行结果是:Hello Closure,由于sayHello()函数在greeting函数执行完毕后,仍然能够访问到了定义在其以内的局部变量text。网络
好了,这个就是传说中闭包的效果,闭包在Javascript中有多种应用场景和模式,好比Singleton,Power Constructor等这些Javascript模式都离不开对闭包的使用。数据结构
ECMAScript究竟是如何实现闭包的呢?想深刻了解的亲们能够获取ECMAScript 规范进行研究,我这里也只作一个简单的讲解,内容也是来自于网络。闭包
在ECMAscript的脚本的函数运行时,每一个函数关联都有一个执行上下文场景(Execution Context) ,这个执行上下文场景中包含三个部分函数
其中第三点this绑定与闭包无关,不在本文中讨论。文法环境中用于解析函数执行过程使用到的变量标识符。咱们能够将文法环境想象成一个对象,该对 象包含了两个重要组件,环境记录(Enviroment Recode),和外部引用(指针)。环境记录包含包含了函数内部声明的局部变量和参数变量,外部引用指向了外部函数对象的上下文执行场景。全局的上下文 场景中此引用值为NULL。这样的数据结构就构成了一个单向的链表,每一个引用都指向外层的上下文场景。
例如上面咱们例子的闭包模型应该是这样,sayHello函数在最下层,上层是函数greeting,最外层是全局场景。以下图: 所以当sayHello被调用的时候,sayHello会经过上下文场景找到局部变量text的值,所以在屏幕的对话框中显示出”Hello Closure” 变量环境(The VariableEnvironment)和文法环境的做用基本类似,具体的区别请参看ECMAScript的规范文档。
前面的我大体了解了Javascript闭包是什么,闭包在Javascript是怎么实现的。下面咱们经过针对一些例子来帮助你们更加深刻的理解闭包,下面共有5个样例,例子来自于JavaScript Closures For Dummies(镜像)。
例子1:闭包中局部变量是引用而非拷贝
function say667() { // Local variable that ends up within closure var num = 666; var sayAlert = function() { alert(num); } num++; return sayAlert; } var sayAlert = say667(); sayAlert()
所以执行结果应该弹出的667而非666。
例子2:多个函数绑定同一个闭包,由于他们定义在同一个函数内。
function setupSomeGlobals() { // Local variable that ends up within closure var num = 666; // Store some references to functions as global variables gAlertNumber = function() { alert(num); } gIncreaseNumber = function() { num++; } gSetNumber = function(x) { num = x; } } setupSomeGlobals(); // 为三个全局变量赋值 gAlertNumber(); //666 gIncreaseNumber(); gAlertNumber(); // 667 gSetNumber(12);// gAlertNumber();//12
例子3:当在一个循环中赋值函数时,这些函数将绑定一样的闭包
function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { var item = 'item' + list[i]; result.push( function() {alert(item + ' ' + list[i])} ); } return result; } function testList() { var fnlist = buildList([1,2,3]); // using j only to help prevent confusion - could use i for (var j = 0; j < fnlist.length; j++) { fnlist[j](); } }
testList的执行结果是弹出item3 undefined窗口三次,由于这三个函数绑定了同一个闭包,并且item的值为最后计算的结果,可是当i跳出循环时i值为4,因此list[4]的结果为undefined.
例子4:外部函数全部局部变量都在闭包内,即便这个变量声明在内部函数定义以后。
function sayAlice() { var sayAlert = function() { alert(alice); } // Local variable that ends up within closure var alice = 'Hello Alice'; return sayAlert; } var helloAlice=sayAlice(); helloAlice();
执行结果是弹出”Hello Alice”的窗口。即便局部变量声明在函数sayAlert以后,局部变量仍然能够被访问到。
例子5:每次函数调用的时候建立一个新的闭包
function newClosure(someNum, someRef) { // Local variables that end up within closure var num = someNum; var anArray = [1,2,3]; var ref = someRef; return function(x) { num += x; anArray.push(num); alert('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'
Singleton 单件:
var singleton = function () { var privateVariable; function privateFunction(x) { ...privateVariable... } return { firstMethod: function (a, b) { ...privateVariable... }, secondMethod: function (c) { ...privateFunction()... } }; }();
这个单件经过闭包来实现。经过闭包完成了私有的成员和方法的封装。匿名主函数返回一个对象。对象包含了两个方法,方法1能够方法私有变量,方法2访 问内部私有函数。须要注意的地方是匿名主函数结束的地方的’()’,若是没有这个’()’就不能产生单件。由于匿名函数只能返回了惟一的对象,并且不能被 其余地方调用。这个就是利用闭包产生单件的方法。