本文是“一個 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 final
、late
與 let
,各自的特色關鍵字
- 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
的型別將被推斷為int
;var 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+ 的
let
和const
以“死區”的方式避免了這個槽點,證明 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. 總結 &
對比
參見下表:
特性 | JS | Dart |
---|---|---|
var | 函式級變數關鍵字 | 塊級變數關鍵字 |
const | 常量關鍵字 | 編譯時常量關鍵字 |
let | 塊級變數關鍵字 | |
final | 執行時常量關鍵字 | |
late | 未指定初值的變數關鍵字 | |
型別宣告 | 支援型別宣告,也可交由 Dart 自動推斷型別 |