首頁 >web前端 >js教程 >JS閉包隨筆

JS閉包隨筆

舍。
舍。原創
2017-08-11 14:57:531158瀏覽

  開始正式介紹之前先看一個比較有難度的閉包的面試題:
      function fun(n,o) {
                   fun :function(m){ 
                return fun(m,n);
         }
        var a = fun(0);  a.fun(1);  a. fun(2);  a.fun(3);
        var b = fun(0).fun(1).fun(2).fun(3);##   . (1);  c.fun(2);  c.fun(3);
        //問:三行a,b,c的產出分別為何?
  
  (原文連結:http://www.cnblogs.com/xxcanghai/p/4991870.html)
  這題是個人拿到手都會感到棘手,尤其對某些基礎不紮實的新手,看著看著就看暈了,我大體解釋一些這個fun函
乾了一些什麼事(文章最後再分析一下這個): 首先,印了一下他的第二個參數,然後return了一個對象,這個對像有一個方法fun,然後這個方法re
turn了fun(m,n)--最外層函數的執行結果;大體弄懂基本的流程,就可以大概分析一下了,不過在在講閉包之前,還是先講一下
js的作用域鏈:
  每一段js程式碼(全域程式碼或函數)(注意一下這裡,敲黑板!!!)都有一個與之關聯的作用域鏈(scope chain)。這
個作用域鍊是一個物件列表或鍊錶,這組物件這組物件定義了這段程式碼作用域中的變量,當js需要找出變數x的值得時候,
(這個過程叫做'變數解析(varaible resolution)'),他會從鏈中的第一個物件查找,如果在第一個物件中找不到就會
查找下一個,以此類推。
  在js頂層程式碼中,作用域鏈由一個全域物件組成。在不包含巢狀的函數體內,作用域鏈上有兩個對象第一個是該函數
定義參數和局部變數的對象,第二個是全域對象。
                                       --以上兩段的內容來自js權威指南,就是那本犀牛書
  當定義一個函數時,它實際上保存一個作用域鏈。當呼叫這個函數時,它會建立一個新的物件來儲存他的局部變量,並將
這個物件加入到保存的那個作用域鏈上,同時創建一個新的更長的表示函數呼叫作用域的鏈。對於巢狀函數來講每次呼叫
外部函數時,作用域鏈都是不同的,內部函數又會重新定義一遍,每次呼叫外部函數時,內部函數的程式碼都是相同的,而
關聯這段程式碼的作用域鏈也不相同。
                                  --以上內容也是來自js權威指南,就是那本犀牛書
  大概概括一下就是每個函數會形成一條作用域鏈,作用域鏈不同,能訪問到的內容也不盡相同,由於作用域鏈上物件的
排序關係存取變數時,可以遵循一個原則--就是就近原則。
    說完作用域之後,就來談談閉包吧。
  簡言之,混亂的作用域鏈就是形成閉包的元兇。
  來一個極簡單的例子:
      var d;
      function outter(){
     (c);
          d = function() { c++; }
      }
      outter();
      d();//0
        d();//1
        d(#);//1
        d(#);一般而言,每次呼叫js函數時,都會建立一個新的物件用來保存局部變量,並將這個物件加入到作
用域鏈中,當函數傳回之後,就從作用域鏈中將這個物件刪除,如果不存在巢狀函數也沒有其他引用指向這個綁定物件,他就
會被當作垃圾回收。如果定義了巢狀函數,每個巢狀函數都會形成自己的作用域鏈,而這個作用域鏈指向一個變數綁定對
象,如果這些巢狀的函數物件在外部函數中保存下來,那麼他們也會和所指向的變數綁定物件一樣被當作垃圾回收。但是如
果實將這些巢狀函數作為返回值返回,或儲存在某處的屬性裡,就會有一個外部的引用指向這個嵌套的函數。它就不會被當做
垃圾回收,並且所指向的變數綁定對象,也不會被當作垃圾回收!
    所以在上邊的程式碼裡當呼叫outter之後,由於外部變數d引用了巢狀的函數,故而在outter執行完畢之後,d指向的巢狀
函數何其所指向的變數綁定物件是沒有被回收的,所以c也沒有回收,所以有了往後的故事。 。 。
  來一個閉包的英文解釋(以下摘自MDN的解釋):
    
    A closure is the combination of a function andthat#Vlex) clared.

   什麼意思?上邊話直接翻譯過來就是:閉包是函數和這個函數被宣告的詞法作用域環境的組合。
   再說開頭說到的這題:
   
   function fun(n,o) {
                    fun:function(m) { 
                return fun(m,n);
         }
        
    var b = fun(0).fun(1).fun(2).fun (3);
   我來解釋這語句執行時會發生的情況:
   fun(0):
   fun(0) => n = 0,o = undefined;
ined;
console.log(o); => o = undefined;列印undefined
   return { fun: function(m){ return fun(m,n )使用) function(m){ return fun(m,n )使用中定義嵌套};並由return的物件的屬性
                                   fun引用,m = undefined,n =0 ,這條作用域鏈保留
   fun(0).fun(1):
       實際上是呼叫返回物件的fun方法:
       function(m)  => m = 1;
       return fun(m,n)##o #       fun(n,o) => n=1,o=0;
       console.log(o) =. => m = undefined,n =1; 同上,作用域鏈被保存
       fun(0).fun(1).fun(2):
      #        function(m) => m=2
        return fun(m,n) m=2,n=1
o ##        然後再印o就是印1啦。 。 。
        接著
        fun(0).fun(1).fun(2).fun(3);
           function(m) => m =3
            return fun(m,n) m=3,n=2
              然後再印o就是印2啦。 。 。
        ...
    最後的b是呼叫了返回對象的fun方法,而fun執行的時候又回傳了一個對象,所以b是一個對象,裡面有一個鍵為fun值函數的
屬性,這種屬性我們一般叫他方法。
  其實最後才是我要講的重點,如果為了炫技或條件必須得情況下,使用閉包外,閉包能避免就避免,因為只要你使用了
閉包就意味著你在記憶體裡劃出了一塊地方,你的程式可能不一定會用,而電腦在運行時其他的程式卻一點不能利用的空間,
對程式的效能無疑有影響。

以上是JS閉包隨筆的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn