Javascript之旅——第十站:為什么都說閉包難理解呢?


  

  研究過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推測出來了,對不對。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM