點燈坊

失くすものさえない今が強くなるチャンスよ

ECMAScript 之 ASI

Sam Xiao's Avatar 2020-04-24

ECMAScript 行尾要不要加 Semicolon 一直是很爭議的議題,傳統都會加上 ;,但最近則發現越來越多 Project 都不加,如 Vue、Redux … 等,到底 ASI 是什麼 ? 行尾不加 ; 會有什麼問題嗎 ?

Version

macOS Catalina 10.15.4
VS Code 1.44.2
Quokka 1.0.287
ECMAScript 2020

ASI

let f = _ => {
  let x = 1, y = 2;

  return 
    x + y;
}

f() // ?

若將 x + y 換行,結果會回傳 undefined

asi000

let f = _ => {
  let x = 1, y = 2;

  return;
  x + y;
}

f() // ?

因為 ASI 會自動在 return 之後加上 ;,因此回傳 undefined

asi001

let f = _ => {
  return 
  {
    title: 'FP in JavaScript'
  }
}

f() // ?

若回傳 object literal 換行,結果也會回傳 undefined

asi003

let f = _ => {
  return; 
  { title: 'FP in JavaScript' };
}

f() // ?

因為 ASI 會自動在 return 之後加上 ;,因此回傳 undefined

ASI
Automatic Semicolon Insertion
ECMAScript 會自動在行尾加上 ;

asi003

ASI Exception

ASI 並不是在所有行尾都會加上 ;,以下是例外:

[]、{}、() 之中的行尾

let f = _ => {
  let a = [ 
    1,
    2,
    3
  ]

  return a
}

f() // ?

[] 中行尾不會有 ASI。

asi004

let f = _ => {
  let o = {
    title: 'FP in JavaScript',
    price: 100
  }

  return o
}

f() // ?

{} 中行尾不會有 ASI。

let f = (x, 
  y, 
  z
) => {
  return x + y + z
}

f(1, 2, 3) // ?

() 中行尾不會有 ASI。

asi005

行尾為 . 或 ,

let f = o => {
  return o.
    price
} 


f({ price: 100 }) // ?

行尾為 . 時不會有 ASI。

asi006

let f = () => {
  let x = 1, 
      y = 2
  
  return x + y
}

f() // ?

行尾為 , 時不會有 ASI。

asi007

結尾為 ++ 或 –

let f = x => {
  ++
  x  
  return x
}

f(3) // ?

行尾為 ++ 時不會有 ASI。

asi008

let f = x => {
  --
  x  
  return x
}

f(3) // ?

行尾為 -- 時不會有 ASI。

asi009

for()、while()、do、if()、else 之後沒 {}

let f = _ => {
  let result = []

  for (let i = 0; i < 3; i++)
    result.push(i)

  return result
}

f() // ?

for() 內若只有一行可省略 {},此時行尾不會有 ASI。

asi010

let f = _ => {
  let result = []

  let i = 0
  while (i < 3)
    result.push(i++)

  return result
}

f() // ?

while() 內若只有一行可省略 {},此時行尾不會有 ASI。

asi011

let f = _ => {
  let result = []

  let i = 0
  do
    result.push(i++) 
  while (i < 3)
    
  return result
}

f() // ?

do() 內若只有一行可省略 {},此時行尾不會有 ASI。

asi012

let f = x => {
  if (x === 1)
    return x + 1
  else
    return x + 2
}

f(1) // ?
f(2) // ?

if()else 內若只有一行可省略 {},此時行尾不會有 ASI。

asi013

下一行為 [(+-*/,.

let f = _ => {
  let a = 
    [1, 2, 3]

  return a
}

f() // ?

下一行為 [ 時,此時行尾不會有 ASI。

asi014

let f = (x, y) => {
  let a = 
    (x + y)
    
  return a
}

f(2, 1) // ?

下一行為 ( 時,此時行尾不會有 ASI。

asi015

let f = (x, y) => {
  let a = x 
    + y
    
  return a
}

f(2, 1) // ?

下一行為 + 時,此時行尾不會有 ASI。

asi016

let f = (x, y) => {
  let a = x 
    * y
    
  return a
}

f(2, 1) // ?

下一行為 * 時,此時行尾不會有 ASI。

asi017

let f = (x, y) => {
  let a = x 
    / y
    
  return a
}

f(2, 1) // ?

下一行為 / 時,此時行尾不會有 ASI。

asi018

let f = o => {
  return o
    .price
} 

f({ price: 100 }) // ?

下一行為 . 時,此時行尾不會有 ASI。

asi019

let f = _ => {
  let x = 1
     ,y = 2
  return x + y
}

f() // ?

下一行為 , 時,此時行尾不會有 ASI。

asi020

Must Use Semicolon

雖然有 ASI 讓我們減少使用 ; 機會,但有些場合卻一定要使用 ;

for()

let f = _ => {
  let result = []

  for (let i = 0; i < 3; i++)
    result.push(i)

  return result
}

f() // ?

for() 內三個 expression 一定要用 ; 隔開。

asi021

Multiple Statement / Expression

let f = _ => {
  let x = 0; x++
  return x
}

f() // ?

let x = 0x++ 兩個 statement 或 expression 要寫在同一行時,一定要使用 ; 隔開。

asi022

switch()

let f = x => {
  let result = 0

  switch(x) {
    case 0: result = x + 1; break
    case 1: result = x + 2; break
    case 2: result = x + 3; break
    default: result = x + 4
  }

  return result
}

f(0) // ?
f(1) // ?
f(2) // ?
f(3) // ?

case 內的 statement 若與 break 要寫在同一行,要使用 ; 隔開。

asi023

[ 開頭

let i = 1
[1, 2, 3].map(x => x + i) // ?

這是 ECMAScript 行尾不加 ; 常遇到問題,因為之前提到 ASI exception 中,當下一行為 [ 時不會加上 ;,因此 let i = 1[1, 2, 3].map() 視為同一行。

asi024

let i = 1;
[1, 2, 3].map(x => x + i); // ?

傳統在行尾都加上 ; 一定沒問題。

asi025

let i = 1
;[1, 2, 3].map(x => x + i) // ?

在行首加上 ; 強制與上一行分開也行。

若使用 ASI,這是少數 ; 要加在行首例外

asi026

以 Backtick 開頭

let x = 123

`${x}` // ?

這是 ECMAScript 行尾不加 ; 常遇到問題,因為之前提到 ASI exception 中,當下一行為 [ 時不會加上 ;,因此 let x = 123${x} 視為同一行。

asi027

let x = 123;

`${x}`; // ?

傳統在行尾都加上 ; 一定沒問題。

asi028

let x = 123

;`${x}` // ?

在行首加上 ; 強制與上一行分開也行。

若使用 ASI,這是少數 ; 要加在行首例外

asi029

+ 開頭

let x = '123'

+x // ?

這是 ECMAScript 行尾不加 ; 常遇到問題,因為之前提到 ASI exception 中,當下一行為 + 時不會加上 ;,因此 let x = 123+x 視為同一行。

asi030

let x = '123';

+x; // ?

傳統在行尾都加上 ; 一定沒問題。

asi031

let x = '123'

;+x // ?

在行首加上 ; 強制與上一行分開也行。

若使用 ASI,這是少數 ; 要加在行首例外

asi032

IIFE

let inc = x => x + 1

(function() {
  let result = inc(2)
  console.log(result)
})()

這是 ECMAScript 行尾不加 ; 常遇到問題,IIFE 會在新的一行使用 () 刮起來,最後使用 () 執行之,因為之前提到 ASI exception 中,當下一行為 ( 時不會加上 ;,因此 IIFE 與 let inc = x => x + 1 視為同一行。

asi033

let inc = x => x + 1;

(function() {
  let result = inc(2)
  console.log(result)
})();

傳統在每行結尾都加上 ; 一定沒問題。

asi034

let inc = x => x + 1

;(function() {
  let result = inc(2)
  console.log(result)
})()

只在 ( 前加上 ; 強制與上一行分開也行。

若使用 ASI,這是少數 ; 要加在行首例外

asi035

let inc = x => x + 1

void function() {
  let result = inc(2)
  console.log(result)
}()

若你覺得 ; 放在最前面很怪,也可以改用 void operator,它也會執行 IIFE,也不須再行首加 ;

asi036

Must Not Use Semicolon

有些場景剛好相反,一定不能使用 ;

for()

let f = _ => {
  let result = []

  for (let i = 0; i < 3; i++;)
    result.push(i)

  return result
}

f() // ?

for() 在括號內最後一個 expression 之後不能加上 ;

asi037

let f = _ => {
  let result = []

  for (let i = 0; i < 3;)
    result.push(i++)

  return result
}

f() // ?

for() 內若只寫兩個 expression 之後加上 ; 是合法的,因為其省略第三個 expression。

asi038

() 之後

let f = x => {
  if (x === 1); {
    return 2
  }
}

f(1) // ?
f(2) // ?

() 之後加上 ; 於 syntax 合法,但邏輯是錯的。

asi039

let f = x => {
  if (x === 1);

  return 2
}

f(1) // ?
f(2) // ?

() 之後加上 ; 相當於分開兩個 statement。

asi040

May Use Semicolon

有些場景 ; 可加可不加,一般建議不加。

{} 之後

function f() {
  return 1
}

f() // ?

Function declaration 使用 {} 時,在行尾 ; 可加可不加,一般建議不加。

asi041

let x = 1

if (x === 1) {
  x = 2
}

x // ?

if 使用 {} 時,在行尾 ; 可加可不加,一般建議不加。

asi042

let f = x => {
  let result = 0

  switch(x) {
    case 0: result = x + 1; break
    default: result = x + 4
  }

  return result
}

f(0) // ?

switch 使用 {} 時,在行尾 ; 可加可不加,一般建議不加。

asi043

Summary

看到以上這麼多規則可能都暈了,事實上只有兩條規則

  • ECMAScript 行尾不用加 ;
  • 一行以 [(+-*/,. 、backtick 開頭,且與上一行無關時,則要在上一行 行尾 加上 ;,或在本行 行首 加上 ;

這個例外常出現在以下情況:

  • [] array literal 開頭使用 Array.prototype 的 method,且與上一行無關時
  • 以 backtick 開頭做 template string,且與上一行無關時
  • + 開頭將 String 轉 Number,且與上一行無關時
  • () 做 IIFE,且與上一行無關時

其他幾乎就很少遇到例外,可安心使用 ASI。

Conclusion

  • ECMAScript 並不是全部都在行尾加上 ; 就了事,有些情況是不能加 ;
  • 反對行尾不加 ; 者多半以 [] 在一行開頭與 IIFE 例外為主要反對理由,實務上會遇到的例外大概只有 4 個
  • ECMAScript 本來就不完美,但若要因為少數例外而因噎廢食,放棄原本美意的 ASI 則相當可惜

Reference

Eddy Chang, JavaScript 裡的語句用分號結尾是個選項嗎 ?
Dr. Axel Rauschmayer, Speaking JavaScript