Dart了解

作者: 平安喜乐698 | 来源:发表于2020-10-25 12:47 被阅读0次
    目录
    
      1. 注释
      2. 关键字
      3. 变量
      4. Dart内置的类型
      5. 运算符
      6. 流程控制语句
      7. 函数
      8. 类
      9. 库
      10. 异步
      11. 元数据
    

    前言

    Dart可以用来开发:App、Web、服务端脚本

    重要概念

    1. 所有变量都是对象(数字、函数、 null等等),所有类都继承自Object类(直接/间接)。
    
    
    2. Dart是强类型语言,但支持类型推断。
    dynamic修饰的变量可以赋值任何类型的对象。
    
    
    3. Dart支持泛型,如 List <int> (整数列表)或 List <dynamic> (任何类型的对象列表)。
    
    4. Dart支持顶级函数(main)、静态函数、实例函数 、嵌套函数 。
    
    
    5. Dart支持顶级变量、静态变量、实例变量(字段或属性)。
    
    
    6. 与Java不同,Dart 没有关键字 “public” , “protected” 和 “private” 。
     在Dart语言中使用下划线(_)前缀标识符,会强制其变成私有的。
    
    
    7. 一条语句通常包含一个或多个表达式,相反表达式不能直接包含语句。
    表达式(有运行时值)和 语句(没有运行时值)。 如,条件表达式 condition ? expr1 : expr2 的值可能是 expr1 或 expr2 。 if-else 语句没有值。 
    
    
    8. 代码问题:警告waring和错误error。 
    警告表明代码可能无法正常工作,但不会阻止程序的执行。 
    错误可能是编译时错误或者运行时错误。 编译时错误会阻止代码的执行; 运行时错误会导致代码在执行过程中引发异常(崩溃)。
    
    
    9. 标识符由字母数字下划线组成,只能以字母或下划线开头。
    区分大小写,不能为保留字。
    

    1. 注释

    Dart 支持单行注释、多行注释和文档注释。

    单行注释
    
    // 之后一直到该行结束之间的内容被编译器忽略
    
    多行注释(可嵌套)
    
    /*
    hello
    world
    */
    所有在 /* 和 */ 之间的内容被编译器忽略 (不会忽略文档注释)。
    
    文档注释
    
    方式一
    /// hello
    /// world
    
    方式二
    /** 
    hello  [Object]  在生成的文档中,[Object] 会成为一个链接, 指向 Object 类的 API 文档。
    world
    */
    
    在文档注释中,除非用中括号括起来,否则Dart 编译器会忽略所有文本。 
    使用中括号可以引用当前作用域内可见的类、 方法、 字段、 顶级变量、 函数、 和参数。 一旦注释转换为文档,引用文字会被替换为文档链接。
    

    2. 关键字

    关键字
    避免使用这些单词作为标识符。
    但是,如有必要,标有上标的关键字可以用作标识符:
        1. 带有 1 上标的单词为 上下文关键字, 仅在特定位置具有含义。 他们在任何地方都是有效的标识符。
        2. 带有 2 上标的单词为 内置标识符, 为了简化将 JavaScript 代码移植到 Dart 的工作, 这些关键字在大多数地方都是有效的标识符, 但它们不能用作类或类型名称,也不能用作 import 前缀。
        3. 带有 3 上标的单词是与 Dart 1.0 发布后添加的异步支持相关的更新,作为限制类保留字。
        4. 不能在标记为 async ,async* 或 sync* 的任何函数体中使用 await 或 yield 作为标识符。
        5. 关键字表中的剩余单词都是保留字,不能将保留字用作标识符。
    

    3. 变量

    声明 方式一(自动推断)

    var可以接收任何类型的变量,和js不同的是:一旦赋值类型便会确定,则不能再改变其类型。因为Dart本身是一个强类型语言,任何变量都是有确定类型的,在Dart中,当用var声明一个变量后,Dart在编译时会根据第一次赋值数据的类型来推断其类型,编译结束后其类型就已经被确定,而JavaScript是纯粹的弱类型脚本语言,var只是变量的声明方式而已。

    // 变量仅存储对象引用,这里的变量是 name ,存储了一个 String 类型的对象引用。 “Bob” 是这个 String 类型对象的值。
    var name = 'Bob';  // name变量的类型被推断为 String 。
    name=1000;   // 报错
    

    声明 方式二(显示指定)

    String name = 'Bob';
    

    dynamic(不限定为单个类型)

    // dynamic与var一样都是关键词,声明的变量可以赋值任意对象。 
    dynamic name = 'Bob';
    
    很容易引入一个运行时错误。
    

    Object

    // Object是Dart所有类的根基类,所以任何类型的数据都可以赋值给Object声明的对象。
    Object x= 'Bob';
    
    dynamic与Object
    相同点:
      声明的变量可以在后期改变赋值类型。
    不同点:
      dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错。
    
    
    例:
    dynamic a;
    Object b;
    main() {
         a = "";
         b = "";
         printLengths();
    }   
    printLengths() {
         // no warning
         print(a.length);
         // warning:报错
         // The getter 'length' is not defined for the class 'Object'
         print(b.length);
    }
    

    默认值

    所有未初始化的变量默认值是 null。

    int lineCount;
    assert(lineCount == null);  // 不会异常
    

    常量(final 、 const)

    常量(不能被修改的变量),可以用final或const来声明。
    两者区别在于:const 变量是一个编译时常量(在编译时就必须固定),final变量在第一次使用时才被初始化。

    非Final 、非const 的变量是可以被修改的,即使这些变量 曾经引用过 const 值。

    // 这里只能用final,不能用const
    final timeStr=new DateTime.now();
    
    final String nickname = 'Bobby';
    // 被final或者const修饰的变量,变量类型可以省略
    final name = 'Bob'; 
    name = 'Alice';   // 会报错: 一个 final 变量只能被设置一次。
    
    1. 最高级final变量或类变量在第一次使用时被初始化。
    2. 实例变量可以是 final 类型但不能是 const 类型。 必须在构造函数体执行之前初始化 final 实例变量(在变量声明中、参数构造函数中、构造函数的初始化列表中)。
    
    Const变量是隐式Final的类型.
    
    // 如果 Const 变量是类级别的,需要标记为 static const。
    const bar = 1000000; // 压力单位 (dynes/cm2)
    const double atm = 1.01325 * bar; // 标准气压
    
    Const 还可以用来创建常量值,以及声明创建常量值的构造函数。 任何变量都可以拥有常量值。
    var foo = const [];
    final bar = const [];
    // 等价 const [] 。声明 const 的初始化表达式中 const 可以被省略。
    const baz = [];
    

    4. Dart内置的类型

        1. Number
        2. String
        3. Boolean
        4. List (也被称为 Array)
        5. Map
        6. Set
        7. Rune (用于在字符串中表示 Unicode 字符)
        8. Symbol
    
    1. 所有的变量都是一个对象(某个类的实例), 所以变量可以使用 构造涵数 进行初始化。 
    一些内建类型拥有自己的构造函数。 例如, 通过 Map() 来构造一个 map 变量。
    
    2. 类型判断:is关键字
    var str='hello';
    if(str is String){  // 是否为String类型
    }
    
    1. Number(2种:int、double)
    int
      整型,64位
    
    double
      浮点型,64位
    
    int、double都继承自num。 num 类型包括基本运算 +, -, /, 和 *, 以及 abs(), ceil(), 和 floor() 等 。 
    

    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.
    

    Number和String相互转换

    // String -> int
    var one = int.parse('1');    // 浮点型时报错,null时报错
    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');
    

    按位运算符定义在int类中

    移位(<<, >>),按位与(&)、 按位或(|)
    
    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;
    
    1. String 字符串

    是一组UTF-16单元序列。

    1. 字符串字面量通过单引号或者双引号创建。
    
    var s1='hello world';
    String s2="hello world";
    
    var s3='''
    hello
    world
    ''';
    
    var s4=''''''
    hello
    world
    '''''';
    
    2. 字符串拼接
    
    方式一:${expression} 内嵌字符串表达式,如果表达式是一个标识符,则 {} 可以省略。 
    var s5="$s1 $s2";
    
    方式二:+ 运算符
    var s6=s1+" "+s2;
    
    3. 判空
    
    if(str.isEmpty){
    }
    
    4. 大写
    
    s1.toUpperCase()}
    
    5. 使用 r 前缀,创建原始字符串:
    
    // 不作字符转义
    var s = r"In a raw string, even \n isn't special.";
    
    5. 一个编译时常量的字面量字符串中,如果存在插值表达式,表达式内容也是编译时常量, 那么该字符串依旧是编译时常量。 
    插入的常量值类型可以是 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 类型数据
    
    6. 调用就对象的 toString() 方法来得到对象相应的字符串。
    
    7. == 运算符用来测试两个对象是否相等。 
    在字符串中,如果两个字符串包含了相同的编码序列,那么这两个字符串相等。
    
    1. Boolean

    只有字面量 true和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);
    
    1. List 列表/数组
    1. 创建
    
    方式1
    //  Dart 推断 list 的类型为 List<int> 。 如果尝试将非整数对象添加到此 List 中, 则分析器或运行时会引发错误。 
    var list = [1, 2, 3];
    
    方式2
    //
    var list2 = new List();
    var list3 = new List<String>();
    
    2. 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.indexOf('hello')  // 不存在时返回-1
    
    3. 在 List 字面量之前添加 const 关键字,可以定义 List 类型的编译时常量。
    
    var constantList = const [1, 2, 3];
    // constantList[1] = 1; // 取消注释会引起错误。
    
    4. 修改相关
    
    添加
    list.add('hello');  // 添加数据
    list.addAll(['hi','world']);  // 添加一个数组中的所有数据
    list.insrt(0,'hello');  // 插入
    list.insrtAll(0,['hello','world']);  // 插入
    
    删除
    list.remove('hello')
    list.remveAt(0)
    
    替换
    list.fillRange(0,2,'hello')  // 从开始坐标到结束坐标之前 的每一个元素都替换为hello
    
    倒序排序
    list.reversed.toList()
    
    5. 字符串和列表相互转换
    
    列表转字符串
    list.join('-');
    
    字符串转列表
    str.split('-')
    
    6. 判空
    
    判断空
    list.isEmpty
    判断非空
    list.isNotEmpty
    
    7. 循环相关
    
    循环
    list.forEach((value){
      print('$value');
    });
    
    循环(修改后的返回新集合)
    List newList=list.map((value){
      return value+2;
    });
    
    循环(条件满足的返回新集合)
    List newList=list.where((value){
      return value>2;
    });
    
    循环(有一个满足条件则返回true)
    var isHave=list.any((value){
      return value>2;
    });
    
    循环(所有满足条件则返回true)
    var isHave=list.every((value){
      return value>2;
    });
    
    1. Set
    1. 创建
    
    方式1
    //  自动推断推断set 类型为 Set<String> 。如果尝试为它添加一个 错误类型的值,分析器或执行时会抛出错误。
    var set = {'张三', '李四', '王五', '马六', '邹七'};
    
    方式2
    // var names = {}; // 这样会创建一个 Map ,而不是 Set 。
    var set=<String>{};
    set["name"]="张三";
    
    方式3
    Set<String> set = {}; 
    set["name"]="张三";
    
    2. 对List进行去重
    
    List list=['hello','hello','world'];
    var set=new Set();
    set.addAll(list);
    print(set);  // {'hello','world'}
    List list2=set.toList();
    
    3. 循环相关
    
    循环
    set.forEach((value){
      print('$key $value');
    });
    
    循环(修改后的返回新集合)
    Set newSet=set.map((key,value){
      return value+2;
    });
    
    循环(条件满足的返回新集合)
    Set newSet=set.where((key,value){
      return value>2;
    });
    
    循环(有一个满足条件则返回true)
    var isHave=set.any((key,value){
      return value>2;
    });
    
    循环(所有满足条件则返回true)
    var isHave=set.every((key,value){
      return value>2;
    });
    
    4. 修改相关
    
    使用 add() 或 addAll() 添加元素:
    set.add('天天向上');
    set.addAll(set2);
    
    5. 获取总个数
    
    set.length
    
    6. 在 Set 字面量前增加 const ,来创建一个编译时 Set 常量,添加数据时会报错。
    
    final constantSet = const {
      'hello',
      'world',
    };
    
    1. Map 哈希表

    用来关联 键和值(keys和values)的对象。

    1. 创建
    
    keys 和 values 可以是任何类型的对象。
    在一个 Map 对象中一个 key 只能出现一次, 但是 value 可以出现多次。
    
    
    方式1
    // 自动推断nameMap的类型推断为Map<String, String>。 如果添加错误的类型,那么分析器或者运行时会引发错误。 
    var nameMap={
      'name':'张三',
      'sex':'男',
    };
    var map={
    "name":"张三",
    "age":10,
    "works":["飞行员","特警"],
    }
    
    方式2
    // 在 Dart 2 中,new 关键字是可选的。
    var map2=new Map();
    map2["name"]="张三";
    
    2. 在Map字面量前加const关键字,可以创建Map类型运行时常量
    final constantMap = const {
      1: '张',
      10: '三',
    };
    // constantMap[2] = 'Helium'; // 取消注释会引起错误。
    
    3. 获取相关
    
    获取一个value(根据key),没有这个key时会返回 null。
    map['key']
    
    键值对的个数
    map.length
    
    所有key
    map.keys.toList()
    
    所有value
    map.values.toList()
    
    4. 修改相关
    
    添加一对 key-value
    map['sex'] = '张三'; 
    
    添加另一个Map中的所有值
    map.addAll({
    "hello":2,
    "world":['hi','he']
    });
    
    删除
    map.remove('hello');
    
    5. 判空
    
    map.isEmpty
    map.isNotEmpty
    
    6. 是否包含
    
    map.containsValue('hello');    // true, 没有时返回false
    
    1. Runes

    Runes:由UTF-32编码字符组成的字符串。

    Unicode定义了一个全球的书写系统编码, 系统中使用的所有字母,数字和符号都对应唯一的数值编码。
    由于 Dart 字符串是一系列UTF-16编码单元, 因此要在字符串中表示32位Unicode值需要特殊语法支持。表示 Unicode 编码的常用方法是, \uXXXX, 这里 XXXX 是一个4位的16进制数, 对于特殊的非4个数值的情况, 需要把编码值放到大括号中。 例如,心形符号 (♥) 是 \u2665,emoji 的笑脸 (�) 是 \u{1f600}。
    
    一个Rune字符对应一个Unicode字符。
    字符串和Rune可直接相互转换。
    
    Runes runes=Runes('\u{1f605} \u{1f60e}');
    String.fromChartCodes(runes)  // 2个图标
    
    String类有一些属性可以获得 rune 数据。 
      属性 codeUnitAt 和 codeUnits 返回16位编码数据。 
      属性 runes 获取字符串中的 Rune 。
    
    例
    var str='\u{1f44f}';
    str.codeUnits  // [55357,56399]
    str.runes.toList()  // [128079]
    

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

    1. Symbol

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

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

    5. 运算符

    创建表达式的时候会用到运算符,例:
      a++
      a + b
      a = b
      a == b
      c ? a : b
      a is T
    
    
    大多数运算符可以被重载。
    对于有两个操作数的运算符,运算符的功能由左边的操作数决定。
    
    运算符优先级
    优先级从高到低:
    一元后缀、一元前缀、乘除、加减、移位、位运算、关系运算和类型判定运算符、相等性运算、逻辑运算、null判断、三元表达式、级联调、赋值运算
    
    可以在优先级模糊的地方使用()来提高可读性。
    
    1. 算术运算符
    算术运算符 自增自减运算符

    6 + 3
    6 - 3
    6 * 3
    6 / 3      // 2.5,双浮点型
    6 ~/ 3    // 2,整型,(整除)
    6 % 3    // 0(取余)
    
    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
    
    1. 关系运算符
    关系运算符
    == 判断值相等。
    identical() 判断是否为同一对象。
    

    2 == 2
    2 != 3
    3 > 2
    2 < 3
    3 >= 3
    2 <= 3
    
    1. 逻辑运算符
    逻辑运算符

    if (!done && (col == 0 || col == 3)) {
      // ...
    }
    
    1. 赋值运算符
    赋值运算符
    使用 = 为变量赋值。 
    使用 ??= 运算符时,只有当被赋值的变量为 null 时才会赋值给它。
    

    // 将值赋值给变量a
    a = value;
    // 如果b为null时,将变量赋值给b,否则,b的值保持不变。
    b ??= value;
    
    
    var a = 2; // 使用 = 赋值
    a *= 3; // 做乘法运算并赋值: a = a * 3
    assert(a == 6);
    
    1. 位运算符
    按位和移位运算符
    在 Dart 中,可以单独操作数字的某一位。 通常情况下整数类型使用按位和移位运算符来操作。
    

    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
    
    1. 类型判定运算符
    类型判定运算符

    as, is, 和 is! 运算符用于在运行时处理类型检查

    if (person is Person) {
      person.firstName = 'Bob';
    }
    
    // 使用 as 运算符将对象强制转换为特定类型。
    // 如果 emp 为 null 或者不是 Person 对象,则会抛出异常。
    (person as Person).firstName = 'Bob';
    
    
    obj is Object 总是 true。
    如果 obj 实现了 T 的接口时, 则 obj is T 是 true。
    
    1. 级联运算符 (..)

    对同一个对像进行一系列的操作(调用函数,访问属性)
    严格来讲,级联语法不是一个运算符。 只是一个 Dart 的特殊语法。

    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')
      ..write('bar'); // Error: 'void' 没哟定义 'write' 函数。 不能在 void 对象上创建级联操作
    
    1. 三目运算符

    替换if-else表达式, 让表达式更简洁。

    condition ? expr1 : expr2
        如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值。
        如果赋值是根据布尔值, 考虑使用 ?:
    
    expr1 ?? expr2
        如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值。 
        如果赋值是基于判定是否为 null, 考虑使用 ??
    

    var visibility = isPublic ? 'public' : 'private';
    String playerName(String name) => name ?? 'Guest';
    
    
    // Slightly longer version uses ?: operator.
    String playerName(String name) => name != null ? name : 'Guest';
    // Very long version uses if-else statement.
    String playerName(String name) {
      if (name != null) {
        return name;
      } else {
        return 'Guest';
      }
    }
    
    1. 其他运算符
    其他运算符
    () 函数
    [] 下标访问
    . 成员访问
    ?. 条件成员访问
    

    6. 流程控制语句

        if and else
    
        for loops
    
        while and do-while loops
    
        break and continue
    
        switch and case
    
        assert
    
        try-catch 和 throw 
    

    判断语句

    Dart 的判断条件必须是布尔值,不能是其他类型

    if else
    
    if (str=='hello') {
    } else if (str=='world') {
    } else {
    }
    
    switch
    
    1. 在 Dart 中 switch 语句使用 == 比较整数,字符串,编译时常量,枚举型。 
    2. 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。 
    3. 在 Dart 中 Switch 语句仅适用于有限的情况下。
    4. 在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句。 除 break 以外,还有可以使用 continue, throw,者 return。
    5. 当没有匹配的 case 语句匹配时,会执行 default 代码。
    6. case 语句可以拥有局部变量, 这些局部变量只能在这个语句的作用域中可见。
    
    var command = 'OPEN';
    switch (command) {
      case 'APPROVED':
        executeApproved();
        break;
      case 'CLOSED':
      case 'PENDING':  // CLOSED、PENDING都会执行
        executePending();
        break;
      case 'DENIED':
        executeDenied();
        continue nowOpen;  // 继续执行nowOpen对应的条件
      nowOpen:
      case 'OPEN':
        executeOpen();
        break;
      default:
        executeUnknown();
    }
    
    asset
    
    只在开发环境有效, 在生产环境是无效的
    如果条件值为false , 会抛出AssertionError异常,程序会被中断。二个参数可以指定错误消息。 
    
    
    // 确认变量值不为空。
    assert(text != null);
    
    // 确认变量值小于100。
    assert(number < 100);
    
    // 确认 URL 是否是 https 类型。
    assert(urlString.startsWith('https'));
    
    assert(urlString.startsWith('https'),
        'URL ($urlString) should start with "https".');
    

    循环语句

    break;  终止循环
    
    continue;  终止本次循环,开始下次循环。
    
    1. while
    
    while (!isDone()) {
      doSomething();
    }
    
    do {
      printLine();
    } while (!atEndOfPage());
    
    while (true) {
      if (shutDownRequested()) break;
      processIncomingRequests();
    }
    
    2. for
    
    for (int i = 0; i < candidates.length; i++) {
      var candidate = candidates[i];
      if (candidate.yearsExperience < 5) {
        continue;
      }
      candidate.interview();
    }
    如果对象实现了 Iterable 接口 (例如,list 或者 set)。 那么上面示例完全可以用另一种方式来实现:
    candidates
        .where((c) => c.yearsExperience >= 5)
        .forEach((c) => c.interview());
    如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择
    
    
    如果对象实现了 Iterable 接口,同样也支持使用 for-in 进行迭代操作 iteration :
    var collection = [0, 1, 2];
    for (var x in collection) {
      print(x); // 0 1 2
    }
    
    
    闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱。
    var callbacks = [];
    for (var i = 0; i < 2; i++) {
      callbacks.add(() => print(i));  // 输出的是 0 和 1。 但是示例中的代码如果在 JavaScript 中会连续输出两个 2 。
    }
    callbacks.forEach((c) => c());
    

    异常

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

    和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。
    
    Dart 提供了 Exception 和 Error 类型, 以及一些子类型。 当然也可以定义自己的异常类型。 但是,Dart 程序也可以抛出任何非 null 对象, 不仅限 Exception 和 Error 对象。
    
    
    抛出异常
    throw FormatException('Expected at least 1 section');
    也可以抛出任意对象:
    throw 'Out of llamas!';
    因为抛出异常是一个表达式, 所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:
    void distanceTo(Point other) => throw UnimplementedError();
    
    1. 捕获异常可以避免异常继续传递(除非重新抛出( rethrow )异常)
    
    try {
      breedMoreLlamas();
    } on OutOfLlamasException {    // on OutOfLlamasException catch(e) {
      buyMoreLlamas();
    }
    
    2.捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。
    
    try {
      breedMoreLlamas();
    } on OutOfLlamasException {
      // 一个特殊的异常
      buyMoreLlamas();
    } on Exception catch (e) {
      // 其他任何异常
      print('Unknown exception: $e');
    } catch (e) {
      // 没有指定的类型,处理所有异常
      print('Something really unknown: $e');
    }
    
    3. 如果仅需要部分处理异常, 那么可以使用关键字 rethrow 将异常重新抛出
    
    void misbehave() {
      try {
        dynamic foo = true;
        print(foo++); // Runtime error
      } catch (e) {
        print('misbehave() partially handled ${e.runtimeType}.');
        rethrow; // Allow callers to see the exception.
      }
    }
    void main() {
      try {
        misbehave();
      } catch (e) {
        print('main() finished handling ${e.runtimeType}.');
      }
    }
    
    4. 不管是否抛出异常, finally 中的代码都会被执行。 如果 catch 没有匹配到异常, 异常会在 finally 执行完成后,再次被抛出:
    
    try {
      breedMoreLlamas();
    } catch (e) {
      print('Error: $e'); // Handle the exception first.
    } finally {
      cleanLlamaStalls(); // Then clean up.
    }
    

    7. 函数

    函数也是对象,类型是Function 。
    即函数可以被赋值给变量或者作为参数传递给其他函数。

    // main函数:程序的入口函数
    void main() {
      var number = 110;   // 声明并初始化一个变量
      printInteger(number); // 调用函数
    }
    
    // 自定义函数
    printInteger(int aNumber) {
      print('The number is $aNumber.');   // 打印
      for (int i = 0; i < 5; i++) {
        print('hello ${i + 1}');
      }
    }
    
    1. 函数声明
    返回类型 方法名(参数类型 参数1, 参数类型 参数2, ...){  // 参数可省略
      //...
      return 返回值;
    }
    
    注意: 
      1. 函数返回值没有类型推断。如果没有显式声明返回值类型时会默认当做dynamic处理:
      2. 如果显示声明了返回类型,则必须与返回值的类型一致,否则报错。
      3. 所有函数都会返回一个值。 如果没有明确指定返回值, 函数体会被隐式的添加 return null; 语句。
    
    例
    
    bool isOK(int num){
      return num>100;
    }
    // 返回类型是dynamic
    isOK(int num){
      return num>100;
    }
    

    简写(箭头函数)

    如果函数中只有一句表达式,可以使用简写语法(箭头语法): => expr; 
    它是 { return expr; } 的简写。在箭头 (=>) 和分号 (;) 之间只能使用一个表达式 ,不能是语句。
    
    isOK(int num) => num>10;
    

    函数作为参数、变量

    作为另一个函数的参数
    void printElement(int element) {
      print(element);
    }
    var list = [1, 2, 3];
    list.forEach(printElement);  // 将 printElement 函数作为参数传递。
    
    
    赋值给一个变量
    var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
    assert(loudify('hello') == '!!! HELLO !!!');
    
    1. 参数类型

    函数有两种参数类型: required 和 optional。
    required 类型参数在参数最前面, optional 类型参数必须在最后面。

    可选参数可以是命名参数或者位置参数,但一个参数只能选择其中一种方式修饰。不能同时使用可选的位置参数和可选的命名参数。
    
    1. 命名可选参数
    使用 {param1, param2, …} 来指定命名参数,调用函数时使用指定命名参数 paramName: value
    命名可选参数可以标记为 “@required” ,表示参数必须传否则报错。
    
    bool isOK(int num,{@required String name,String sex}){
      if(name!=null){
        return true;
      }
      return num>100;
    }
    isOK(100,name:'张三')
    
    1. 位置可选参数
    将参数放到 [] 中来标记参数是可选的,可以有多个
    
    bool isOK(int num,[String name,String sex]){
      if(name!=null){
        return true;
      }
      return num>100;
    }
    
    isOK(100)
    isOK(100,'张三')
    isOK(100,'张三','男')
    

    默认参数值

    // 旧版本代码中可能使用的是冒号 (:) 而不是 = 来设置参数默认值。 原因是起初命名参数只支持 : 。 这种支持可能会被弃用。
    使用 = 来定义可选参数的默认值,默认值只能是编译时常量。
    如果没有提供默认值,则默认值为 null。
    
    bool isOK(int num,String name='张三'){
      return num>100;
    }
    

    main() 函数

    程序的入口

    main() 函数返回值为空,参数为一个可选的 List<String> 。位于顶层。
    
    
    下面是 web 应用的 main() 函数:
    void main() {
      querySelector('#sample_text_id')
        ..text = 'Click me!'
        ..onClick.listen(reverseText);
    }
    以上代码中的 .. 语法为 级联调用。 使用级联调用, 可以简化在一个对象上执行的多个操作。
    
    
    下面是一个命令行应用的 main() 方法,并且使用了输入参数:
    // 这样运行应用: dart args.dart 1 test
    void main(List<String> arguments) {
      print(arguments);
      assert(arguments.length == 2);
      assert(int.parse(arguments[0]) == 1);
      assert(arguments[1] == 'test');
    }
    

    匿名函数

    没有名字的函数

    ([[Type] param1[, …]]) {
    }; 
    
    匿名函数可以赋值到一个变量中,也可作为函数参数。
    
    
    
    下面例子中定义了一个包含一个无类型参数 item 的匿名函数。 list 中的每个元素都会调用这个函数,打印元素位置和值的字符串。
    var list = ['apples', 'bananas', 'oranges'];
    list.forEach((item) {
      print('${list.indexOf(item)}: $item');
    });
    如果函数只有一条语句, 可以使用箭头简写。
    list.forEach(
        (item) => print('${list.indexOf(item)}: $item'));
    
    自执行方法
    ((int num){
      print('$num');
    })(100)
    

    作用域

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

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

    闭包

    闭包:方法中返回另一个方法。常驻内存,又不污染全局。
    即一个函数对象,即使函数对象的调用在它原始作用域之外, 依然能够访问在它词法作用域内的变量。函数可以封闭定义到它作用域内的变量。

    全局变量特点:常驻内存,会污染全局。
    局部变量特点:不常驻内存,会被垃圾机制回收,不会污染全局。
    
    // 
    var a=100;
    func(){
       a++;
       print(a);
    }
    fun();
    fun();
    fun();
    输出:101 102 103
    
    这样的做法会污染全局,其他人也可能使用变量a,有可能引发错误。换成闭包:
    
    func(){
      var a=100;
      return (){
        a++;
       print(a);
      }
    }
    var fun=func();
    fun();
    fun();
    fun();
    输出:101 102 103
    
    func(int a){
      return (num i)=>a+i;
    }
    var add2=func(2);
    add2(5) // 7
    add2(8)  // 10
    

    函数是否相等

    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内置支持两种生成器函数:
    
    1. Synchronous 同步生成器: 返回一个 Iterable 对象。
    通过在函数体标记 sync*, 可以实现一个同步生成器函数。 使用 yield 语句来传递值:
    // 从0到n-1的整数序列
    Iterable<int> naturalsTo(int n) sync* {
      int k = 0;
      while (k < n) yield k++;
    }
    
    2. Asynchronous 异步生成器: 返回一个 Stream 对象。
    通过在函数体标记 async*, 可以实现一个异步生成器函数。 使用 yield 语句来传递值:
    // 从0到n-1的整数序列
    Stream<int> asynchronousNaturalsTo(int n) async* {
      int k = 0;
      while (k < n) yield k++;
    }
    
    
    如果生成器是递归的,可以使用 yield* 来提高其性能:
    // 从1到n的整数序列
    Iterable<int> naturalsDownFrom(int n) sync* {
      if (n > 0) {
        yield n;    // 返回n
        yield* naturalsDownFrom(n - 1);  // 继续调用naturalsDownFrom函数
      }
    }
    

    typedef 类型定义

    用来命名已有类型。目前,typedefs 只能使用在函数类型上。

    在 Dart 中,函数也是对象,就像字符和数字对象一样。 
    1. 使用 typedef ,或者 function-type alias 为函数起一个别名, 别名可以用来声明字段及返回值类型。 
    2. 当函数类型分配给变量时,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 是函数,但是函数是什么类型 ?
      assert(coll.compare is Function);
    }
    
    当把 f 赋值给 compare 的时候,类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 但是 compare 得到的类型是 Function 。
    如果使用显式的名字并保留类型信息, 这样开发者和工具都可以使用这些信息:
    
    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!
    }
    ```
    
    
    #### 8. 类
    
    Dart是一种基于类和mixin继承机制的面向对象的语言。 
    
    基于Mixin继承意味着每个类(除 Object 外) 都只有一个超类。
    每个对象都是某个类的实例,所有的类都继承自Object类。 
    一个类由属性和方法组成。
    
    ```
    对象的runtimeType属性回返回一个 Type 对象, 可以在运行时获取对象的类型。
    
    print('The type of a is ${a.runtimeType}');
    ```
    > 例
    
    ```
    class Person{
      String name='张三';    // 声明实例变量name,初始值为 '张三' 。
      int age=100;
      String sex;    // 默认初始值为 null 
      String _job;    // 加下滑线为私有,同文件下无效(仍可访问)。
    
      static int handsNum2;  // 静态属性,在外部使用类名访问,在内部直接使用
    
      // 默认构造函数,只能有一个
      Person(){
      }
      // 调用构造函数之前初始化实例变量。初始化列表
      Person():name='张三丰',age=120{
      }
      Person(String name,int age){
        this.name=name;
        this.age=age;
      }
      Person(this.name,this.age,this.sex);
      // 命名构造函数,可以有多个
      Person.hello(String name,int age){
        this.name=name;
        this.age=age;
      }
      Person.world(){
      }
    
    
      // 自定义方法
      printInfo(){
        print('姓名:$name  年龄:$age');
      }
      updateInfo(int age){
        this.age=age;
      }
    
    
      static void showHands(){    // 静态方法,在外部使用类名访问,在内部直接使用
        // 静态方法不能访问非静态的变量和方法,丰静态方法能访问静态的变量和方法。
      }
    
      String getJob(){
       return this._job;
      }
      String runFunc(){
        this._run();
      }
       // 加下滑线为私有方法,
      _run(){
      }
    
      get info{
        return this.name;
      }
      set personName(value){
        this.name=value;
      }
    }
    
    void main(){
      var person=new Person();
      person.printInfo();
      person.updateInfo(110);
      person.printInfo();
      person.personName='张三丰';
      String name=person.info;
    
      var person2=new Person('张三',110);
    
      var person3=new Person.hello('张三',110);
    }
    ```
    
    > 实例变量
    
    对象由实例变量和方法组成。 
    ```
    1. 未初始化实例变量的默认值为 “null” 。
    2. 所有实例变量都生成隐式 getter 方法,非 final 的实例变量同样会生成隐式 setter 方法。
    3. 如果在声明时进行了实例变量的初始化, 那么初始化值会在实例创建时赋值给变量, 该赋值过程在构造函数及其初始化列表执行之前。
    4. 方法的调用要通过对象来完成: 调用的方法可以访问其对象的其他方法和实例变量。
    ```
    ```
    5. 使用.来引用实例对象的变量和方法:
    
    var p = Point(2, 2);
    // 为实例的变量 y 设置值。
    p.y = 3;
    // 获取变量 y 的值。
    assert(p.y == 3);
    // 调用 p 的 distanceTo() 方法。
    num distance = p.distanceTo(Point(4, 4));
    ```
    ```
    6. 使用 ?. 来代替 . , 可以避免因为左边对象可能为 null , 导致的异常:
    
    // 如果 p 为 non-null,设置它变量 y 的值为 4。
    p?.y = 4;
    ```
    
    
    
    > 构造函数
    
    声明构造函数:与类同名的函数。CLassName和ClassName.identifer
    ```
    1. 在没有声明构造函数时, Dart会提供一个默认的无参构造函数,会调用父类的无参构造函数。
    父类的构造函数在子类构造函数体开始执行的位置被调用。 
    构造函数不能够被继承,即子类不会继承父类的构造函数。 
    ```
    ```
    2. 初始化实例变量。执行顺讯由上自下
        0. 声明时实例化。
        1. 始化参数列表(initializer list),各参数的初始化用逗号分隔
        2. 调用父类构造函数
        3. 本类构造函数
    
    
    初始化参数列表。
      final实例变量只可以在声明时和初始化参数列表时赋值。
      在开发期间, 可以使用 assert 来验证输入的初始化列表。
    Point.fromJson(Map<String, num> json)
        : x = json['x'],
          y = json['y'] {
      print('In Point.fromJson(): ($x, $y)');
    }
    Point.withAssert(this.x, this.y) : assert(x >= 0) {  // 也可以: assert(x >= 0) ,hello=x*y
      print('In Point.withAssert(): ($x, $y)');
    }
    
    
    调用父类构造函数。
      在当前构造函数冒号 (:) 之后,函数体之前,声明调用父类构造函数。
      如果父类中没有匿名无参的构造函数, 需要手工调用父类的其他构造函数。 
      父类的构造函数的参数可以是一个表达式或者一个方法调用(无法访问 this, 可以是静态函数但是不能是实例函数)。
    class Employee extends Person {
      Employee() : super.fromJson(getDefaultData());
      // ···
    }
    ```
    
    
    ```
    3. 命名构造函数(见名知意),可以有多个。非命名构造函数只能有一个。
    
    class Point {
      num x, y;
      // 
      Point(this.x, this.y);
      // 命名构造函数
      Point.origin() {
        x = 0;
        y = 0;
      }
    }
    
    var point=Point.origin();
    var point2=Point(30,60);
    ```
    
    ```
    4. 简写:CLassName(this.变量名)
    
    class Point {
      num x, y;
      Point(num x, num y) {
        // 使用 this 关键字引用当前实例
        // 仅当存在命名冲突时,使用 this 关键字。 否则,按照 Dart 风格应该省略 this 。
        this.x = x;
        this.y = y;
      }
      Point.origin(num x, num y) {
        this.x = x;
        this.y = y;
      }
    }
    
    简写(等价):
    
    class Point {
      num x, y;
      // 在构造函数体执行前,
      Point(this.x, this.y);
      Point.origin(this.x, this.y);
    }
    ```
    ```
    5. 重定向构造函数
      有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 
      重定向构造函数的函数体为空, 构造函数的调用在冒号 (:) 之后。
    
    class Point {
      num x, y;
      // 类的主构造函数。
      Point(this.x, this.y);
      // 指向主构造函数
      Point.alongXAxis(num x) : this(x, 0);
    }
    ```
    ```
    6. 常量构造函数
      如果该类生成的对象是固定不变的, 那么就可以把这些对象定义为编译时常量。
      需要定义一个const 构造函数, 所有实例变量声明为final。
      常量构造函数创建的实例并不总是常量。
    
    
    class ImmutablePoint {
      static final ImmutablePoint origin =
          const ImmutablePoint(0, 0);
      final num x, y;
      const ImmutablePoint(this.x, this.y);
    }
    
    
    使用常量构造函数,在构造函数名之前加 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 建立常量上下文。其余的可全部省略,
    // 在 Dart 2 中,一个常量上下文中的 const 关键字可以被省略。
    const pointAndLine = {
      'point': [ImmutablePoint(0, 0)],
      'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
    };
    
    
    如果常量构造函数在常量上下文之外, 且省略了 const 关键字, 此时创建的对象是非常量对象
    var a = const ImmutablePoint(1, 1); // 创建一个常量对象
    var b = ImmutablePoint(1, 1); // 创建一个非常量对象
    assert(!identical(a, b)); // 两者不是同一个实例!
    ```
    ```
    7. 工厂构造函数,使用 factory 关键字。
      用来返回类的新实例或缓存中的实例时。 
      工厂构造函数无法访问 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');
    ```
    
    
    
    
    
    
    > 方法
    
    方法是为对象提供行为的函数。
    ```
    1. 实例方法
      对象的实例方法可以访问 this 和实例变量。
    
    import 'dart:math';
    class Point {
      num x, y;
      Point(this.x, this.y);
      num distanceTo(Point other) {
        var dx = x - other.x;
        var dy = y - other.y;
        return sqrt(dx * dx + dy * dy);
      }
    }
    ```
    ```
    2. 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);
    }
    ```
    ```
    3. 抽象方法
      实例方法, getter, 和 setter 方法可以是抽象的, 只定义接口不进行实现,而是留给其他类去实现。 
      抽象方法只存在于 抽象类 中。
      调用抽象方法会导致运行时错误。
    
    
    abstract class Doer {
      // 定义实例变量和方法 ...
    
      // 定义一个抽象函数,使用分号 (;) 来代替函数体:
      void doSomething(); // 定义一个抽象方法。
    }
    class EffectiveDoer extends Doer {
      void doSomething() {
        // 提供方法实现,所以这里的方法就不是抽象方法了...
      }
    }
    ```
    
    > 类变量和类方法
    
    使用static关键字修饰类变量(静态变量)和类方法(静态方法)
    ```
    1. 类变量直到被使用时才进行初始化
    2. 类方法不能访问this(this代表的是实例对象)。
    3. 类方法是编译时常量。 例如,可以将静态方法作为参数传递给常量构造函数。
    对于常见或广泛使用的工具和函数, 应该考虑使用顶级函数而不是静态方法。
    
    
    例
    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);
    }
    ```
    
    > 抽象类 
    
    使用 abstract 修饰符来定义 抽象类
    ```
    抽象类不能直接实例化。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。
    抽象类通常用来定义接口,具有属性、构造函数、方法、抽象方法。 
    
    
    abstract class Person {
      // 方法
      void printInfo(){
      }
      // 抽象方法(没有方法体的方法)。 继承该抽象类则必须实现该方法。
      void eat(); 
    }
    class Student extends Person {
      @override
      void eat(){
      }
    }
    ```
    > 接口
    
    使用implements关键字来实现一个或多个接口时,需要覆写所有属性和方法,否则报错。
    ```
    没有interface关键字。
    普通类和抽象类(建议)都可作为接口。
    
    
    abstract class Person {  // 约定/规范
      String name;
      // 方法
      void printInfo(){
      }
      void eat(); 
      void eat(); 
    }
    abstract class Child{
    }
    class Student implements Person,Child {
      @override
      String name;
    
      @override
      void eat(){
      }
      @override
      void run(){
      }
    }
    ```
    ```
    隐式接口
    
    每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口,不包含构造函数。 
    ```
    
    > 继承(扩展类)
    
    使用 extends 关键字来创建子类, 使用 super 关键字来引用父类。
    ```
    class Person {
      String name;
      Person(this.name);
      Person.hello(this.name);
      void run() {
      }
      // ···
    }
    class Student extends Person {
      String sex;
      Student(String name,String sex):super.hello(name){  
        this.sex=sex;
      }
    
      void runFunc() {
        // super.run();  // 调用父类方法
        // ···
      }
      
     // 子类可以重写实例方法,getter 和 setter。 
     // 建议加 @override 注解,指出重写的成员。
      @override
      void run(){
      }
    }
    
    main(){
      Student student=new Student();
      student.name='张三';
      student.run();
      student.runFunc();
    
      Student student2=new Student('张三');
    }
    ```
    
    ```
    1. 多态
    将子类实例赋值给父类类型的指针,同一个方法调用会有不同效果
    
    abstract class Person {
      void run();
    }
    class Student extends Person{
      void run() {
        print('Student run');
      }
    }
    class Teacher extends Person{
      void run() {
        print('Teacher run');
      }
    }
    
    Person *person;
    // person=new Teacher();
    person=new Student();
    person.run();
    ```
    
    ```
    2. 重写运算符
      使用 operator 可以覆盖 算术运算、相等运算、比较运算、位运算、下标访问等运算符。
      如果要重写 == 操作符,还需要重写对象的 hashCode getter 方法。 
    
    例(一个类重写 + 和 - 操作符):
    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);
    
      // 运算符 == 
      @override
      int get hashCode{ // hashCode就是一个整数
        int result=110;
        result=999*result+x.hashCode;
        result=999*result+y.hashCode;
        return result;
      }
      @override
      bool operator ==(dynamic other){
        if(other is! Vector){
          return false;
        }
        Vector vector=other;
        return (vector.x==x&&vector.y==y);
      }
    
    }
    void main() {
      final v = Vector(2, 3);
      final w = Vector(2, 2);
      assert(v + w == Vector(4, 5));
      assert(v - w == Vector(0, 1));
    }
    ```
    ```
    3. 覆盖noSuchMethod()拦截未定义方法或实例变量的访问
    
    
    class A {
      // 如果不重写 noSuchMethod,访问
      // 不存在的实例变量时会导致 NoSuchMethodError 错误。
      @override
      void noSuchMethod(Invocation invocation) {
        print('You tried to use a non-existent member: ' +
            '${invocation.memberName}');
      }
    }
    
    除非符合下面的任意一项条件, 否则没有实现的方法不能够被调用:
        1. receiver 具有 dynamic 的静态类型 。
        2. receiver 具有静态类型,用于定义为实现的方法 (可以是抽象的), 并且 receiver 的动态类型具有 noSuchMethod() 的实现, 该实现与 Object 类中的实现不同。
    ```
    > 枚举类型(又称 enumerations 或 enums)
    
    是一种特殊的类,用于表示数量固定的常量值。
    ```
    1. 枚举类型不能被实例化,不能被子类化,混合或实现。
    ```
    ```
    2. 枚举中的每个值都有一个 index getter 方法, 该方法返回值所在枚举类型定义中的位置(从 0 开始)。 
    例如,第一个枚举值的索引是 0 , 第二个枚举值的索引是 1。
    
    enum Color { red, green, blue }
    assert(Color.red.index == 0);
    assert(Color.green.index == 1);
    assert(Color.blue.index == 2);
    ```
    ```
    3. 使用枚举的 values 常量, 获取所有枚举值列表( list )
    
    List<Color> colors = Color.values;
    assert(colors[2] == Color.blue);
    ```
    ```
    4. 可用于switch语句,case要处理所有可能情况。
    
    var aColor = Color.blue;
    switch (aColor) {
      case Color.red:
        print('Red as roses!');
        break;
      case Color.green:
        print('Green as grass!');
        break;
      default: // 没有这个,会看到一个警告。
        print(aColor); // 'Color.blue'
    }
    ```
    
    > mixin混入
    
    实现类似多继承的功能。
    通过with来混入一个或多个Mixin。
    
    ```
    1. 作为mixins的类
      1. 不能继承其他类,只能直接继承自Object。
      2. 不能有构造函数。
    
    // 比如下面的Person就不能继承其他类,不能有构造函数。
    class Person{
      void run(){
      }
    }
    class Animal{
      void eat(){
      }
    }
    class Child{
      void cry(){
      }
      void run(){
      }
    }
    class Student extends Animal with Person,Child{      // 同名方法,在后面的优先级高,会覆盖(也会覆盖继承类的同名方法)。
    }
    
    main(){
      Student student=new Student();
      student.run();
      student.eat();
      student.cry();
      
      // 类型是超类(Animal、Person、Child)的子类型
      // 都是true
      student is Animal
      student is Student
      student is Person
      student is Child  
    }
    ```
    ```
    2. 如果不希望 Mixin作为普通类被使用,使用关键字 mixin 替换 class 。 
    mixin 关键字在 Dart 2.1 中被引用支持。 早期版本中的代码通常使用 abstract class 代替。 
    
    mixin Person{
      void run();
    }
    class Student with Person{
    }
    ```
    ```
    3. 使用on来限制可以使用的混入类
    
    // 指定可以使用 Mixin 的父类类型
    mixin MusicalPerformer on Musician {
    }
    ```
    
    > 可调用类
    
    通过实现类的 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');
    }
    ```
    
    
    > 泛型 
    
    解决代码重用和类型校验
    ```
    例
    
    T getData<T>(T value){
      return value;
    }
    // getData<T>(T value){
    //  return value;
    // }
    
    getData<String>('hello');
    getData<String>(123);  // 报错
    ```
    
    ```
    例(内置)
    列表和哈希表的构造函数和字面量都支持类型参数
    
    List<E> 。 <…> 符号将 List 标记为 泛型 (或 参数化) 类型。 这种类型具有形式化的参数。 通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等。
    var nameList = List<String>();
    nameList.add('hello');
    nameList.add(123);    // 报错
    
    
    在调用构造函数的时,在类名字后面使用尖括号(<...>)来指定泛型类型。 例如:
    var nameSet = Set<String>.from(names);
    var views = Map<int, View>();
    
    
    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>{
      'index.html': 'Homepage',
      'robots.txt': 'Hints for web robots',
      'humans.txt': 'We are people, not machines'
    };
    ```
    ```
    1. Dart 中泛型类型是 固化的,即泛型的类型信息会保留到运行时。 
    而Java中的泛型会被 擦除 ,也就是说在运行时泛型类型参数的信息是不存在的。 在Java中,可以测试对象是否为 List 类型, 但无法测试它是否为 List<String> 。
    
    var names = List<String>();
    names.addAll(['Seth', 'Kathy', 'Lars']);
    print(names is List<String>); // true
    ```
    ```
    2. 使用extends限制类型参数的可选范围。
    
    class Foo<T extends SomeBaseClass> {
      // Implementation goes here...
      String toString() => "Instance of 'Foo<$T>'";
    }
    class Extender extends SomeBaseClass {...}
    
    可以使用 SomeBaseClass 或其任意子类作为通用参数:
    var someBaseClassFoo = Foo<SomeBaseClass>();
    var extenderFoo = Foo<Extender>();
    
    也可以不指定泛型参数
    var foo = Foo();
    print(foo); // Instance of 'Foo<SomeBaseClass>'
    
    指定任何非 SomeBaseClass 类型会导致错误:
    var foo = Foo<Object>();  // 报错
    ```
    
    ```
    3. 泛型支持类、方法。
    最初 Dart 的泛型只能用于类。 新语法允许在方法和函数上使用类型参数。
    
    T first<T>(List<T> ts) {
      // Do some initial work or error checking, then...
      T tmp = ts[0];
      // Do some additional checking or processing...
      return tmp;
    }
    
    这里的 first (<T>) 泛型可以在如下地方使用参数 T :
        函数的返回值类型 (T).
        参数的类型 (List<T>).
        局部变量的类型 (T tmp).
    ```
    ```
    4. 范型接口
    
    abstract class Person<T>{
      run(int meter,T value);
    }
    class Child<T> implements  Person<T>{
      run(int meter,T value){
      }
    }
    ```
    
    
    #### 9. 库
    
    库是模块化的、可共享的代码包。
    
    ```
    1. 以下划线 (_) 开头的标识符仅在库内可见。 
    
    import 和 library 指令可以用来创建一个模块化的,可共享的代码库。
    库可以通过包来分发。
    每个Dart应用都是一个库(虽然没有使用library指令)。
    ```
    ```
    2. 通过import引导入其他库(只需要一个指向库的URI参数)
      内置库的URI前缀为 dart:
      包管理工具(如 pub 工具)提供库的URI前缀为 package:
    
    例:
    import 'dart:html';
    import 'package:test/test.dart';
    ```
    ```
    3. 如果导入多个库之间存在冲突标识符, 可使用as指定引用前缀来避免。 
    
    例
    如果 library1 和 library2 都有一个 Element 类, 那么可以通过下面的方式处理:
    import 'package:lib1/lib1.dart';
    import 'package:lib2/lib2.dart' as lib2;
    // 使用 lib1 中的 Element。
    Element element1 = Element();
    // 使用 lib2 中的 Element。
    lib2.Element element2 = lib2.Element();
    ```
    ```
    4. 如果只使用库的部分功能,则可以选择需要导入的内容。
    
    例如:
    // Import only foo.
    import 'package:lib1/lib1.dart' show foo;
    // Import all names EXCEPT foo.
    import 'package:lib2/lib2.dart' hide foo;
    ```
    ```
    5. deferred as 延迟加载库(懒加载库)
      
    第一步:要延迟加载一个库,需要先使用 deferred as 来导入:
    import 'package:greetings/hello.dart' deferred as hello;
    第二步:当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库:
    Future greet() async {
      // 在一个库上可以多次调用 loadLibrary() 函数。但是该库只是载入一次。
      await hello.loadLibrary();  // 使用 await 关键字暂停代码执行一直到库加载完成。 
      hello.printGreeting();
    }
    
    下面是一些使用延迟加载库的场景:
        1. 减少 APP 的启动时间。
        2. 执行 A/B 测试,例如 尝试各种算法的 不同实现。
        3. 加载很少使用的功能,例如可选的屏幕和对话框。
    
    使用延迟加载库的时候,需要注意:
        1. 延迟加载库的常量在导入的时候是不可用的。 只有当库加载完毕的时候,库中常量才可以使用。
        2. 在导入文件的时候无法使用延迟库中的类型。 如果需要使用类型,则考虑把接口类型移动到另外一个库中, 让两个库都分别导入这个接口库。
        3. Dart 隐含的把 loadLibrary() 函数导入到使用 deferred as 的命名空间 中。 loadLibrary() 方法返回一个 Future。
    ```
    ```
    6. 组织库的源文件
    
    使用 export 命令。
    使用 part 命令。
    使用 library 命令。
    ```
    
    #### 10. 异步
    
    Dart 库中包含许多返回 Future 或 Stream 对象的函数,这些函数被称为异步函数。
    ```
    使用 async 和 await 关键字实现异步编程:
      1. async让方法变异步。async函数返回Future对象,普通返回值会自动包装成Future对象。
      2. await等待异步方法执行完成。函数在执行到await语句时将暂停,直到await语句运行完成。
    
    注意:
      1. 调用async方法必须使用await关键字;
      2. 只有在async方法中才能使用await关键字调用方法。
      3. async函数中可以多次使用await。
    
    使用场景
      1. 在设置完耗时任务(例如 I/O 曹组)后立即返回,不用等待耗任务完成。 
      2. 请求接口。
    
    例
    import 'dart:async';  // 引入内置的异步库
    func() async{    
      return 'hello world';
    }
    void main() async{
     var result=await func();
    }
    ```
    > Future
    
    Future与JavaScript中的Promise非常相似,表示一个异步操作的最终完成(或失败)及其结果值的表示。
    简单来说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。
    
    ```
    1. 一个Future只会对应一个结果,要么成功,要么失败。
    Future 的所有API的返回值仍然是一个Future对象,所以可以进行链式调用。
    Future对象指明返回一个对象的承诺(promise)。 
    如果函数没有返回有效值, 需要设置其返回类型为 Future<void> 。
    
    
    可以通过下面两种方式,获得 Future 执行完成的结果:
        1. 使用 async 和 await.
        2. 使用 Future API。
    ```
    ```
    2. Future.then
    接收异步结果,然后去处理。
    
    例
    创建了一个延时任务(实际场景会是一个真正的耗时任务,比如一次网络请求),即2秒后返回结果字符串"hi world!",然后在then中接收异步结果并打印结果。
    Future.delayed(new Duration(seconds: 2),(){
       return "hi world!";
    }).then((data){
       print(data);
    });
    ```
    
    
    ```
    3. Future.catchError
    如果异步任务发生错误,可以在catchError中捕获错误。
    
    
    将上面示例改为:
    Future.delayed(new Duration(seconds: 2),(){
       //return "hi world!";
       throw AssertionError("Error");  
    }).then((data){
       //执行成功会走到这里  
       print("success");
    }).catchError((e){
       //执行失败会走到这里  
       print(e);
    });
    
    并不是只有 catchError回调才能捕获错误,then方法还有一个可选参数onError,也可以它来捕获异常:
    Future.delayed(new Duration(seconds: 2), () {
        //return "hi world!";
        throw AssertionError("Error");
    }).then((data) {
        print("success");
    }, onError: (e) {
        print(e);
    });
    ```
    
    
    ```
    4. Future.whenComplete
    无论异步任务执行成功或失败都会执行
    
    
    Future.delayed(new Duration(seconds: 2),(){
       //return "hi world!";
       throw AssertionError("Error");
    }).then((data){
       //执行成功会走到这里 
       print(data);
    }).catchError((e){
       //执行失败会走到这里   
       print(e);
    }).whenComplete((){
       //无论成功或失败都会走到这里
    });
    ```
    
    
    ```
    5. Future.wait
    等待多个异步任务都执行结束后才进行一些操作。
    接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调,只要有一个Future执行失败,就会触发错误回调。
    
    
    Future.wait([
      // 2秒后返回结果  
      Future.delayed(new Duration(seconds: 2), () {
        return "hello";
      }),
      // 4秒后返回结果  
      Future.delayed(new Duration(seconds: 4), () {
        return " world";
      })
    ]).then((results){
      print(results[0]+results[1]);
    }).catchError((e){
      print(e);
    });
    ```
    
    ```
    6. 回调地狱(Callback Hell)
    如果代码中有大量异步逻辑,并且出现大量异步任务依赖其它异步任务的结果时,必然会出现Future.then回调中套回调情况。过多的嵌套会导致的代码可读性下降以及出错率提高,并且非常难维护,这个问题被形象的称为回调地狱(Callback Hell)。
    解决:通过Future和async/await消除嵌套问题。
    
    
    例
    //先分别定义各个异步任务
    Future<String> login(String userName, String pwd){
        //用户登录
    };
    Future<String> getUserInfo(String id){
        //获取用户信息 
    };
    Future saveUserInfo(String userInfo){
        // 保存用户信息 
    };
    login("alice","******").then((id){
     //登录成功后通过,id获取用户信息    
     getUserInfo(id).then((userInfo){
        //获取用户信息后保存 
        saveUserInfo(userInfo).then((){
           //保存用户信息,接下来执行其它操作
            ...
        });
      });
    })
    
    
    使用Future消除Callback Hell
    login("alice","******").then((id){
          return getUserInfo(id);
    }).then((userInfo){
        return saveUserInfo(userInfo);
    }).then((e){
       //执行接下来的操作 
    }).catchError((e){
      //错误处理  
      print(e);
    });
    
    
    使用async/await消除callback hell。
    可以像写同步代码那样来执行异步任务而不使用回调。
    无论是在JavaScript还是Dart中,async/await都只是一个语法糖,编译器或解释器最终都会将其转化为一个Promise(Future)的调用链。
    task() async {
       // 使用 try-catch来处理代码中使用await导致的错误。
       try{
        String id = await login("alice","******");
        String userInfo = await getUserInfo(id);
        await saveUserInfo(userInfo);
        //执行接下来的操作   
       } catch(e){
        //错误处理   
        print(e);   
       }  
    }
    ```
    
    > Stream
    
    Stream也用于接收异步事件数据。
    和Future不同的是,它可以接收多个异步操作的结果(成功或失败)。即,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 
    
    Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。
    ```
    当需要从 Stream 中获取数据值时, 可以通过一下两种方式:
        1. 使用 async 和 一个 异步循环 (await for)。
          可以通过break或return中止接收 stream 的数据, 这样就跳出了for循环,并且从 stream上取消注册。
          在使用 await for 前,确保代码清晰, 并且确实希望等待所有流的结果。 
          例如,通常不应该使用 await for 的UI事件侦听器, 因为UI框架会发送无穷无尽的事件流。
        2. 使用 Stream API。
    ```
    ```
    1. await for
    必须位于async方法中
    
    例
    var s=Stream.fromIterable([1,2,3]);
    // 如果有返回值,必须是Stream类型
    await for (var value in s) {
      print(value);
    }
    ```
    ```
    2. Stream.fromFutures
    
    Stream.fromFutures([
      // 1秒后返回结果
      Future.delayed(new Duration(seconds: 1), () {
        return "hello 1";
      }),
      // 抛出一个异常
      Future.delayed(new Duration(seconds: 2),(){
        throw AssertionError("Error");
      }),
      // 3秒后返回结果
      Future.delayed(new Duration(seconds: 3), () {
        return "hello 3";
      })
    ]).listen((data){
       print(data);
    }, onError: (e){
       print(e.message);
    },onDone: (){
    
    });
    
    依次会输出:
    hello 1
    Error
    hello 3
    ```
    
    
    
    
    
    
    
    #### 11. 元数据
    
    给代码添加额外的数据。
    ```
    元数据注释以@字符开头, 后跟对编译时常量或对常量构造函数的调用。
    使用反射可以在运行时获取元数据信息。
    元数据可以在 library(库)、 class(类)、 typedef(类型定义)、 type parameter(类型参数)、 constructor(构造函数)、 factory(工厂构造函数)、 function(函数)、 field(类成员)、 parameter(函数参数) 、 variable(变量声明)、import (导入)、 export(导出)  之前使用。 
    ```
    ```
    1. 内置的元数据类解:@deprecated弃用 和 @override覆写。 
    
    class Television {
      /// _Deprecated: Use [turnOn] instead._ 老方法activate弃用
      @deprecated
      void activate() {
        turnOn();
      }
      void turnOn() {...}
    }
    ```
    ```
    2. 可以自定义元数据注解
    
    library todo;
    class Todo {
      final String who;
      final String what;
      const Todo(this.who, this.what);
    }
    
    import 'todo.dart';
    @Todo('seth', 'make this do something')
    void doSomething() {
      print('do something');
    }
    ```
    #### isolates(隔离区)
    ```
    大多数计算机中,甚至在移动平台上,都在使用多核CPU。 
    为了有效利用多核性能,开发者一般使用共享内存数据来保证多线程的正确执行。 然而, 多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。
    
    所有 Dart 代码都在隔离区内运行,而不是线程。 
    每个隔离区都有自己的内存堆,确保每个隔离区的状态都不会被其他隔离区访问。
    ```
    
    
    
    
    
    
    [Dart入门官网](https://www.dartcn.com/)s

    相关文章

      网友评论

        本文标题:Dart了解

        本文链接:https://www.haomeiwen.com/subject/zemisktx.html