研究過js的朋友大多會說,理解了js的原型和閉包就可以了,然后又說這些都是js的高級內容,然后就又扯到了各種神馬的作用域。。。然后不少
人就會被忽悠的雲里霧里。。。下面我也試着來說說閉包,看我說的這個是否淺顯易懂。。。
一:閉包含義
閉包是個專業詞匯,這樣才能顯得在js中是高大上的貨色,官方定義我這里就不敢修改它,定義如下:就是有權訪問另一個函數作用域的變量的函數。
二:一個簡單的場景
上面的定義大概也能看得懂,但是不知道為什么不把“另一個函數” 改成 “包含函數”,因為我覺得“包含函數”可能更通俗易懂些,光有定義還不行,我
還得找個經典的例子看一看。
1 <script type="text/javascript"> 2 3 //比較函數 4 function createComparison(propertyName) { 5 6 return function (obj1, obj2) { 7 var item1 = obj1[propertyName]; 8 var item2 = obj2[propertyName]; 9 10 if (item1 < item2) 11 return -1; 12 13 if (item1 > item2) 14 return 1; 15 16 if (item1 == item2) 17 return 0; 18 } 19 } 20 21 //比較name 22 var compare = createComparison("name"); 23 24 var result = compare({ name: "d", age: 20 }, { name: "c", age: 27 }); 25 </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 <script type="text/javascript"> 2 3 var arr = new Array(); 4 5 function Person() { 6 for (var i = 0; i < 10; i++) { 7 8 //要記住,這個屬性函數申明,只有立即執行才會取scope屬性 9 var item = function () { 10 return i; 11 }; 12 13 arr.push(item); 14 } 15 } 16 17 Person(); 18 19 for (var i = 0; i < arr.length; i++) { 20 console.log(arr[i]()); 21 } 22 </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推測出來了,對不對。