前端要給力之:語句在JavaScript中的值

Hug7123 8年前發布 | 12K 次閱讀 JavaScript開發 JavaScript ECMAScript

目錄

這兩天在寫語言精髓那本書的第三版,討論到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:

  1. Let exprRef be the result of evaluating Expression.
  2. If ToBoolean(GetValue(exprRef)) is true, then
    a. Return the result of evaluating the first Statement.
  3. Else,
eval("1;;;;;")
eval("1;{}")
eval("1;var a;")

value producing item ”這個說法在ES5中是叫“ value producing Statement ”。但是,我并沒有在ES5/ES6中找到一個明確的說法:哪些語句是生成值的語句呢?

研究這個是不是閑得那個啥疼?

那個啥疼不疼跟這個毛線關系也沒有。研究這個其實是很重要的一件事情,因為下面這行代碼到底怎么解釋,取決于我們這里的研究:

// sourceText at remote
if (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語句結果也就為空。他的定義很簡單,是這么寫的:

The production IfStatement :

if ( Expression ) Statement else Statement

is evaluated as follows:

  1. Let exprRef be the result of evaluating Expression.

  2. If ToBoolean(GetValue(exprRef)) is true, thena. Return the result of evaluating the first Statement.

  3. 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中得到了統一。

來自: http://blog.csdn.net/aimingoo/article/details/51136511

 本文由用戶 Hug7123 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!