本文是“一个 JSer 的 Dart 学习日志”系列的第三篇,本系列文章次要以开掘 JS 与 Dart 异同点的形式,在温习和坚固 JS 的同时安稳地过渡到 Dart 语言。
鉴于作者尚属 Dart 初学者,所以意识可能会比拟浮浅和全面,如您慧眼识虫,心愿不吝指正。
如无非凡阐明,本文中 JS 蕴含了自 ES5 至 ES2021 的全副个性,Dart 版本则为 2.0 以上版本。在 ES6 问世之前,宽泛风行的 JS 面向对象编程是应用 原型链 而非应用类,开发者须要对相干个性有足够的理解,并遵循一些默认的规定,能力勉强模拟出一个大抵可用的“类”。即使是 ES6 引入了
class
关键字来补救,作为新一代 JS 基础设施的类还是有待欠缺。
相比之下,Dart 对类的反对就要欠缺和弱小得多。
一. 类似的整体构造
-
两种语言中,用于定义类的语法结构高度类似,次要包含
class
关键字、类名、包裹在花括号{}
外部的成员。> /* Both JS and Dart */ > class ClassName { > attrA; > attrB = 1; > > methodA(a, b){ > // do something > this.attrA = a; > this.attrB = b; > } > }
二. 构造函数
相同之处
- 构造函数在实例化类的时候调用,用于解决实例化参数、初始化实例属性等;
- 应用
super
拜访超类的构造函数; - 没有超类,或超类的构造函数没有参数的时候,构造函数能够省略,省略构造函数的子类实例化的时候会隐式地调用超类的构造函数。
不同之处
1. constructor
vs SameName
- JS 中的构造函数为
constructor
; -
Dart 中的构造函数为 与类名统一的函数。
> /* JS */ | /* Dart */ > class Point{ | class Point{> constructor(){| Point(){>} | } > } | }
Dart 构造函数特有的性质
命名式构造函数
-
在 Dart 中 能够为一个类申明多个命名式构造函数,来表白更明确的用意,比方将一个
Map
对象映射为一个实例:> class PointCanBeEncode{ > int x = 0; > > // 名为 `eval` 的命名式构造函数 > Point.eval(Map<String, dynamic> map){> x = map['x']; > } > > encode(): Map<String, dynamic>{ > return { > 'x': this.x > } > } > }
2. 属性赋值语法糖
-
大多数状况下,构造函数的作用包含 将给定的值作为实例的属性,Dart 为此情景提供了一个非常不便的语法糖:
> /* Dart */ | /* Also Dart */ > class Point { | class Point {> Point(this.x, this.y); | Point(x, y){>} | this.x = x; > | this.y = y; > | } > | }
↑ 能够看到右边的代码显著要简洁得多。
3. 初始化列表
-
Dart 能够在构造函数执行之前初始化实例变量:
class Point { final double x, y, distanceFromOrigin; Point(double x, double y) : x = x, y = y, distanceFromOrigin = sqrt(x * x + y * y) {print('still good'); } }
初始化列表的执行理论甚至早于父类构造函数的执行机会。
4. 重定向构造函数
-
Dart 能够有多个构造函数,可将某个构造函数重定向到另一个构造函数:
class Point { double x, y; Point(this.x, this.y); Point.alongXAxis(double x) : this(x, 0); }
除了默认参数外没看到啥应用场景,试了一下
alongXAxis
仿佛不能有函数体。。。
5. 常量构造函数
-
如果类生成的对象都是不变的,能够在生成这些对象时就将其变为编译时常量。你能够 在类的构造函数前加上
const
关键字 并确保所有实例变量均为final
来实现该性能。class ImmutablePoint { // 所有变量均为 final final double x, y; // 构造函数为 const const ImmutablePoint(this.x, this.y); }
6. 工厂构造函数
- JS 是一门相当灵便的语言,构造函数没有返回值的时候可视为返回新的实例,但同时构造函数也能够返回任何值作为新的实例;
-
Dart 中则能够应用
factory
关键字,来申明有返回值的构造函数。> /*************** 别离用两种语言实现单例模式 *****************/ > /* JS */ | /* Dart */ > class A { | class A { > static single; | static var single; > | A(){} > constructor() { | factory A.single(){> if(!A.single) {| if(A.single == null) {> A.single = this; | A.single = A(); > } | } > return A.single; | return A.single; > } | } > } | }
工厂构造函数内不能拜访
this
。
7. 抽象类
-
应用
abstruct
关键字申明抽象类,抽象类罕用于申明接口办法、有时也会有具体的办法实现。上面会提到形象办法,形象办法只能在抽象类中。
三. 应用
相同之处
- 均可应用
new
关键字实例化类; - 应用
.
拜访成员; - 应用
extends
关键字扩大类,并继承其属性。
> /* Both JS and Dart */
> var instance = new ClassName('propA', 42);
> instance.attrA; // 'propA'
不同之处
1. Dart 可省略 new
关键字
-
Dart 实例化类的
new
关键字能够省略,像应用一个函数那样地初始化类:> var instance = ClassName('propA', 42);
;
- ES5 的
类
也是函数,省略new
关键字的话等于执行这个函数,而 ES6 的类不再是函数,省略new
关键字会出错。
2. Dart 命名式构造函数
-
有了“命名式构造函数”,就能以更为灵便的形式创立一个实例,比方疾速地将一个
Map
的属性映射成一个实例:> var instance = PointCanBeEncode.eval({'x': 42});
如果有存储、传输实例的需要,能够通过
实例 -> Map/List -> JSON 字符串
的计划序列化一个实例,而后通过JSON 字符串 -> Map/List -> 新实例
的办法将其“复原”。
3. Dart 的编译时常量实例
-
常量构造函数能够实例化编译时常量,加重运行时累赘:
var a = const ImmutablePoint(1, 1);
;
- 而 JS 基本没有编译时常量。
侧面阐明了原生类型的构造函数都是常量构造函数。
4. 重写超类的成员
- 在 JS 中,子类的静态方法能够通过
super.xxx
拜访超类的静态方法,子类成员会笼罩同名的超类成员,但子类可在成员函数中用super.xxx
调用超类的成员(须为非动态成员、且要先在constructor
中调用super
); -
在
Dart
中,通过@override
注解来标名重写超类的办法(实测发现不写注解也能够,Lint 会有提醒,应该和环境设置无关)。> /* JS */ | /* Dart */ > class Super{ | class Super{> test(){} | test(){} > } | } > class Sub{ | class Sub{ > /* just override it */ | @override > test(){ | test(){> super.test(); | super.test()'; > } | } > } | }
Dart 的 mixin
-
在 Dart 中申明类的时候,用
with
关键字来混入一个没有constructor
的类,该类可由mixin
关键字申明:mixin Position{ top: 0; left: 0; } class Side with Position{}
四. 成员属性和成员办法
相同之处
- 成员函数外部应用
this
拜访以后实例,应用点号 (.
) 拜访成员; -
定义
getter
和setter
:> /* JS */ | /* Dart */ > class Test { | class Test { > #a = 0; | private a = 0; > get b(){ | get b(){ > return this.#a; | return this.a; > } | } > set b(val){| set b(val){ > this.#a = a; | this.a = val; > } | } > } | }
static
定义动态变量 / 属性。
不同之处
1. 类的闭合作用域
- JS 的类没有作用域,因而拜访成员必须用
this
; - Dart 的类有笔和作用域,在成员函数中能够间接拜访其余成员,而不用指明
this
。
> /* JS */ | /* Dart */
> const a = 3; | const a = 3;
> class Test{ | class Test{
> a = 1; | a = 1;
> test(){ | test(){> console.log(a === this.a); | print('${a == this.a}');
> /* 'false' */ | // 'true'
> } | }
> } | }
2. 公有变量 / 属性
- JS 中类实例的公有成员应用
#
前缀申明,拜访时也要带上此前缀; - Dart 中实例的公有成员应用
_
前缀申明,在“类作用域”中间接能够拜访。
> /* JS */ | /* Dart */
> class Test{ | class Test{
> #secret = 1234; | _secret = 1234;
> test(){ | test(){> console.log(this.#secret); | print('${_secret}');
> } | }
> } | }
JS 的公有成员是一个很“年老”的属性,在此之前,应用下划线命名公有成员是一个被 JS 社区宽泛承受的约定。
ES 最终没有钦定_
作为公有成员的申明计划,也没有采纳Java
和TS
中应用的private
,而是采纳了#
号语法。
TypeScript
:TC39 老哥,你这样让我很难办诶!
3. 操作符重载
JS 并不反对操作符重载,所以在某些场景中咱们须要自定义一些办法来进行实例之间的运算,比方多个向量相加可能会写成:
const d = a.add(b).add(c)
。
-
Dart 反对重载操作符,开发者能够为实例间的运算定制逻辑,比方向量运算:
class Vector{ final double x, y; const Vector(this.x, this.y); Vector operator +(Vector obj) => Vector(obj.x + x, obj.y + y); }
向量相加就能够写作
const c = a + b + c
。凭借操作符重载能够定义一些十分直观的语法,例如应用
&&
、||
求汇合与图形的交、并集。
4. 形象办法
- Dart 中的实例办法、Getter 办法以及 Setter 办法都能够是形象的,定义一个接口办法而不 去做具体的实现让实现它的类去实现该办法 ,形象办法 只能存在于抽象类中。