本文档需要你在别的编程语言上有经验

一个简单的Dart程序

下面这部分代码使用了很多Dart语言的功能:

  1. // Define a function.
  2. void printInteger(int aNumber) {
  3. print('The number is $aNumber.'); // Print to console.
  4. }
  5. // This is where the app starts executing.
  6. void main() {
  7. var number = 42; // Declare and initialize a variable.
  8. printInteger(number); // Call a function.
  9. }

// 这是一个注释
一个单行注释,Dart同样支持多行注释和文档注释

void
这是一个特殊的类型,这种类型的返回值将不会被使用。像print()或者main()之类不需要明确返回值的函数将会使用void类型

int
这是另一种类型,用于指代整数。其他还有很多类似的内建类型,如String、List和bool

42
一个数字字面量,数字字面量是一种编译时常量

print()
一个简便输出内容的方式

‘…’和”…”
字符串字面量

$variableName和${expression}
字符串模板: 在字符串中插入变量或者表达式的一种方法

main()
一个特殊且必需的用于应用启动的顶层函数

var
当没有指定类型时声明变量使用,变量的实际类型由它的初始值决定

重要概念

在学习Dart语言时,你需要时刻在脑海里保持这些认知和概念

  • 所有可以放入变量中的都是对象,所有的对象都是类的实例。不论是数字、函数或者null都是对象。除了null以外的其他对象都是继承自Object这个类

null safety 在Dart 2.12版本中加入

  • 如果你开启了空安全,那么变量除非在你特别指定的情况外都不能包含null值。你可以在一个变量的类型后面跟上?符号来将该变量声明为可空类型。如 int? 声明的变量则既可以为整型数字也可以为null。如果你知道某个表达式的结果肯定不为空,那么你可以使用 ! 来强制使用非空类型。如: int a = (这是一个必定不为空的表达式)!

  • 如果你想要使用的地方任何类型都允许, 且你必须要显式的指定类型,那么你可以使用 Object? 类型

  • Dart支持泛型,例如 List 整型数字列表,List 对象类型列表

  • Dart支持顶级函数,像main(),也支持静态函数,实例函数

  • Dart支持顶级变量

  • Dart没有类似Java的public、protected和private等权限控制关键字。如果你使用了_开头的名字作为变量名时,该变量为包内私有

  • Dart变量名使用小写字母或者下划线开头,然后接字母和数字的组合

  • 关键字

    abstract else import super as
    enum in switch assert export
    interface sync async extends is
    this await extension library throw
    break external mixin true case
    factory new try catch false
    null typedef class final on
    var const finally operator void
    continue for part while covariant
    Function rethrow with default get
    return yield deferred hide set
    do if show dynamic implements
    static

    上面这些关键字都是系统保留字,不能用于变量名

    变量

    下面是一个创建变量并初始化的例子:

    1. var name = 'Bob';

    一个名字叫name的变量,它内部有一个引用指向一个值为Bob的字符串对象

    这个变量的类型会根据值自动推断为String,但是你可以显式的指定它的类型,如果某个对象的类型不是某种单一的类型,那么指定为Object类型或者dynamic类型

    1. Object name = 'Bob';

    也可以直接声明为值对应的类型:

    1. String name = 'Bob';

    对于局部变量,使用var来声明变量会比其他类型声明更符合规范

    默认值

    没有初始化的可空类型变量将会被赋上null值,甚至是数字类型,因为它和其他一切类型一样在Dart中都是对象

    1. int? lineCount;
    2. assert(lineCount == null);

    生产环境中将会忽略assert函数调用,在开发环境中,如果assert不满足内部的条件将会抛出一个异常

    如果你开启了空安全设置,那么你在使用这些变量之前必须给变量初始化一个非空的值

    1. int lineCount = 0;

    如果你在声明变量时没有初始化值,那么你在使用前需要给该变量分配值。比如下面这个例子,代码是合规的因为Dart可以感知 lineCount 在被print函数调用的时候是非空的:

    1. int lineCount;
    2. if (weLikeToCount) {
    3. lineCount = countLines();
    4. }else {
    5. lineCount = 0;
    6. }
    7. print(lineCount);

    Late变量

    Dart2.12版本中添加了 late 修饰符,它有两个作用:

    • 声明一个可以在声明后再进行初始化的非空变量
    • 延迟初始化一个变量

    上面的例子中,我们可以发现,Dart可以感知到一个变量在使用的时候是一个非空的变量,但是有时候这种感知是会出错的。比较常见的两种情况是顶级变量和实例变量,Dart很难去查看它们是否被设置值,所以在这些情况下就不会去做推断

    如果你确定某个变量在使用前是非空的,但是Dart报错了,那么你可以使用 late 关键字,来规避这个错误

    1. late String description;
    2. void main() {
    3. description = 'Feijoada!';
    4. print(description);
    5. }

    如果你使用了late修饰符去声明late变量,但是在使用的时候没有赋初值,那么将会产生一系列的错误

    如果你用 late 声明了某个变量,但是在声明的时候就初始化了,那么这个变量仍旧要到第一次使用的时候才会被进行初始化,这种特性在有的时候很方便:

    • 某个变量可能运行中并不需要,且初始化成本很高
    • 某个对象实例化的时候,构造函数需要依赖该变量

    下面这个例子中,如果 _readThermometer()函数不被调用的话,temperature变量将永远不会被使用

    1. late String temperature = _readThermometer();

    final 和 const

    如果你不想修改某个变量,那么在声明的时候,使用final或者const去替代var或者其他确切的类型。一个常量只能被初始化一次,是一个编译时常量。顶级变量或者类内变量如果设置为常量,将会在第一次使用的时候初始化

    实例变量可以被final修饰但是不能使用const修饰

    下面的例子展示如何声明常量

    1. final name = 'Bob';
    2. final String nickName = 'Bobby';
    3. //你不能去修改常量
    4. name = 'Alice'; // Error: a final variable can only be set once.

    使用 const 修饰编译时常量,如果常量在类级别,那就使用 static const 修饰。当设置常量时,将其值设置为数字、字符串字面量或者表达式的返回值等等:

    1. const bar = 1000000;
    2. const double atm = 1.01325 * bar;

    const关键字不仅用于声明常量,,你还可以使用它来创建常量值,以及声明创建常量值的构造函数

    1. var foo = const [];
    2. final bar = const [];
    3. const baz = []; // 这等同于 const[]

    如果声明变量的时候使用了const,那么值里面就不需要再额外写上const,如上面例子中不需要写成

    1. const baz = const [];

    这样的用法是冗余的

    如果一个变量是非const的,那么你可以修改它的值,即使它曾经被赋值了常量

    1. var foo = const [];
    2. foo = [1, 2, 3]; //这样的修改是被允许的,因为foo不是常量

    反之则不行

    1. const baz = [];
    2. baz = [42]; //这样是错误的

    常量的定义中可以插入表达式

    1. const Object i = 3;
    2. const list = [i as int];
    3. const map = {if (i is int) i : "int"};
    4. const set = {if (list is List<int>) ...list};

    final 和 const 的区别在于,final虽然不能修改最终的变量,但是可以修改变量内部的值,如用 final 声明了一个数组常量,该常量本身不能在被赋值,但是其内部的数组是可以添加修改值的,而const修饰的常量则无法改变

    内建类型

    Dart对以下类型有特殊支持

    • Numbers(int, double)
    • Strings(String)
    • Booleans(bool)
    • Lists(List, Array)
    • Sets(Set)
    • Maps(Map)
    • Runes(Runes, characters)
    • Symbols(Symbol)
    • null(Null)

    这些特殊的类型,可以通过字面量的方式去创建对象,如 ‘this is a string’ 就是一个字符串字面量, true 就是一个布尔值字面量

    因为Dart中的变量都是对象(类的实例),那么也可以通过构造函数的方式去实例化变量。这些内建类型中也有一些有它们自己的构造函数,如你可以使用 Map() 去创建一个map

    这些类型中有一些类型在Dart中扮演着特殊的角色:

    • Object: 除Null以外的所有类的父类
    • Future 和 Stream: 在异步编程中使用
    • Iterable: 在for-in循环以及迭代生成函数中使用
    • Never: 用于表示表达式无法执行,常用于函数抛出异常
    • dynamic: 你不知道具体类型,所以使用动态类型来规避静态检查,通常可以使用Object或者Object?来替换
    • void: 标识一个值永远不会被使用,常作为返回值类型

    Numbers

    Dart的数字由两部分组成

    int

    具体值依赖于平台,整型值一般不能大于64 bits,在原生平台上,整型值从 -2^63 到 2^63 -1,在Web平台,整型值会被作为javascript数字(64位浮点数不带小数部分),值从 -2^53 到 2^53 - 1

    double

    IEEE 754标准指定的64位(双精度)浮点数。

    int 和 double都是num的子类型。num类型支持基础的运算符,如加减乘除,也可以使用诸如 abs() ceil() 和 floor()以及其他数学函数。如果你需要某个计算方法,可以去 dart:math 库中寻找

    下面是一些使用数字字面量定义整型变量的例子:

    1. var x = 1;
    2. var hex = 0xDEADBEEF;
    3. var exponent = 8e5;

    如果一个数字有小数部分,那么它就是double型:

    1. var y = 1.1;
    2. var exponents = 1.42e5;

    你可以将变量声明为num类型,这样变量将同时拥有整型和浮点型两种值:

    1. num x = 1;
    2. x += 2.5;

    整型值在需要的时候,可以自动转为浮点型:

    1. double z = 1; //此时 1 自动转为 1.0

    下面展示,如何在 string 和 number 之间互相转换:

    1. // String -> int
    2. var one = int.parse('1');
    3. // String -> double
    4. var onePointOne = double.parse('1.1');
    5. // int -> String
    6. String oneAsString = 1.toString();
    7. // double -> String
    8. String piAsString = 3.14159.toStringAsFixed(2);

    数字字面量是编译时常量,许多计算表达式也是编译时常量,只要它们的操作数在计算时是常量即可

    1. const msPerSecond = 1000;
    2. const secondsUntilRetry = 5;
    3. const msUntilRetry = secondsUntilRetry * msPerSecond;

    Strings

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

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

    你可以使用 ${expression} 的形式,在字符串中使用表达式插入值,如果你插入的是一个变量,那么可以省略大括号。如果你塞入的是一个对象, 那么字符串模板将会去调用对象的 toString 方法来往字符串中传值

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

    Dart中的 == 可以判断两个对象是否相等,判断两个字符串是否相等,取决于两个字符串是否拥有相同的字符

    你可以使用 + 号来连接两个字符串:

    1. var s1 = 'String '
    2. 'concatenation'
    3. " works even over line breaks.";
    4. assert(s1 ==
    5. 'String concatenation works even over '
    6. 'line breaks.');
    7. var s2 = 'The + operator ' + 'works, as well.';
    8. assert(s2 == 'The + operator works, as well.');

    你可以使用三引号来创建多行字符串:

    1. var s1 = '''
    2. You can create
    3. multi-line strings like this one.
    4. ''';
    5. var s2 = """This is also a
    6. multi-line string.""";

    你可以使用 r 前缀来创建一个原生raw字符串,即字符串中的特殊符号不转义:

    1. var s = r'In a raw string, not even \n gets special treatment.';

    字符串字面量也是编译时常量,所以只要插值表达式在计算时,它的操作元素为字符串、数字、布尔值或者null,这个表达式都是编译时常量

    1. // These work in a const string.
    2. const aConstNum = 0;
    3. const aConstBool = true;
    4. const aConstString = 'a constant string';
    5. // These do NOT work in a const string.
    6. var aNum = 0;
    7. var aBool = true;
    8. var aString = 'a string';
    9. const aConstList = [1, 2, 3];
    10. const validConstString = '$aConstNum $aConstBool $aConstString';
    11. // const invalidConstString = '$aNum $aBool $aString $aConstList';

    Booleans

    Dart中的布尔值类型叫做 bool ,它有 true 和 false 两个编译时常量值
    Dart的类型安全保证了你使用 if (非布尔值) 或者 assert (非布尔值) 时,不会出错,例如:

    1. // Check for an empty string.
    2. var fullName = '';
    3. assert(fullName.isEmpty);
    4. // Check for zero.
    5. var hitPoints = 0;
    6. assert(hitPoints <= 0);
    7. // Check for null.
    8. var unicorn;
    9. assert(unicorn == null);
    10. // Check for NaN.
    11. var iMeantToDoThis = 0 / 0;
    12. assert(iMeantToDoThis.isNaN);

    Lists

    在许多常用的编程语言中最常见的集合就是数组了,在Dart中数组就是List

    Dart的list字面量类似javascript的数组字面量,如

    1. var list = [1, 2 ,3];

    Dart会推断list的类型为List,如果你试图添加非整型的对象到列表中,就会引发报错

    你可以在集合最后添加逗号,添加的逗号不会引发错误,但是可以规避掉许多复制粘贴的bug

    1. var list = [
    2. 'Car',
    3. 'Boat',
    4. 'Plane',
    5. ];

    List的索引是从0开始的,list.length - 1 就是列表的最后一项索引。你可以像在javascript中一样获取长度,以及其中的每个元素

    1. var list = [1, 2, 3];
    2. assert(list.length == 3);
    3. assert(list[1] == 2);
    4. list[1] = 1;
    5. assert(list[1] == 1);

    创建一个常量List

    1. var constantList = const [1, 2, 3];
    2. // constantList[1] = 1; // This line will cause an error.

    Dart 2.3 提供了扩展运算符 (… 或者 …?) ,这个运算符提供往集合中添加多个值的简便方式
    例如: 你可以使用 … 来将一个List中的值插入到另一个List中

    1. var list = [1, 2, 3];
    2. var list2 = [0, ...list];
    3. assert(list2.length == 4);

    如果扩展运算符的操作对象可能是null ,那么可以使用 …? 来避免异常发生,null的对象也不会加入到集合中

    1. var list;
    2. var list2 = [0, ...?list];
    3. assert(list2.length == 1);

    Dart提供了 条件if 和 条件for ,让你可以在创建集合的时候使用 if 和 for

    1. var nav = [
    2. 'Home',
    3. 'Furniture',
    4. 'Plants',
    5. if (promoActive) 'Outlet'
    6. ];
    7. var listOfInts = [1, 2, 3];
    8. var listOfStrings = [
    9. '#0',
    10. for (var i in listOfInts) '#$i'
    11. ];
    12. assert(listOfStrings[1] == '#1');

    Sets

    Dart中的Set是一些无序不重复元素的集合。你可以通过set字面量或者构造函数去创建Set

    1. var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

    Dart会自动推断halogens的类型为Set,如果往其中添加类型不符的元素将会引发报错

    想要创建一个空的集合,需要使用{}并携带类型来创建,或者明确指定类型,单纯的{}并不能创建空Set

    1. var names = <String>{}; //类型加{}
    2. // Set<String> names = {}; // 明确指定变量类型
    3. // var names = {}; // 单纯使用{}只能创建一个Map

    map的字面量与set的字面量是类似的,但是因为map优先,所以{}默认是指代map类型,如果没有在 {} 前指定类型,那么创建出来的map 将会默认使用 Map 类型

    使用add或者addAll将元素添加到已经存在的Set中

    1. var elements = <String>{};
    2. elements.add('fluorine');
    3. elements.addAll(halogens);

    使用 .length 来获取集合中元素的数量

    1. var elements = <String>{};
    2. elements.add('fluorine');
    3. elements.addAll(halogens);
    4. assert(elements.length == 5);

    想要创建一个编译时常量的Set,可以在创建的字面量前加上const

    1. final constantSet = const {
    2. 'fluorine',
    3. 'chlorine',
    4. 'bromine',
    5. 'iodine',
    6. 'astatine',
    7. };
    8. // constantSet.add('helium'); // This line will cause an error.

    Set也是支持的扩展运算符 … 和 …? ,条件 if 和 条件 for 也是支持的

    Maps

    Map是用key 和 value,键值对组成的对象,key 和 value 可以是任何类型,同一个key在Map只能有一个,但是同一value可以有多个。Map也可以通过字面量和构造函数两种方式创建

    1. var gifts = {
    2. // Key: Value
    3. 'first': 'partridge',
    4. 'second': 'turtledoves',
    5. 'fifth': 'golden rings'
    6. };
    7. var nobleGases = {
    8. 2: 'helium',
    9. 10: 'neon',
    10. 18: 'argon',
    11. };

    上面的例子中,gifts会被自动推断为 Map 类型,nobleGases会被自动推断为 Map类型。如果试图向其中添加不符合类型的键值对,那么将会产生异常

    使用构造函数创建Map

    1. var gifts = Map<String, String>();
    2. gifts['first'] = 'partridge';
    3. gifts['second'] = 'turtledoves';
    4. gifts['fifth'] = 'golden rings';
    5. var nobleGases = Map<int, String>();
    6. nobleGases[2] = 'helium';
    7. nobleGases[10] = 'neon';
    8. nobleGases[18] = 'argon';

    如果你使用过 C# 或者 Java之类的语言,你可能会在使用构造函数的时候,使用 new Map(),但其实new关键字在Dart中是可选的,可以省略

    你可以像在javascript中一样的方式给Map添加新的键值对

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

    获取值的方式也和在javascript中一样

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

    如果你获取值的那个key不在map中,那么你将会得到null

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

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

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

    想要创建一个编译时常量,可以在map字面量之前加上const

    1. final constantMap = const {
    2. 2: 'helium',
    3. 10: 'neon',
    4. 18: 'argon',
    5. };
    6. // constantMap[2] = 'Helium'; // This line will cause an error.

    Map也是支持的扩展运算符 … 和 …? ,条件 if 和 条件 for 也是支持的

    Runes和字符集

    在Dart中,Runes代表字符串中的每个字符的类型。Unicode定义了全球各种文字中的字符,Dart使用了UTF-16字符集,因此在Dart中输出特殊字符需要一些特别的写法,比如表示一个unicode,通常使用的是 \uXXXX,XXXX是一个四位数字的十六进制值。如字符 (♥)的值就是 \u2665,如果十六进制数字多于4位,使用大括号包裹,如笑脸 (😆) 字符的标识就是 \u{1f606}

    如果你想要读写字符串中的字符,可以使用定义在String上的getter

    1. import 'package:characters/characters.dart';
    2. ...
    3. var hi = 'Hi 🇩🇰';
    4. print(hi);
    5. print('The end of the string: ${hi.substring(hi.length - 1)}');
    6. print('The last character: ${hi.characters.last}\n');

    Symbols

    在Dart中一些关键字或者运算符被称为Symbol,这些Symbol是不能用于作为变量名的

    如果要使用的话,使用 # 加上对应标识符的名字

    1. #radix
    2. #bar

    Symbol是编译时常量

    函数

    Dart是面向对象的语言,所以函数在Dart中也是一种对象,它们也有自己的类型 Function ,这意味着函数可以被赋值给变量或者作为别的函数的参数

    下面是一个函数实现的小例子

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

    尽管规范推荐给函数加上返回值类型修饰,但是如果不加的话,函数依旧可以正常运行

    1. isNoble(atomicNumber) {
    2. return _nobleGases[atomicNumber] != null;
    3. }

    如果函数仅仅包含一个表达式,那么你可以以一个更加简短的方式实现

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

    => expr 就是 { return expr; } 的一种简短写法,我们一般称之为箭头写法

    只有单个表达式时才能使用箭头语法,如果是一个代码块则不能使用。就像你不能使用 if 代码块,但是可以使用条件表达式

    参数

    函数可以拥有任意数量的位置参数,它们可以在后面跟上命名参数或者可选参数,但是不能同时有这两者

    命名参数

    命名参数是可选的,除非它们被加上 required 标识
    在你调用函数的时候,要使用 name: value 的形式明确指定命名参数:

    1. enableFlags(bold: true, hidden: false);

    在定义函数的时候,使用 { param1 , param2, …} 来指定命名参数

    1. void enableFlags({bool? bold, bool? hidden}) {...}

    如果一个参数是可选的,但是不能为空,那么你可以给它提供一个默认值

    尽管命名参数是一种可选参数,但是依旧可以在参数前面加上 required 来强制必须传入参数:

    1. const Scrollbar({Key? key, required Widget child})

    这样当使用者创建Scrollbar时,如果没有传入child那么就会报错

    可选位置参数

    使用 [] 将参数包裹起来后,参数将变为可选位置参数

    1. String say(String from, String msg, [String? device]) {
    2. var result = '$from says $msg';
    3. if (device != null) {
    4. result = '$result with a $device';
    5. }
    6. return result;
    7. }

    下面是调用可选参数函数时,不传入可选参数:

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

    下面是传入可选参数的例子

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

    参数默认值

    命名参数和位置参数都可以使用 = 来设置默认值,提供的值必须是编译时常量,如果没有提供默认值,那么默认值为null

    下面就是给命名参数设置默认值的例子:

    1. /// Sets the [bold] and [hidden] flags ...
    2. void enableFlags({bool bold = false, bool hidden = false}) {...}
    3. // bold will be true; hidden will be false.
    4. enableFlags(bold: true);

    下面的例子是给位置参数设置默认值:

    1. String say(String from, String msg,
    2. [String device = 'carrier pigeon']) {
    3. var result = '$from says $msg with a $device';
    4. return result;
    5. }
    6. assert(say('Bob', 'Howdy') ==
    7. 'Bob says Howdy with a carrier pigeon');

    你也可以给List 或者 Map设置默认值:

    1. void doStuff(
    2. {List<int> list = const [1, 2, 3],
    3. Map<String, String> gifts = const {
    4. 'first': 'paper',
    5. 'second': 'cotton',
    6. 'third': 'leather'
    7. }}) {
    8. print('list: $list');
    9. print('gifts: $gifts');
    10. }

    main函数

    每一个程序都必须有顶级函数main来作为应用的入口,main函数是void类型,参数为可选的List类型参数

    下面是一个简单的main函数例子

    1. void main() {
    2. print('Hello, World!');
    3. }

    下面是一个main函数接收参数的命令行应用的例子

    1. // 使用 dart args.dart 1 test 命令运行程序
    2. void main(List<String> arguments) {
    3. print(arguments);
    4. assert(arguments.length == 2);
    5. assert(int.parse(arguments[0]) == 1);
    6. assert(arguments[1] == 'test');
    7. }

    函数也是对象

    你可以把函数作为参数传给另一个函数:

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

    你也可以把函数赋值给一个变量:

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

    这个例子用到了匿名函数,匿名函数将在下面介绍

    匿名函数

    大多数函数都是有名字的,像main() 或者 printElement()。你也可以创建没有名字的函数(称之为匿名函数)或者lambda表达式、闭包。你可以将匿名函数赋值给一个变量,以便你可以在集合中添加或者删除它

    匿名函数看起来类似于命名函数-括号之间零个或多个参数,以逗号和可选的类型注释分隔

    函数体包含以下的代码块:

    1. ([[Type] param1 [, ...]]) {
    2. codeBlock;
    3. }

    下面的例子中使用的匿名函数中的参数是没有类型的 item ,这个函数遍历list的每一个元素来推断具体的item的类型

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

    如果函数只有一个表达式或者只返回一个值,你可以使用箭头函数来简写

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

    闭包

    闭包是一种在范围内访问定义在范围外的变量的一种语法。
    函数可以捕获在其周围范围内的变量,在下面的例子中,makeAdder()可以捕获变量addBy,无论内部返回的函数在哪里调用,它始终会保存addBy这个变量

    1. /// Returns a function that adds [addBy] to the
    2. /// function's argument.
    3. Function makeAdder(int addBy) {
    4. return (int i) => addBy + i;
    5. }
    6. void main() {
    7. // Create a function that adds 2.
    8. var add2 = makeAdder(2);
    9. // Create a function that adds 4.
    10. var add4 = makeAdder(4);
    11. assert(add2(3) == 5);
    12. assert(add4(3) == 7);
    13. }

    操作符

    Dart支持以下的操作符

    • 表达式后缀 expr++ expr— () [] . ?.
    • 表达式前缀 -expr !expr ~expr ++expr —expr await expr
    • 乘除 * / % ~/
    • 加减 + -
    • 位运算 << >> >>>
    • 与或异或 & ^ |
    • 关系运算符 >= > <= < as is is!
    • 等于不等于 == !=
    • 逻辑与 &&
    • 逻辑或 ||
    • 如果空 ??
    • 条件运算符 expr1 ? expr2 : expr3
    • 级联运算符 .. ?..
    • 结果运算符 = *= /= += -= &= ^=

    当你使用了运算符,那你就创建了表达式,下面是一些表达式的例子:

    1. a++
    2. a + b
    3. a = b
    4. a == b
    5. c ? a : b
    6. a is T

    在上面的运算符表中,其中的运算符的优先级是依次递减的。举个例子,%运算符的优先级要比 == 的优先级高,== 的优先级又比 && 的优先级要高:

    1. if ((n % i == 0) && (d % i == 0)) ...
    2. if (n % i == 0 && d % i == 0) ...

    算数运算符

    操作符 含义
    +
    -
    -expr 负数
    *
    /
    ~/ 整除
    % 求余

    举个例子:

    1. assert(2 + 3 == 5);
    2. assert(2 - 3 == -1);
    3. assert(2 * 3 == 6);
    4. assert(5 / 2 == 2.5); // Result is a double
    5. assert(5 ~/ 2 == 2); // Result is an int
    6. assert(5 % 2 == 1); // Remainder
    7. assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

    Dart同样支持使用前缀或后缀加减符号来完成自加自减操作

    操作符 含义
    ++var var = var + 1 (先加再赋值)
    var++ var = var + 1 (先赋值再加)
    —var var = var - 1 (先减再赋值)
    var— var = var - 1 (先赋值再减)

    举个例子:

    1. var a, b;
    2. a = 0;
    3. b = ++a; // Increment a before b gets its value.
    4. assert(a == b); // 1 == 1
    5. a = 0;
    6. b = a++; // Increment a AFTER b gets its value.
    7. assert(a != b); // 1 != 0
    8. a = 0;
    9. b = --a; // Decrement a before b gets its value.
    10. assert(a == b); // -1 == -1
    11. a = 0;
    12. b = a--; // Decrement a AFTER b gets its value.
    13. assert(a != b); // -1 != 0

    相等和关系运算符

    操作符 含义
    == 相等
    != 不相等
    > 大于
    < 小于
    >= 大于等于
    <= 小于等于

    想要测试两个对象是否相等,使用 == 运算符来进行运算(在极少数情况下你需要知道两个对象是否完全相同,可以使用identical函数来测试)

    1. assert(2 == 2);
    2. assert(2 != 3);
    3. assert(3 > 2);
    4. assert(2 < 3);
    5. assert(3 >= 3);
    6. assert(2 <= 3);

    类型测试运算符

    运算符 含义
    as 类型转换
    is 类型判断(如果类型相同返回true)
    is! 类型判断(如果类型不相同返回true)

    如果对象obj实现了T接口,那么 obj is T 就会返回true,就像 obj is Object总是返回true。如果你确定某个对象是某种类型使用 as 运算符来进行类型转换

    1. (employee as Person).firstName = 'Bob';

    如果你不确定obj是不是T类型的,可以在使用该对象之前使用 is T 来测试类型

    1. if (employee is Person) {
    2. // Type check
    3. employee.firstName = 'Bob';
    4. }

    上面两端代码效果是不一样的,如果employee不是Person类型,或者值为null,前者会抛出异常,而后者将不会执行代码

    赋值运算符

    你可以使用 = 符号来给变量进行赋值,想要给null值变量赋值可以使用 ??= 符号

    1. // 给a赋值
    2. a = value;
    3. // 如果b是null则给b赋值value, 如果b不为null则不进行操作,保持原值
    4. b ??= value;

    赋值运算符都可以和其他的算数运算符结合使用

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

    逻辑运算符

    你可以使用逻辑运算符来操作布尔表达式

    操作符 含义
    !expr 将布尔表达式的值置反
    || 逻辑或
    && 逻辑与

    下面是一个使用逻辑运算符的例子

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

    移位运算符

    你可以在Dart中将这些移位运算符和整型数字组合,用于操作数字中的每一位

    运算符 含义
    & 按位与
    | 按位或
    ^ 按位异或
    ~expr 按位补码
    << 左移位
    >> 右移位

    下面是使用移位运算符的例子:

    1. final value = 0x22;
    2. final bitmask = 0x0f;
    3. assert((value & bitmask) == 0x02); // AND
    4. assert((value & ~bitmask) == 0x20); // AND NOT
    5. assert((value | bitmask) == 0x2f); // OR
    6. assert((value ^ bitmask) == 0x2d); // XOR
    7. assert((value << 4) == 0x220); // Shift left
    8. assert((value >> 4) == 0x02); // Shift right

    条件运算符

    在Dart中可以使用二元条件运算符来替代原先需要使用if-else的代码块

    • condition ? expr1 : expr2

    如果条件为true则会执行expr1反之执行expr2

    • expr1 ?? expr2

    如果expr1的返回值不为null则返回对应value,如果为null则返回expr2的value

    下面是使用的例子:

    1. var visibility = isPublic ? 'public' : 'private';
    2. String playerName(String? name) => name ?? 'Guest';

    级联运算符

    级联运算符允许你对同一个对象进行一系列的操作,除了函数调用可以使用级联,对象上的字段也可以使用级联,这样经常可以省去创建临时变量的操作,让你的代码更加流畅

    例子:

    1. var paint = Paint()
    2. ..color = Colors.black
    3. ..strokeCap = StrokeCap.round
    4. ..strokeWidth = 5.0;

    Paint()构造函数会返回一个Paint对象,在这个返回对象后面的级联操作会自动忽略掉返回值
    这段代码和下面的代码是等价的

    1. var paint = Paint();
    2. paint.color = Colors.black;
    3. paint.strokeCap = StrokeCap.round;
    4. paint.strokeWidth = 5.0;

    如果你进行级联操作的对象可能为空,则可以考虑在第一个操作的位置使用 ?.. 级联操作符,这样如果对象为空接下来对其的操作都不会生效

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

    这段代码和下面的代码是等价的

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

    级联操作符是可以嵌套的

    1. final addressBook = (AddressBookBuilder()
    2. ..name = 'jenny'
    3. ..email = 'jenny@example.com'
    4. ..phone = (PhoneNumberBuilder()
    5. ..number = '415-555-0100'
    6. ..label = 'home')
    7. .build())
    8. .build();

    在使用级联代码的时候要小心,级联要用在返回对象上,如下面的代码就是错误的

    1. var sb = StringBuffer();
    2. sb.write('foo')
    3. ..write('bar'); // Error: method 'write' isn't defined for 'void'.

    sb.write() 返回值是void,级联不能用在void上

    其他操作符

    操作符 名称 含义
    () 函数运算符 代表执行函数
    [] 列表访问运算符 返回列表对应索引位置的值
    . 成员访问运算符 访问对象上的属性或者函数
    ?. 可选成员访问 如果成员不存在,则不会执行访问操作