一个|一个 JSer 的 Dart 学习日志(五)(基本数据类型)

致读者

本文是“一个 JSer 的 Dart 学习日志”系列的第五篇,本系列文章主要以挖掘 JS 与 Dart 异同点的方式,在复习和巩固 JS 的同时平稳地过渡到 Dart 语言。
如无特殊说明,本文中 JS 包含了自 ES5 至 ES2021 的全部特性, Dart 版本则为 2.0 以上版本。
鉴于作者尚属 Dart 初学者,所以认识可能会比较肤浅和片面,对知识的概括难免有所疏漏,如您慧眼识虫,望不吝指正。本系列文章首发于思否平台,勘误及新增内容也仅限于思否,如您是在别的地方看到本文,请移步思否以免看到未修订的内容。
关于
【一个|一个 JSer 的 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; // 空安全模式下,需要使用`?`,才能为变量声明初值

    推荐阅读