本文是“一个 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 办法都能够是形象的,定义一个接口办法而不去做具体的实现让实现它的类去实现该办法,形象办法只能存在于抽象类中。