rakulang, dartlang, nimlang, golang, rustlang, lang lang no see

Dart 语言速查表

焉知非鱼

Dart速查表。

字符串插值 #

使用 ${expression} 将表达式的值放到字符串里面。如果表达式是一个标识符, 就可以省略 {}

下面是字符串插值的例子:

字符串 结果
‘${3 + 2}’ ‘5’
‘${“word”.toUpperCase()}’ ‘WORD’
‘$myObject’ The value of myObject.toString()

代码示例 #

下面的函数接收两个整数作为参数。使其返回一个包含两个整数的字符串,并以空格分隔。例如 stringify(2, 3) 应该返回 ‘2 3’。

String stringify(int x, int y) {
    return '$x $y';
}

Null 无感知操作符 #

Dart 提供了一些方便的操作符来处理可能为空的值。其中一个是 ??= 赋值运算符,只有当一个变量当前为空时,它才会给这个变量赋值:

int a;    // a 的初始值为 null
a ??= 3;
print(a); // 打印 3

a ??= 5;
print(a); // 仍然打印 3

另一个 null-aware 操作符是 ??,它返回其左边的表达式,除非该表达式的值为 null,在这种情况下,它计算并返回其右边的表达式:

print(1 ?? 3);     // 打印 1
print(null ?? 12); // 打印 12 

代码示例 #

String foo = 'a string';
String bar; // Unassigned objects are null by default.

// makes 'a string' be assigned to baz.
String baz = foo ?? bar;

void updateSomeVars() {
  // makes 'a string' be assigned to bar.
  bar ??= 'a string';
}

有条件的属性访问 #

要保护对对象的一个可能为空的属性或方法的访问,请在点(.)前加上一个问号(?):

myObject?.someProperty

上述代码等同于以下代码:

(myObject != null) ? myObject.someProperty : null

你可以在一个表达式中把 ?. 的多个使用链接在一起:

myObject?.someProperty?.someMethod()

如果 myObjectmyObject.someProperty 为 null,前面的代码将返回 null(并且从不调用 someMethod())。

代码示例 #

尝试使用条件属性访问来完成下面的代码片段。

// This method should return the uppercase version of `str`
// or null if `str` is null.
String upperCaseIt(String str) {
  // Try conditionally accessing the `toUpperCase` method here.
  return str?.toUpperCase();
}

集合字面量 #

Dart 内置了对列表、映射和集合的支持。你可以使用字面量创建它们:

final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings  = {'one', 'two', 'three'};
final aMapOfStringsToInts = {
  'one': 1,
  'two': 2,
  'three': 3,  
}

Dart 的类型推理可以为你分配类型给这些变量。在本例中,推断的类型是 List<String>Set<String>Map<String, int>

或者你可以自己指定类型:

final aListOfInts = <int>[];
final aSetOfInts  = <int>{};
final aMapOfIntToDouble = <int, double>{};

当你用子类型的内容初始化一个列表,但仍然希望列表是 List<BaseType> 时,指定类型是很方便的:

final aListOfBaseType = <BaseType>[SubType(), SubType()];

代码示例 #

尝试将以下变量设置为指定的值。

// Assign this a list containing 'a', 'b', and 'c' in that order:
final aListOfStrings = ['a', 'b', 'c'];

// Assign this a set containing 3, 4, and 5:
final aSetOfInts = {3, 4, 5};

// Assign this a map of String to int so that aMapOfStringsToInts['myKey'] returns 12:
final aMapOfStringsToInts = {'myKey': 12};

// Assign this an empty List<double>:
final anEmptyListOfDouble = <double>[];

// Assign this an empty Set<String>:
final anEmptySetOfString = <String>{};

// Assign this an empty Map of double to int:
final anEmptyMapOfDoublesToInts = <double, int>{};

箭头语法 #

你可能在 Dart 代码中看到过 => 符号。这种箭头语法是一种定义函数的方式,该函数执行其右边的表达式并返回其值。

例如,考虑这个对 List 类的 any() 方法的调用:

bool hasEmpty = aListOfStrings.any((s) {
  return s.isEmpty;
});

这里有一个更简单的方法来写这个代码:

bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);

代码示例 #

试着完成以下使用箭头语法的语句:

class MyClass {
  int _value1 = 2;
  int _value2 = 3;
  int _value3 = 5;

  // Returns the product of the above values:
  int get product => _value1 * _value2 * _value3;
  
  // Adds one to _value1:
  void incrementValue1() => _value1++; 
  
  // Returns a string containing each item in the
  // list, separated by commas (e.g. 'a,b,c'): 
  String joinWithCommas(List<String> strings) => strings.join(',');
}

级联 #

要对同一对象进行一系列操作,可以使用级联(...)。我们都见过这样的表达式:

myObject.someMethod()

它在 myObject 上调用 someMethod(),表达式的结果是 someMethod() 的返回值。

下面是同样的表达式,有一个级联:

myObject..someMethod()

虽然它仍然在 myObject 上调用 someMethod(),但表达式的结果并不是返回值-它是对 myObject 的引用! 使用级联,你可以将原本需要单独语句的操作串联起来。例如,请看以下代码:

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

有了级联,代码就会变得短得多,而且你也不需要 button 变量:

querySelector('#confirm')
..text = 'Confirm'
..class.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));

代码示例 #

使用级联来创建一个单一的语句,将一个 BigObjectanIntaStringaList 属性设置为 1、‘String!’ 和 [3.0](分别地),然后调用 allDone()

class BigObject{
  int anInt = 0;
  String aString = '';
  List<double> aList = [];
  bool _done = false;

  void allDone() {
      _done = true;
  }    
}

BigObject fillBigObject(BigObject obj) {
    return obj
      ..anInt = 1
      ..aString = 'String!'
      ..aList.add(3)
      ..allDone();
}

getters 和 setters #

当你需要对一个属性进行更多的控制时,你可以定义 getter 和 setter,而不是简单的字段。

例如,你可以确保一个属性的值是有效的:

class MyClass {
  int _aProperty = 0;

  int get aProperty => _aProperty;

  set aProperty(int value) {
    if (value >= 0) {
      _aProperty = value;    
    }    
  }
}

你也可以使用 getter 来定义计算属性:

class MyClass {
  List<int> _values = [];

  void addValue(int value) {
    _values.add(value);    
  }

  // 一个计算属性
  int get count {
    return _values.length;
  }
}

代码示例 #

想象一下,你有一个购物车类,它保存了一个私有的 List<double> 的价格。添加以下内容:

  • 一个叫做 total 的 getter,返回价格的总和。
  • 用一个新的列表替换列表的 setter,只要新的列表不包含任何负价格(在这种情况下,setter 应该抛出一个 InvalidPriceException)。
class InvalidPriceException {}

class ShoppingCart {
  List<double> _prices = [];
  
  double get total => _prices.fold(0, (e, t) => e + t);
  
  set prices(List<double> value) {
    if (value.any((p) => p < 0)) {
      throw InvalidPriceException();
    }
    
    _prices = value;
  }
}

可选位置参数 #

Dart 有两种函数参数:位置参数和命名参数。位置参数是你可能熟悉的那种:

int sumUp(int a, int b, int c) {
  return a + b  + c;
}

// ...
int total = sumUp(1, 2, 3);

在 Dart 中,你可以将这些位置参数用括号包裹起来,使其成为可选的参数:

int sumUpToFive(int a, [int b, int c, int d, int e]) {
  int sum = a;
  if (b != null) sum += b;
  if (c != null) sum += c;
  if (d != null) sum += d;
  if (e != null) sum += e;
  return sum;
}

// ...
int total = sumUpToFive(1,2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);

可选的位置参数在函数的参数列表中总是最后一个。它们的默认值是空的,除非你提供了另一个默认值:

int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
// ···
}
// ···
int newTotal = sumUpToFive(1);
print(newTotal); // <-- prints 15

代码示例 #

实现一个名为 joinWithCommas() 的函数,接受 1 到 5 个整数,然后返回一个用逗号分隔的数字字符串。下面是一些函数调用和返回值的例子:

函数调用 返回值
joinWithCommas(1) ‘1’
joinWithCommas(1, 2, 3) ‘1,2,3’
joinWithCommas(1, 1, 1, 1, 1) ‘1,1,1,1,1’
main() {
  var res = joinWithCommas(1,2,3,4);
  print(res);
}

String joinWithCommas(int a, [int b, int c, int d, int e]) {
  List<int> sum = [];
  sum.add(a);
  if (b != null) sum.add(b);
  if (c != null) sum.add(c);
  if (d != null) sum.add(d);
  if (e != null) sum.add(e);

  return sum.join(',');
}

可选命名参数 #

使用大括号语法,你可以定义有名称的可选参数。

void printName(String firstName, String lastName, {String suffix}) {
  print('$firstName $lastName ${suffix ?? ''}');
}
// ···
printName('Avinash', 'Gupta');
printName('Poshmeister', 'Moneybuckets', suffix: 'IV');

正如你所期望的,这些参数的值默认为空,但你可以提供默认值。

void printName(String firstName, String lastName, {String suffix = ''}) {
  print('$firstName $lastName $suffix');
}

一个函数不能同时拥有可选的位置参数和可选的命名参数。

代码示例 #

MyDataObject 类添加一个 copyWith() 实例方法。它应该接受三个命名参数:

  • int newInt
  • String newString
  • double newDouble

当调用时,copyWith() 应该基于当前实例返回一个新的 MyDataObject,并将前面参数(如果有的话)的数据复制到对象的属性中。例如,如果 newInt 是非空的,那么将其值复制到 anInt 中。

class MyDataObject {
  final int anInt;
  final String aString;
  final double aDouble;

  MyDataObject({
     this.anInt = 1,
     this.aString = 'Old!',
     this.aDouble = 2.0,
  });

  MyDataObject copyWith({int newInt, String newString, double newDouble}) {
    return MyDataObject(
      anInt:      newInt ?? this.anInt,
      aString: newString ?? this.aString,
      aDouble: newDouble ?? this.aDouble,
    );
  }
}

异常 #

Dart 代码可以抛出和捕获异常。与 Java 相比,Dart 的所有异常都是未检查的异常。方法不声明它们可能会抛出哪些异常,你也不需要捕捉任何异常。

Dart 提供了 ExceptionError 类型,但你可以抛出任何非空对象:

throw Exception('Something bad happened.');
throw 'Waaaaaaah!';

在处理异常时使用 tryoncatch 关键字:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

try 关键字的工作原理和其他大多数语言一样。使用 on 关键字按类型过滤特定的异常,使用 catch 关键字获取异常对象的引用。

如果不能完全处理异常,可以使用 rethrow 关键字来传播异常:

try {
  breedMoreLlamas();
} catch (e) {
  print('I was just trying to breed llamas!.');
  rethrow;
}

无论是否抛出异常,都要执行代码,使用 final:

try {
  breedMoreLlamas();
} catch (e) {
  // ... handle exception ...
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}

代码示例 #

实现下面的 tryFunction()。它应该执行一个不可信的方法,然后做如下操作:

  • 如果 untrustworthy() 抛出一个 ExceptionWithMessage,调用 logger.logException,并提供异常类型和消息(尝试使用 oncatch)。
  • 如果 untrustworthy() 抛出一个 Exception,调用 logger.logException,并注明异常类型(尝试使用 on)。
  • 如果 untrustworthy() 抛出任何其他对象,不要捕获异常。
  • 当所有的东西都被捕获和处理后,调用 logger.doneLogging(尝试使用 finally)。
typedef VoidFunction = void Function();

class ExceptionWithMessage {
  final String message;
  const ExceptionWithMessage(this.message);
}

abstract class Logger {
  void logException(Type t, [String msg]);
  void doneLogging();
}

void tryFunction(VoidFunction untrustworthy, Logger logger) {
  try {
    untrustworthy();
  } on ExceptionWithMessage catch (e) {
    logger.logException(e.runtimeType, e.message);
  } on Exception {
    logger.logException(Exception);
  } finally {
    logger.doneLogging();
  }
}

在构造函数中使用 this #

Dart 提供了一个方便的快捷方式来为构造函数中的属性赋值:在声明构造函数时使用 this.propertyName:

class MyColor {
  int red;
  int green;
  int blue;

  MyColor(this.red, this.green, this.blue)    
}

final color = MyColor(80, 80, 128);

这种技术也适用于命名参数。属性名成为参数的名称:

class MyColor {
  ...
  MyColor({this.red, this.green, this.blue});    
}
final color = MyColor(red: 80, green: 80, blue: 80);

对于可选参数,默认值按预期工作:

MyColor([this.red = 0, this.green = 0, this.blue = 0]);
// or
MyColor({this.red = 0, this.green = 0, this.blue = 0});

代码示例 #

MyClass 添加一个单行构造函数,使用 this. 语法来接收和分配类的三个属性的值:

class MyClass {
  final int anInt;
  final String aString;
  final double aDouble;
  
  MyClass(this.anInt, this.aString, this.aDouble);
}

初始化器列表 #

有时候,当你实现一个构造函数时,你需要在构造函数体执行之前做一些设置。例如,在构造函数体执行之前,final 字段必须有值。在一个初始化器列表中做这些工作,它位于构造函数的签名和它的主体之间。

Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');  
}

初始化器列表也是一个方便放置断言的地方,它只在开发过程中运行:

NonNegativePoint(this.x, this.y)
    : assert(x >= 0),
      assert(y >= 0) {
  print('I just made a NonNegativePoint: ($x, $y)');        
}

代码示例 #

完成下面的 FirstTwoLetters 构造函数。使用初始化器列表将 word 中的前两个字符分配给 letterOneLetterTwo 属性。为了获得额外的积分,可以添加一个断言来捕获少于两个字符的单词。

class FirstTwoLetters {
  final String letterOne;
  final String letterTwo;

  // Create a constructor with an initializer list here:
  FirstTwoLetters(String word)
    : assert(word.length >=2),
      letterOne = word[0],
      letterTwo = word[1]; 
}

命名构造器 #

为了允许类有多个构造函数,Dart 支持命名构造函数:

class Point {
  double x, y;

  Point(this.x, this.y);

  Point.origin() {
    x = 0;
    y = 0;    
  }    
}

要使用命名构造函数,请使用它的全名来调用它:

final myPoint = Point.origin();

代码示例 #

Color 类一个名为 Color.black 的构造函数,将三个属性都设置为 0。

class Color {
  int red;
  int green;
  int blue;
  
  Color(this.red, this.green, this.blue);

  Color.black() {
    red = 0;
    green = 0;
    blue = 0;
  } 
}

工厂构造函数 #

Dart 支持工厂构造函数,它可以返回子类型甚至 null。要创建一个工厂构造函数,请使用 factory 关键字:

class Square extends Shape {}

class Circle extends Shape {}

class Shape {
  Shape();

  factory Shape.fromTypeName(String typeName) {
    if (typeName == 'square') return Square();
    if (typeName == 'circle') return Circle();

    print('I don\'t recognize $typeName');
    return null
  }    
}

代码示例 #

填入名为 IntegerHolder.fromList 的工厂构造函数,使其做以下工作:

  • 如果列表有一个值,就用这个值创建一个 IntegerSingle
  • 如果列表有两个值,则用该值依次创建一个 IntegerDouble
  • 如果列表有三个值,则按顺序创建一个 IntegerTriple
  • 否则,返回 null。
class IntegerHolder {
  IntegerHolder();
  
  factory IntegerHolder.fromList(List<int> list) {
    if (list?.length == 1) {
      return IntegerSingle(list[0]);
    } else if (list?.length == 2) {
      return IntegerDouble(list[0], list[1]);
    } else if (list?.length == 3) {
      return IntegerTriple(list[0], list[1], list[2]);
    } else {
      return null;
    } 
  }
}

class IntegerSingle extends IntegerHolder {
  final int a;
  IntegerSingle(this.a); 
}

class IntegerDouble extends IntegerHolder {
  final int a;
  final int b;
  IntegerDouble(this.a, this.b); 
}

class IntegerTriple extends IntegerHolder {
  final int a;
  final int b;
  final int c;
  IntegerTriple(this.a, this.b, this.c); 
}

重定向构造函数 #

有时,一个构造函数的唯一目的是重定向到同一类中的另一个构造函数。重定向构造函数的主体是空的,构造函数调用出现在冒号(:)之后。

class Automobile {
  String make;
  String model;
  int mpg;

  // 这个类的主构造函数
  Automobile(this.make, this.model, this.mpg);

  // 代理到主构造函数
  Automobile.hybrid(String make, String model) : this(make, model, 60);

  // 代理到命名构造函数
  Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2'); 
}

代码示例 #

还记得上面的 Color 类吗?创建一个名为 black 的命名构造函数,但不是手动分配属性,而是将其重定向到默认构造函数,参数为 0。

class Color {
  int red;
  int green;
  int blue;
  
  Color(this.red, this.green, this.blue);

  Color.black() : this(0, 0, 0);
}

常量构造函数 #

如果你的类产生的对象永远不会改变,你可以让这些对象成为编译时常量。要做到这一点,请定义一个 const 构造函数,并确保所有的实例变量都是最终变量。

class ImmutablePoint {
  const ImmutablePoint(this.x, this.y);

  final int x;
  final int y;

  static const ImmutablePoint origin = ImmutablePoint(0, 0);
}

代码示例 #

修改 Recipe 类,使它的实例可以是常量,并创建一个常量构造函数,执行以下操作。

  • 有三个参数: ingredients, caloriesmilligramsOfSodium(按顺序)。
  • 使用 this. 语法,自动将参数值分配给同名的对象属性。
  • 是常量,在构造函数声明中,const 关键字就在 Recipe 前面。
class Recipe {
  final List<String> ingredients;
  final int calories;
  final double milligramsOfSodium;

  const Recipe(this.ingredients, this.calories, this.milligramsOfSodium);
}

下一步是什么? #

我们希望你喜欢使用这个 codelab 来学习或测试你对 Dart 语言一些最有趣的功能的知识。这里有一些关于现在要做什么的建议。