從ES5 的 函式宣告與函數運算式說起.

來源:互聯網
上載者:User
我們先從阿灰 的蛋疼的例子開始.  demo1:
  function test() {        var x = 1;        with ({x: 2}) {            eval('function foo() { console.log(x); }');            eval('var bar = function() { console.log(x); }');        }        foo();        bar();    }    test();
  大多數按照ES5實現的引擎(除了Opera12-)的結果. 列印的都是 兩個2 .我必須先強調下.這個結果,按照標準是錯的. 按照標準都應該是 1 , 2 . Opera12- 會列印兩個1 .這個我們就不討論了.因為錯的更離譜了. 開始正題:為什麼說兩個2不對呢. 這就要從ES5的函式宣告,以及函數運算式說起了.坦白說.寫這篇東西的時候我很抗拒.因為要說清楚這裡邊的細節.實在涉及到太多的東西了.如果每個涉及到的概念都要說到位.有些浪費對ES5很熟悉的朋友的時間.而我並不認為不熟悉ES5的朋友通過一篇詳細的介紹就能建立起正確的認識.所以還是一切從簡吧. 我在這裡假設您知道什麼是 函式宣告的概念,以及函數運算式的概念,以及二者在文法層面的區別.以及 Lexical Environments , LexicalEnvironment,  VariableEnvironmet 以及之間的關係 以及 Environment Records ,  Declarative Environment Records, Object Environment Records 這些東西的相關概念.       ES5 中函數定義章節描述的兩種定義方式 :          1. 函式宣告          2. 函數運算式      這兩種方式定義時不僅僅是文法上的差異.還有建立函數對象時的一些細節差異. 我們要討論的就是 函式宣告使用其所在執行環境的當前的VariableEnvironmet  作為建立函數對象時提供的scope參數.而函數運算式則使用 LexicalEnvironment 作為scope參數.這就導致我們上面例子中的最直接的問題了.      我們知道,在非strict 模式下,ES5是允許使用with( expressionStatement 文法的. 但是我們要先明確幾件事.          1.  Statement  文法產生式中.是不能出現  FunctionDeclaration  的. 相對於我們類似的 catch從句的  block 文法產生式也是如此.(顯然所有瀏覽器都沒有按標準實現這部分規範,也就是說我所見過的所有javascript引擎的都允許在with,catch裡 甚至if  statement中等等位置使用函式宣告)          2. 我們的例子和第一條並不衝突. 因為我們前面例子中的函式宣告是放在eval中. 而eval code是 Program .是允許進行函式宣告的.          3. with會在其作用範圍內 建立一個新的LexicalEnvironment .並把其所在執行環境相關聯的LexicalEnvironment 設定為這個新建立的LexicalEnvironment .(也就是說with改變了當前執行環境的LexicalEnvironment)          4. with並沒有改變VariableEnvironmet.在改變LexicalEnvironment 前, LexicalEnvironment 和 VariableEnvironmet指向相同的引用.(with強迫他倆各玩各的了)          5. with 還會為這個LexicalEnvironment 建立一個object environment record .目的自然是 with( expression) 中的ToObject( expression)的結果作為標識符搜尋的整個責任鏈的頂端.          6. 非strict 模式下的eval內部的代碼被執行時.會使用eval的調用環境LexicalEnvironment,以及VariableEnvironmet  作為其內部代碼的執行環境的對應的 Lexical Environments(注意我這裡是複數.且單詞間有空格,希望你理解這裡的意思.不詳細解釋了)                現在問題來了.你應該注意到按照我們的已知條件. foo是函式宣告.那麼它的標識符尋找的下一個 Lexical Environment 顯然是原來的 VariableEnvironmet. 而 bar 的則是with建立的新的 LexicalEnvironment . 那麼顯然整個例子的結果就應該是1,2 . 但遺憾的是,我們還沒有找到一款已知的javascript引擎,在此處的實現是符合標準的....     但是知道這些,並不是我們今天討論的重點.我們的重點是.為啥標準會這樣定義.      以下部分為我個人猜測.  demo2:
function test(){
    var x = 1;
   

    with({x:2}){        function foo(){
            console.log(x);
        }

        var bar = function(){
                console.log(x);
        };
    }
    
    foo();

    bar();

}

test();

          如果我的對標準的解讀沒有錯誤的話。 我就可以斷定此處規範的制定者,自己陷入混亂. 他似乎就是在考慮類似demo2情形是.思路是這樣的:     foo的函式宣告,所定義的函數對象,不應該受到with的影響. 但是他似乎忘記了兩件事.           1. 函式宣告是在 控制器一進入執行環境,進行變數初始化階段進行的. 而with 語句的執行,卻是在解釋執行期進行的. 顯然foo的函數定義是早於 with執行的.當with執行時篡改當前執行環境相關聯的  LexicalEnvironment 時. 所有函式宣告早就處理結束了.foo.[[Scope]]早就引用了最初的LexicalEnvironment 了.根本就不應該受到with的影響. 所以這個設計完全是多餘的..            2. 設計者忘了 Statement 以及  blok 兩個文法產生式中根本就不存在 FunctionDeclaration   .那還折騰什麼呢? 所以從標準角度.也許根本就不應該生生的把Lexical Environments 拆分成 LexicalEnvironment 和 VariableEnvironmet  . 要對付with和catch前插的責任鏈節點. 只需要概念描述下即可.但可能是為了描述精準吧?畢竟他要描述with本身特有的那種特性.總之這個屁股並沒有在ES5裡被擦乾淨.           ps: 雖然所有已知引擎都沒有遵守第二條. 但我們討論的畢竟是標準.而不是實現的差異.     ps: catch從句有類似的建立並篡改當前執行環境LexicalEnvironment 的能力.不過它對應的產生式是 block.且catch並不會使用object environment record           ps: SpiderMoneky的早期版本 包括中間的各種跟猴子的分支.都支援  Block FunctionDeclaration  .這種被稱為語句塊函式宣告的特殊的,非標準文法.它會導致上面的demo2的結果有差異.不屬於本文討論範圍  簡單來說就是:
     typeof fn;// undefined      {         function fn(){}     }
        最後. 似乎ES6草案中 函式宣告也不再使用VariableEnvironmet  作為其[[Scope]]了. 不知道標準委員會中,是否有人意識到了這個問題.而做出了修正. 暫時沒精力繼續去問了. 我對ES6的態度還處於觀望狀態.什麼時候release狀態了.再去跟進好了..      好吧.就這樣吧. 感謝阿灰寫出了這個蛋疼的例子. 感謝 Bosn 和 kenny 還有abcd同學花時間大家一起討論它. 最後.真心話.相比ES5 取消了 scope chain 的概念而用 內外層詞法環境來代替. 我更喜歡ES3的描述.因為那種描述直接是資料結構上的描述.更適合碼農啊顯然.      
 最後,附加一點東西, 因為前陣子有看到,有朋友認為 function declaration 不能被當做statment那樣出現在 block中,是ES5strict 模式的一個限制. 下面來說說這個問題 .

關於 函式宣告出現在block 或 其他statement應該出現的地方的問題, ES5 12.0 章節末尾有這樣一段描述:

Several widely used implementations of ECMAScript are known to support the use of FunctionDeclaration as a Statement. However there are significant and irreconcilable variations among the implementations in the semantics applied to such FunctionDeclarations. Because of these irreconcilable differences, the use of a FunctionDeclaration as a Statement results in code that is not reliably portable among implementations. It is recommended that ECMAScript implementations either disallow this usage of FunctionDeclaration or issue a warning when such a usage is encountered. Future editions of ECMAScript may define alternative portable means for declaring functions in a Statement context.

 

基本上就是說,已知的絕大多數ES引擎,都支援把函式宣告,作為一個語句來處理的這種實現.   而實際上這帶來了語意衝突.  ES5的建議是禁止這種行為, 或者發出一個警告..  並且在將來的ES版本中 基於這兩種建議中的一個.來作為標準的行為… 

 

也就是說,更加強調ES引擎,不應該讓函式宣告 出現在block 等地方…   所以部分引擎(比如v8和猴子)在實現ES5 strict 模式時, 順便也明確禁止了這種行為(但應該明白, 這個strict 模式限制,並不來自ES5 strict 模式的官方限制.而是引擎實現者自己出於某種考慮而實現的)… 但我個人認為. 這個約束是多餘的. 因為本身我們觀察產生式就會發現.  本來就不應該出現這種支援的. 也就是說, 非strict 模式變體 , 也不應該出現這種使用函式宣告的方式… 何必多此一舉的在樣模式中強調呢? .. 哦 當然, 出於對事實現狀的尊重,如果粗暴進位非strict 模式變體代碼也拋異常的話, 老代碼可能就會出問題….所以 這也許是 部分引擎實現者的彌補措施吧.

 

至於ES6如何規定這裡, 我們拭目以待吧. 坐等ES6 release .

 

另附ES5strict 模式 一篇連結 : http://www.cnblogs.com/_franky/articles/2184461.html

 
    
        

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.