阅读 language-tour 时做的一些笔记。
Important concepts
Dart 中一切皆对象,除了 null 之外,所有对象都继承自 Object
Dart 是强类型语言,但是类型标注是可选的,因为 Dart 中存在类型推断的机制,使用 var
关键字后编译器可以推断出具体的类型
当开启了空安全(dart ≧ v2.12)之后,对于可为空的值必须明确指出(用 ?
),可为空的值转换成不为空的值时使用 !
当需要接收任意对象时使用 Object?
Dart 支持泛型,比如 List<int>
Dart 支持顶层函数和局部函数,同样也支持顶层变量
标识符可以以字母或者 _
开头,Dart 中 _
开头的方法表示私有方法
Dart 中也分为表达式 expressions 和声明 statements ,表达式有返回值而声明无返回值
Dart 工具提供了两种类型的问题:警告和错误。警告表示代码可能无法正确执行,错误分为编译期错误和运行时错误,编译期错误会导致程序无法执行,而运行时错误是代码执行过程中抛出的异常。
Variables
变量保存了引用,上面的例子中,name 变量包含了一个引用,该引用指向了一个值为 Bob 的 String 对象。
当使用 var
关键字时,变量的类型可以自动被推断出来,当然你也可以显式指出对象的类型。除此之外,如果你不想限定变量的类型,则可以使用 Obejct
或者 dynamic
。
默认值 如果没有开启空安全 (dart ≧ v2.12),则所有未被初始化的变量都会被初始化为 null,哪怕是数字型的变量,因为 Dart 中一切都是对象。但是,如果开启了空安全,则所有不可为空的变量会被要求先初始化才能使用。
Late 变量 Dart 2.12 之后添加了 late
修饰符,主要有两种用途:
声明不可为空的顶层变量或者成员变量而不直接初始化值
延迟初始化变量(使用到时才初始化)
第一种情况很好理解,因为如果开启了空安全,则顶层变量和成员变量必须要在声明的同时进行初始化,否则编译期无法保证空安全,所以引进 late
之后表明我们不想立马初始化该变量,但是它会在稍后被初始化。
第二种情况则适用于,一个变量对于程序来说是非必须的,或者性能消耗比较大,则可以用 late
修饰,这样,只有在使用到该变量时程序才会初始化它。比如:
1 2 late String temperature = _readThermometer();
final 和 const final
修饰的变量只能被赋值一次,并且 final
修饰的顶层变量会在第一次被使用时初始化。
1 2 3 4 final name = 'Bob' ; final String nickname = 'Bobby' ; name = 'Alice' ;
const
变量是编译期常量(同时也是 final 的)。如果 const
修饰的是成员变量,则需要用 static const
。
1 2 const bar = 1000000 ; const double atm = 1.01325 * bar;
final
和 const
的不同之处在于,final
修饰的对象不能被修改,但是其属性可以被修改;而 const
修饰的对象和属性都不能被修改,它们是不可变的 。
Build-in types Numbers
int 和 double 都是 num 的子类,num 类型的数据包含了基本的操作符,比如加减乘以及 abs(), ceil(), floor(), 位运算等。
Strings
Dart 中使用 String 可以用单引号或者双引号。不过,在单引号中的 String 对象中需要对 '
进行转义。
我们可以使用 ${expression}
在 String 中引用变量或者表达式。
我们可以用 ==
比较两个 String 的值是否一致。
我们可以在 String 值之前加 r
来表示原始类型 (raw) 的 String,这样就不需要转义。
我们可以使用三个单引号或者双引号来创建多行 String,同样不需要在其中使用转义符。
我们可以使用字符相乘,但是字符必须在乘数系数的左边,如 🔥 * 3
。
我们可以使用字符填充方法,比如 paddingLeft
和 paddingRight
。
Booleans Dart 中使用 bool
表示布尔类型的值。
Lists Dart 中数组是用 List
表示的。可以用如下的方式创建数组:
1 2 3 4 5 6 var list = [1 , 2 , 3 ];var list = [ 'Car' , 'Boat' , 'Plane' , ];
Dart 2.3 之后添加了扩展运算符 (...
) 和空值敏感的扩展运算符 (...?
):
1 2 3 var list = [1 , 2 , 3 ];var list2 = [0 , ...list];assert (list2.length == 4 );
Dart 中还有提供了集合 if 和集合 for 的操作:
1 2 3 4 5 6 7 8 9 10 11 12 var nav = [ 'Home' , 'Mall' , 'Mine' , if (promoActive) 'Outlet' ];var listOfInts = [1 , 2 , 3 ];var listOfStrings = [ '#0' , for (var i in listOfInts) '#$i ' ];
关于其它常见的 API 见 collections 。
Sets Dart 中同样用 Set
类型表示无序且唯一的集合。
1 var halogens = {'fluorine' , 'chlorine' , 'bromine' , 'iodine' , 'astatine' };
Set 和 List 一样,支持扩展运算符、集合 if 和集合 for 的操作。
Maps Map 是包含了 key 和 value 集合,其中 key 必须唯一。Dar 中的 Map 同样使用花括号 {}
表示,而且优先级比集合 Set 更高:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var gifts = { 'first' : 'partridge' , 'second' : 'turtledoves' , 'fifth' : 'golden rings' }; var gifts = Map <String , String >(); gifts['first' ] = 'partridge' ; gifts['second' ] = 'turtledoves' ; gifts['fifth' ] = 'golden rings' ;var names = {}; const lightRed = Colors.red[200 ];
Runes and grapheme clusters Dart 中,使用 Runes 表示字符串的 Unicode 字符集。我们可以使用 characters 包下的类来操作字符。
1 2 3 4 5 6 import 'package:characters/characters.dart' ; ...var hi = 'Hi ✡️' ;print (hi);print ('The end of the string: ${hi.substring(hi.length - 1 )} ' );print ('The last character: ${hi.characters.last} \n' );
Dart 中的字符串使用 UTF-16 编码,如果要用字符串表示 Unicode 字符则需要使用 \uXXXX
的语法。
Symbols Dart 中使用 Symbol 对象表示操作符或者标识符,用 #
+ 修饰符表示。
Functions Dart 中一切都是对象,函数的类型用 Function 表示,而且函数也可以作为变量或者作为方法的参数进行传递。
1 2 3 4 5 6 7 8 9 void say(String content) { print (content); }bool isEvenNumber(int num ) => num > 0 && num % 2 == 0 ;void calculate<T>(Future<T> Function () body) {}
参数 Dart 中的函数除了可以有普通的参数,还可以是具名参数 或者可选位置参数 。如果使用了 Sound null safety (sdk ≧ 2.12),且没有指明默认值,则这两种类型参数都必须是可为空的,即参数后跟 ?
。
具名参数 具名参数 (Named parameters) 是必须写出名字的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void enableFlags({bool? bold, int? index}) {}void enableFlags({bool? bold = true }) {}void enableFlags({required bool? bold}) {} enableFlags() enableFlags( bold: true , hidden: false );
Dart 2.17 之后,可选具名参数的位置可以在普通参数之前:
1 2 3 4 5 List .generate(5 , (index) => index, growable: true );List .generate(5 , growable: true , (index) => index);
可选位置参数 可选位置参数 (Optional positional parameters) 和普通的参数唯一的不同之处是它是可选的😅。用 []
圈起来的参数即可选位置参数。比如:
1 2 3 4 5 6 7 8 void say(String title, [String? subtitle]) { print (title); if (subtitle != null ) print (subtitle); }void sing(String song, [String? instrument = 'Piano' ]) { }
main() 函数 每个应用都有一个 main() 函数作为应用的入口。main() 函数通常返回空,并且可以有数组作为参数。
1 2 3 void main(List <String > arguments) { print (arguments); }
匿名函数 Dart 中的匿名函数和其它语言中类似,可以有多个参数或者没有参数,后跟方法体,形式如下:
1 2 3 ([[Type ] param1[, …]]) { codeBlock; };
如果只有单个的表达式或者只有返回值,可以使用箭头表达式:
1 ([[Type ] param1[, …]]) => expression;
Lexical scope Dart 同样是具有词法作用域 或静态作用域的语言,即只要还在代码作用域内的值,都能访问到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 bool topLevel = true ;void main() { var insideMain = true ; void myFunction() { var insideFunction = true ; void nestedFunction() { var insideNestedFunction = true ; assert (topLevel); assert (insideMain); assert (insideFunction); assert (insideNestedFunction); } } }
Lexical closures 词法闭包也叫函数闭包 ,和 JS 中的闭包概念类似,即定义在函数中的函数,同时使得该函数能够访问其它函数作用域中的变量。
1 2 3 4 5 6 7 8 9 Function makeAdder(int addBy) { return (int i) => addBy + i; }void main() { var add2 = makeAdder(2 ); print (add2(3 )); print (makeAdder(10 )(3 )); }
Operators Dart 中支持的操作符表,见 Operators 。
Cascade notation 1 2 3 4 5 6 7 var paint = Paint() ..color = 'Black' ..strokeCap = 'Round' ..strokeWidth = 5.0 ;print (paint);
Control flow statements switch and case 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var command = 'CLOSED' ;switch (command) { case 'EARLY_CLOSE' : case 'CLOSED' : var youCantAccess = 1 ; print ('bye bye' ); continue afterClosed; afterClosed: case 'CLOSED_AFTER' : print ('see you tomorrow' ); break ; }
Exceptions Dart 中所有异常都是 unchecked exceptions,意味着 Dart 不会强制你去捕捉异常。
Catch Dart 中的 try catch 语法:
1 2 3 4 5 6 7 8 9 10 11 12 try { breedMoreLlamas(); } on OutOfLlamasException { buyMoreLlamas(); } on Exception catch (error, stacktrace) { print ('Unknown exception: $error ' ); } catch (error) { print ('Something really unknown: $error ' ); }
Classes Dart 中一切皆对象,除了 null 之外,所有对象都是某个类的实例。除了可以继承类之外,Dart 还提供了一种基于 mixin 的继承,也就是说可以通过 with 关键字继承某个没有构造器的类来扩展功能,还可以使用扩展函数 。
Using class members 所有对象都由成员变量和方法构成,使用 .
的语法访问对象的属性或者方法,使用 ?.
访问可为空对象的属性和方法。
Using constructors Dart 中创建构造器除了可以使用类名,也可以使用 类名.构造器名()
的形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class DummyClass { int i = 0 ; DummyClass(this .i); DummyClass.create(int i, String message) { print (message); this .i = i; } DummyClass.ten() : i = 10 { print ('Ten ${this .i} ' ); } }
Getting an object’s type 我们可以使用 runtimeType
获得对象的类型 Type 。
1 2 var dummy = DummyClass.create(1 );print ('The type of a is ${dummy.runtimeType} ' );
Instance variables
所有未初始化可为空的实例变量都会被初始化为 null。
所有实例变量都会默认自带 getter 函数,非 final 的实例变量和不带初始化器的 late final 实例变量会自动生成 setter 函数。
非延迟初始化的实例变量的值会在对象创建之后,构造器和初始化器列表执行之前,就被赋值。可为空的赋值为 null,不可为空的赋值为初始值。
实例变量可以是 final 的,但是必须在构造器或者初始化列表中进行赋值。
Constructors Dart 中类的构造器和其它语言类似:
1 2 3 4 5 6 7 8 9 class Point { double x = 0 ; double y = 0 ; Point(double x, double y) { this .x = x; this .y = y; } }
不过,Dart 提供了一种语法糖的写法:
1 2 3 4 5 6 class Point { double x = 0 ; double y = 0 ; Point(this .x, this .y); }
具名构造器 我们可以通过具名构造器 (Named constructors) 为一个类实现多个构造器。不过具名构造器不能被继承,如果想在子类中使用和父类相同的构造器,只能为子类单独实现。
1 2 3 4 5 6 7 8 9 class Weather { var humidity = '' ; Weather.display(this .humidity); }class Shower extends Weather { Shower.display(String humidity) : super .display(humidity); }
构造器初始化列表 除了调用父类构造器之外,我们还可以在构造器方法体执行之前初始化成员变量,称为 Initializer list 。
1 2 3 4 5 6 Point.fromJson(Map <String , double > json) : x = json['x' ]!, y = json['y' ]!, super (x) { print ('In Point.fromJson(): ($x , $y )' ); }
初始化起列表中,调用父类构造器必须放到最后。
构造器初始化顺序 默认情况下,子类会先调用父类中的默认构造函数(未定义的话会生成一个无参构造器),如果存在构造器初始化列表 (Initializer list) 则会先调用它们。所以,当使用默认构造函数创建对象时,构造器的初始化顺序为:
构造器初始化列表
父类无参构造器
子类无参构造器
另外,如果父类未定义默认构造器,则子类实现构造器时必须先调用父类的某个具名构造器。
重定向构造器 1 2 3 4 5 6 7 8 class Point { double x, y; Point(this .x, this .y); Point.alongXAxis(double x) : this (x, 0 ); }
常量构造器 当你想要创建的对象是编译期常量时(比如用于创建注解),可以使用 const
修饰构造器:
1 2 3 4 5 6 7 class ImmutablePoint { static const ImmutablePoint origin = ImmutablePoint(0 , 0 ); final double x, y; const ImmutablePoint(this .x, this .y); }
使用常量构造器 (Constant Constructors) 初始化对象时,使用相同的值的对象只会在第一次创建时被初始化,也就是说相同值的对象只会初始化一次 。
工厂构造器 使用 factory
关键字的构造器,适用于不需要每次创建新对象的情况,比如使用了缓存。另一个使用场景是通过 JSON 数据初始化对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Logger { final String name; bool mute = false ; static final Map <String , Logger> _cache = <String , Logger>{}; factory Logger(String name) { return _cache.putIfAbsent(name, () => Logger._internal(name)); } factory Logger.fromJson(Map <String , Object > json) { return Logger(json['name' ].toString()); } Logger._internal(this .name); void log(String msg) { if (!mute) print (msg); } }
Getters and setters 使用 getters 和 setters 方法定义一些需要通过计算得到的值。
1 2 3 4 5 6 7 8 9 10 11 12 class Rectangle { double left, top, width, height; Rectangle(this .left, this .top, this .width, this .height); double get right => left + width; set right(double value) => left = value - width; double get bottom => top + height; set bottom(double value) => top = value - height; }
Abstract classes Dart 中的抽象类同样无法被直接实例化,如果需要实例化可以通过工厂构造器 。
1 2 3 4 5 abstract class AbstractContainer { void updateChildren(); }
Implicit interfaces Dart 中所有的类都可以作为接口被实现 (通过 implement
关键字):
1 2 class Point implements Comparable , Location {...}
我们可以使用直接继承获得所有父类的实现,也可以使用 implements
将类作为接口实现,此时子类必须提供所有 父类的属性和方法的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 abstract class Animal { abstract String name; int age; Animal(this .age); }class Dog extends Animal { @override String name = "Dog" ; Dog() : super (2 ); }class Cat implements Animal { @override String name = "Cat" ; @override int age = 3 ; }
Extending a class Dart 中所有类都可以被继承,使用 extends
关键字继承父类,使用 super
引用父类。
Dart 2.17 之后允许在构造器中使用 super 语法:
1 2 3 4 5 6 7 8 9 10 class OutlinedButton extends Button { const OutlinedButton({ super .key, super .onPress = () { print ('Pressed OutlinedButton' ); }, super .onLongPress, required Widget super .child }); }
这样就不需要调用父类构造器,写出 super(…) 这样的模板代码了。
Extension methods 和 Kotlin 类似,在 Dart 中我们同样可以通过扩展函数 扩充函数库。
1 2 3 4 5 6 7 8 9 extension NumberParsing on String { int parseInt() { return int .parse(this ); } double parseDouble() { return double .parse(this ); } }
Enumerated types Dart 中的枚举类和 Java 中类似。
1 enum Color { red, green, blue }
Dart 2.17 之后允许在 enum 类中使用成员变量:
1 2 3 4 enum Water { final int temperature; const Water(this .temperature); }
枚举类有以下限制:
无法继承、实现或者混用 (mixin) 枚举类
无法直接实例化
Adding features to a class: mixins Dart 中还提供了另外一种复用代码的方式 mixin,我们可以把可复用的代码放到 mixin 类中。子类使用 with
关键字进行关联。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 mixin Musical { bool canPlayPiano = false ; bool canCompose = false ; bool canConduct = false ; void entertainMe() { if (canPlayPiano) { print ('Playing piano' ); } else if (canConduct) { print ('Waving hands' ); } else { print ('Humming to self' ); } } }class Musician with Musical { }class Maestro extends Person with Musical { Maestro(String maestroName) : super (maestroName) { canPlayPiano = true ; } }
除此之外,我们还可以限制 mixin 的使用范围 (on
),以及像接口一样使用逗号 (,
) 分割继承多个 mixin:
1 2 3 4 5 6 7 8 9 class Musician { }mixin MusicalPerformer on Musician { }class SingerDancer extends Musician with MusicalPerformer , DancerPerformer { }
Class variables and methods Dart 中的类同样可以使用静态变量和常量,以及静态方法。
1 2 3 4 5 class DummyClass { static const a = 1 ; static var b = 2 ; static void f() { } }
Generics Dart 中的泛型(参数化类型)和 Java 以及 Kotlin 等语言非常相似,使用 <>
表示泛型。
为什么使用泛型 使用泛型一般有两个目的:
指明泛型类型之后,减少代码出错。比如在集合中加入不该加入的值。
使用泛型减少复制粘贴的代码。比如持有某个对象的类,当需要改变持有的对象而其它部分不发生变化时,如果使用了泛型就不用重新复制一个新的类。
Using collection literals 即在使用集合时定义集合类型,形式如 <type>[]
or <type>{}
or <keyType, valueType>{}
。
1 2 var names = <String >['Seth' , 'Kathy' , 'Lars' ];var uniqueNames = <String >{'Seth' , 'Kathy' , 'Lars' };
泛型集合及其类型 Java 中,由于泛型存在类型擦除 ,所以是无法确定集合的确切类型的。但是,Dart 中的泛型是 reified 的,也就是说在运行时也能得到集合的类型信息。
1 2 3 var names = <String >[]; names.addAll(['Seth' , 'Kathy' , 'Lars' ]);print (names is List <String >);
泛型参数类型的限制 Dart 中限制泛型参数边界同样使用 extends
关键字。
1 2 3 4 5 6 class Foo <T extends SomeBaseClass > { String toString() => "Instance of 'Foo<$T >'" ; }class Extender extends SomeBaseClass {...}
Libraries and visibility Dart 中,我们可以通过 import
和 library
指示符创建模块化的、可共享的代码库。另外,即使不使用 library
指示符,每个 dart app 都是一个 library。
导入一个 library 的语法:
其它库可以使用文件路径或者 package:
命名空间:
1 import 'package:test/test.dart' ;
指定库的前缀 如果两个库有相同的名称,可以通过 as
指定前缀解决冲突。
1 2 3 4 5 6 7 8 import 'package:lib1/lib1.dart' ;import 'package:lib2/lib2.dart' as lib2;Element element1 = Element (); lib2.Element element2 = lib2.Element ();
部分导入 如果只使用到库的一部分,可以使用部分导入的语法,通过 show
和 hide
做到。
1 2 3 4 5 import 'package:lib1/lib1.dart' show foo;import 'package:lib2/lib2.dart' hide foo;
实现 libraries 见 Create Library Packages
Asynchrony support Dart 中有很多返回 Future 和 Stream 对象的函数,我们可以使用它们编写出异步的代码。除此之外,Dart 还提供了 async
和 await
关键字,让你可以像 JS 一样,更方便地写出一些更具易读性的异步代码。
常见的用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 Future<String > lookUpVersion() async => '1.0.0' ; Future<String > checkVersion() async { var version = '' ; try { version = await lookUpVersion(); } catch (e) { } return verion; }
Stream 和 Future 的不同之处在于,它可以连续接收多个异步事件。我们可以通过 StreamBuilder 对持续发生变化的数据流进行监听,比如通过 WebSocket 获取数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 StreamBuilder( stream: webSocketChannel.stream, builder: (context, snapshot) { String message = '' ; if (snapshot.connectionState != ConnectionState.active || snapshot.hasError) { message = 'has error' ; } if (snapshot.hasData) { message = snapshot.data.toString(); } else { message = 'no data' ; } return Container( padding: EdgeInsets.symmetric(vertical: 10 ), child: Text('$message ' , style: Theme.of(context).textTheme.headline6), ); }, ), )
另外,我们还可以通过 Stream 实现事件总线的功能,比如 event_bus 就是基于 Stream 实现的。
Generators Dart 中的 Generator 函数和 ES6 中相似,如果你想要延迟生成一系列值,可以考虑使用生成器。Dart 中的 Generator 函数分为两种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Iterable <int > naturalsTo(int n) sync * { int k = 0 ; while (k < n) yield k++; } Stream<int > asynchronousNaturalsTo(int n) async * { int k = 0 ; while (k < n) yield k++; }Iterable <int > naturalsDownFrom(int n) sync * { if (n > 0 ) { yield n; yield * naturalsDownFrom(n - 1 ); } }
Callable classes 我们可以通过给某个类实现 call()
方法,使得该类可以像方法一样被调用。
1 2 3 4 5 6 7 8 9 class WannabeFunction { String call(String a, String b, String c) { print ('WannabeFunction.call()' ); return '$a $b $c !' ; } }var wf = WannabeFunction(); wf('Hi' , 'there,' , 'gang' )
Isolates 与其它语言的并发机制不同,Dart 中并没有采用共享状态的并发机制 (shared-state concurrency),而是使用了 isolates 。所有的代码都运行在自己的 isolate
中,每个 isolate
都有自己的内存堆,保证了相互独立性。
更多资料见:Isolates 。
Typedefs Dart 中,一切皆是对象,函数也是对象,但是函数对象的信息在运行时往往会被丢失,比如下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 class SortedCollection { Function compare; SortedCollection(int f(Object a, Object b)) : compare = f; }int sort(Object a, Object b) => 0 ; SortedCollection sc = SortedCollection(sort);assert (sc.compare is Function );
typedef
就是为了解决上面这个问题而出现的,我们可以给方法类型一个别名,这样,当方法被赋值到一个变量上时就能保留其类型信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef Compare = int Function (Object a, Object b);class SortedCollection { Compare compare; SortedCollection(this .compare); }int sort(Object a, Object b) => 0 ; SortedCollection sc = SortedCollection(sort);assert (sc.compare is Compare);
typedef
也可以使用泛型:
1 2 3 4 5 typedef Compare<T> = int Function (T a, T b);int sort(int a, int b) => a - b;assert (sort is Compare<int >);
通过元信息注解,我们可以为类和方法提供额外的信息。Dart 中自带的注解有 @deprecated
和 @override
,除此之外,你也可以自定义注解,只要在类的构造器上使用 const
关键字就可以了:
1 2 3 4 5 6 class Todo { final String who; final String what; const Todo(this .who, this .what); }
Dart 支持三种类型的注释:单行注释、多行注释和文档注释。
1 2 3 4 5 6 7 8 9 10 void main() { print ('Howdy!' ); print ('Hola!' ); }