(收藏自 技術狂)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訪 問內部私有函數。須要注意的地方是匿名主函數結束的地方的’()’,若是沒有這個’()’就不能產生單件。由於匿名函數只能返回了惟一的對象,並且不能被 其餘地方調用。這個就是利用閉包產生單件的方法。