一個 JSer 的 Dart 學習日誌(二):變數、常量

知名噴子發表於2021-10-02
本文是“一個 JSer 的 Dart 學習日誌”系列的第二篇,本系列文章主要以挖掘 JS 與 Dart 異同點的方式,在複習和鞏固 JS 的同時平穩地過渡到 Dart 語言。
鑑於作者尚屬 Dart 初學者,所以認識可能會比較膚淺和片面,如您慧眼識蟲,希望不吝指正。
如無特殊說明,本文中 JS 包含了自 ES5 至 ES2021 的全部特性, Dart 版本則為 2.0 以上版本。

1. 關鍵字

(表面上的)共同點

  • 變數關鍵字 var
  • 常量關鍵字 const

不同點

1.1 var 變數的作用域

  • JS 中 var 關鍵字宣告的變數作用域是所在函式作用域,與此關聯的還有一個很經典的例子(就是 for迴圈裡 setTimeout 那個,不贅述);
  • Dart 中 var 關鍵字宣告的變數作用域是塊級作用域。
> /* JS */                          | // Dart
> function foo(){                   | foo(){
>     if(true){                     |     if(true){
>         var a = 321;              |         var a = 321;
>     }                             |     }
>     console.log(`a = ${a}`);      |     print('a = $a');
>   /* a = 321; */                  | // Getter not found: 'a'.
> }                                 | }

1.2 const 的語義不同

  • JS 中, const 宣告的常量可以是執行過程中臨時計算得來的;
  • Dart 裡,const 宣告的常量必須是編譯時常量,即在執行前就必須確定它的值。
> /* JS */                          | // Dart
> var a = Date.now();    /* OK */   | var a = new DateTime.now();   // OK
> const b = Date.now();  /* OK */   | const b = new DateTime.now(); // NOT OK

2. 各自的特色

這裡算是兩種語言的特色語法大賞,因此不再列舉它們的共同點。

2.1 finallatelet,各自的特色關鍵字

  • JS 中引入了 let 關鍵字來彌補 var 的設計缺陷,但是 Dart 中的 var 本身沒有這些缺陷,大概是因此,Dart 沒有 let 關鍵字,捨不得 let 的話,可以去隔壁學習 Rust;
  • Dart 中的 const 關鍵字宣告的常量需要在編譯時確定值,但是在日常程式設計的時候,我們有需求固定一些在執行中才能確定其值的變數,防止意外被修改,也可以給編譯器提供一定的優化參考,這時候我們可以用 final,其特性可以參考 JS 中的 const
  • JS 中未指定初值的變數預設值為 undefined,而在 Dart 中雖然也有配合“空安全”的預設值 null,但是為了編寫健壯性更高的程式碼, 可以使用 late 宣告變數,表示變數沒有初值,時機成熟後會為其賦值。
/* JS only */                            | /* Dart only */
> let x = 0;                             |
>                                        | final x = new DateTime.now();
> let msg;                               | late String msg;

2.2 Dart 支援宣告變數型別

  • Dart 是一門強型別的語言,宣告變數/常量的同時也可以顯式地宣告其型別,但變數型別與var關鍵字不能並列使用。

    > var int a = 0; // 錯誤,`var` 與變數名不得並列使用
    > var     a = 0; // 正確,Dart 會推斷型別
    >     int a = 0; // 正確,得到一個型別為 int 的變數
    >
    > const int a = 0; // 正確,得到一個型別為 int 的編譯時常量
    > final int a = 0; // 正確,得到一個型別為 int 的常量
    >
    > Set<num> a = {0}; // 正確, 所得 Set 可包含 int 和 double 子項
事實上,如果不顯式宣告變數型別,Dart 會根據所賦的值來推斷變數的型別。var a = 0中,a的型別將被推斷為 intvar a = 0.1中,a 的型別則為 double

2.3 Dart 用const確定編譯時常量

Dart 中的 const 宣告的變數值在編譯時就已經確定了,這應該是出於效能優化考慮,將一些執行時的計算量轉移到編譯過程,或避免在同一程式中重複執行的額外開銷。
  • 編譯時就能確定變數值這麼好的功能,如果只有 const 能享受的話,就太浪費了,所以宣告變數的值為複雜資料型別的時候,可以用 const 關鍵字標名這個值是一個編譯時常量:

    var a = const { 123456 };
    Set<num> a = const { 123456 };

    ,但這個語法不適用於簡單型別:

    var a = const 123456; // 這將無法通過編譯

2.4 JS 省略關鍵字 var

  • 在 JS 中,可以省略 var ,直接命名一個變數並賦值,這個變數會自動成為一個全域性變數,此即 JS 的“隱式全域性變數”,這是一個名聲不佳的 JS 特性,開發中應避免使用;
本文 2.2 已經提到:Dart 的 var 關鍵字與型別宣告不能並列使用,從某種意義上來講,也算是省略了關鍵字 var

2.5 Dart 不存在變數提升

  • 變數提升也是 JS 中的一個槽點,所幸 ES6+ 的 letconst 以“死區”的方式避免了這個槽點,證明 TC39 也不喜歡這個特性;
  • Dart 是一門“正常”的語言,遵循變數先宣告後使用的原則,不存在變數提升。
> /* JS */                         | // Dart
> var b = a + 1;                   | var b = a + 1;
> var a = 100;                     | var a = 100;
> console.log(`b = ${b}`);         | print('b = $b');
> // b = NaN                       | // 無法通過編譯

3. 總結 & 對比

參見下表:

特性JSDart
var函式級變數關鍵字塊級變數關鍵字
const常量關鍵字編譯時常量關鍵字
let塊級變數關鍵字沒有此關鍵字
final沒有此關鍵字執行時常量關鍵字
late沒有此關鍵字未指定初值的變數關鍵字
型別宣告不支援型別宣告支援型別宣告,也可交由 Dart 自動推斷型別

相關文章