其實本來是想寫很長的,但是後來讀到阮一峯老師的《ES6標準入門》,感覺“前人之述備矣”,無心再寫下去。姑且放上已經寫好的這一節。
本文基於EMCA 2019規範。
之所以下定決心寫這個東西,是因爲今天遇到了一個問題。你肯定見過這樣的語句(暫且先不考慮==
和===
的問題,雖然我覺得一般都會用===
):
let a = 0;
while (a == 10) { ++a; }
可能你也和我一樣,曾經因爲少寫了一個等號而導致死循環:
let a = 0;
while (a = 10) { ++a; }
爲此,有一些人建議把變量寫在後面,因爲10不是一個左值,所以一旦少寫一個等號,就會報錯;這樣可以就在編譯期發現錯誤了:
let a = 0;
while (10 = a) { ++a; } // Expression must be a modifiable lvalue
說了半天,還沒說到主題;這只是一個引子。我不知道你有沒有想過,爲什麼類似於while (a = 10) {}
這樣的語句會死循環?按理來說,while語句是在條件爲真的情況下才會繼續運行,所以,a = 10
爲真嗎?或者循環結束的條件不是true這麼簡單?循環語句的工作原理是什麼?
諸如此類的奇怪的問題還有很多,比如:
-
for (let i = 0; i < 10; ++i) {} i; // Uncaught ReferenceError: i is not defined for (var i = 0; i < 10; ++i) {} i; // 10
-
{} + [] // 0 [] + {} // "[object Object]"
-
[] == ![] // true
也許你會覺得這種問題很無聊,記住就行了。我知道,網上流傳着各種各樣的解釋,確實有很多人講得通俗易懂,但是我覺得,無論如何,還是有必要稍微瞭解一下語言規範的;至少語言規範不太可能出錯(如果出錯,那未免有點太嚇人了),更何況這些都可以在語言規範裏找到答案。
其實,最主要的原因是,我始終覺得,無論別人講得再好,也不如自己親自去看看。用《東邪西毒》裏的那句臺詞概括一下吧:
每個人都會經歷這個階段,看見一座山,就想知道山後面是什麼。
切入正題。
完成記錄
可能有的人已經知道,表達式是有“返回值”的。不知道你有沒有留意過,在控制檯輸入表達式的時候,會有一個“返回值”出現,比如a = 10
返回的是10:
a = 10; // 10
當然,這個值一般是無法獲取到的。不過,有一個很不安全的方法可以獲取到這個值,那就是eval
:
let b = eval('a = 10;');
b; // 10
eval
的危害不必多說,我覺得你應該比我更清楚。所以,之前曾經有這麼一個提案,就是所謂的“do表達式”,專門用來獲取這個“返回值”的,可以讓代碼更加FP(函數式編程)一點:
let x = do {
let tmp = f();
tmp * tmp + 1
};
最主要的應用場景,可能還是像提出者所說的那樣,應用在JSX上:
return (
<nav>
<Home />
{
do {
if (loggedIn) {
<LogoutButton />
} else {
<LoginButton />
}
}
}
</nav>
)
扯遠了。事實上,表達式的“返回值”不僅僅是一個值,而是一個對象。在規範裏,這個“返回值”有一個專門的名字,叫完成記錄(Completion Record)。看看原文是咋說的:
The Completion type is a Record used to explain the runtime propagation of values and control flow such as the behaviour of statements (break, continue, return and throw) that perform nonlocal transfers of control.
這個主要是強調了兩點:
-
這個“返回值”是一個**記錄(Record)**類型。記錄類型是啥呢,按照規範裏的說法,相當於就是一個用來描述數據類型的鍵值對,同時鍵名加上
[[ ]]
,表示這是個內部屬性。大概長成這樣:{ [[Field1]]: 42, [[Field2]]: false, [[Field3]]: empty }
當然了,這裏的值都是抽象值,比如這個
empty
,不要誤會成變量了。 -
這個“返回值”是用來描述運行時的值和控制流的。
剛纔我們已經看到了記錄的結構,完成記錄作爲一種特殊的記錄,它的結構是這樣的:
鍵 | 值 | 解釋 |
---|---|---|
[[Type]] | normal, break, continue, return, 或throw | 完成的類型,用於描述控制流 |
[[Value]] | 任意值,或者爲空(empty) | 產生的值 |
[[Target]] | 字符串,或者爲空(empty) | 控制流的轉移目標,有點像goto語句 |
在控制檯顯示的,應該就是它的[[Value]]
的值。