Javascript閉包執行個體詳解_javascript技巧

來源:互聯網
上載者:User

什麼是閉包

閉包是什麼?閉包是Closure,這是靜態語言所不具有的一個新特性。但是閉包也不是什麼複雜到不可理解的東西,簡而言之,閉包就是:

閉包就是函數的局部變數集合,只是這些局部變數在函數返回後會繼續存在。

閉包就是就是函數的“堆棧”在函數返回後並不釋放,我們也可以理解為這些函數堆棧並不在棧上分配而是在堆上分配當在一個函數內定義另外一個函數就會產生閉包上面的第二定義是第一個補充說明,抽取第一個定義的主謂賓——閉包是函數的‘局部變數'集合。只是這個局部變數是可以在函數返回後被訪問。(這個不是官方定義,但是這個定義應該更有利於你理解閉包)

理解Javascript的閉包非常關鍵,本篇試圖用最簡單的例子理解此概念。

function greet(sth){  return function(name){    console.log(sth + ' ' + name);  }}//hi darrengreet('hi')('darren');

或者可以寫成這樣:

var sayHi = greet('hi');sayHi('darren');


我們要提的問題是:為什麼greet的內建函式能使用sth這個變數?

其內部大致運作如下:

→ 建立全域上下文
→ 執行var sayHi = greet('hi');語句,建立greet上下文,變數sth儲存在greet上下文中。
→ 繼續執行greet函數內的語句,返回一個匿名函數,雖然greet上下文從堆棧上消失,但sth變數依舊存在於記憶體的某個空間。
→ 繼續執行sayHi('darren');建立了sayHi上下文,並試圖搜尋sth變數,但在sayHi這個上下文中沒有sth變數。sayHi上下文會沿著一個範圍鏈找到sth變數對應的那個記憶體。 外層函數就像一個閉包,其內建函式可以使用外部函數的變數。

一個閉包的簡單例子

function buildFunctions(){  var funcArr = [];  for(var i = 0; i < 3; i++){    funcArr.push(function(){console.log(i)});  }  return funcArr;}var fs = buildFunctions();fs[0](); //3fs[1](); //3fs[2](); //3

以上,為什麼結果不是0, 1, 2呢?

--因為i作為一個閉包變數,當前值為3,被內建函式使用。要實現想要的效果,可以在遍曆的時候每一次遍曆建立一個獨立的上下文使其不受閉包影響。而自觸發函數可以實現獨立上下文。

function buildFunctions(){  var funcArr = [];  for(var i = 0; i < 3; i++){    funcArr.push((function(j){      return function(){       console.log(j);      };    }(i)));  }  return funcArr;}var fs = buildFunctions();fs[0](); //0fs[1](); //1fs[2](); //2

本篇的兩個例子正好體現了閉包的2個方面:一個是內建函式使用閉包變數,另一個是把內建函式寫在自觸發函數中從而避免受閉包影響。

做為局部變數都可以被函數內的代碼訪問,這個和靜態語言是沒有差別。閉包的差別在於局部變變數可以在函數執行結束後仍然被函數外的代碼訪問。這意味 著函數必須返回一個指向閉包的“引用”,或將這個”引用”賦值給某個外部變數,才能保證閉包中局部變數被外部代碼訪問。當然包含這個引用的實體應該是一個 對象,因為在Javascript中除了基本類型剩下的就都是對象了。可惜的是,ECMAScript並沒有提供相關的成員和方法來訪問閉包中的局部變 量。但是在ECMAScript中,函數對象中定義的內建函式() inner function是可以直接存取外部函數的局部變數,通過這種機制,我們就可以以如下的方式完成對閉包的訪問了。

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 規範進行研究,我這裡也只做一個簡單的講解,內容也是來自於網路。

在ECMAscript的指令碼的函數運行時,每個函數關聯都有一個執行內容情境(Execution Context) ,這個執行內容情境中包含三個部分

文法環境(The LexicalEnvironment)
變數環境(The VariableEnvironment)
this綁定

其中第三點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; }}setupSomeGolbals(); // 為三個全域變數賦值gAlertNumber(); //666gIncreaseNumber();gAlertNumber(); // 667gSetNumber(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訪 問內部私人函數。需要注意的地方是匿名主函數結束的地方的'()',如果沒有這個'()'就不能產生單件。因為匿名函數只能返回了唯一的對象,而且不能被 其他地方調用。這個就是利用閉包產生單件的方法。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.