JavaScript的“閉包”到底是什麼(2)

來源:互聯網
上載者:User

JavaScript的“閉包”到底是什麼(2)
我的上篇部落格標題不對,造成一些誤解。我認為部落格的宗旨不是背教科書,而是分享研發心得。我的上篇標題因該改成“JavaScript 閉包的一個議題:它對outer scope 的影響”,因為我沒有嚴格地去分析閉包的定義,而是分析了實現閉包的其中一個語義問題。 講清楚閉包是件麻煩事,我也沒有看到什麼關於JavaScript的權威性著作(比如像C++語言有 Bjarne Stroustrup的C++ programming language)。所以除了苦讀JavaScript語言國際標準《Standard ECMA-262 specification》,我無法推薦一個論述“閉包”的最好的教材。 網友“穆己”的“scope chaining”的確是比較接近實質,但也不全面。我只好拋磚引玉,再做一次企圖。 閉包的含義包含了下列三個主要概念: Lexical Scope and Scope ChainLexical Scope的概念並不是Javacript發明,但是它作為JavaScript函數的組成部分,是一個在“傳統”函數概念上的附加值。   傳統函數(C, C++, Java, C#等)的lexical scope 和runtime scope 是一樣的。JavaScript 的lexical scope指的是函數定義時的“環境”,而不是函數運行時的環境。  對於一個特定函數來說,其”自由變數”是這個函數閉包中需要俘獲的主要內容。自由變數(本函數沒有定義的變數)的lexical capture(俘獲)順序是 (也就是scope chaining 的順序):               A, 母函數的local 變數             B. 母函數的input argument             C.在母函數的母函數中重複A,B,直到最頂層(GLOBAL scope)   在下面 的 myObj  的定義中: var x = 1000;                               // line 0 function myObj(x, y) {                // Line1               this.func1 = function() { // Line2                         x++;                         y --;             }             this.get1 = function ()             {                         return x;             }             this.get2 = function ()             {                         return y;             }             var x = 0;                // Line 3 } myObj.prototype.AddTwo = function(z) {             return this.get1() + this.get2() + z; } var m1 = new myObj(10, 20);      // Line 4 var m2 = new myObj(30, 70);      // Line 5 console.log('m1.x: ' + m1.get1());  // Line 6 console.log('m1.y: ' + m1.get2());  // Line 7 console.log('m2.x: ' + m2.get1());  // Line 8 console.log('m2.y: ' + m2.get2());  // Line 9   對於上面的例子,如果不是lexical scope, line 6 ~ line 9 列印的應該是10, 20, 30, 70。 但是因為lexical  scope俘獲順序,x 是0(見line 3), 所以列印的是:0, 20, 0, 70。 注釋掉line3,根據俘獲順序,列印的就成了10, 20, 30, 70。 把myObj(x, y)改成myObj(z, y),列印的就成了1000, 20, 1000, 70。其中 1000是從global裡(Line 0)俘獲的。 Lexical 俘獲是在parsing stage進行的上面的俘獲順序必須在函數的parsing階段進行。函數的資料結構中在parsing後已經包含了所有“俘獲變數的reference”,運行階段不會改變了。這就是為什麼上面的line 3定義的可以優先於input 參數x的原因。若是執行時capture, line 3 是在函數的定義之後,該capture的因該說是input 參數x了。 C,C++等編譯語言是直接翻譯成native 函數的,所有的函數運行資訊都靠stack frame來動態擷取。唯一和閉包有所接近的概念是“全程變數(global variable)”. 這些global變數在編譯時間也都轉換成記憶體位址,運行時可以“就地解決”,無需一個獨立的閉包。這些函數不是object,不需動態產生,所以無需一個“靜態`”的閉包。 JavaScript之所以需要一個獨立的閉包,本人認為是因為所有的JavaScript都是object,可以“動態產生”,但是定義(第一道parsing)卻是靜態,這個“靜態”的部分需要閉包,動態部分和傳統函數一樣,靠runtime context 支撐。 這種“實現上的複雜性”,是為了閉包所帶來的,處理非同步事件時的方便付出的代價。 Lexical 俘獲是reference不是value這是我的上篇部落格想要強調的地方。如果上面的myObj執行時,如果俘獲的是x的值,那麼這三個函數func1,get1,get2就不會有任何聯絡了。   因為俘獲的是x的reference,  所以上面三個函數所看到的x是同一個變數。   這一點很重要,因為JavaScript中的local 變數並不是都是heap中的。起碼 GOOGLE  V2 就不是。但是上面line 3 的x必須在heap中“出生和生活”,否則func1,get1,get2就會在已經毀滅了的stack 變數x上工作,使得上面的程式變得毫無意義了。   也就是說,閉包的runtime代價是將所被閉包的變數從stack中轉到heap中。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.