標籤:js 閉包
1.變數在聲明他們的函數體以及這個函數體嵌套的任意函數體內都是有定義的。eg:
alert(tmp);
var tmp=123;//未定義而不會報錯
2.範圍鏈的一個經典例子:
name="lwy";
functiont(){
var name="tlwy";
function s(){
var name="slwy";
console.log(name);
}
function ss(){
console.log(name);
}
s();
ss();
}
t();
在上述代碼中第一個輸出slwy,第二個輸出是tlwy,原因是:當執行js時,將建立函數的執行環境(調用對象),並將該對象置於鏈表開頭,然後將函數的調用對象連結在之後,最後是全域對象。然後從鏈表開頭尋找變數name,很明顯name是”slwy”。
但在執行ss()時,範圍鏈是:ss()->t()->window,所以name是”tlwy”。
650) this.width=650;" src="http://s3.51cto.com/wyfs02/M00/4B/CE/wKioL1Qz-u-Sr18CAAFyI5w-UsU569.jpg" title="1 (1).png" alt="wKioL1Qz-u-Sr18CAAFyI5w-UsU569.jpg" />
說到範圍鏈,不得不說with語句。with語句主要用來臨時擴充範圍鏈,將語句中的對象添加到範圍的頭部。eg:
person={name:"yhb",age:22,height:175,wife:{name:"lwy",age:21}};
with(person.wife){
console.log(name);
}
with語句將person.wife添加到範圍鏈的部,所以輸出的是:”lwy”;
with語句結束後,範圍鏈恢複正常。顯然with語句存在著將對象copy並移動到範圍的頭部的過程,所以產生了效能問題。
3.範圍鏈:簡單的說範圍鏈就是函數在定義的時候建立的,用於尋找使用到的變數的值的一個索引,而他內部的規則是,把函數自身的本地變數放在最前面,把自身的父級函數中的變數放在其次,把再高一級函數中的變數放在更後面,依次類推直至全域對象為止。當函數中需要查詢一個變數的值的時候,js解譯器會去範圍鏈去尋找,從最前面的本地變數中先找,如果沒有找到對應的變數,則到下一級的範圍鏈上找,一旦找到了變數,則不再繼續。如果找到最後也沒找到需要的變數,則解譯器返回undefined。
4.閉包:瞭解了範圍鏈,再來看js的記憶體回收機制,一般來說,一個函數在執行的時候,會給其中定義的變數劃分記憶體空間儲存,以備後面的語句所用,等到函數執行完畢返回了,這些變數就被認為是無用的了,對應的內空間也就被回收了。下次在執行此函數的時候,所有的變數又回到最初的狀態,重新賦值使用。但如果這個函數內部又嵌套了另一個函數,而這個函數是有可能在外部被調用到的,並且這個內建函式又使用了外部函數的某些變數的話。這種記憶體回收機制就會出現問題。如果在外部函數返回後,又直接調用了內建函式,那麼內建函式就無法讀取到他所需要的外部函數中變數的值了。所以js解譯器在遇到函數定義的時候,會自動把函數和他可能使用的變數(包括本地變數和父級和祖先級函數的變數(自由變數))一起儲存起來。也就是構建一個閉包,這些變數將不會被記憶體回收器所回收,只有當內部的函數不可能被調用後(例如被刪除了,或者沒有了指標),才會銷毀這個閉包。即當沒有任何一個閉包引用變數存在時,才會被下一次記憶體回收啟動時所回收。
也就是說,有了閉包,嵌套的函數結構才可以運作,這也是符合我們預期的。eg:
varresult=[];
functionfoo(){
var i= 0;
for (;i<3;i=i+1){
result[i]=function(){
alert(i)
}
}
};
foo();
result[0]();// 3
result[1]();// 3
result[2](); // 3
這段代碼中,程式員希望foo函數中的變數i被內建函式使用,並且能分別獲得他們的索引,而實際上,只能獲得該變數最後保留的值,也就是說,閉包中所有記錄的自由變數,只是對這個變數的一個引用,而非變數的值,當這個變數被改變了,閉包裡擷取到的變數值,也會被改變。
解決的方法之一,是讓內建函式在迴圈建立的時候立即被執行,並且捕捉當前的索引值,然後記錄在自己的一個本地變數裡。然後利用返回函數的方法,重寫內建函式,讓下一次調用的時候,返回本地變數的值,改進後的代碼:
varresult=[];
functionfoo(){
var i= 0;
for (;i<3;i=i+1){
result[i]=(function(j){
return function(){
alert(j);
};
})(i);
}
};
foo();
result[0]();// 0
result[1]();// 1
result[2](); // 2
5.閉包就是函數內部的函數,本質上閉包就是將函數內部和函數外部串連起來的一座橋樑
6.閉包的作用:一個是可以讀取函數內部的變數,另一個就是讓這些變數始終保持在記憶體中
7.使用閉包注意的問題
1) 由於閉包會使得函數中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在IE中可能導致記憶體泄露。解決的方法是,在退出函數之前,將不使用的局部變數全部刪除。
2) 閉包會在父函數外部,改變父函數內部變數的值。所以,如果把父函數當做對象使用,把閉包當做它的公用方法,把內部變數當做它的私人屬性,這時一定要小心,不要隨便改變父函數變數的值。
8.閉包:是一個擁有許多變數和綁定了這些變數的環境運算式(通常是一個函數),因而這些變數也是該運算式的一部分,通俗的來說這句話是說所有javascript中的function都是一個閉包。不過一般來說,嵌套的function所產生的閉包更為強大,也是大部分時候我們所謂的“閉包”。
當函數的內建函式被函數被函數外的一個變數引用的時候,就建立了一個閉包。所謂閉包就是再建構函式體內定義另外的函數作為目標對象的方法函數,而這個對象函數反過來引用外層函數體中的臨時變數。這使得只要目標對象在生存期內始終能夠保持其方法,就能間接保持原建構函式體當時用到的臨時變數值。儘管最開始的建構函式調用已經結束,臨時變數的名稱也都消失了,但在目標對象的方法內部卻始終能引用到該變數的值,而且該值只能通過這種方法來訪問。即使再次調用相同的建構函式。但只會產生新對象和方法,新的臨時變數只是對應新的值,和上次那次調用的是各自獨立的。
9.閉包的底層解釋:對函數
650) this.width=650;" src="http://s3.51cto.com/wyfs02/M00/4B/CC/wKiom1Qz-rnT8kPaAABMJflS3mU332.jpg" title="1 (2).png" style="float:none;" alt="wKiom1Qz-rnT8kPaAABMJflS3mU332.jpg" />
當定義函數a的時候,js解譯器會將函數a的範圍鏈(scopechain)設定為定義a時a所在的“環境”,如果a是一個全域函數,則scope chain中只有window對象。
當執行函數a的時候,a會進入相應的執行環境(excutioncontext)。
在建立執行環境的過程中,首先會為a添加一個scope屬性,即a的範圍,其值就為第1步中的scope chain。即a.scope=a的範圍鏈。
然後執行環境會建立一個使用中的物件(call object)。使用中的物件也是一個擁有屬性的對象,但它不具有原型而且不能通過JavaScript代碼直接存取。建立完使用中的物件後,把使用中的物件添加到a的範圍鏈的最頂端。此時a的範圍鏈包含了兩個對象:a的使用中的物件和window對象。
下一步是在使用中的物件上添加一個arguments屬性,它儲存著調用函數a時所傳遞的參數。
最後把所有函數a的形參和內部的函數b的引用也添加到a的使用中的物件上。在這一步中,完成了函數b的的定義,因此如同第3步,函數b的範圍鏈被設定為b所被定義的環境,即a的範圍。
到此,整個函數a從定義到執行的步驟就完成了。此時a返回函數b的引用給c,又函數b的範圍鏈包含了對函數a的使用中的物件的引用,也就是說b可以訪問到a中定義的所有變數和函數。函數b被c引用,函數b又依賴函數a,因此函數a在返回後不會被GC回收。
當函數b執行的時候亦會像以上步驟一樣。因此,執行時b的範圍鏈包含了3個對象:b的使用中的物件、a的使用中的物件和window對象,如所示:
650) this.width=650;" src="http://s3.51cto.com/wyfs02/M01/4B/CC/wKiom1Qz-rizziYDAADlxLSZKWQ226.jpg" title="1 (1).jpg" style="float:none;" alt="wKiom1Qz-rizziYDAADlxLSZKWQ226.jpg" />
,當在函數b中訪問一個變數的時候,搜尋順序是:
先搜尋自身的使用中的物件,如果存在則返回,如果不存在將繼續搜尋函數a的使用中的物件,依次尋找,直到找到為止。
如果函數b存在prototype原型對象,則在尋找完自身的使用中的物件後先尋找自身的原型對象,再繼續尋找。這就是Javascript中的變數尋找機制。
如果整個範圍鏈上都無法找到,則返回undefined。
上文提到了兩個重要的詞語:函數的定義與執行。文中提到函數的範圍是在定義函數時候就已經確定,而不是在執行的時候確定(參看步驟1和3)。用一段代碼來說明這個問題:
function f(x) {
var g = function () { returnx; }
return g;
}
var h = f(1);
alert(h());
這段代碼中變數h指向了f中的那個匿名函數(由g返回)。
假設函數h的範圍是在執行alert(h())確定的,那麼此時h的範圍鏈是:h的使用中的物件->alert的使用中的物件->window對象。
假設函數h的範圍是在定義時確定的,就是說h指向的那個匿名函數在定義的時候就已經確定了範圍。那麼在執行的時候,h的範圍鏈為:h的使用中的物件->f的使用中的物件->window對象。
如果第一種假設成立,那輸出值就是undefined;如果第二種假設成立,輸出值則為1。
運行結果證明了第2個假設是正確的,說明函數的範圍確實是在定義這個函數的時候就已經確定了。
10.閉包的作用:
1) 保護函數內的變數安全。
2) 在記憶體中維持一個變數。
3) 通過保護變數的安全實現js私人屬性和私人方法
本文出自 “虎哥的部落格” 部落格,請務必保留此出處http://7613577.blog.51cto.com/7603577/1561087
js閉包小結