Dart编程语言概览

Dart编程语言概览

一个简单的Dart程序:

  • 注释,单行、多行
  • 数据类型、字面量、输出方式
  • 字符串插值
  • main()函数:特定的顶级函数
  • 定义变量var:通过这种方式定义变量不需要指定变量类型
// 定义一个函数
printInteger(int aNumber) {
  // 打印
  print('The number is $aNumber)');
}

// 应用从这里开始
main() {
  var number = 42;
  printInteger(number);
}

重要的概念:

  • 一切皆对象,所有对象都有对应的一个的实例;无论数字、函数和null都是对象;所有对象都继承自Object类;
  • Dart是强类型语言,但可以推断类型;如果要明确说明不需要任何类型,需要使用特殊类型dynamic动态类型;
  • Dart支持泛型,如List<int>整数列表、List<dynamic>任何类型的对象列表;
  • Dart对函数的支持:
    • 支持顶级函数main()
    • 绑定在类上——静态函数
    • 绑定在对象上——实例函数
    • 支持函数内创建函数(嵌套或 局部函数)
  • Dart对变量的支持:
    • 支持顶级变量
    • 绑定在类上——静态变量
    • 绑定在对象上——实例变量(字段/属性)
  • Dart没有关键字public/protected/private,如果标识符以下划线_开头,则它相对于库是私有的;
  • Dart表达式(运行时有值),语句(运行时无值);condition?expr1:expr2值可能是二者之一,if-else语句没有值;语句可以包含表达式,但是表达式不能直接包含语句;
  • Dart工具提示两种类型问题:警告 和 错误(编译时错误会阻止代码执行 或 运行时错误会导致代码在执行过程中引发异常);

Dart关键字解析:

  • abstract:定义 抽象类 — 抽象类不能实例化;抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现;
// 这个类被定义为抽象类,
// 所以不能被实例化。
abstract class AbstractContainer {
  // 定义构造行数,字段,方法...

  void updateChildren(); // 抽象方法。
}
  • asasisis!运算符用于在运行时处理类型检查;
    • 例如, obj is Object 总是 true。 但是只有 obj 实现了 T 的接口时, obj is T 才是 true。
    • 使用 as 运算符将对象强制转换为特定类型;
if (emp is Person) {
  (emp as Person).firstName = 'Bob';
}
  • assert:如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断

    • 例如,assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
    • assert 语句只在开发环境中有效, 在生产环境是无效的;
    • 断言失败,会抛出异常 (AssertionError);
  • asyncawait

    • Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。
    • 要使用 await , 代码必须在 异步函数(使用 async 标记的函数)中;
    • 使用 try, catch, 和 finally 来处理代码中使用 await 导致的错误。
    • 在一个异步函数中可以多次使用 await
    • 在 await 表达式 中, 表达式 的值通常是一个 Future 对象; 如果不是,这是表达式的值会被自动包装成一个 Future 对象。
Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}

try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
  • breakcontinue
    • 使用 break 停止程序循环,使用 continue 跳转到下一次迭代;
    • 如果对象实现了 Iterable 接口 (例如,list 或者 set)。 那么示例完全可以用另一种方式来实现:
while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());
  • caseswitchdefault
    • 在Dart中switch语句使用==比较比较整数,字符串,或者编译时常量
    • 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。
    • 枚举类型 可以用于 switch 语句
    • 在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句;除 break 以外,还有可以使用 continue, throw,者 return。
    • Dart 支持空 case 语句, 允许程序以 fall-through 的形式执行。
    • 在非空 case 中实现 fall-through 形式, 可以使用 continue 语句结合 lable 的方式实现
    • case 语句可以拥有局部变量, 这些局部变量只能在这个语句的作用域中可见。
var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    // break; // 缺省break会报错
  case 'PENDING':
    // executePending(); //但支持空case语句
    // break;
  case 'APPROVED':
    executeApproved();
    continue open;
  case 'DENIED':
    executeDenied();
    break;
  open:
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}
  • catchfinallyrethrow:捕获异常可以避免异常继续传递(除非重新抛出( rethrow )异常)。 可以通过捕获异常的机会来处理该异常
    • 通过指定多个 catch 语句,可以处理可能抛出多种类型异常的代码。
    • catch 语句未指定类型, 则该语句可以处理任何类型的抛出对象
    • catch() 函数可以指定1到2个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 ( 一个 StackTrace 对象 )。
    • 如果仅需要部分处理异常, 那么可以使用关键字 rethrow 将异常重新抛出。
    • 不管是否抛出异常, finally 中的代码都会被执行。 如果 没有用catch 匹配异常, 异常会在 finally 执行完成后,再次被抛出;任何匹配的 catch 执行完成后,再执行 finally ;
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 一个特殊的异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 其他任何异常
  print('Unknown exception: $e');
} catch (e, s) {
  // 没有指定的类型,处理所有异常
  print('Something really unknown: $e');
  rethrow; 
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}
  • throw
    • 高质量的生产环境代码通常会实现 ErrorException 类型的异常抛出
// 抛出异常
throw FormatException('Expected at least 1 section');

// 抛出任意对象
throw 'Out of llamas!';

// 因为抛出异常是一个表达式, 所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();

  • classthis:class 用于声明类;
    • 所有实例变量都生成隐式 getter 方法。 非 final 的实例变量同样会生成隐式 setter 方法
    • 构造函数中,使用 this 关键字引用当前实例;仅当存在命名冲突时,使用 this 关键字。 否则,按照 Dart 风格应该省略 this ;(通常模式下,会将构造函数传入的参数的值赋值给对应的实例变量)
class Point {
  num x; // 声明示例变量 x,初始值为 null 。
  num y; // 声明示例变量 y,初始值为 null 。
  num z = 0; // 声明示例变量 z,初始值为 0 。

  // 生成构造函数
  Point(num x, num y) {
    // 还有更好的方式来实现下面代码,敬请关注。
    this.x = x;
    this.y = y;
  }

}
  • constfinal
    • 使用过程中从来不会被修改的变量, 可以使用 final 或 const, 而不是 var 或者其他类型
    • Final 变量的值只能被设置一次;Const 变量在编译时就已经固定 (Const 变量 是隐式 Final 的类型.)
    • 实例变量可以是 final 类型但不能是 const 类型。
    • 如果 Const 变量是类级别的,需要标记为 static const
    • Const 关键字不仅可以用于声明常量变量,还可以用来创建常量值(const关键字在声明常量构造函数时还有应用,参考关键字new的描述)
// 声明常量变量
const bar = 1000000;
// 创建常量值
var foo = const [];
  • deferred
    • Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再加载库
    • 常用场景:减少 APP 的启动时间。执行 A/B 测试,例如 尝试各种算法的 不同实现。加载很少使用的功能,例如可选的屏幕和对话框。
    • 延迟加载库的常量在导入的时候是不可用的,在导入文件的时候也无法使用延迟库中的类型;
// 要延迟加载一个库,需要先使用 deferred as 来导入
import 'package:greetings/hello.dart' deferred as hello;

// 当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库:
Future greet() async {
  // 可以多次调用 loadLibrary() 函数。但是该库只是载入一次
  await hello.loadLibrary();
  hello.printGreeting();
}
  • dodo-while
while (!isDone()) {
  doSomething();
}

do {
  printLine();
} while (!atEndOfPage());
  • dynamic:动态的数据类型
  • else if:和 JavaScript 不同, Dart 的判断条件必须是布尔值,不能是其他类型
if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

  • enum:枚举类型也称为 enumerations 或 enums , 是一种特殊的类,用于表示数量固定的常量值
    • 枚举中的每个值都有一个 index getter 方法, 该方法返回值所在枚举类型定义中的位置(从 0 开始)
    • 使用枚举的 values 常量, 获取所有枚举值列表( list )
    • 可以在 switch 语句 中使用枚举, 如果不处理所有枚举值,会收到警告
    • 枚举不能被子类化,混合或实现
    • 枚举不能被显式实例化
enum Color { red, green, blue }

assert(Color.red.index == 0);

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);


  • export
    • 库代码位于lib目录下,对其他包是公开的。您可以根据需要在lib下创建任何层次结构。按照惯例,实现代码放在lib/src下。lib/src下的代码被认为是私有的;其他包永远不需要导入src/…要使lib/src下的api公开,可以从直接位于lib下的文件导出lib/src文件;
// 目录结构
- src
  - cascade.dart
  - ...
- shelf.dart
- shelf_io.dart


// shelf.dart, exports several files from lib/src:
export 'src/cascade.dart';
export ...

  • extendssuper:使用 extends 关键字来创建子类, 使用 super 关键字来引用父类
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}
  • factory:工厂构造函数
    • 当执行构造函数并不总是创建这个类的一个新实例时,则使用 factory 关键字。
    • 一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。
    • 工厂构造函数无法访问 this。
class Logger {
  final String name;
  bool mute = false;

  // 从命名的 _ 可以知,
  // _cache 是私有属性。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

// 工厂构造函的调用方式与其他构造函数一样 
var logger = Logger('UI');
logger.log('Button clicked');

  • falsetrue
  • for
    • 闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱
    • 如果要迭代一个实现了 Iterable 接口的对象, 可以使用 forEach() 方法
    • 实现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作 iteration
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}


var callbacks = [];
for (var i = 0; i < 2; i++) {
  // 输出的是 0 和 1。 但是示例中的代码在 JavaScript 中会连续输出两个 2 
  callbacks.add(() => print(i));
}

// 如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择;
callbacks.forEach((c) => c());

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}

  • Function

    • Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function;
    • 这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。
    • 箭头 语法=> expr; 语法是 { return expr; } 的简写;
    • 在箭头 (=>) 和分号 (;) 之间只能使用一个 表达式 ,不能是 语句
    • 函数有两种参数类型: required 和 optional。 required 类型参数在参数最前面, 随后是 optional 类型参数。 命名的可选参数也可以标记为 “@required
    • 可选参数可以是命名参数或者位置参数,但一个参数只能选择其中一种方式修饰。
  • 命名参数 & 位置参数:

    • 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。
    • list 或 map 可以作为默认值传递;
// 位置参数声明方式
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
// 位置参数的调用方式
isNoble(100);

// 命名参数声明方式
void enableFlags({bool bold, bool hidden= false}) {...}
// 命名参数的调用方式
enableFlags(bold: true, hidden: false);


// Flutter 创建实例的表达式可能很复杂, 因此窗口小部件构造函数仅使用命名参数
const Scrollbar({Key key, @required Widget child})

// 位置可选参数
String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

// list 或 map 可以作为默认值传递
void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}

Required 被定义在 meta package。 无论是直接引入(import) package:meta/meta.dart ,或者引入了其他 package,而这个 package 输出(export)了 meta,比如 Flutter 的 package:flutter/material.dart

  • implements:隐式接口
    • 每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。
    • 一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口要求的 API。
// 一个类对应一个 隐式的接口Person
class Person {
  // 包含在接口里,但只在当前库中可见。
  final _name;

  // 不包含在接口里,因为这是一个构造函数。
  Person(this._name);

  // 包含在接口里。
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// person 接口的实现。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

// 调用
String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}


// 实现多个接口
class Point implements Comparable, Location {...}
  • getset
    • Getter 和 Setter 是用于对象属性读和写的特殊方法
    • 使用 get 和 set 关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。
class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 定义两个计算属性: right 和 bottom。
  num get right => left + width;
  set right(num value) => left = value - width;

  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}
  • import:通过 import 指定一个库命名空间中的内如如何在另一个库中使用
    • import 参数只需要一个指向库的 URI(URI 代表统一资源标识符)
    • 对于内置库,URI 拥有自己特殊的dart: 方案
    • 对于其他的库,使用系统文件路径或者 package: 方案
    • package: 方案指定由包管理器(如 pub 工具)提供的库
    • 如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀
// Dart Web应用程序通常使用 dart:html 库,它们可以像这样导入:
import 'dart:html';

// 指定库前缀
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// 使用 lib1 中的 Element。
Element element1 = Element();

// 使用 lib2 中的 Element。
lib2.Element element2 = lib2.Element();

  • hideshow:如果你只使用库的一部分功能,则可以选择需要导入的 内容
// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

  • in

    • for-in 进行迭代操作
  • interface

    • Yes. The interface keyword was removed from Dart. Instead all classes have implicit interfaces. So if you want to define an interface you can use an abstract class instead.
  • library:库和可见性

    • import 和 library 指令可以用来创建一个模块化的,可共享的代码库。
    • 库不仅提供了 API ,而且对代码起到了封装的作用: 以下划线 (_) 开头的标识符仅在库内可见。
    • 每个 Dart 应用程序都是一个库 ,虽然没有使用 library 指令
    • 库可以通过包(Package)来分发,pub(集成在SDK中的包管理器)
  • mixinwith

    • Dart 是一种基于类和 mixin 继承机制的面向对象的语言;
    • Mixin 提供了复用类代码的一种途径, 复用的类可以在不同层级,之间可以不存在继承关系。
    • 通过 with 后面跟一个或多个混入的名称,来 使用 Mixin
    • 通过创建一个继承自 Object 且没有构造函数的类,来 实现 一个 Mixin 。如果 Mixin 不希望作为常规类被使用,使用关键字 mixin 替换 class;
    • 指定只有某些类型可以使用的 Mixin - 比如, Mixin 可以调用 Mixin 自身没有定义的方法 - 使用 on 来指定可以使用 Mixin 的父类类型;
// 通过 with 后面跟一个或多个混入的名称,来 使用 Mixin 
class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}


// Mixin
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');
    }
  }
}

// Musician是一个类, MusicalPerformer这个mixin 通过on关键字 就可以调用类的方法
mixin MusicalPerformer on Musician {
  // ···
}

mixin 关键字在 Dart 2.1 中被引用支持

  • new
    • 构造函数 创建对象。 构造函数的名字可以是 ClassName 或者 ClassName.identifier(这可能是一个工厂函数);
    • 构造函数前面的的 new 关键字是可选的
    • 一些类提供了常量构造函数。 使用常量构造函数,在构造函数名之前加 const 关键字,来创建编译时常量;
    • 在 常量上下文 中, 构造函数或者字面量前的 const 可以省略;
// 例如, 以下代码使用 Point 和 Point.fromJson() 构造函数创建 Point 对象:
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});


// 常量构造函数
var p = const ImmutablePoint(2, 2);

// 构造两个相同的编译时常量会产生一个唯一的, 标准的实例
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 它们是同一个实例。


// 这里有很多的 const 关键字。
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
// 保留第一个 const 关键字,其余的全部省略:
// 仅有一个 const ,由该 const 建立常量上下文。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

  • null
    • 未初始化的变量默认值是 null,即使变量是数字 类型默认值也是 null,因为在 Dart 中一切都是对象,数字类型 也不例外;
int lineCount;
// 在生产环境代码中 assert() 函数会被忽略,不会被调用。 在开发过程中, assert(condition) 会在非 true 的条件下抛出异常
assert(lineCount == null);
  • on

    • 在捕获异常try-catch语句中,使用on来指定异常类型,使用catch来捕获异常对象;
    • mixin声明中,指定只有某些类型可以使用这个mixin
  • operator:操作符

    • 可以被重写的操作运算符:<+|[]>/^[]=<=~/&~>=*<<==%>>
    • 如果要重写 == 操作符,需要重写对象的 hashCode getter 方法。

你可能会被提示 != 运算符为非可重载运算符。 因为 e1 != e2 表达式仅仅是 !(e1 == e2) 的语法糖。

// 重写 + -
class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // 运算符 == 和 hashCode 部分没有列出。 有关详情,请参考下面的代码段。
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

// 重写 == 操作符
class Person {
  final String firstName, lastName;

  Person(this.firstName, this.lastName);

  // 重写 hashCode,实现策略源于  Effective Java,
  // 第11章。
  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + firstName.hashCode;
    result = 37 * result + lastName.hashCode;
    return result;
  }

  // 如果重写了 hashCode,通常应该从新实现 == 操作符。
  @override
  bool operator ==(dynamic other) {
    if (other is! Person) return false;
    Person person = other;
    return (person.firstName == firstName &&
        person.lastName == lastName);
  }
}

void main() {
  var p1 = Person('Bob', 'Smith');
  var p2 = Person('Bob', 'Smith');
  var p3 = 'not a person';
  assert(p1.hashCode == p2.hashCode);
  assert(p1 == p2);
  assert(p1 != p3);
}

  • part

    • You may have heard of the part directive, which allows you to split a library into multiple Dart files. We recommend that you avoid using part and create mini libraries instead.
  • return:返回函数返回值;

  • static:使用 static关键字实现类范围的变量和方法

    • 静态变量(类变量)对于类级别的状态是非常有用的;
    • 静态变量只到它们被使用的时候才会初始化;
    • 静态方法(类方法)不能在实例上使用,因此它们不能访问 this
    • 静态函数可以当做编译时常量使用。 例如,可以将静态方法作为参数传递给常量构造函数。
// 静态变量
class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

// 静态方法
import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

对于常见或广泛使用的工具和函数, 应该考虑使用顶级函数而不是静态方法;

代码准守风格推荐指南中的命名规则, 使用 lowerCamelCase 来命名常量。

  • sync yieldsync*async*):
    • 当您需要延迟生成( lazily produce )一系列值时, 可以考虑使用_生成器函数_。 Dart 内置支持两种生成器函数:
      • Synchronous 生成器: 返回一个 Iterable 对象
      • Asynchronous 生成器: 返回一个 Stream 对象
// 通过在函数体标记 sync*, 可以实现一个同步生成器函数。 使用 yield 语句来传递值
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

// 通过在函数体标记 async*, 可以实现一个异步生成器函数。 使用 yield 语句来传递值:
Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

// 如果生成器是递归的,可以使用 yield* 来提高其性能:
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield* naturalsDownFrom(n - 1);
  } else {
    yield n;
  }
}
  • typedef
    • 在 Dart 中,函数也是对象,就想字符和数字对象一样。 使用 typedef ,或者 function-type alias 为函数起一个别名, 别名可以用来声明字段及返回值类型。 当函数类型分配给变量时,typedef会保留类型信息。
// 未使用typedef
class SortedCollection {
  Function compare;

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

// Initial, broken implementation. // broken ?
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // 虽然知道 compare 是函数,
  // 但是函数是什么类型 ?
  // 当把 f 赋值给 compare 的时候,类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 但是 compare 得到的类型是 Function
  assert(coll.compare is Function);
}

目前,typedefs 只能使用在函数类型上

// 使用typedef为函数起一个别名
typedef Compare = int Function(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

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


// 判断任意函数的类型
typedef Compare<T> = int Function(T a, T b);

int sort(int a, int b) => a - b;

void main() {
  assert(sort is Compare<int>); // True!
}
  • var:创建一个变量,初始化之后,变量仅存储对象引用;

  • void:函数无返回值的类型描述;如果不是void声明的函数返回值类型,那么函数就一定有返回值,实际上所有函数都会返回一个值。 如果没有明确指定返回值, 函数体会被隐式的添加 return null; 语句;

Dart 内建数据类型

支持的内建类型:

  • Number
  • String
  • Boolean
  • List(也被称为Array)
  • Map
  • Set
  • Rune(用于在字符串中表示Unicode字符)
  • Symbol

这些类型都可以被初始化为字面量;

因为在 Dart 所有的变量终究是一个对象(一个类的实例), 所以变量可以使用 构造涵数 进行初始化。 一些内建类型拥有自己的构造函数。 例如, 通过 Map() 来构造一个 map 变量。

几个参考链接,便于使用查阅:

Number

Dart 语言的 Number 有两种类型:

  • int:整数值不大于64位, 具体取决于平台。
  • double:64位(双精度)浮点数,依据 IEEE 754 标准

intdouble 都是 num. 的亚类型

  • num 类型包括基本运算 +-/, 和 *, 以及 abs()ceil(), 和 floor(), 等函数方法。
  • 如果 num 及其亚类型找不到你想要的方法, 尝试查找使用 dart:math 库。
  • 按位运算符,例如»,定义在 int 类中。int 特有的传统按位运算操作,移位(<<, >>),按位与(&)以及 按位或(|)。
var x = 1;
var hex = 0xDEADBEEF;
var y = 1.1;
var exponents = 1.42e5;

// 从 Dart 2.1 开始,必要的时候 int 字面量会自动转换成 double 类型
double z = 1; // 相当于 double z = 1.0


assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111


// 数字类型字面量是编译时常量。 在算术表达式中,只要参与计算的因子是编译时常量, 那么算术表达式的结果也是编译时常量
const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;

字符串 与 数字 的相互转换:


// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

String

Dart 字符串是一组 UTF-16 单元序列:

  • 字符串通过单引号或者双引号创建。
  • 字符串可以通过 ${expression} 的方式内嵌表达式,如果表达式是一个标识符,则 {} 可以省略,如$var
  • 在 Dart 中通过调用就对象的 toString() 方法来得到对象相应的字符串
  • 可以使用 + 运算符来把多个字符串连接为一个(把多个字面量字符串写在一起也可以实现字符串连接);
  • 使用连续三个单引号或者三个双引号实现多行字符串对象的创建:
  • 使用 r 前缀,可以创建 “原始 raw” 字符串:

== 运算符用来测试两个对象是否相等。 在字符串中,如果两个字符串包含了相同的编码序列,那么这两个字符串相等

一个编译时常量的字面量字符串中,如果存在插值表达式,表达式内容也是编译时常量, 那么该字符串依旧是编译时常量。 插入的常量值类型可以是 null,数值,字符串或布尔值:

// const 类型数据
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// 非 const 类型数据
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString'; //const 类型数据
// const invalidConstString = '$aNum $aBool $aString $aConstList'; //非 const 类型数据

字符常用方法和正则表达式:

  • 使用正则表达式 (RegExp 对象) 可以在字符串内搜索和替换部分字符串
  • String 定义了例如 split()contains()startsWith()endsWith() 等方法
  • 字符串是不可变的对象,也就是说字符串可以创建但是不能被修改,例如,方法 replaceAll() 返回一个新字符串, 并没有改变原始字符串;
  • 具体参看如下代码示例;
// 检查一个字符串是否包含另一个字符串。
assert('Never odd or even'.contains('odd'));

// 一个字符串是否以另一个字符串为开头?
assert('Never odd or even'.startsWith('Never'));

// 一个字符串是否以另一个字符串为结尾?
assert('Never odd or even'.endsWith('even'));

// 查找一个字符串在另一个字符串中的位置。
assert('Never odd or even'.indexOf('odd') == 6);

// 抓取一个子字符串。
assert('Never odd or even'.substring(6, 9) == 'odd');

// 使用字符串模式分割字符串。
var parts = 'structured web apps'.split(' ');
assert(parts.length == 3);
assert(parts[0] == 'structured');

// 通过下标获取 UTF-16 编码单元(编码单元作为字符串)。
assert('Never odd or even'[0] == 'N');

// 使用 split() 传入一个空字符串参数,
// 得到一个所有字符的 list 集合;
// 有助于字符迭代。
for (var char in 'hello'.split('')) {
  print(char);
}

// 获取一个字符串的所有 UTF-16 编码单元。
var codeUnitList =
    'Never odd or even'.codeUnits.toList();
assert(codeUnitList[0] == 78);


// 转换为首字母大写。
assert('structured web apps'.toUpperCase() ==
    'STRUCTURED WEB APPS');

// 转换为首字母小写。
assert('STRUCTURED WEB APPS'.toLowerCase() ==
    'structured web apps');


// Trim a string.
assert('  hello  '.trim() == 'hello');

// 检查字符串是否为空。
assert(''.isEmpty);

// 空格字符串不是空字符串。
assert('  '.isNotEmpty);


// 替换部分字符串
var greetingTemplate = 'Hello, NAME!';
var greeting =
    greetingTemplate.replaceAll(RegExp('NAME'), 'Bob');

// greetingTemplate 没有改变。
assert(greeting != greetingTemplate);


// 要以代码方式生成字符串,可以使用 StringBuffer
// 在调用 toString() 之前, StringBuffer 不会生成新字符串对象。 writeAll() 的第二个参数为可选参数,用来指定分隔符, 本例中使用空格作为分隔符。
var sb = StringBuffer();
sb
  ..write('Use a StringBuffer for ')
  ..writeAll(['efficient', 'string', 'creation'], ' ')
  ..write('.');
  // .. 是 级联运算符

var fullString = sb.toString();

assert(fullString ==
    'Use a StringBuffer for efficient string creation.');


// RegExp类提供与JavaScript正则表达式相同的功能。 使用正则表达式可以对字符串进行高效搜索和模式匹配。
// 下面正则表达式用于匹配一个或多个数字。
var numbers = RegExp(r'\d+');

var allCharacters = 'llamas live fifteen to twenty years';
var someDigits = 'llamas live 15 to 20 years';

// contains() 能够使用正则表达式。
assert(!allCharacters.contains(numbers));
assert(someDigits.contains(numbers));

// 替换所有匹配对象为另一个字符串。
var exedOut = someDigits.replaceAll(numbers, 'XX');
assert(exedOut == 'llamas live XX to XX years');

// 你也可以直接使用RegExp类。 Match 类提供对正则表达式匹配对象的访问。
var numbers = RegExp(r'\d+');
var someDigits = 'llamas live 15 to 20 years';

// 检查正则表达式是否在字符串中匹配到对象。
assert(numbers.hasMatch(someDigits));

// 迭代所有匹配对象
for (var match in numbers.allMatches(someDigits)) {
  print(match.group(0)); // 15, then 20
}

Boolean

Dart 使用 bool 类型表示布尔值。 Dart 只有字面量 true and false 是布尔类型, 这两个对象都是编译时常量。

Dart 的类型安全意味着不能使用 if (nonbooleanValue) 或者 assert (nonbooleanValue)。 而是应该像下面这样,明确的进行值检查:

// 检查空字符串。
var fullName = '';
assert(fullName.isEmpty);

// 检查 0 值。
var hitPoints = 0;
assert(hitPoints <= 0);

// 检查 null 值。
var unicorn;
assert(unicorn == null);

// 检查 NaN 。
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

List

在 Dart 中的 Array 就是 List 对象, 通常称之为 List 。

  • Dart 集合 API
  • var list = [1, 2, 3];
  • Lists 的下标索引从 0 开始,第一个元素的索引是 0。 list.length - 1 是最后一个元素的索引
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);


// 在 List 字面量之前添加 const 关键字,可以定义 List 类型的编译时常量
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 取消注释会引起错误。


// 使用 List 构造函数。
var vegetables = List();

// 或者仅使用一个 list 字面量。
var fruits = ['apples', 'oranges'];

// 添加一个元素到 list 对象。
fruits.add('kiwis');

// 添加多个元素到 list 对象。
fruits.addAll(['grapes', 'bananas']);

// 获取 list 长度。
assert(fruits.length == 5);

// 移除一个元素到 list 对象。
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 4);

// 移除多个元素到 list 对象。
fruits.clear();
assert(fruits.length == 0);


// 使用 sort() 方法排序一个 list 
// 下面示例中使用 compareTo() 函数, 该函数在 Comparable 中定义, 并被 String 类实现
var fruits = ['bananas', 'apples', 'oranges'];

// 排序一个 list 。
fruits.sort((a, b) => a.compareTo(b));
assert(fruits[0] == 'apples');


// list 是参数化类型, 因此可以指定 list 应该包含的元素类型
// 这个 list 只能包含字符串类型。
var fruits = List<String>();

fruits.add('apples');
var fruit = fruits[0];
assert(fruit is String);

// 产生静态分析警告,num 不是字符串类型。
fruits.add(5); // BAD: Throws exception in checked mode.


Set

在 Dart 中 Set 是一个元素唯一且无需的集合。

  • Dart 为 Set 提供了 Set 字面量和 Set 类型
  • var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};,Dart 推断 halogens 类型为 Set 。如果尝试为它添加一个 错误类型的值,分析器或执行时会抛出错误;
  • 要创建一个空集,使用前面带有类型参数的 {} ,或者将 {} 赋值给 Set 类型的变量;

虽然 Set 类型 一直是 Dart 的核心部分, 但在 Dart2.2 中才引入了 Set 字面量 。

var names = <String>{};
// Set<String> names = {}; // 这样也是可以的。
// var names = {}; // 这样会创建一个 Map ,而不是 Set 这会创建一个类型为 Map<dynamic, dynamic> 的对象;


var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
assert(ingredients.length == 3);

// 添加一个重复的元素是无效的。
ingredients.add('gold');
assert(ingredients.length == 3);

// 从 set 中移除一个元素。
ingredients.remove('gold');
assert(ingredients.length == 2);


// 在 Set 字面量前增加 const ,来创建一个编译时 Set 常量:
final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};


// 使用 contains() 和 containsAll() 来检查一个或多个元素是否在 set 中。
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);

// 检查一个元素是否在该 set 中。
assert(ingredients.contains('titanium'));

// 检查多个元素是否在该 set 中。
assert(ingredients.containsAll(['titanium', 'xenon']));



// 交集是另外两个 set 中的公共元素组成的 set 
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);

// 创建两个 set 的交集。
var nobleGases = Set.from(['xenon', 'argon']);
var intersection = ingredients.intersection(nobleGases);
assert(intersection.length == 1);
assert(intersection.contains('xenon'));

Map

通常来说, Map 是用来关联 keys 和 values 的对象:

  • keys 和 values 可以是任何类型的对象。
  • 在一个 Map 对象中一个 key 只能出现一次。 但是 value 可以出现多次。
  • Dart 中 Map 通过 Map 字面量 和 Map 类型来实现。
  • 如果 Map 中不包含所要查找的 key,那么 Map 返回 null
  • 使用 .length 函数获取当前 Map 中的 key-value 对数量
  • 创建 Map 类型运行时常量,要在 Map 字面量前加上关键字 const
  • 使用 remove() 方法从 map 中移除键值对
var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};


var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

上面代码,Dart 会将 gifts 的类型推断为 Map<String, String>, nobleGases 的类型推断为 Map<int, String> 。 如果尝试在上面的 map 中添加错误类型,那么分析器或者运行时会引发错误

// map 是参数化类型;
// 可以指定一个 map 中 key 和 value 的类型。
var nobleGases = Map<int, String>();


// 
var nobleGases = {54: 'xenon'};

// 使用 key 检索 value 。
assert(nobleGases[54] == 'xenon');

// 检查 map 是否包含 key 。
assert(nobleGases.containsKey(54));

// 移除一个 key 及其 value。
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));


var hawaiianBeaches = {
  'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai': ['Hanalei', 'Poipu']
};

// 获取的所有的 key 是一个无序集合
// (可迭代 list 对象)。
var keys = hawaiianBeaches.keys;

assert(keys.length == 3);
assert(Set.from(keys).contains('Oahu'));

// 获取的所有的 value 是一个无序集合
// (可迭代 list 对象).
var values = hawaiianBeaches.values;
assert(values.length == 3);
assert(values.any((v) => v.contains('Waikiki')));


assert(hawaiianBeaches.containsKey('Oahu'));
assert(!hawaiianBeaches.containsKey('Florida'));

公共集合方法

List, Set, 和 Map 共享许多集合中的常用功能。 其中一些常见功能由 Iterable 类定义, 这些函数由 List 和 Set 实现。

虽然Map没有实现 Iterable, 但可以使用 Map keys 和 values 属性从中获取 Iterable 对象。

var coffees = [];
var teas = ['green', 'black', 'chamomile', 'earl grey'];
assert(coffees.isEmpty);
assert(teas.isNotEmpty);


var teas = ['green', 'black', 'chamomile', 'earl grey'];

teas.forEach((tea) => print('I drink $tea'));


// 当在 map 对象上调用 `forEach() 方法时,函数必须带两个参数(key 和 value)
hawaiianBeaches.forEach((k, v) {
  print('I want to visit $k and swim at $v');
  // 我想去瓦胡岛并且在
  // [Waikiki, Kailua, Waimanalo]游泳, 等等。
});


var teas = ['green', 'black', 'chamomile', 'earl grey'];

//  map() 方法返回的对象是一个 懒求值(lazily evaluated)对象: 只有当访问对象里面的元素时,函数才会被调用。
var loudTeas = teas.map((tea) => tea.toUpperCase());
loudTeas.forEach(print);
// 使用 map().toList() 或 map().toSet() , 可以强制在每个项目上立即调用函数。
var loudTeas =
    teas.map((tea) => tea.toUpperCase()).toList();


// 使用 Iterable 的 where() 方法可以获取所有匹配条件的元素。 使用 Iterable 的 any() 和 every() 方法可以检查部分或者所有元素是否匹配某个条件。
var teas = ['green', 'black', 'chamomile', 'earl grey'];

// 洋甘菊不含咖啡因。
bool isDecaffeinated(String teaName) =>
    teaName == 'chamomile';

// 使用 where() 来查找元素,
// 这些元素在给定的函数中返回 true 。
var decaffeinatedTeas =
    teas.where((tea) => isDecaffeinated(tea));
// 或者 teas.where(isDecaffeinated)

// 使用 any() 来检查集合中是否至少有一个元素满足条件。
assert(teas.any(isDecaffeinated));

// 使用 every() 来检查集合中是否所有元素满足条件。
assert(!teas.every(isDecaffeinated));



// 如果当且仅当该 key 不存在于 map 中,且要为这个 key 赋值, 可使用putIfAbsent()方法。 该方法需要一个方法返回这个 value 
var teamAssignments = {};
teamAssignments.putIfAbsent(
    'Catcher', () => pickToughestKid());
assert(teamAssignments['Catcher'] != null);

Rune

Rune 用来表示字符串中的 UTF-32 编码字符

Unicode 定义了一个全球的书写系统编码, 系统中使用的所有字母,数字和符号都对应唯一的数值编码。 由于 Dart 字符串是一系列 UTF-16 编码单元, 因此要在字符串中表示32位 Unicode 值需要特殊语法支持。

表示 Unicode 编码的常用方法是, \uXXXX, 这里 XXXX 是一个4位的16进制数。 例如,心形符号 () 是 \u2665。 对于特殊的非 4 个数值的情况, 把编码值放到大括号中即可。 例如,emoji 的笑脸 () 是 \u{1f600}

String 类有一些属性可以获得 rune 数据。 属性 codeUnitAt 和 codeUnit 返回16位编码数据。 属性 runes 获取字符串中的 Rune 。

下面是示例演示了 Rune 、 16-bit code units、 和 32-bit code points 之间的关系:

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));
}

// console
👏
[55357, 56399]
[128079]
♥  😅  😎  👻  🖖  👍

谨慎使用 list 方式操作 Rune 。 这种方法很容易引发崩溃, 具体原因取决于特定的语言,字符集和操作;

Symbol

一个 Symbol 对象表示 Dart 程序中声明的运算符或者标识符。

  • 你也许永远都不需要使用 Symbol ,但要按名称引用标识符的 API 时, Symbol 就非常有用了。
  • 因为代码压缩后会改变标识符的名称,但不会改变标识符的符号。
  • 通过字面量 Symbol ,也就是标识符前面添加一个 # 号,来获取标识符的 Symbol 。
  • Symbol 字面量是编译时常量
Symbol obj = new Symbol('name'); 
Symbol obj = #radix


import 'dart:mirrors'; 
void main(){ 
   Symbol lib = new Symbol("foo_lib"); 
   String name_of_lib = MirrorSystem.getName(lib); 
   
   print(lib); 
   print(name_of_lib); 
}

// log:
// Symbol("foo_lib")   
// foo_lib

函数——一等对象

Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function

// 虽然在 Effective Dart 中推荐 公共API中声明类型, 但是省略了类型声明,函数依旧是可以正常使用的
isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}


// 箭头 语法:=> expr 语法是 { return expr; } 的简写
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;



// 命名可选参数:使用 {param1, param2, …} 来指定命名参数
void enableFlags({bool bold, bool hidden}) {...}

// 使用 @required 注释表示参数是 required 性质的命名参数
const Scrollbar({Key key, @required Widget child})

// 位置可选参数:将参数放到 [] 中来标记参数是可选的
String say(String from, String msg, [String device]) {...} 


// 默认参数值
void enableFlags({bool bold = false, bool hidden = false}) {...}

// 为位置参数设置默认值
String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {...}

// list 或 map 可以作为默认值传递
void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {...}


// main() 函数
// 任何应用都必须有一个顶级 main() 函数,作为应用服务的入口。 main() 函数返回值为空,参数为一个可选的 List<String> 
void main(List<String> arguments){...}


// 匿名函数
// 有时候也被称为 lambda 或者 closure 
([[Type] param1[,]]) {
  codeBlock;
};

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

// 如果函数只有一条语句, 可以使用箭头简写
list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));


测试函数是否相等:

void foo() {} // 顶级函数

class A {
  static void bar() {} // 静态方法
  void baz() {} // 示例方法
}

void main() {
  var x;

  // 比较顶级函数。
  x = foo;
  assert(foo == x);

  // 比较静态方法。
  x = A.bar;
  assert(A.bar == x);

  // 比较实例方法。
  var v = A(); // A的1号实例
  var w = A(); // A的2号实例
  var y = w;
  x = w.baz;

  // 两个闭包引用的同一实例(2号),
  // 所以它们相等。
  assert(y.baz == x);

  // 两个闭包引用的非同一个实例,
  // 所以它们不相等。
  assert(v.baz != w.baz);
}

词法作用域和词法闭包

词法作用域:

  • Dart 是一门词法作用域的编程语言,就意味着变量的作用域是固定的, 简单说变量的作用域在编写代码的时候就已经确定了。 花括号内的是变量可见的作用域。

词法闭包:

  • 闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外, 依然能够访问在它词法作用域内的变量
//  makeAdder() 捕获了变量 addBy

/// 返回一个函数,返回的函数参数与 [addBy] 相加。
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // 创建一个加 2 的函数。
  var add2 = makeAdder(2);

  // 创建一个加 4 的函数。
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}


运算符

熟悉常用运算符即可:

  • 运算符
  • 算数运算符
  • 关系运算符
  • 类型判定运算符(参考关键字 asisis!
  • 赋值运算符
  • 复合赋值运算符((如 += )将算术运算符和赋值运算符组合在了一起)
    • =–=/=%=>>=^=+=*=~/=<<=&=|=
  • 逻辑运算符:!done && (col == 0 || col == 3)
  • 按位和移位运算符
  • 条件表达式
  • 级联运算符(..):.. 语法为 级联调用 (cascade)。 使用级联调用, 可以简化在一个对象上执行的多个操作。
  • 其他运算符

对于有两个操作数的运算符,运算符的功能由左边的操作数决定。 例如, 如果有两个操作数 Vector 和 Point, aVector + aPoint 使用的是 Vector 中定义的 + 运算符。

算数运算符使用举例:

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // 结果是双浮点型
assert(5 ~/ 2 == 2); // 结果是整型
assert(5 % 2 == 1); // 余数

var a, b;

a = 0;
b = ++a; // a自加后赋值给b。
assert(a == b); // 1 == 1

a = 0;
b = a++; // a先赋值给b后,a自加。
assert(a != b); // 1 != 0

a = 0;
b = --a; // a自减后赋值给b。
assert(a == b); // -1 == -1

a = 0;
b = a--; // a先赋值给b后,a自减。
assert(a != b); // -1 != 0

关系运算符-判等:

  • 要测试两个对象x和y是否表示相同的事物, 使用 == 运算符
  • 在极少数情况下, 要确定两个对象是否完全相同,需要使用 identical() 函数
  • == 运算符的工作原理:
    • 如果 x 或 y 可以 null,都为 null 时返回 true ,其中一个为 null 时返回 false。
    • 结果为函数 x.==(y)的返回值,== 运算符执行的是第一个运算符的函数。 我们甚至可以重写很多运算符;

赋值运算符:

  • 使用 = 为变量赋值。 使用 ??= 运算符时,只有当被赋值的变量为 null 时才会赋值给它
// 将值赋值给变量a
a = value;
// 如果b为空时,将变量赋值给b,否则,b的值保持不变。
b ??= value;


// 使用赋值和复合赋值运算符
var a = 2; // 使用 = 复制
a *= 3; // 复制并做乘法运算: a = a * 3
assert(a == 6);

按位和移位运算符:

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right

条件表达式:

// 如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值
condition ? expr1 : expr2

// 如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值
expr1 ?? expr2

级联运算符(..):

  • 级联运算符 (..) 可以实现对同一个对像进行一系列的操作
  • 除了调用函数, 还可以访问同一对象上的字段属性。 这通常可以节省创建临时变量的步骤, 同时编写出更流畅的代码。
querySelector('#confirm') // 获取对象 获取的对象依次执行级联运算符后面的代码 代码执行后的返回值会被忽略
  ..text = 'Confirm' // 调用成员变量。
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));


// 代码等价于
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));


// 级联运算符可以嵌套
final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();


// 在返回对象的函数中谨慎使用级联操作符
var sb = StringBuffer();
sb.write('foo') // sb.write() 函数调用返回 void, 不能在 void 对象上创建级联操作
  ..write('bar'); // Error: 'void' 没哟定义 'write' 函数。

严格的来讲, “两个点” 的级联语法不是一个运算符。 它只是一个 Dart 的特殊语法。

其他运算符:

  • ():函数调用
  • []:获取List指定索引的值
  • .:对象成员获取,如foo.bar
  • ?.:带条件的对象成员获取,如foo?.bar
  • foo?.bar selects property bar from expression foo unless foo is null

控制流程语句

参考相关关键字:ifelseforwhiledo-whilebreakcontinueswitchcaseassert,使用try-catchthrow也可以改变程序流程;

异常

Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行:

  • 和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。
  • Dart 提供了 ExceptionError 类型, 以及一些子类型;当然也可以定义自己的异常类型。
  • 此外 Dart 程序可以抛出任何非 null 对象, 不仅限 ExceptionError 对象。
  • 抛出异常是一个表达式, 所以可以在 => 语句中使用

参考相关关键字:trycatchrethrowonfinally

  • 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象;
  • 不管是否抛出异常, finally 中的代码都会被执行;
  • 如果 catch 没有匹配到异常, 异常会在 finally 执行完成后,再次被抛出;
  • 任何匹配的 catch 执行完成后,再执行 finally

try {
   
} on OutOfLlamasException {
  // 指定具体异常
} on Exception catch (e) {
  // 指定异常
} catch (e) {
  // 没有指定的类型
}finally {
  // Always clean up, even if an exception is thrown.
}


try {
  // ···
} on Exception catch (e) {
   
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
  rethrow;
}

Dart 是一种基于类和 mixin 继承机制的面向对象的语言。 每个对象都是一个类的实例,所有的类都继承于 Object.

类的基本使用


var p = Point(2, 2);
p.y = 3;
num distance = p.distanceTo(Point(4, 4));
p?.y = 4;


var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
// Dart 2 中 new是可选的
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});


// 常量构造函数:在构造函数名之前加 const 关键字,来创建编译时常量时
var p = const ImmutablePoint(2, 2);

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 它们是同一个实例


// 这里有很多的 const 关键字。
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
// 保留第一个 const 关键字,其余的全部省略:在 常量上下文 中, 构造函数或者字面量前的 const 可以省略
// 仅有一个 const ,由该 const 建立常量上下文。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

获取对象的类型

使用对象的 runtimeType 属性, 可以在运行时获取对象的类型, runtimeType 属性回返回一个 Type 对象。

a.runtimeType
print('The type of a is ${a.runtimeType}');

类的结构

class Point {
  num x; // 声明示例变量 x,初始值为 null 。
  num y; // 声明示例变量 y,初始值为 null 。
  num z = 0; // 声明示例变量 z,初始值为 0 。

  Point(num x, num y) {
    // 还有更好的方式来实现下面代码。
    this.x = x;
    this.y = y;
  }
}

class Point {
  num x, y;

  // 在构造函数体执行前,
  // 语法糖已经设置了变量 x 和 y。
  Point(this.x, this.y);
}

默认构造函数:

  • 在没有声明构造函数的情况下, Dart 会提供一个默认的构造函数。 默认构造函数没有参数并会调用父类的无参构造函数。
  • 子类不会继承父类的构造函数。 子类不声明构造函数,那么它就只有默认构造函数 (匿名,没有参数) ,构造函数不会被继承;

命名构造函数:

  • 使用命名构造函数可为一个类实现多个构造函数;
  • 构造函数不能够被继承, 这意味着父类的命名构造函数不会被子类继承。 如果希望使用父类中定义的命名构造函数创建子类, 就必须在子类中实现该构造函数;
class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名构造函数
  Point.origin() {
    x = 0;
    y = 0;
  }
}

调用父类非默认构造函数:

  • 默认情况下,子类的构造函数会自动调用父类的默认构造函数(匿名,无参数)。 父类的构造函数在子类构造函数体开始执行的位置被调用。 如果提供了一个 initializer list (初始化参数列表), 则初始化参数列表在父类构造函数执行之前执行。
    • initializer list (初始化参数列表
    • superclass’s no-arg constructor (父类的无名构造函数)
    • main class’s no-arg constructor (主类的无名构造函数)
  • 如果父类中没有匿名无参的构造函数, 则需要手工调用父类的其他构造函数。 在当前构造函数冒号 (:) 之后,函数体之前,声明调用父类构造函数。
    • 调用父类构造函数的参数无法访问 this
class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).

  // super.fromJson(data)的参数data可以是一个表达式或者一个方法调用
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

初始化列表:

  • 除了调用超类构造函数之外, 还可以在构造函数体执行之前初始化实例变量
  • 各参数的初始化用逗号分隔。
  • 在开发期间, 可以使用 assert 来验证输入的初始化列表。
  • 使用初始化列表可以很方便的设置 final 字段
// 在构造函数体执行之前,
// 通过初始列表设置实例变量。
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}


// 语法糖已经设置了变量 x 和 y。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}


// 使用初始化列表可以很方便的设置 final 字段
class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}

其他构造函数:

  • 重定向构造函数:有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的函数体为空, 构造函数的调用在冒号 (:) 之后。
  • 常量构造函数:如果该类生成的对象是固定不变的, 那么就可以把这些对象定义为编译时常量。 为此,需要定义一个 const 构造函数, 并且声明所有实例变量为 final
  • 工厂构造函数:当执行构造函数并不总是创建这个类的一个新实例时,则使用 factory 关键字(工厂构造函数无法访问 this);
// 重定向构造函数
class Point {
  num x, y;

  // 类的主构造函数。
  Point(this.x, this.y);

  // 指向主构造函数
  Point.alongXAxis(num x) : this(x, 0);
}


// 常量构造函数
class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}


// 工厂构造函数
class Logger {
  final String name;
  bool mute = false;

  // 从命名的 _ 可以知,
  // _cache 是私有属性。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

抽象类:

  • 使用 abstract 修饰符来定义 抽象类 — 抽象类不能实例化。 抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。

方法

  • 对象的实例方法可以访问 this 和实例变量
  • Getter 和 Setter,使用 get 和 set 关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。
  • 最开始实现 Getter 和 Setter 也许是直接返回成员变量; 随着需求变化, Getter 和 Setter 可能需要进行计算处理而使用方法来实现;
  • 抽象方法:只定义接口不进行实现,而是留给其他类去实现。 抽象方法只存在于 抽象类 中
  • 调用抽象方法会导致运行时错误
abstract class Doer {
  // 定义实例变量和方法 ...

  void doSomething(); // 定义一个抽象方法。
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 提供方法实现,所以这里的方法就不是抽象方法了...
  }
}

隐式接口:

  • 每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。
  • 一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口要求的 API。如class Point implements Comparable, Location {...}
// person 类。 隐式接口里面包含了 greet() 方法声明。
class Person {
  // 包含在接口里,但只在当前库中可见。
  final _name;

  // 不包含在接口里,因为这是一个构造函数。
  Person(this._name);

  // 包含在接口里。
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// person 接口的实现。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

扩展类——继承:

  • 使用 extends 关键字来创建子类, 使用 super 关键字来引用父类
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

重写类成员:

  • 子类可以重写实例方法,getter 和 setter。 可以使用 @override 注解指出想要重写的成员
class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

重写运算符:参考关键字 operator

noSuchMethod()

  • 当代码尝试使用不存在的方法或实例变量时, 通过重写 noSuchMethod() 方法,来实现检测和应对处理
class A {
  // 如果不重写 noSuchMethod,访问
  // 不存在的实例变量时会导致 NoSuchMethodError 错误。
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

类变量和方法

参考相关关键字:static

枚举

参考相关关键字:enum

为类添加功能

参考相关关键字:mixin

泛型

在 API 文档中你会发现基础数组类型 List 的实际类型是List<E><…> 符号将 List 标记为 泛型 (或 参数化) 类型。 这种类型具有形式化的参数。 通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等

  • 正确指定泛型类型可以提高代码质量。
  • 使用泛型可以减少重复的代码。
// 一个用于缓存对象的接口
abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

// 一个相同功能的字符串类型接口
abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

// 泛型可以省去创建所有这些接口的麻烦
abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

使用集合字面量:

  • List , Set 和 Map 字面量也是可以参数化的。 参数化字面量和之前的字面量定义类似, 对于 List 或 Set 只需要在声明语句前加 <type> 前缀, 对于 Map 只需要在声明语句前加 <keyType, valueType> 前缀
  • var names = <String>['Seth', 'Kathy', 'Lars'];
  • var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
  • var pages = <String, String>{};

使用泛型类型的构造函数:

  • 在类名字后面使用尖括号(<...>)来指定泛型类型;
    • Set<String>.from(names);
    • Map<int, View>();

运行时中的泛型集合:

  • Dart 中泛型类型是 固化的,也就是说它们在运行时是携带着类型信息的。 例如, 在运行时检测集合的类型(Java中的泛型会被 擦除);
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

限制泛型类型:

  • 可以使用 extends 实现参数类型的限制
    • class Foo<T extends SomeBaseClass> {...},如果Foo()时不指定泛型,那么T就是SomeBaseClass

使用泛型函数:

  • T first<T>(List<T> ts) {};

库和可见性

参考相关关键字:importlibraryasshowhidedeferred as

实现库

实现库、包

  • 如何组织库的源文件。
  • 如何使用 export 命令。
  • 何时使用 part 命令。
  • 何时使用 library 命令。

异步支持

Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程;

  • Future处理
  • 声明异步函数
  • 处理Stream
  • 生成器:参考相关关键字syncyield

Future处理:

  • 使用 asyncawait;(使用 trycatch, 和 finally 来处理代码中使用 await 导致的错误)
  • 使用 Future API

声明异步函数:

  • 函数体被 async 标示符标记的函数,即是一个_异步函数_;
  • async 关键字添加到函数使其返回Future;
  • Future<String> lookUpVersion() async => '1.0.0';

注意,函数体不需要使用Future API。 如有必要, Dart 会创建 Future 对象。如果函数没有返回有效值, 需要设置其返回类型为 Future<void>

处理Stream:

  • 从 Stream 中获取数据值的两种方式;
  • 使用 async 和 一个 异步循环 (await for
  • 使用 Stream API
Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

上面 表达式 返回的值必须是 Stream 类型。 执行流程如下:

  • 等待,直到流发出一个值。
  • 执行 for 循环体,将变量设置为该发出的值
  • 重复1和2,直到关闭流。
  • 使用 break 或者 return 语句可以停止接收 stream 的数据, 这样就跳出了 for 循环, 并且从 stream 上取消注册

提示: 在使用 await for 前,确保代码清晰, 并且确实希望等待所有流的结果。 例如,通常不应该使用 await for 的UI事件侦听器, 因为UI框架会发送无穷无尽的事件流。

可调用类

通过实现类的 call() 方法, 能够让类像函数一样被调用

class WannabeFunction {
  call(String a, String b, String c) => '$a $b $c!';
}

main() {
  var wf = new WannabeFunction();
  var out = wf("Hi","there,","gang");
  print('$out');
}

Isolates

大多数计算机中,甚至在移动平台上,都在使用多核CPU。 为了有效利用多核性能,开发者一般使用共享内存数据来保证多线程的正确执行。 然而, 多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。

所有 Dart 代码都在隔离区( isolates )内运行,而不是线程。 每个隔离区都有自己的内存堆,确保每个隔离区的状态都不会被其他隔离区访问

元数据

使用元数据可以提供有关代码的其他信息。 元数据注释以字符 @ 开头, 后跟对编译时常量 (如 deprecated) 的引用或对常量构造函数的调用

对于所有 Dart 代码有两种可用注解:

  • @deprecated
  • @override

可以自定义元数据注解

元数据可以在 library、 class、 typedef、 type parameter、 constructor、 factory、 function、 field、 parameter 或者 variable 声明之前使用,也可以在 import 或者 export 指令之前使用。

使用反射可以在运行时获取元数据信息

注释

  • 单行注释
  • 多上注释
  • 文档注释

文档注释以 /// 或者 /** 开始。 在连续行上使用 /// 与多行文档注释具有相同的效果

/// A domesticated South American camelid (Lama glama).
///
/// 自从西班牙时代以来,
/// 安第斯文化就将骆驼当做肉食类和运输类动物。
class Llama {
  String name;

  /// 喂养骆驼 [Food].
  ///
  /// 典型的美洲驼每周吃一捆干草。
  void feed(Food food) {
    // ...
  }

  /// 使用 [activity] 训练骆驼
  /// [timeLimit] 分钟。
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

解析 Dart 代码并生成 HTML 文档,可以使用 SDK 中的 documentation generation tool.

总结

不清晰的地方:

  • 库、包的开发;
  • 元数据编程;
  • 异步Stream编程;
  • 文档生成;
  • 隔离区isolates的使用;
  • 生成器

Dart语言概述 over!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值