標籤:官方 忽略 comment split 取值 ack source state es6
檔案夾
- 檔案夾
- 問題是語句有值嗎
- 那麼說你騙我咯
- 有啥米用呢
- 研究這個是不是閑得那個啥疼
- ES5ES6有什麼差異呢
- 結論是ES6是改了規則但更合理
- 最後不不過if語句
這兩天在寫語言精髓那本書的第三版,討論到ES6跟ES5中間對“語句的值”的不同處理。正好Weibo上也有同學對這個問題有興趣,所以專門整理了這篇。
寫部落格能夠囉嗦點,寫書就不行了。所以這篇文章跟書上能看到的還是會不一樣的。
問題是:語句有值嗎?
非常不幸。我們面臨的的確是一門連語句都有值的語言。在JavaScript中。代碼是按語句行(Statement Lists)來解釋的,所以eval()本質上還是啟動並執行語句行。比如:
eval("1+2+3")
實際上並非在計算運算式,而是在解釋運行“代碼文本”。由於一個文字區塊隱含的有一個“文本/檔案結束符(EOF)”,它與行結束符(EOL)一樣能夠等效於JavaScript的語句分隔字元。所以上述代碼等效於:
eval("1+2+3;")
假設你不想瞭解得這麼具體,那麼記住“JavaScript是按語句行啟動並執行”就好了。
那麼……這個語句的值究竟是什麼呢?非常不幸,上面這個示範範例中,語句的值和運算式值是一樣的,也是6。
那麼說,你騙我咯?
你看,JavaScript裡面有一類語句,叫運算式語句。非常不幸,你見到的絕大多數語句都是運算式語句。比如
Object.toString()
這裡是一個方法調用(運算式)。你在後面加個分號(;),在文法上那就成了一個語句行,於是就成了運算式語句。由於其實JavaScript也存在單值運算式,所以一個單值也能夠是一個語句。
這其實也就是函數或塊首放上個”use strict”一點點也不違和的原因——它是符合JavaScript傳統的文法慣例的:
function foo() { "use strict"; 1; true;}
上面的代碼是合法的,函數foo()內有三個單值語句。函式宣告本身也是一個語句。函式宣告(以及全部的顯式聲明語句)是沒有傳回值的——在ECMAScript中它被定義為返回Empty。
函數調用的傳回值是由於調用運算子“()”來決定的。這個運算子要求用return來傳回值,假設沒有則視為undefined。這就是JavaScript函數的一些特性的根源了。
“運算式運算”和“語句有值”能夠解釋JavaScript文法特性中的很多迷題,是這門語言在設計上的一些基本性質。
有啥米用呢?
由於eval()本質上是運行語句而不是運算式,所以語句怎樣傳回值就成了這個函數的終於特性。須要注意的是:不是eval()在求值,而是JavaScript代碼塊/語句行本身有值,而eval()不過返回這個值而已。假設沒有這個特性。Ajax也就做不成了,由於我們通經常使用Ajax/JSONP從遠端取個值過來,就是eval()“解析”一下,這裡就是用的“運行語句並取值”的特性。
在JavaScript中,語句有值,而語句塊(複合陳述式)的值就是這個塊中最後一個有值語句的值。按ECMAScript的原文是:
The value of a StatementList is the value of the last value producing item in the StatementList. For example, the following calls to the eval function all return the value 1:
The production IfStatement : if ( Expression ) Statement else Statement is evaluated as follows:
- Let exprRef be the result of evaluating Expression.
- If ToBoolean(GetValue(exprRef)) is true, then
a. Return the result of evaluating the first Statement.
- Else,
eval("1;;;;;")eval("1;{}")eval("1;var a;")
“value producing item”這個說法在ES5中是叫“value producing Statement”。可是,我並沒有在ES5/ES6中找到一個明白的說法:哪些語句是產生值的語句呢?
研究這個是不是閑得那個啥疼?
那個啥疼不疼跟這個毛線關係也沒有。
研究這個其實是非常重要的一件事情,由於以下這行代碼究竟怎麼解釋,取決於我們這裡的研究:
// sourceText at remoteif (x) ( function aa() {})else ( function bb() {})
我們假設上面的程式碼片段來自於遠端。然後我們在通過ajax的方式得到它。稱為”sourceText”,那麼以下的代碼究竟是什麼結果呢?
x = true;foo = eval(sourceText);console.log(foo.name); // "name" property define in ES6
先解釋一下當中的aa/bb函數。注意這裡的兩個函數在文法上不是“函式宣告語句”,而是“函數運算式”——注意這裡用了“()”來強制它們為運算式。
這個也不是我亂講,在MDN(Mozilla Developer Network)官方文檔上面就是這麼分類的。“function statement”和“function expression”是兩個不同的東西。
“函式宣告語句”是無值的。而函數運算式是有值的,進而“函數運算式語句”也就是有值的。所以sourceText中。假設x是真。則if語句應該返回aa的值,否則該返回bb的值。於是。示範範例代碼中的:
foo = eval(sourceText);
才有意義,而終於控制台才會輸出”aa”。表明foo函數來自於aa()函數運算式。如今看來,以下這句話是真的實用了吧:
if語句的值。是其then/else分支中的statementList最後一個有值語句的值。
ES5/ES6有什麼差異呢?
這兩天寫書的時候發現一點跟此前理解的不同的地方(正好我又用了Nodejs中舊版的V8)。所以實在搞不清楚ECMAScript的定義出了問題,還是V8的實現出了問題。
於是乎在上抓了Hax要討論。無奈乎那個傢伙不理我——所以我決定這周去上海找他算賬。此話容後再講。
有什麼不同呢?
問題出在ES5中說。假設then/else分支中沒有語句,也就是statementList為empty,那麼if語句結果也就為空白。他的定義非常easy,是這麼寫的:
The production IfStatement :
? if ( Expression ) Statement else Statement
is evaluated as follows:
Let exprRef be the result of evaluating Expression.
If ToBoolean(GetValue(exprRef)) is true, then
a. Return the result of evaluating the first Statement.
Else,
a. Return the result of evaluating the second Statement.
假定“first Statement”為emptyStatement。結果當然就是empty。而對於empty,JavaScript會忽略這個“語句的值”。
這個意思是說:
1;{};;
上面三個語句中,第2、3兩行實際都是空語句——它們的值是empty。被忽略。所以整個代碼文本會返回1。
那麼依照這個規則。以下的代碼:
1; if (true);// 或1; if (false);
這兩種情況都應該返回1。這個就是在ES5中的情況了。
然而在ES6裡面,這段規範被寫成以下這樣:
// 4~5: let stmtCompletion be the result of first/second Statement
// 6: ReturnIfAbrupt(stmtCompletion).
7: If stmtCompletion.[[value]] is not empty, return stmtCompletion.
8: Return NormalCompletion(undefined).
這裡的意思是說:假設then/else的結果不是empty那麼就返回它們,否則。就得返回“undefined”。
於是以下這種示範範例:
1; if (true);
就該返回undefined了。
我當然一早就讀明白了ES6,我當時的問題在於。我用了Nodejs中的舊版V8,以及firefox/chrome的舊版本號碼來做測試——它們聲明支援了ES6。然而在這項特性上表現出來的,仍然是ES5的那個樣子。
於是我就懞逼了:這些聲稱支援ES6的引擎錯了。還是標準沒寫對呢?
正是由於對瞭解標準比瞭解指掌還要多的Hax沒有如期出現。所以一向覺得
“標準都是人寫的。是人寫的就會錯”
的我選擇了相信…… 相同也是一堆人(以及也是相同一堆人)寫的ES5。
——假設ES5是對的,那麼就是ES6寫錯了。
結論是:ES6是改了規則,但更合理
驗證這個結論的方法是:Chrome的新版中的新V8引擎,以及Firefox的新版本號碼都採用了ES6中的規範。當然,非常不幸,你假設用Nodejs來測試。至少目前的版本號(4.4.2/5.10.1)中還是錯誤的、依照ES5的規範來實現的。
那麼為什麼我終於會覺得ES6就“更合理”一點呢?
還是得回到“語句該不該有值”這個根本問題上來討論。
首先,ECMAScript是承認語句有值的,並且也同一時候承認“某些語句是沒有值/不產生值”的。比如說,空語句就不產生值,函式宣告、變數聲明等等也不產生值 。
——對於成批的語句來說,不產生值則在代碼上下文中對結果值無影響,產生值則影響結果。所以明白”哪些有值,哪些沒有值“是非常重要的。而ES5中,這個問題導致if語句的結果有不確定性。
既然:
假設then/else中的語句有值,則if有值。假設無值,則if無值。
那麼以下的代碼就是不確定語義的:
// sourceText at remote"hello";if (x) ( function aa() {})
當x是true時,if語句有意義。當x為false時,if語句在上下文中就沒意義了——它對結果值沒有影響。而
【ES5】if語句對結果值的影響存在不確定性
這個結果在語義設計上就是非常失敗的。
而到了ES6中:
【ES6】if語句總是有結果值的,要麼是then/else的結果,要麼是undefined
這就使得if有著確定的語義了。
最後。不不過if語句
寫ECMAScript 262的那票人真不是吃閑飯的(除了寫4th的時候),有些問題人家是真想得清楚。比方還是這個語句的值的問題,根本上來說不是“if語句怎麼回事”,而是“怎樣處理語句的值”的問題。
我昨晚基本的工作就是整理了全部這些語句在值上的效果,if/for/while/try等等語句在值的處理上驚人的一致。除了這些語句和運算式語句之外,就唯獨return/yield/throw用來顯式地返回結果了。
所以說。語句在“產生值(value producing)”上面的行為,在ES6中得到了統一。
前端要給力之:語句在JavaScript中的值