研究過js的朋友大多會說,理解了js的原型和閉包就可以了,然後又說這些都是js的高階內容,然後就又扯到了各種神馬的作用域。。。然後不少人就會被忽悠的雲裡霧裡。。。下面我也試著來說說閉包,看我說的這個是否淺顯易懂。。。
一:閉包含義
閉包是個專業詞彙,這樣才能顯得在js中是高大上的貨色,官方定義我這裡就不敢修改它,定義如下:就是有權訪問另一個函式作用域的變數的函式。
二:一個簡單的場景
上面的定義大概也能看得懂,但是不知道為什麼不把“另一個函式” 改成 “包含函式”,因為我覺得“包含函式”可能更通俗易懂些,光有定義還不行,我還得找個經典的例子看一看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<script type="text/javascript"> //比較函式 function createComparison(propertyName) { return function (obj1, obj2) { var item1 = obj1[propertyName]; var item2 = obj2[propertyName]; if (item1 < item2) return -1; if (item1 > item2) return 1; if (item1 == item2) return 0; } } //比較name var compare = createComparison("name"); var result = compare({ name: "d", age: 20 }, { name: "c", age: 27 }); </script> |
這是一個說閉包原理的經典例子,經典在哪裡?如例子中我使用compare時,我的function是可以訪問到createComparison函式中的propertyName欄位的,其實這個理解並不複雜,我們去看看瀏覽器的scope variables就一清二楚了。
我們可以清楚的看到,在chrome的本地變數表中清楚的記錄著當前執行函式中的本地變數列表,並且還進行了分類,比如上面的”區域性函式變數(Local)“,”包含函式變數(Closure)”,“全域性變數(Global)”,那下面有個有趣的問題就來了,chrome怎麼知道我程式碼執行到20行的時候,當前的local variables有哪些呢?而且還能給我分門別類,是不是太奇葩了????但是仔細推敲一下就能豁然開朗,肯定有一個變數儲存著當前的variables,不然的話,chrome去哪讀取呢?對不對????????
三:解開謎底
其實在每個function裡面都有一個scope屬性,當然這個屬性被引擎遮蔽了,你是看不見也摸不著的,它裡面就儲存著當前函式的 local variables,如果應用到上面demo的話,就是全域性函式中有一個scope,createComparison有一個scope,匿名的compare有一個scope,而且這三個scope還是通過連結串列連結的,畫個簡圖如下:
從上面簡圖中可以看到,其實整個函式中有三個scope,每個scope都是用next指標連結,這樣就形成了一個連結串列,當我執行下面程式碼的時候
1 |
var result = compare({ name: "d", age: 20 }, { name: "c", age: 27 }); |
js引擎會拿到當前compare的scope,通過scope屬性的next指標,就可以區分哪些變數屬於哪個函式,這樣你就看到了chrome對variables的分門別類了。
四:對一個案例的加深理解
我想讀到這裡,你應該明白了閉包的原理,其實沒什麼稀奇的,就是一個讀取scope屬性的問題。只是被裝逼成高大上了,下面看段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<script type="text/javascript"> var arr = new Array(); function Person() { for (var i = 0; i < 10; i++) { //要記住,這個屬性函式申明,只有立即執行才會取scope屬性 var item = function () { return i; }; arr.push(item); } } Person(); for (var i = 0; i < arr.length; i++) { console.log(arr[i]()); } </script> |
在這個例子中,我想做一個function()陣列的array,並且最後都能輸出各自的值(1,2,3,4,5…10),但是結果又是怎樣呢?可以看到下圖中輸出的其實是10個10。。。這樣就違背了我的原始意圖。
上面這個陷阱的最大問題在於你自以為我在匿名function中寫了return i;就認為它是屬於匿名函式的,其實這就大錯特錯了,因為這個i就算走到天涯海角都不屬於匿名函式,而是屬於它的包含函式Person,所以原理應該是這樣,比如你看,當我執行arr[0]()的時候,這時候匿名函式就會通過scope去找i,但是在匿名函式的scope中沒有i,所以就通過next找到了Person函式,確實在Person中找到了i,但是這個時候i已經是10了,然後結束scope查詢輸出10。解決方案也很簡單,給每個匿名function一個副本就好了,具體原理我想你應該可以用scope推測出來了,對不對。