一個 JSer 的 Dart 學習日誌(五):基本資料型別

知名噴子發表於2021-10-17

致讀者

本文是“一個 JSer 的 Dart 學習日誌”系列的第五篇,本系列文章主要以挖掘 JS 與 Dart 異同點的方式,在複習和鞏固 JS 的同時平穩地過渡到 Dart 語言。
如無特殊說明,本文中 JS 包含了自 ES5 至 ES2021 的全部特性, Dart 版本則為 2.0 以上版本。
鑑於作者尚屬 Dart 初學者,所以認識可能會比較膚淺和片面,對知識的概括難免有所疏漏,如您慧眼識蟲,望不吝指正。本系列文章首發于思否平臺,勘誤及新增內容也僅限於思否,如您是在別的地方看到本文,請移步思否以免看到未修訂的內容。

關於

ES5 的五種基本型別(NumberStringNullUndefinedBoolean)是老生常談的面試題,ES2015 帶來的 Symbol 和 ES2019 新增的 BigInt 豐富了 JS 可用的基本型別。
在資料結構方面,JSer 們一開始只能基於內建的 ObjectArray 組織資料,其間常常伴著一些不成文的約束,直到 ES6 帶來了 MapSet 。而在內建資料型別方面,Dart 的起點明顯要比 JS 高。

JS 作為一門解釋性的語言,其語言標準對執行的宿主有所約束(雖然廠商未必遵循此標準),所以在 JS 的標準中可以明確記憶體的使用方式,例如除了變數的引用之外,某些資料也存放在棧記憶體中,這些資料即所謂“基本型別”資料。
而 Dart 往往需要編譯到其他語言,通過標準明確地約束宿主的行為只會徒增工作量,這也許就是 Dart 沒有明確規定“基本型別”的原因。

但這並不妨礙我們用 Dart 中那些功能相仿的型別與 JS 的基本型別作比。

一、 Number

共同點

  • 使用十進位制數字字面量宣告;
  • 使用十六進位制的字面量宣告;
  • 使用科學計數法宣告:

    /****** Both JS and Dart ******/
    var dec = 42;
    var hex = 0X2A;
    var sic = 4e2;

不同點

1. Dart 沒有二進位制字面量

  • JS 中使用 0b 開頭的語法宣告二進位制字面量(b也可大寫);
  • Dart 不支援二進位制字面量:

    > /*  JS  */                           | // Dart
    > var bin = 0b101010;                  | // var bin = 0b101010;
    >                                      | // 去掉上面的註釋,將無法通過編譯

2. intdouble

  • JS 的Number型別均以雙精度浮點型別儲存和計算;
  • Dart 的Number分為整形int和雙精度浮點型別double,以應對不同的表示和運算需求:

    > /*  JS  */                           | // Dart
    > var i = 1;                           | var i = 1;
    > var d = 1.1;                         | var d = 1.1;
    > console.log(typeof i);               | print(i.runtimeType);
    > console.log(typeof d);               | print(d.runtimeType);
    > // number                            | // int
    > // number                            | // double
    不過,使用 Dartpad 實測得知:
    如果小數點後只有 0的話,d.runtimeTypeint
    但如果將 d 宣告為 numint ,並作為double型別的函式引數,將無法通過編譯。
    是否說明 Dart 設計者其型別推斷功能過於自信?
另外在 Dart 中:
如果兩個數字相減為 0,那麼使用==判斷相等的時候,返回的是true
doubleint 相加,結果的型別為 double

3. 從其他型別建立數字

  • 在 JS 中使用 Number 建構函式可以建立數字(但不能使用new關鍵字),如果引數是一個其他型別的表示式的話,會將此表示式轉為數字型別;
  • 在 Dart 中僅可使用 num.parse/num.tryParse將字串轉為數字,:

    var a = Number('2');                | var a = num.parse('2');
    parse 遇到無法轉為數字的字串會拋錯;
    tryParse 遇到無法轉為數字的字串則返回null
    二者都會進行嚴格的引數型別匹配。

4. 特殊的數值

  • 對應 JS 中的 NaN,但是沒有NaN字面量;
  • Dart 中的 double.maxFinite對應 JS 中的 Number.MAX_VALUE(但是具體值要看宿主環境);
  • Dart 中的 double.minPositive 對應 JS 中的 Number.MIN_VALUE
  • Dart 中的 double.infinity 對應於 JS 中的 Number.POSITIVE_INFINITY
  • Dart 中的 double.negativeInfinity 對應於 JS 中的 Number.NEGATIVE_INFINITY
不知道算共同點還是不同點。
Dart 中的 int 型別更像 JS 中的 BigInt,上面這幾個值,它們——都!沒!有!

二、Boolean VS 布林型別

共同點

  • 使用字面量宣告。

不同點

1. 沒有隱式型別轉換和萬能的建構函式

  • JS 中可以在本該使用 Boolean 型別的地方使用值為任何型別的表示式,這些表示式的值在運算時會被轉換為一個布林值,此過程涉及一個非常複雜的真值表,其效果等同於呼叫Boolean(exp)
  • Dart 中使用bool型別的地方必須使用該型別,並且bool不是一個可執行的函式或建構函式,也沒有轉換其他型別值的方法。
因此在任何地方判斷真假的時候一定要顯式地將表示式轉為布林型別。

三、String VS Strings

共同點

  • 可使用單雙引號(即"')宣告字串字面量;
  • 使用反斜槓(\)轉義特殊字元;
  • 使用加號(+)拼接字串:

    /******  Both JS and Dart ******/
    var stringA = 'This is a string';
    var stringB = 'This\'s a string';
    var stringC = stringA + stringB;

不同之處

1. 多行字串

  • ES5 是沒有多行字串字面量的,使用 \n 表示換行符,ES6 新增了反引號(`)語法,可以直接回車輸入換行;
  • Dart 使用三引號('''""")語法表示多行字串:

    > /*  JS  */                           | // Dart
    > const a =                            | const a =
    >   `Line 1st                          |   '''Line 1st;
    > Line 2nd`;                           | Line 2nd''';
    > console.log(a);                      | print(a);
    > // Line 1st \nLine 2nd               | // Line 1st \nLine 2nd 

2. 字串模板

  • ES5 同樣沒有字串模板功能,開發者通常使用字串拼接或片段替換來向字串嵌入表示式,而 ES6 新增的反引號語法中可以使用${exp}向字串中來達到同樣的效果;
  • Dart 中所有字串均可使用 ${exp}$varName將表示式/變數的值嵌入字串中:

    > /*  JS  */                           | // Dart
    > const val = 'expression';            | const val = 'expression';
    > var oldVer = 'This is ' + val;       | const oldVer = "This is $val";
    > const newVer = `This is ${val}`;     | const newVer = 'This is ${val}';
    > console.log(oldVer === newVer);      | print(oldVer == newVer);
    > // true                              | // true

3. 沒有隱式轉換和建構函式

  • 在 JS 中,我們可以使用字串連線任何表示式得到一個新的字串,該表示式的.toString方法會被預設執行,另外也可以使用String(exp)將表示式轉為字串;
  • 在 Dart 中字串不能與其他型別的表示式進行連線,Strings也不是可呼叫的函式或建構函式。
藉助操作符過載功能,我們應該可以在 Dart 中實現隱式型別轉換,唯一的問題就是:我們有這個必要嗎?

4. raw字串

  • JS 沒有 raw字串,也沒有類似的概念;
  • Dart 中,在引號前加一個r,表示此字串中的斜槓不是轉義符:

    // Dart only
    const raw = r'In a raw string, not even \n gets special treatment.';

    這裡的 \n 不會被視作換行符。

當然,作為 UI 程式設計中最為常見的基本型別,字串有著很多便利的方法和豐富的用法,預計列舉這些異同點會佔用較大篇幅,此處按下不表。

四、Symbol

共同點

  • 使用 Symbol(String t)建立一個 Symbol

    /****** Both JS and Dart ******/
    const symbol = Symbol('symbol');

不同點

1. 不同 Symbol 例項的相等性

  • JS 中的不同 Symbol 是不相等的,即便建立時傳入的字串一致;
  • Dart 中向Symbol傳入的字串一致,則所得例項相等:

    > /*  JS  */                         | // Dart
    > const a = Symbol('symbol');        | const a = Symbol('symbol');
    > const b = Symbol('symbol');        | const b = Symbol('symbol');
    > console.log(a == b);               | console.log(a == b);
    > // false                           | // true

2. #號語法

  • 在 Dart 中可以使用 # 快捷地宣告一個常量Symbol

    #symbol;
    print('This is ${#symbol}');
    // This is Symbol("symbol")

    注意

    • 使用的時候也要帶上 # 號;
    • 宣告的Symbol明確是編譯時常量,因此不能再使用varconstlate等關鍵字,也不能使用 Symbol 宣告其型別。

五、Null VS Null && Undefined

  • JS 中有兩種空值:undefinednull,型別分別為undefinedNull,前者常作為未賦值變數的預設值,而後者往往作為一些介面無目標值時的返回值;
  • Dart 中只有 null 空值,作為未賦值變數的預設值,在空安全(sound null safety)模式下須使用 Type? 語法才能賦值:

    String? someString = null;
    // 空安全模式下,需要使用`?`,才能為變數宣告初值

相關文章