精讀ECMAScript規範:完成記錄(Completion Record)

其實本來是想寫很長的,但是後來讀到阮一峯老師的《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.

這個主要是強調了兩點:

  1. 這個“返回值”是一個**記錄(Record)**類型。記錄類型是啥呢,按照規範裏的說法,相當於就是一個用來描述數據類型的鍵值對,同時鍵名加上[[ ]],表示這是個內部屬性。大概長成這樣:

    { 
        [[Field1]]: 42, 
        [[Field2]]: false, 
        [[Field3]]: empty 
    }
    

    當然了,這裏的值都是抽象值,比如這個empty,不要誤會成變量了。

  2. 這個“返回值”是用來描述運行時的值和控制流的。

剛纔我們已經看到了記錄的結構,完成記錄作爲一種特殊的記錄,它的結構是這樣的:

解釋
[[Type]] normal, break, continue, return, 或throw 完成的類型,用於描述控制流
[[Value]] 任意值,或者爲空(empty) 產生的值
[[Target]] 字符串,或者爲空(empty) 控制流的轉移目標,有點像goto語句

在控制檯顯示的,應該就是它的[[Value]]的值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章