在JavaScript中,閉包恐怕是很多人不能理解的一個概念了,甚至很多人也會把閉包和匿名函數混淆。
閉包是有權訪問另一個函數作用域中的變量的函數。首先要明白的就是,閉包是函數。由於要求它可以訪問另一個函數的作用於中的變量,所以我們往往是在一個函數的內部創建另一個函數,而“另一個函數”就是閉包。
比如之前提到過的作為比較函數:
function createComparisonFunction(propertyName){ return function(object1,object2){ var value1=object1[propertyName]; var value2=object2[propertyName]; if(value1<value2){ return="" -1;="" }else="" if(value1="">value2){ return 1; }else{ return 0; } }; } </value2){>
在這個函數中,由於return的函數它訪問了包含函數(外部函數)的變量propertyName,所以我們認為這個函數即為閉包。即使這個閉包被返回了,而且是在其他地方調用了,但是它仍然可以訪問propertyName,之所以還能夠訪問到propertyName這個變量,是因為內部函數(閉包)的作用域鏈中包含著createComparisonFunction函數的作用域。因此,要徹底搞清楚閉包,就需要徹底搞清楚函數被調用時發生了什麼以及作用域鏈的有關知識。
當某個函數被調用時,會創建一個執行環境(函數一旦被調用,則進入函數執行環境)和相應的作用域鏈(作用域鏈是隨著執行環境的不同而動態變化的)。(對於函數而言)之後使用arguments和其他命名參數的值來初始化函數的活動對象(每個執行環境都有一個變量對象,對於函數成為活動對象)。對於有閉包的函數而言,在作用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象始終處於第三位。。。直至作為作用域鏈終點的全局執行環境。
下面撇開閉包不談,先通過一個簡單的例子來理解作用域鏈以及變量對象和活動對象。
function compare(value1,value2){ if(value1<value2){ return -1; }else if(value1>value2){ return 1; }else{ return 0; } }<br> var result=compare(5,10);
以上代碼首先定義了compare()函數,然後又在全局作用域中調用了它。當調用compare函數時,首先創建一個函數執行環境,每個執行環境又對應這一個變量對象,也就是說作用域鏈和函數執行環境是同時創建的,其中作用域鏈的前端即為compare函數的活動對象(在函數中,變量對象又稱為活動對象)。在compare活動對象中包含了arguments、value1、value2(關鍵:盡管arguments數組對象包含value1和value2,但是我們還是要分開列舉,而不是僅僅認為只有arguments包含於compare的活動對象,因為value1和value2也包含於compare的活動對象)。
對於上述代碼而言,全局執行環境的變量對象(再次聲明:每一個執行環境都存在相應的變量對象)中包含result和compare,該變量對象在compare()執行環境的作用域鏈的第二位。
當我們創建compare()函數時,會創建一個預先包含全局變量對象的作用域鏈,這個作用域鏈被保存在compare函數內部的[[scope]]屬性中,當調用compare函數時,會為函數創建一個執行環境,然後通過復制函數的[[scope]]屬性中的對象構建起執行環境的作用域鏈。如下:
作用域鏈的本質就是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。無論什麼時候在函數中訪問一個變量,就會從作用域鏈的前端沿著作用域鏈搜索具有相應名字的變量。我們知道,全局環境的變量對象始終存在,而局部環境(如compare()函數執行環境)的變量對象只在函數執行的時候存在,一旦執行完畢,局部變量對象(活動對象)就會被銷毀。但在閉包中,卻與此不同。
把博文開始的代碼復制如下:
function createComparisonFunction(propertyName){ return function(object1,object2){ var value1=object1[propertyName]; var value2=object2[propertyName]; if(value1<value2){ return -1; }else if(value1>value2){ return 1; }else{ return 0; } }; }
由於在一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到它的作用域鏈中。因此,在createComparisonFunction函數內部定義的匿名函數的作用域中實際包含著外部函數的活動對象。如果我們執行如下代碼:
var compare=createComparisonFunction("name"); var result=compare({name:"zzw"},{name:"ht"});
這時候匿名函數的作用域鏈將引用著外部函數的活動對象。因為匿名函數從外部函數中被返回後,它的作用域鏈被初始化為包含外部函數的活動對象和全局變量對象。這樣,匿名函數就可以訪問外部函數中定義的所有變量。更為重要的是,即使外部函數在執行完畢後,其活動對象也不會被銷毀,因為匿名函數的作用域鏈仍然在引用這個活動對象。換句話說,當createComparison()函數返回後,其執行環境的作用域鏈會被銷毀,但是她的活動對象仍然保存在內存中。等到倪敏函數被銷毀後,外部函數的活動對象才會被銷毀。
由於閉包會攜帶者包含他的函數的作用域,因此回避其他函數占用更多的內存。過度的使用閉包可能會導致內存占用過多,我們建議只在絕對必要的時候再考慮使用閉包。
模仿塊級作用域
(function(){ var now=new Date(); if(now.getMonth()==0&&now.getDate()==1){ alert("happy new year"); } })();
這就是模仿塊級作用域,即定義並立即調用了一個匿名函數。
如下為演示其作用:
function outputNumbers(count){ (function(){ for (var i=0;i<count;i++){ console.log(i); } })(); console.log(i); } outputNumbers(5);
這是在模仿塊級作用域之外的console.log(i)就會導致錯誤,因為i未被定義。說明在執行了模仿塊級作用域之後,內部的變量就被銷毀了。
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,同時也希望多多支持!