Dartlang



特别点

  • 任何能作为变量使用的都是对象, 每一个对象都是一个类的实例. 甚至 数组, 函数, null 都是对象,所有对象都继承自 Object class.
  • Dart 是强类型的语言,但是也支持类型推导, 比如 final a = 1 会推导出 aint. 还可以使用 dynamic表示类型在运行时才确定
  • Dart 支持泛型,例如 List<int>(一个 int 集合), List<dynamic>(任何类型的对象的集合).
  • Dart 支持 top-level函数, 类实例函数, 类静态函数, 嵌套函数, 局部函数.
  • Dart 支持 top-level变量, 类实例变量, 类静态变量.
  • Dart 只有 library 私有 和 library 公有 两种可见性, 以 _ 开头的标识符就代表 library 私有. 比如int _privateNum = 0;
  • Dart 支持条件表达式, condition ? expr1 : expr2.

特别的关键字

以下是和 Java 比较起来, Dart 中一些特别用途的关键字.

  • dynamic

    • 声明一个类型是任意类型的, 表示变量类型在运行时才确定

library 相关

  • show

    • 当只导入一个 library 的一部分时使用

    • 1
      2
      // Import only foo.
      import 'package:lib1/lib1.dart' show foo;
  • hide

    • 当导入一个 library 中所有内容, 但排除某个时使用

    • 1
      2
      // Import all names EXCEPT foo.
      import 'package:lib2/lib2.dart' hide foo;
  • as

    • 给导入的 library 指定别名

    • 1
      2
      3
      4
      import 'package:lib2/lib2.dart' as lib2;

      // Uses Element from lib2.
      lib2.Element element2 = lib2.Element();
  • deferred

    • 延期导入 library. 当需要使用时再导入, 使用场景:

      • 减少应用的启动时间
      • A/B Test
      • 加载很少会用的的方法
  • 1
    2
    3
    4
    5
    6
    7
    8
    // 必须使用 deferred as
    import 'package:greetings/hello.dart' deferred as hello;

    // 执行 loadLibrary 真正导入
    Future greet() async {
    await hello.loadLibrary();
    hello.printGreeting();
    }
  • loadLibrary 即使多次调用, library 也只会导入一次

  • 注意

    • 延期导入的 library 中的常量, 直到 library 被真正导入后才可用
    • 不能使用延期导入的 library 中的类型, 比如 class 等. 最好将包含类型声明的 library 移到一个 libraryA , 然后让 延期导入的 library 和导包的文件都导入这个 libraryA.
    • loadLibrary 函数返回的是一个 Future, 需要注意异步使用.
  • export

    • 在一个 dart 文件中批量 export 多个 library, 这样就只用 import 这一个文件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 在 shelf.dart 中声明如下:
      export 'src/cascade.dart';
      export 'src/handler.dart';
      export 'src/handlers/logger.dart';
      export 'src/hijack_exception.dart';
      export 'src/middleware.dart';
      export 'src/pipeline.dart';
      export 'src/request.dart';
      export 'src/response.dart';
      export 'src/server.dart';
      export 'src/server_handler.dart';

      // 最后只用 import shelf.dart 一个包, 就能导入上面所有包
  • part (不推荐使用)

    • 拆分一个 library 到多个文件

异步编程支持

  • async

    • 结合 await 使用
  • await

    • 在使用 async 标记了的方法中, 等待一个 Future 执行完毕
      1
      2
      3
      4
      5
      6
      Future<String> lookUpVersion() => Future.delayed(Duration(seconds: 2), () => "1.0");

      void checkVersion() async {
      var version = await lookUpVersion();
      print(version); // 1.0
      }

Generators 中使用

  • sync
  • yield

函数相关

  • external

    • 声明一个函数的具体实现在其他地方, 目前仅用于 DartVM 在不同平台实现同一个函数.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 声明
      class Foo {
      external int bar(int x);
      }

      // dart2js 中实现
      @patch class Foo {
      @patch int bar(int x) => somethingMagical(x);
      }
  • typedef

    • 定义一个函数类型. 当一个函数分配给一个变量时, typedef 能够保留函数的类型信息
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      typedef Compare = int Function(Object a, Object b);

      int sort(Object a, Object b) => 0;

      class SortedCollection {
      Compare compare;
      SortedCollection(this.compare);
      }

      void main() {
      SortedCollection coll = SortedCollection(sort);
      assert(coll.compare is Function); // True
      assert(coll.compare is Compare); // True
      }
  • Function

    • 声明一个变量是函数类型

类相关

  • factory

    • 声明一个构造函数并不总是创建一个新的实例, 也可能返回一个缓存对象, 一个子类对象
    • factory 构造函数不能访问 this
      1
      2
      3
      4
      5
      6
      7
      8
      9
      factory Logger(String name) {
      if (_cache.containsKey(name)) {
      return _cache[name];
      } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
      }
      }
  • mixin

    减少继承造成的复杂结构和多重继承下的问题

    • 在多个类中复用一个类的代码, 而不用继承类(类似: 继承人们的资产, 且不用是他们的孩子)
    • 当 继承 或 mixin 的类中有相同的方法时, 优先使用最后 with 的类中的方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      class Teacher {
      void teaching() => print("Something");
      }

      mixin MathTeacher {
      void teaching() => print("Math");
      }

      mixin EnglishTeacher {
      void teaching() => print("English");
      }

      class SeniorSchoolTeacher with MathTeacher, EnglishTeacher {}

      void main() {
      SeniorSchoolTeacher teacher = SeniorSchoolTeacher();
      teacher.teaching(); // English
      }
  • with

    • 声明使用 mixin 的类
  • convariant

    • 允许用子类覆盖重写参数的类型
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      class Animal {
      void chase(Animal x) { ... }
      }

      class Mouse extends Animal { ... }

      class Cat extends Animal {
      // 正常情况下,是不允许覆盖参数类型的,会报 Invalid override. 使用 covariant 表明我们知道自己在做什么, 将忽略报错
      void chase(covariant Mouse x) { ... }
      }
  • get set

    • 为一个类添加额外的 getter setter 属性
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class Rectangle {
      int left, top, width, height;

      int get right => left + width;
      set right(int value) => left = value - width;

      int get bottom => top + height;
      set bottom(int value) => top = value - height;
      }
      void main(){
      final rect = Rectangle(3, 4, 20, 15);
      assert(rect.left == 3);
      rect.right = 0;
      assert(rect.left == -20);
      }

异常

  • rethrow
    • catch 一个异常之后,再次抛出
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      void misbehave() {
      try {
      dynamic foo = true;
      print(foo++); // Runtime error
      } catch (e) {
      print('misbehave() partially handled ${e.runtimeType}.');
      rethrow; // Allow callers to see the exception.
      }
      }

其他

  • operator
    • 操作符重载. 可以重载常见的操作符.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class Point {
      int x, y;

      Point(this.x, this.y);

      Point operator +(Point p) => Point(x + p.x, y + p.y);

      Point operator -(Point p) => Point(x - p.x, y - p.y);
      }

      void main() {
      final p1 = Point(1, 2);
      final p2 = Point(2, 1);
      assert((p1 + p2).x == 3);
      assert((p1 - p2).x == -1);
      assert((p2 - p1).x == 1);
      }

变量

  • Dart 中的变量也是存储的对象的引用, 当在函数中传递时,与 Java 类似是 值传递.
  • 没有初始化的变量默认为 null, 没有初始化的数字类型也是 null.
  • final 声明不可变的变量. 类中的 final 属性, 必须在构造函数的 body 执行前被赋值.
  • const 声明编译期常量.
  • static 声明类变量和类方法, Dart 中的类变量和类方法不能通过类的实例调用

Built-in Type

  • numbers
    • int 不超过 64 位的整数, 在 DartVM 是 -2^63 to 2^63 - 1, 在编译为 JS 后是 -2^53 to 2^53 - 1
    • double 64 位的双精度浮点
  • strings

    • 对应类 String
    • 和 Java 类似, 也是不可变的
    • UTF-16 编码
    • 使用单引号或双引号创建, 使用三个单引号可以写到多行
    • 使用 $ 将变量嵌入字符串
    • 使用 r'' 创建原始字符串
    • == 操作符, 比较两个字符串的内容是否相等
    • const 声明的字符串字面量是编译期常量
  • booleans

    • 关键字 bool
  • lists (also known as arrays)

    • 对应类 List, 类似 Java 中的 ArrayList
    • 快速创建 var l = [1, 2, 3, 4, 5];
    • 常量list var constantList = const [1, 2, 3]; 常量 list 不能再 add 或 修改内部元素
  • sets

    • 对应类 Set
    • 快速创建 var s = {"dog", "pic", "fish"};
  • maps

    • 对应类 Map
    • 快速创建
      1
      2
      3
      4
      5
      6
      var gifts = {
      // Key: Value
      'first': 'partridge',
      'second': 'turtledoves',
      'fifth': 'golden rings'
      };
  • runes (for expressing Unicode characters in a string)

    • 对应类 Runes
    • UTF-32 编码的字符串
    • 超过4个数的 uniCode 需要包裹在 {}

      1
      2
      3
      4
      5
      6
      7
      8
      9
      main() {
      var clapping = '\u{1f44f}';
      print(clapping); // 👏
      print(clapping.codeUnits);
      print(clapping.runes.toList());

      Runes input = new Runes('\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
      print(new String.fromCharCodes(input));
      }
  • symbols

    Dart中的 symbol 是不透明的动态字符串名称,用于反映库中的元数据。 简而言之,symbol 是一种存储人类可读字符串与优化供计算机使用的字符串之间关系的方法。Reflection 是一种在运行时获取类型元数据的机制,如类中的方法数,它具有的构造函数数或函数中的参数数。 您甚至可以调用在运行时加载的类型的方法。在 Dart 反射中,dart:mirrors 包中提供了特定的类。 此库适用于 Web 应用程序和命令行应用程序。

    • 对应类 Symbol
    • 感觉主要用在 Dart 的反射上
      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
      // foo.dart
      library foo_lib;

      class Foo {
      m1() => print("Inside m1");

      m2() => print("Inside m3");
      }

      // main.dart
      import 'dart:mirrors';
      import 'foo.dart';

      main() {
      final libName = Symbol("foo_lib");
      final className = Symbol("Foo");

      MirrorSystem mirror = currentMirrorSystem();
      LibraryMirror libraryMirror = mirror.findLibrary(libName);
      if (libraryMirror.declarations.containsKey(className)) {
      ClassMirror classMirror = libraryMirror.declarations[className];
      classMirror.instanceMembers
      .forEach((symbol, methodMirror) => print(symbol));
      }
      }

Functions

  • 在 Dart 里, 函数也是对象, 对应类为 Function.
  • 简短写法 bool isOdd(int number) => number%2 != 0;
  • 在函数的参数声明里可简写为: T f(T e)
1
2
3
4
5
6
7
8
9
10
11
12
// 模拟一个 map 方法
List<T> map<T, E>(Iterable<E> list, T f(E e)) {
final result = List<T>();
list.forEach((item) => {result.add(f(item))});
return result;
}

main() {
final ints = const [1, 2, 3, 4];
final ints2 = map(ints, (i) => i * i);
print(ints2); // 1, 4, 9, 16
}

参数

  • 支持两种类型参数, 必传参数和可选参数, 第一个参数为必传,第二个可以为可选参数
  • 可选命名参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// a 必传. b,c 可选
void f(int a, {int b, int c}) {}
f(1, b: 2);
f(1, c: 3);
f(1, b: 2, c: 3);

// a,b 都可选
void f({int a, int b}) {}
f();
f(a: 1);
f(b: 2);
f(a: 1, b: 2);

// b 默认值为 100
void f(int a, {int b = 100})
f(1);
f(1, b: 2);
  • 可选位置参数
1
2
3
4
5
6
7
8
9
10
// c 可选
void f(int a, int b, [int c]){}
f(1, 2);
f(1, 2, 3);

// c,d 为可选, d 默认值为 100
void f(int a, int b, [int c, int d = 100]){}
f(1, 2);
f(1, 2, 3);
f(1, 2, 3, 4);

main 函数

  • 程序的入口
  • 参数 List<String> 可选

函数作为参数

1
2
3
4
5
6
7
8
void printElement(int element) {
print(element);
}

var list = [1, 2, 3];

// printElement 作为参数.
list.forEach(printElement);

变量存储函数引用

1
2
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

匿名函数

1
2
3
([[Type] param1[, …]]) { 
codeBlock;
};
1
2
3
4
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});

如果函数只包含一行,可以使用剪头符号:

1
list.forEach((item) => print('${list.indexOf(item)}: $item'));

Operators

  • 乘除运算符

    • ~/ 取模: 201 ~/ 100 == 2
    • / 商: double 结果 1 / 100 == 0.01, 100 / 1 == 100.0
    • % 取余: 3 % 5 == 3
  • 类型测试符

    • as 类型转换
    • is 如果对象拥有指明的类型, true
    • is! 如果对象拥有指明的类型, false
      1
      2
      3
      1 is int //true
      1 is! int //false
      1 is! String //true
  • 赋值运算符

    • ??= 如果被赋值的变量是 null, 则将右值赋给它; 否则不进行赋值
      1
      2
      3
      4
      5
      int d = null;
      d ??= 1;
      print(d); // 1
      d ??= 2;
      print(d); // 1
  • 条件表达式

    • 三目运算: condition ? expr1 : expr2
    • 条件赋值: expr1 ?? expr2, 如果 expr1 不为 null, 就返回 expr1 的值; 否则返回 expr2 的值
      1
      2
      3
      print(null ?? 100);   // 100
      print(null ?? null); // null
      print(100 ?? null); // 100
  • null 安全访问符: ?.

1
2
3
4
5
6
int a = null;
print(a.abs()); // NoSuchMethodError: The method 'abs' was called on null.

print(a?.abs()); // null
a = -100;
print(a?.abs()); // 100
  • 链式调用符: .. 链式调用对象的多个方法或属性, 最终会返回对象本身
1
2
3
4
5
6
7
8
9
10
11
12
13
class Book {
String name;
double price;

String getSequence() => "$name-$price";
}

final book = Book()
..name = "Dartlang"
..price = 100.0
..getSequence();

print(book); //Instance of 'Book'

Class

构造函数

1
2
3
4
class Book {
String name;
double price;
}
  • 默认构造函数, 没有任何参数
  • 带参数构造函数

    1
    2
    3
    4
    5
    6
    Book(this.name, this.price);
    // 等价于
    Book(String name, double price) {
    this.name = name;
    this.price = price;
    }
  • 命名构造函数

    1
    2
    3
    4
    Book.fromJson(Map<String, dynamic> json) {
    this.name = json['name'];
    this.price = json['price'];
    }
    • 初始化列表
      1
      2
      3
      Book.fromJson(Map<String, dynamic> json)
      : name = json['name'],
      price = json['price']{}
  • 重定向构造函数: 调用其他构造函数

    1
    Book.fromJson(Map<String, dynamic> json) :this(json['name'], json['price']);
  • 常量构造函数: 返回一个编译期常量对象, 这种情况下属性必须是final的, 构造函数必须是 const

    1
    2
    3
    4
    5
    6
    7
    8
    class Book {
    final String name;
    final double price;

    const Book(this.name, this.price);

    static final Book origin = const Book("dart", 100.0);
    }
  • 工厂构造函数: 可用于不一定返回新对象的情况, 比如:

    • 从缓存返回
    • 返回一个子类对象
    • 单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 单例模式例子
class SingleClass {
static SingleClass _instance;

SingleClass._inner();

factory SingleClass() {
if (_instance == null) {
_instance = SingleClass._inner();
}
return _instance;
}
}

// 调用
final a = SingleClass();
final b = SingleClass();
print(a == b); // true

方法

实例方法

与 Java 类似, 对象都会拥有实例方法

geter seter

  • 会隐式的为类的属性提供 gettersetter 方法.
  • 可通过 get set 关键字提供额外的 gettersetter(参照上面关键字部分)

抽象方法 abstract method

抽象方法只能存在抽象类中, 且子类(如果子类不是抽象类)必须实现抽象方法

抽象类

与 Java 类似

隐式接口

Dart 为每个类隐式定义了一个接口, 接口包含了类的所有属性和实例方法. 如果你想提供和一个类相同的能力, 而又不想继承它, 那可以直接使用 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
26
27
28
29
30
31
32
33
34
35
36
37
38
class Person {
// 会包含在隐式接口中, 因为 name 是 final, 所以只会隐式生成 getter, 则实现接口的类也应该提供一个 name 的 getter 方法
final String name;

// 会包含在隐式接口中, 因为 age 不是 final, 所以会隐式生成 getter 和 setter, 则实现接口的类也应该提供 age 的 getter 和 setter 方法
int age;

// 不会包含在隐式接口中
Person(this.name);

// 会包含在隐式接口中
String greet(String who) => "Hi $who, I'm $name";
}

// 继承自 Person, 用于 Person 类的所有
class Man extends Person {
Man(String name) : super(name);
}

// 实现了 Person 类的隐式接口, 需要实现包含在接口中的方法
class Women implements Person {

String nickName;

@override
String get name => nickName;

@override
String greet(String who) => "Hi $who. Do you know who I am";

@override
int get age => 0;

@override
void set age(int _age) {
this.age = _age;
}
}

使用 mixin

请移步 “Dart 中的Mixin”

类变量和方法

  • 使用 static 声明类变量, 与Java 不同, 类变量只能通过类调用, 不能通过实例调用
  • 使用 static 声明类方法, 与Java 不同, 类方法只能通过类调用, 不能通过实例调用

泛型

基本使用与 Java 类似, 但有一个最大不同, Dart 的泛型在运行时不会被擦除, 在运行时可以知道泛型的类型.

Dart:

1
2
3
4
5
6
7
8
9
10
11
class Wrap<T> {
final T data;

Wrap(this.data);
}

void main() {
final data = Wrap<String>("ABC");
print(data is Wrap<String>); // true
print(data.runtimeType); // Wrap<String>
}

Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Wrap<T> {
T data;

public Wrap(T data) {
this.data = data;
}
}

public static void main(String[] args) {
Wrap<String> data = new Wrap<>("ABC");
System.out.println(data instanceof Wrap<String>); // 不支持的操作
System.out.println(data.getClass().getName()); // Wrap
}

异步

创建异步方法

返回 FutureStream 的方法就是异步方法, 不需要等待这些方法中进行的耗时操作就可以返回.

1
2
3
4
5
6
7
8
9
void main() {
print("start");
asyncGetNum();
print("end");
}

Future<void> asyncGetNum() {
return Future.delayed(Duration(seconds: 1), () => print("async"));
}

输出:

1
2
3
start
end
async

等待 Future 的结果

  • async 方法中使用 await 等待一个 Future 执行结束
1
2
3
4
void printNun() async {
final num = await asyncGetNum();
print(num);
}
  • 使用 Futurethen 接口
1
2
3
4
void printNun(){
final Future<int> numFuture = asyncGetNum();
numFuture.then((num) => print(num));
}

捕获异常

抛出异常:

1
2
3
4
5
6
Future<int> asyncGetNum() {
return Future.delayed(Duration(seconds: 1), () {
throw Exception("exception");
return 100;
});
}

  • 使用 try catch 捕获
1
2
3
4
5
6
try {
final num = await asyncGetNum();
print(num);
} catch (e) {
print(e);
}
  • 使用 FuturecatchError 捕获
1
2
final Future<int> numFuture = asyncGetNum();
numFuture.then((num) => print(num)).catchError((error) => print(error));

等待 Stream 的结果

使用 Stream:

1
2
3
Stream<int> streamNum() {
return Stream.fromIterable([1, 2, 3, 4, 5]);
}

  • async 方法中使用 await for 等待一个循环
1
2
3
4
5
void printNun() async {
await for (int i in streamNum()) {
print(i);
}
}
  • 使用 Streamlisten 接口
1
2
3
void printNun() {
streamNum().listen((i) => print(i));
}

Generators 生成器

当需要延迟产生序列值时(不会在内存储存整个序列, 只有当需要下一个值时才计算一下个值), 可以使用 generator 方法, Dart 支持两种 generator 方法:

  • 同步的生成器: 返回 Iterable 对象
  • 异步的生成器: 返回 Stream 对象

实现同步生成器:

  • 方法返回 Interable
  • 方法使用 sync* 标记
  • 使用 yield 抛出值
1
2
3
4
5
6
7
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) {
print("produce");
yield k++;
}
}

实现异步生成器:

  • 方法返回 Stream
  • 方法使用 async* 标记
  • 使用 yield 抛出值
1
2
3
4
5
6
7
Stream<int> asyncNaturalsTo(int n) async* {
int k = 0;
while (k < n) {
print("async produce");
yield k++;
}
}

如果逻辑是递归的, 可以使用 yield* 优化性能:

1
2
3
4
5
6
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}

Callable class

为一个类实现 call 方法, 该类就能像一个方法一样被调用.

1
2
3
4
5
6
class Dog {
void call() => print("wang wang");
}

final dog = Dog();
dog(); // wang wang

Isolates

在 Dart 中, 程序运行在自己的 Isolate 中, 类似 Java 里的 Thread, 但是 Isolate 彼此独立, 内存不能互相访问, 每个 Isolate 有自己的内存堆.

请移步 “Dart 中的异步”

Typedefs 类型定义

为方法定义一个类型, 当方法被一个变量引用时, 这个类型信息也会能被使用. 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SortedCollection {
Function compare;

SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}

int sort(Object a, Object b) => 0;

void main() {
SortedCollection coll = SortedCollection(sort);
// 只能知道 compare 是一个方法, 但是不能知道它具体是什么方法
assert(coll.compare is Function);
}

使用 typedef:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef Compare = int Function(Object a, Object b);

class SortedCollection {
Compare compare;

SortedCollection(this.compare);
}

int sort(Object a, Object b) => 0;

void main() {
SortedCollection coll = SortedCollection(sort);
// 可以知道 compare 是一个方法, 也能知道它具体是什么类型的方法
assert(coll.compare is Function);
assert(coll.compare is Compare);
}

原数据 Metadata

类似 Java 中的注解. 可以在运行时通过反射拿到 Metadata.

定义自己的注解:

1
2
3
4
5
6
7
8
library todo;

class Todo {
final String who;
final String what;

const Todo(this.who, this.what);
}
1
2
@Todo("me", "fix something")
void doSomething() {}