掌握Flutter开发,从Dart入门开始(1)

发表时间: 2019-05-31 07:01

欢迎大家关注我的微信公众号“IT工匠”获取更多资源(涉及算法、数据结构、java、深度学习、计算机网络、python、Android等互联网技术资料)。

变量

下面是声明变量并赋值的方法:

var name = 'Bob';

变量实际上是一个引用,上面名字为 name 的变量引用了 一个内容为 “Bob”的 String 对象。

默认值

没有初始化的变量会自动获取一个默认值 null。类型为数字的变量如果没有初始化其值也是 null,因为数字类型也是对象。

int lineCount;assert(lineCount == null);

注意: assert()起到断言的作用,当assert()中的条件为true时语句会正常执行,如果条件为false 则会抛出一个异常。

可选的类型

在声明变量的时候可以选择性地加上具体的类型,比如:

String name = 'Bob';

添加类型可以更加清晰的表达你的意图。 IDE 编译器等工具有可以通过你声明的类型为你提供代码补全、提前发现 bug 等功能。

注意:根据 代码风格推荐 部分的建议,对于局部变量我们应该使用 var 而不是具体的类型。

final以及const

如果你之后不打算修改某一个变量,则应该使用 final 或者 const修饰该变量。 一个 final 变量只能赋值一次,一个 const 变量是编译时常量(const 变量是特殊的 final 变量), 顶级的 final 变量或者类中的 final 变量在 第一次使用的时候初始化。

这里可能有读者会有疑惑,什么是编译时常量?我们用一个例子来说明final和const的真正区别:

buildName() { return 'running name';}finalAndConstDemo() { final finalName = buildName(); const constName = 'Joey';}

如上代码,buildName()是一个方法,作用是返回一个字符串,注意,只有在程序真正运行的时候才会知道这个buildName()方法到底返回的是什么,即这个buildName()方法返回的不是”编译时常量”,因此,不能使用其对const修饰的变量进行赋值,但是,final的要求比const低一点,它只要求只能对其修饰的变量赋值一次,至于是编译时赋值还是运行时赋值是不要求的,所以上面的代码块会通过编辑并正确执行,倘若使用:

const constName=buildName()

则编译器会毫不留情地报错,因为buildName()的返回值不能再编译时被确定。

如果 const 变量在类中,则应该定义为 static const。 可以直接定义 const 和其值,也可以使用其他 const 变量的值来初始化一个 const 变量:

const bar = 1000000; const atm = 1.01325 * bar;

const 关键字不仅可以用来修饰常量,还可以用来修饰构造函数,这种类型的构造函数创建的对象是不可改变的,比如:

var foo = const []; final bar = const []; const baz = const [];

[] 的作用是创建一个空的list,const [] 的作用是创建一个空的、不可改变的list(empty, immutable list (EIA)),对上面的三行代码解释如下:

  1. 第一行代码比表示foo当前引用的是一个空的、不可改变的list,但是我们可以在之后让foo引用其他对象,比如foo=name,这是合法的,但是在此之前不能进行像foo.add("name")这样的操作,因为此时foo指向的是一个不可改变的list。
  2. 第二行代码表示bar将会一直是一个空的、不可改变的list,在之后的代码中不能对bar进行修改操作,例如bar.add("name")将会抛出异常。
  3. 第三行代码表示baz将会在编译时就是一个空的、不可改变的list,在之后的代码中不能对baz进行修改操作,例如baz.add("name")将会抛出异常。

内置的类型

Dart 内置支持下面这些类型:

  • numbers
  • strings
  • booleans
  • lists (也被称之为 arrays)
  • maps
  • runes (用于在字符串中表示 Unicode 字符)
  • symbols

你可以直接使用字面量来初始化上面的这些类型。 例如 'this is a string' 是一个字符串字面量, true 是一个布尔字面量。

由于 Dart 中每个变量引用的都是一个对象 (一个类的实例), 所以你通常应该使用构造函数来初始化变量。 一些内置的类型具有自己的构造函数。例如, 可以使用 Map()构造函数来创建一个 map, 比如: new Map()。

Numbers(数值)

Dart 支持两种类型的数字:

  • int
  • 整数值,其取值通常位于 -253 和 253 之间。
  • double
  • 64-bit (双精度) 浮点数,符合 IEEE 754 标准。

int 和 double 都是 num的子类。 num 类型定义了基本的操作符,例如 +, -, /, *, 还定义了 abs()、ceil()、和 floor() 等函数。 (位操作符,例如 >> 定义在 int 类中) 如果 num 或者其子类型具有的的基本运算不满足你的需求,请查找 dart:math(地址:
https://api.dartlang.org/stable/dart-math/dart-math-library.html)库以寻找更多的运算支持。

整数是不带小数点的数字。下面是一些定义整数的方式:

var x = 1;var hex = 0xDEADBEEF;var bigInt = 34653465834652437659238476592374958739845729;

如果一个数带小数点,则其为 double, 下面是定义 double 的一些方式:

var y = 1.1;var exponents = 1.42e5;

下面是字符串和数字之间转换的方式:

// String -> intvar one = int.parse('1');assert(one == 1);// String -> doublevar onePointOne = double.parse('1.1');assert(onePointOne == 1.1);// int -> StringString oneAsString = 1.toString();assert(oneAsString == '1');// double -> StringString piAsString = 3.14159.toStringAsFixed(2);assert(piAsString == '3.14');

整数类型支持传统的位移操作符,比如左移(<<),右移(>>), 与 (&), 或 (|):

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

数字字面量为编译时常量。 很多算术表达式只要其操作数是常量,则表达式结果也是编译时常量:

const msPerSecond = 1000;const secondsUntilRetry = 5;const msUntilRetry = secondsUntilRetry * msPerSecond;

如上代码,msPerSecond和secondsUntilRetry在编译时时常量,所以由secondsUntilRetry * msPerSecond赋值的msUntilRetry在编译时也会是常量。

Strings(字符串)

Dart 字符串是 UTF-16 编码的字符序列。 可以使用单引号或者双引号来创建字符串:

var s1 = 'Single quotes work well for string literals.';var s2 = "Double quotes work just as well.";var s3 = 'It\'s easy to escape the string delimiter.';var s4 = "It's even easier to use the other delimiter.";

可以在字符串中使用表达式,用法为: ${expression}。

如果表达式是一个变量,可以省略 {}。 如果表达式的结果为一个对象,则 Dart 会调用对象的 toString() 函数来获取一个字符串。

var s = 'string interpolation';assert('Dart has $s, which is very handy.' == 'Dart has string interpolation, ' + 'which is very handy.');assert('That deserves all caps. ' + '${s.toUpperCase()} is very handy!' == 'That deserves all caps. ' + 'STRING INTERPOLATION is very handy!');

注意: == 操作符判断两个对象的内容是否一样。 如果两个字符串包含一样的字符编码序列, 则他们是相等的。

拼接字符串有两种方法:

  1. 使用 + 操作符来把多个字符串链接为一个
  2. 直接把多个字符串放到一起
var s1 = 'String ' 'concatenation' " works even over line breaks.";//直接将字符串放在一起,会进行自动拼接assert(s1 == 'String concatenation works even over ' 'line breaks.');var s2 = 'The + operator ' + 'works, as well.';//使用+运算符来进行字符串拼接assert(s2 == 'The + operator works, as well.');

使用三个单引号或者三个双引号来创建多行字符串对象:

var s1 = '''You can createmulti-line strings like this one.''';var s2 = """This is also amulti-line string.""";

通过提供一个 r 前缀可以创建一个原始(raw)字符串,即字符串中的转义字符会不起作用:

stringDemo() { var s1 = r'\n1234'; var s2 = '\n1234'; print('s1:' + s1); print('s2:' + s2);}

上面代码的执行结果为:

image-20190530214504777

可以看到,加了r前缀后\n中的转义符\失效了。

Booleans(布尔值)

为了代表布尔值,Dart 有一个 bool 类型。 只有两个对象是布尔类型的:true 和 false , 这两个对象也都是编译时常量。

当 Dart 需要一个布尔值的时候,只有 true 对象才被认为是 true。 所有其他的值都是 flase,像 1、 "aString"、 以及 someObject 等值都被认为是 false。

例如:

var name = 'Bob';if (name) { print('You have a name!');}

在Dart生产模式下运行上述代码,不会输出'You have a name!',因为name的值不是true,所以就会认为是false。

Lists(列表)

array (或者有序集合)几乎是所有编程语言中最常见的集合类型。 Dart 中的List对象指的就是数组,通常我们都称之为 lists。

下面是一个 Dart list 的示例:

var list = [1, 2, 3];

Lists 的下标索引从 0 开始,第一个元素的索引是 0,最后一个元素的索引是list.length - 1 。

List类还有很多操作,我会在后面讲解泛型和集合的时候展开叙述。

Maps

通常来说,Map 是一个键值对相关的对象。 键和值可以是任何类型的对象。每个键只出现一次, 而一个值则可以出现多次。Dart 通过 map 字面量 和 Map类支持 map。

下面是一些创建简单 map 的示例:

var gifts = { // Keys Values 'first' : 'partridge', 'second': 'turtledoves', 'fifth' : 'golden rings'};var nobleGases = { // Keys Values 2 : 'helium', 10: 'neon', 18: 'argon',};

使用 Map 构造函数也可以实现同样的功能:

var gifts = new Map();gifts['first'] = 'partridge';gifts['second'] = 'turtledoves';gifts['fifth'] = 'golden rings';var nobleGases = new Map();nobleGases[2] = 'helium';nobleGases[10] = 'neon';nobleGases[18] = 'argon';

往 map 中添加新的键值对:

var gifts = {'first': 'partridge'};gifts['fourth'] = 'calling birds'; // Add a key-value pair

获取 map 中的对象:

var gifts = {'first': 'partridge'};assert(gifts['first'] == 'partridge');

如果所查找的键不存在,则返回 null:

var gifts = {'first': 'partridge'};assert(gifts['fifth'] == null);

使用 .length 来获取 map 中键值对的数目:

var gifts = {'first': 'partridge'};gifts['fourth'] = 'calling birds';assert(gifts.length == 2);

同样使用 const 可以创建一个 编译时常量的 map:

final constantMap = const { 2: 'helium', 10: 'neon', 18: 'argon',};// constantMap[2] = 'Helium'; // Uncommenting this causes an error.

Runes

在Dart中,Runes代表字符串的UTF-32字符集, Unicode为每一个字符、标点符号、表情符号等都定义了一个唯一的数值,由于Dart字符串是UTF-16的字符序列,所以在字符串中表达32位的的字符序列时就需要新的语法了。通常使用\uXXXX的方式来表示, 这里的XXXX是4个16进制的数, 如,心形符号(♥)是\u2665,对于非4个数值的情况,把编码值放到大括号中即可, 如笑脸emoji () 是\u{1f600}

String类有一些属性可以提取 rune 信息:

  1. codeUnitAt 和 codeUnit 属性返回16位字符
  2. 使用 runes 属性来获取字符串的 runes 信息
runesDemo() { Runes runes = new Runes('\u2665, \u{1f605}, \u{1f60e}'); print(runes); print(new String.fromCharCodes(runes));}

上述代码的输出为:

image-20190530220322602

方法

Dart 是一个面向对象语言,方法也是对象并且具有一种类型:Function。 这意味着,方法可以赋值给变量,也可以当做其他方法的参数,同时也可以把 Dart 类的实例当做方法来调用。

下面是定义方法的示例:

bool isNoble(int atomicNumber) { return _nobleGases[atomicNumber] != null;}

你也可以选择不显示指定返回类型:

isNoble(atomicNumber) { return _nobleGases[atomicNumber] != null;}

对于只有一个表达式的方法,你可以选择使用缩写语法来定义:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

这个 => expr 语法是 { return expr; } 形式的缩写。=> 形式有时候也称之为胖箭头语法。

注意: 在箭头 (=>) 和分号 (;) 之间只能使用一个 表达式而 – 不能使用语句

方法可以有两种类型的参数:必需的可选的。 必需的参数在参数列表前面,可选的参数在参数列表后面。

可选参数

可选参数可以是命名参数或者基于位置的参数,但是这两种参数不能同时当做可选参数,也就是说,可选参数要么是命名参数,要么是基于位置的参数,不能说某一个方法中的可选参数既有命名参数,也有基于位置的参数。

(可选)命名参数

调用方法的时候,你可以使用这种形式 paramName: value 来指定命名参数。例如:

enableFlags(bold: true, hidden: false);

在定义方法的时候,使用 {param1, param2, …} 的形式来指定命名参数:

enableFlags({bool bold, bool hidden}) { // ...}

(可选)位置参数

把一些方法的参数放到 [] 中就变成可选位置参数了:

String say(String from, String msg, [String device]) { var result = '$from says $msg'; if (device != null) { result = '$result with a $device'; } return result;}

下面是不使用可选参数调用上面方法的示例:

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

下面是使用可选参数调用上面方法的示例:

assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');

默认参数值

在定义方法的时候,可以使用 = 来定义可选参数的默认值。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。

下面是设置可选参数默认值的示例:

void enableFlags({bool bold = false, bool hidden = false}) { // ...}// bold将会是true; hidden将会是false.enableFlags(bold: true);

版本问题: 旧版本代码可能需要使用一个冒号 (:) 而不是 = 来设置参数默认值。 原因在于 Dart SDK 1.21 之前的版本,命名参数只支持 :。 : 设置命名默认参数值在以后版本中将不能使用, 所以推荐你使用 = 来设置默认值, 并指定 Dart SDK 版本为 1.21 或者更高的版本。

下面的示例显示了如何设置位置参数的默认值:

String say(String from, String msg, [String device = 'carrier pigeon', String mood]) { var result = '$from says $msg'; if (device != null) { result = '$result with a $device'; } if (mood != null) { result = '$result (in a $mood mood)'; } return result;}assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

还可以为 list 或者 map 设置默认值。 下面的示例定义了一个方法 doStuff(), 并分别为 list 和 gifts 参数指定了 默认值:

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

入口方法

每个应用都需要有个顶级的 main() 入口方法才能执行。 main() 方法的返回值为 void 并且有个可选的 List<String> 参数。

下面是一个 web 应用的 main() 方法:

void main() { querySelector("#sample_text_id") ..text = "Click me!" ..onClick.listen(reverseText);}

注意: 前面代码中的 .. 语法为 级联调用(cascade)。 使用级联调用语法, 你可以在一个对象上执行多个操作。

下面是一个命令行应用的 main() 方法,并且使用了方法参数作为输入参数:

// Run the app like this: dart args.dart 1 testvoid main(List<String> arguments) { print(arguments); assert(arguments.length == 2); assert(int.parse(arguments[0]) == 1); assert(arguments[1] == 'test');}

一等方法对象

可以把方法当做参数调用另外一个方法。例如:

printElement(element) { print(element);}var list = [1, 2, 3];// Pass printElement as a parameter.list.forEach(printElement);

方法也可以赋值给一个变量:

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

上面的方法为下面即将介绍的匿名方法。

匿名方法

大部分方法都带有名字,例如 main() 或者 printElement()。 你有可以创建没有名字的方法,称之为 匿名方法,有时候也被称为 lambda 或者 closure 闭包。 你可以把匿名方法赋值给一个变量, 然后你可以使用这个方法,比如添加到集合或者从集合中删除。

匿名方法和命名方法看起来有点类似,都是在括号之间可以定义一些参数,参数使用逗号分割,也可以是可选参数。 后面大括号中的代码为方法体:

([[Type] param1[, …]]) { codeBlock; };

下面的代码定义了一个参数为i (该参数没有指定类型)的匿名函数。 list中的每个元素都会调用这个函数来 打印出来,同时来计算了每个元素在 list 中的索引位置。

var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];list.forEach((i) { print(list.indexOf(i).toString() + ': ' + i);});

如果方法只包含一个语句,可以使用胖箭头语法缩写:

list.forEach((i) => print(list.indexOf(i).toString() + ': ' + i));

静态作用域

Dart 是静态作用域语言,变量的作用域在写代码的时候就确定过了。 大括号里面定义的变量就只能在大括号里面访问,和 Java 作用域类似。

下面是作用域的一个示例:

var topLevel = true;main() { var insideMain = true; myFunction() { var insideFunction = true; nestedFunction() { var insideNestedFunction = true; assert(topLevel); assert(insideMain); assert(insideFunction); assert(insideNestedFunction); } }}

注意 nestedFunction() 可以访问所有的变量, 包含顶级变量,但是在main()方法中就不能访问myFunction()以及nestedFunction()中的变量,也就是说,变量是向下可见、向上不可见的。

词法闭包

一个 闭包 是一个方法对象,不管该对象在何处被调用, 该对象都可以访问其作用域内的变量。

方法可以封闭定义到其作用域内的变量。 下面的示例中,makeAdder() 捕获到了变量 addBy。 不管你在那里执行 makeAdder() 所返回的函数,都可以使用 addBy 参数。

/// Returns a function that adds [addBy] to the/// function's argument.Function makeAdder(num addBy) { return (num i) => addBy + i;}main() { // Create a function that adds 2. var add2 = makeAdder(2); // Create a function that adds 4. var add4 = makeAdder(4); assert(add2(3) == 5); assert(add4(3) == 7);}

测试函数是否相等

下面是测试顶级方法、静态方法和实例方法相等的示例:

foo() {} // A top-level functionclass A { static void bar() {} // A static method void baz() {} // An instance method}main() { var x; // Comparing top-level functions. x = foo; assert(foo == x); // Comparing static methods. x = A.bar; assert(A.bar == x); // Comparing instance methods. var v = new A(); // Instance #1 of A var w = new A(); // Instance #2 of A var y = w; x = w.baz; // These closures refer to the same instance (#2), // so they're equal. assert(y.baz == x); // These closures refer to different instances, // so they're unequal. assert(v.baz != w.baz);}

返回值

所有的方法都返回一个值。如果没有指定返回值,则默认把语句 return null;作为函数的最后一个语句执行。

操作符

下表是 Dart 中定义的操作符。 很多操作符都可以重载:

描述操作符一元后缀expr++ expr-- () [] . ?.一元前缀-expr !expr ~expr ++expr --expr乘除余* / % ~/加减+ -移位<< >>位运算& ^ ``relational and type test>= > <= < as is is!equality== !=logical AND&& ``if null??conditionalexpr1 ? expr2 : expr3cascade..assignment= *= /= ~/= %= += -= <<=>>= &= ^= `=`??=

在使用操作符的时候,会自动创建表达式。下面是一些操作符表达式:

a++a + ba = ba == ba ? b: ca is T

在上表中所列的操作符都是按照优先级顺序从左到右,从上到下的方式来列出的, 上面和左边的操作符优先级要高于下面和右边的。 例如 % 操作符优先级高于 ==,而 等号 高于 &&。所以下面的代码执行结果是一样的:

// 1: Parens improve readability.if ((n % i == 0) && (d % i == 0))// 2: Harder to read, but equivalent.if (n % i == 0 && d % i == 0)

警告: 对于有两个操作数的操作符,左边的操作数决定了操作符的功能。 例如,如果有一个 Vector 对象和一个 Point 对象, aVector + aPoint 使用的会是Vector 对象中定义的 + 操作符。

算术操作符

Dart 支持常用的算术操作符,如下:

操作符解释+加号–减号-*expr*负号*乘号/除号~/除号,但是返回值为整数%取模

示例:

assert(2 + 3 == 5);assert(2 - 3 == -1);assert(2 * 3 == 6);assert(5 / 2 == 2.5); // Result is a doubleassert(5 ~/ 2 == 2); // Result is an integerassert(5 % 2 == 1); // Remainderprint('5/2 = ${5~/2} r ${5%2}'); // 5/2 = 2 r 1

Dart 还支持递增、递减前缀 和后缀操作:

操作符解释++varvar = var + 1 (表达式的值是 var + 1,因为先进行+1操作,再执行表达式)var++var = var + 1 (表达式的值是 var,因为先执行表达式,再进行+1操作)--varvar = var – 1 (表达式的值是 var – 1,因为先进行-1操作,再执行表达式)var--var = var – 1 (表达式的值是 var,因为先执行表达式,再进行+1操作)

示例:

var a, b;a = 0;b = ++a; // Increment a before b gets its value.assert(a == b); // 1 == 1a = 0;b = a++; // Increment a AFTER b gets its value.assert(a != b); // 1 != 0a = 0;b = --a; // Decrement a before b gets its value.assert(a == b); // -1 == -1a = 0;b = a--; // Decrement a AFTER b gets its value.assert(a != b); // -1 != 0

相等相关的操作符

下表是和相等操作相关的操作符。

操作符解释==相等!=不等>大于<小于>=大于等于<=小于等于

要测试两个对象代表的是否为同样的内容,使用 == 操作符。如果你需要知道两个对象是否是同一个对象,应该使用identical()方法。

下面是 == 操作符工作原理解释:

  1. 如果 x 或者 y 是 null:如果两个都是 null 则返回 true,如果只有一个是 null 返回 false。
  2. 如果x和y都不是null:返回 x.==(y)这个函数的返回值(像 == 这种操作符是定义在左边对象上的方法。 你甚至还可以重写这些操作符对应的方法。,在后面的章节将会具体介绍。)

下面是相等关系操作符的 使用示例:

assert(2 == 2);assert(2 != 3);assert(3 > 2);assert(2 < 3);assert(3 >= 3);assert(2 <= 3);

类型判定操作符

as、 is、 和 is! 操作符是在运行时判定对象类型的操作符:

操作符解释as类型转换is如果对象是指定的类型返回 Trueis!如果对象是指定的类型返回 False

只有当 obj 实现了 T 的接口, obj is T 才是 true。例如 obj is Object 总是 true。

使用 as 操作符把对象转换为特定的类型。 一般情况下,你可以把它当做用 is 判定类型然后调用所判定对象的函数的缩写形式。例如下面的 示例:

if (emp is Person) { // Type check emp.firstName = 'Bob';}

使用 as 操作符可以简化上面的代码:

(emp as Person).firstName = 'Bob';

注意: 上面这两个代码效果是有区别的。如果 emp 是 null 或者不是 Person 类型, 则第一个示例使用 is 则不会执行条件里面的代码,而第二个情况使用 as 则会抛出一个异常

赋值操作符

使用 = 操作符来赋值,用 ??= 操作符用来指定值为 null 的变量的值。

a = value; // 给 a 变量赋值b ??= value; // 如果 b 是 null,则将value赋值给 b; // 如果不是 null,则 b 的值保持不变

还有复合赋值操作符 += 等可以 赋值:

=-=/=%=>>=^=+=*=~/=<<=&=`=`

下面是复合赋值操作符工作原理解释:

复合赋值操作符对应的完整表达式对于 操作符 op:a op = ba = a op b示例:a += ba = a + b

比如:

var a = 2; // Assign using =a *= 3; // Assign and multiply: a = a * 3assert(a == 6);

逻辑操作符

可以使用逻辑操作符来操作布尔值:

操作符解释!expr对表达式结果取反(true 变为 false,false 变为 true)``逻辑或&&逻辑与

下面是使用示例:

if (!done && (col == 0 || col == 3)) { // ...Do something...}

位和移位操作符

在 Dart 中可以单独操作数字的某一位, 下面操作符同样应用于整数:

操作符解释&与``或^异或~expr按位取反<<Shift left>>Shift right

使用位于和移位操作符的示例:

final value = 0x22;final bitmask = 0x0f;assert((value & bitmask) == 0x02); // ANDassert((value & ~bitmask) == 0x20); // AND NOTassert((value | bitmask) == 0x2f); // ORassert((value ^ bitmask) == 0x2d); // XORassert((value << 4) == 0x220); // Shift leftassert((value >> 4) == 0x02); // Shift right

条件表达式

Dart 有两个特殊的操作符可以用来替代 if-else语句:

  • condition ? expr1 : expr2
  • 如果 condition 是 true,执行 expr1 (并返回执行的结果), 否则执行 expr2 并返回其结果。
  • expr1?? expr2
  • 如果 expr1 不为null,返回其值, 否则执行 expr2 并返回其结果。

如果你需要基于布尔表达式的值来赋值, 考虑使用 ?:。

var finalStatus = m.isFinal ? 'final' : 'not final';

如果布尔表达式是测试值是否为 null, 考虑使用 ??。

String toString() => msg ?? super.toString();

下面是一样效果的实现, 但是代码不是那么简洁:

// Slightly longer version uses ?: operator.String toString() => msg == null ? super.toString() : msg;// Very long version uses if-else statement.String toString() { if (msg == null) { return super.toString(); } else { return msg; }}

级联操作符

级联操作符 (..) 可以在同一个对象上连续调用多个函数以及访问成员变量。 使用级联操作符可以避免创建临时变量, 并且写出来的代码看起来更加流畅:

例如下面的代码:

querySelector('#button') // Get an object. ..text = 'Confirm' // Use its members. ..classes.add('important') ..onClick.listen((e) => window.alert('Confirmed!'));

第一个方法 querySelector() 返回了一个 selector 对象。 后面的级联操作符都是调用这个对象的成员, 并忽略每个操作所返回的值。

上面的代码和下面的代码功能一样:

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

级联调用也可以嵌套:

final addressBook = (new AddressBookBuilder() ..name = 'jenny' ..email = 'jenny@example.com' ..phone = (new PhoneNumberBuilder() ..number = '415-555-0100' ..label = 'home') .build()) .build();

在方法上使用级联操作符需要非常小心, 例如下面的代码就是不合法的:

// Does not workvar sb = new StringBuffer();sb.write('foo')..write('bar');

sb.write() 函数返回一个 void, 无法在 void 上使用级联操作符。

注意: 严格来说, 两个点的级联语法不是一个操作符, 只是一个 Dart 特殊语法。

其他操作符

下面是其他操作符:

OperatorNameMeaning()使用方法代表调用一个方法[]访问 List访问 list 中特定位置的元素.访问 Member访问元素,例如 foo.bar 代表访问 foo 的 bar 成员?.条件成员访问和 . 类似,但是左边的操作对象不能为 null,例如 foo?.bar 如果 foo 为 null 则返回 null,否则返回 bar 成员

关于 .、 ?.、和 .. 的详情,请参考 Classes。

流程控制语句

可以使用下面的语句来控制 Dart 代码的流程:

  • if 以及 else
  • for 循环
  • while以及 do-while 循环
  • break 和 continue
  • switch 和 case
  • assert

If和else

Dart 支持 if 语句以及可选的 else,例如下面的示例:

if (isRaining()) { you.bringRainCoat();} else if (isSnowing()) { you.wearJacket();} else { car.putTopDown();}

For循环

可以使用标准的 for 循环:

var message = new StringBuffer("Dart is fun");for (var i = 0; i < 5; i++) { message.write('!');}

Dart for 循环中的闭包会捕获循环的 index 索引值,例如:

var callbacks = [];for (var i = 0; i < 2; i++) { callbacks.add(() => print(i));}callbacks.forEach((c) => c());

输出的结果为所期望的 0 和 1。

如果要遍历的对象实现了 Iterable 接口,则可以使用 forEach()方法。如果没必要使用当前遍历的索引,则使用 forEach() 方法是个非常好的选择:

candidates.forEach((candidate) => candidate.interview());

List 和 Set 等实现了 Iterable 接口,另外实现了Iterable的类还支持 for-in 形式的遍历:

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

While 以及 do-while

while 循环在执行循环之前先判断条件是否满足:

while (!isDone()) { doSomething();}

而 do-while 循环是先执行循环代码再判断条件:

do { printLine();} while (!atEndOfPage());

Break 和 continue

使用 break 来终止循环:

while (true) { if (shutDownRequested()) break; processIncomingRequests();}

使用 continue 来开始下一次循环:

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

上面的代码在实现 Iterable 接口的对象上可以使用下面的写法:

candidates.where((c) => c.yearsExperience >= 5) .forEach((c) => c.interview());

Switch 和 case

Dart 中的 Switch 语句使用 == 比较 integer、string、或者编译时常量。 比较的对象必须都是同一个类的实例(也不能是其子类),而且对应类必须没有重写 == 操作符。

每个非空的 case 语句都必须有一个 break 语句。 另外还可以通过 continue、 throw 或者 return 来结束非空 case 语句。

当没有 case 语句匹配的时候,可以使用 default 语句来匹配这种默认情况。

var command = 'OPEN';switch (command) { case 'CLOSED': executeClosed(); break; case 'PENDING': executePending(); break; case 'APPROVED': executeApproved(); break; case 'DENIED': executeDenied(); break; case 'OPEN': executeOpen(); break; default: executeUnknown();}

下面的示例代码在 case 中省略了 break 语句, 编译的时候将会出现一个错误:

var command = 'OPEN';switch (command) { case 'OPEN': executeOpen(); // ERROR: Missing break causes an exception!! case 'CLOSED': executeClosed(); break;}

但是,在 Dart 中的空 case 语句中可以不要 break 语句:

var command = 'CLOSED';switch (command) { case 'CLOSED': // Empty case falls through. case 'NOW_CLOSED': // Runs for both CLOSED and NOW_CLOSED. executeNowClosed(); break;}

如果你需要实现这种继续到下一个 case 语句中继续执行,则可以 使用 continue 语句跳转到对应的标签(label)处继续执行:

var command = 'CLOSED';switch (command) { case 'CLOSED': executeClosed(); continue nowClosed; // Continues executing at the nowClosed label.nowClosed: case 'NOW_CLOSED': // Runs for both CLOSED and NOW_CLOSED. executeNowClosed(); break;}

每个 case 语句都可以有局部变量,局部变量只有在这个语句内可见。

Assert(断言)

如果条件表达式结果不满足需要,则可以使用 assert 语句来打断代码的执行。 下面介绍如何使用断言。 下面是一些示例代码:

// Make sure the variable has a non-null value.assert(text != null);// Make sure the value is less than 100.assert(number < 100);// Make sure this is an https URL.assert(urlString.startsWith('https'));

注意: 断言只在检查模式下运行有效,如果在生产模式运行,则断言不会执行。

assert 方法的参数可以为任何返回布尔值的表达式或者方法。 如果返回的值为 true, 断言执行通过,执行结束。 如果返回值为 false, 断言执行失败,会抛出一个AssertionError异常 。

异常(Exceptions)

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

和 Java 不同的是,所有的 Dart 异常是非检查异常。 方法不一定声明了他们所抛出的异常, 并且不要求你捕获任何异常。

Dart 提供了 Exception 和 Error 类型, 以及一些子类型。你还可以定义自己的异常类型。但是, Dart 代码可以抛出任何非 null 对象作为为异常,而不仅仅是实现了 Exception 或者 Error 的对象。

Throw

下面是抛出一个异常的示例:

throw new FormatException('Expected at least 1 section');

还可以抛出任意的对象:

throw 'Out of llamas!';

由于抛出异常是一个表达式,所以可以在 => 语句中使用,也可以在其他能使用表达式的地方抛出异常。

distanceTo(Point other) => throw new UnimplementedError();

Catch

捕获异常可以避免异常继续传递(你在catch语句中又重新抛出异常除外)。 捕获异常给你一个处理该异常的机会:

try { breedMoreLlamas();} on OutOfLlamasException { buyMoreLlamas();}

对于可以抛出多种类型异常的代码,你可以指定多个捕获语句。每个语句分别对应一个异常类型, 如果捕获语句没有指定异常类型,则该语句可以捕获任何异常类型:

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

如之前代码所示,你可以使用on 或者 catch 来声明捕获语句,也可以同时使用。使用 on 来指定异常类型,使用 catch 来 捕获异常对象。

函数 catch() 可以带有一个或者两个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 (一个 StackTrace 对象)。

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

使用 rethrow 关键字可以 把捕获的异常给 重新抛出。

final foo = '';void misbehave() { try { foo = "You can't change a final variable's value."; } catch (e) { print('misbehave() partially handled ${e.runtimeType}.'); rethrow; // Allow callers to see the exception. }}void main() { try { misbehave(); } catch (e) { print('main() finished handling ${e.runtimeType}.'); }}

Finally

要确保某些代码执行,不管有没有出现异常都需要执行,可以使用 一个 finally语句来实现。如果没有 catch 语句来捕获异常, 则在执行完 finally 语句后, 异常被抛出了:

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

定义的 finally 语句在任何匹配的 catch 语句之后执行:

try { breedMoreLlamas();} catch(e) { print('Error: $e'); // Handle the exception first.} finally { cleanLlamaStalls(); // Then clean up.}