Dart 基本语法

Dart 语言集合了 Java、JavaScript、C、TypeScript 等语言的优势,可以说是集百家之长的一门编程语言,可以用与 web 编程 服务端编程 以及 app 编程(Flutter) ,目前主要用于 Flutter 开发。以下是几点重要的概念:

  • Dart 是强类型语言,但是又兼有 JS 的动态性
  • 类型声明是可选的,可通过类型推导而来
  • 语句以分号结尾,不能省略
  • 1、基本语法

    1.1、变量

  • var 关键字
    可以用var 声明一个变量,一旦赋值,类型就不能再改变,但值可以变化
  • var str = 'abc';
    str = 'def';  // ok
    str = 123; // error
    
  • 变量声明时,若未指定初始值,则默认值都是null,而不是JS 中的 undefined
  • 类型可选,类型推断的存在使得声明变量时,变量类型时可选的
  • String str = 'abc' // 与 var str='abc' 等价
    
  • dynamic和Object
    1、Object 是dart所有对象的根基类,也就是说所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象.
    2、dynamic与var一样都是关键词,声明的变量可以赋值任意对象. 而dynamic与Object相同之处在于,他们声明的变量可以在后期改变赋值类型.
  • dynamic t;
     Object x;
     t = "hi world";
     x = 'Hello Object';
     //下面代码没有问题
     t = 1000;
     x = 1000;
    

    dynamic与Object不同的是,dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错.

  • final & const 关键字
    1、声明常量可用 final 或者 const , const 属于编译时常量,而 final 属于运行时常量;
    2、const 还可以用来创建不变的值
  •  var foo = const [1];  // 表示这个数组的值不能变化,而不是数组的地址
     foo[0]=1;  // error
    
  • num 数字类型
    包含两个子类型 int & double。以下是字符串类型和数字类型的互转
  • // String -> int
    var one = int.parse('1');
    assert(one == 1);
    // String -> double
    var onePointOne = double.parse('1.1');
    assert(onePointOne == 1.1);
    // int -> String
    String oneAsString = 1.toString();
    assert(oneAsString == '1');
    // double -> String
    String piAsString = 3.14159.toStringAsFixed(2);
    assert(piAsString == '3.14');
    
  • String
    可用单双三引号来创建string,字符串中支持变量插值
  • String a='123';
    var b='123'; // 等价于 var b='$a';
    Dart 中只有 true 对象才被认为是 true。 所有其他的值都是 flase。这点和 JavaScript 不一样, 像 1、 "aString"、 以及 someObject 等值都被认为是 false。
    
    var name = 'Bob';
    if (name) {
      // Prints in JavaScript, not in Dart.
      print('You have a name!');
    就是JS中的 Array,在 list 字面量之前添加 const 关键字,可以 定义一个不变的 list 对象(编译时常量)
    
    var list = [1, 2, 3];
    assert(list.length == 3);
    var constantList = const [1, 2, 3];
    constantList[1] = 1; // causes an error.
    键和值可以是任何类型的对象,比JS中的Object强大一点儿(key不能为对象),
    如果所查找的键不存在,则返回 null,而不是 undefined
    var gifts = {
    // Keys      Values
      'first' : 'partridge',
      'second': 'turtledoves',
      'fifth' : 'golden rings'
    var gifts = new Map();
    gifts['first'] = 'partridge';
    gifts['second'] = 'turtledoves';
    gifts['fifth'] = 'golden rings';
    
  • Symbol & Runes 暂时略过
  • enum 枚举
  • enum Color {
      green,
    

    枚举类型中的每个值都有一个 index getter 函数, 该函数返回该值在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的位置为 0, 第二个为 1.

    1.2、函数 Function

    函数如果没有返回值,则一律返回 null
    bool isNoble(int atomicNumber) {
    1、对于只有一个表达式的方法,你可以选择 使用缩写语法来定义。
    2、箭头函数即使只有一个参数,也不能省略括号
    bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
    [1,2,3].forEach(a=>print(a)); // err
    [1,2,3].forEach((a)=>print(a)); // ok
    
  • 可选参数(必须放在必须参数后面)
    1、可选命名参数
    定义方法的时候,使用 {param1, param2, …} 的形式来指定命名参数
  • enableFlags({bool bold, bool hidden}) {
      // ...
    

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

    enableFlags(bold: true, hidden: false);
    

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

    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');
    函数可选参数可以指定默认值,必须参数不能指定
    
    void enableFlags({bool bold = false, bool hidden = false}) {
      // ...
    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');
    
  • main 入口函数
    每个应用都需要有个顶级的 main() 入口方法才能执行。 main() 方法的返回值为 void 并且有个可选的 List<String> 参数。
  • void main(List<String> arguments) {
      print(arguments);
      assert(arguments.length == 2);
      assert(int.parse(arguments[0]) == 1);
      assert(arguments[1] == 'test');
    就是无函数名的函数,一般用于迭代器
    
    var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
    list.forEach((i) {
      print(list.indexOf(i).toString() + ': ' + i);
    
  • 如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断
  • 1.3、操作符

    只记录与JS语言不一致的操作符

  • 算术操作符
    除法结果取整: ~/
  • assert(5 / 2 == 2.5);   // Result is a double
    assert(5 ~/ 2 == 2); 
    
  • 相等操作符
    无三等号操作符,双等号操作符比较的是内容是否相同,而不是内存地址,如果要比较内存地址是否相同,请用 identical
  • void main() {
      const c = [1,2];
      var a = c;
      var b = c;
      print(a == b);
      print(identical(a,b));
    
  • 类型判定操作符
    as:类型转换;is 相当于 instanceof;is!
  • if (emp is Person) { // Type check
      emp.firstName = 'Bob';
    (emp as Person).firstName = 'Bob';
    
  • 赋值操作符
    使用 = 操作符来赋值。 但是还有一个 ??= 操作符用来指定 值为 null 的变量的值
  • a = value;   // 给 a 变量赋值
    b ??= value; // 如果 b 是 null,则赋值给 b;
                 // 如果不是 null,则 b 的值保持不变
    
  • 条件表达式
    除了? : ,多了一个双问号 ??,相当于JS里短路运算符 ||
  • expr1 ?? expr2 // expr1 为 null 时执行 expr2
    
  • 级联操作符
    级联操作符 (..) 可以在同一个对象上 连续调用多个函数以及访问成员变量。 使用级联操作符可以避免创建 临时变量, 并且写出来的代码看起来 更加流畅,相当于JS中的链式调用:
  • querySelector('#button') // Get an object.
      ..text = 'Confirm'   // Use its members.
      ..classes.add('important')
      ..onClick.listen((e) => window.alert('Confirmed!'));
    
  • 对象访问操作符
    新增了一个 .?
    左边的操作对象不能为 null,例如 foo?.bar 如果 foo 为 null 则返回 null,否则返回 bar 成员
  • 1.4、类

    与JS的 constructor 不同,把类中同名函数作为构造函数
    class Point {
      num x;
      num y;
      Point(this.x, this.y);
    
  • 命名构造函数
    使用命名构造函数可以为一个类实现多个构造函数
  • class Point {
      num x;
      num y;
      Point(this.x, this.y);
      // Named constructor 只有当名字冲突的时候才使用 this。否则的话, Dart 代码风格样式推荐忽略 this。
      Point.fromJson(Map json) {
        x = json['x'];
        y = json['y'];
    

    注意:构造函数不能继承,所以超类的命名构造函数 也不会被继承。如果你希望 子类也有超类一样的命名构造函数, 你必须在子类中自己实现该构造函数。

  • 调用超类构造函数
    1、默认情况下,子类的构造函数会自动调用超类的 无名无参数的默认构造函数,
    2、若提供了初始化参数列表,则初始化参数列表在超类构造函数执行之前执行。
    3、构造函数执行顺序:a、initializer list(初始化参数列表)b、superclass’s no-arg constructor(超类的无名构造函数)c、main class’s no-arg constructor(主类的无名构造函数)
    4、如果超类没有无命名无参数构造函数, 则你需要手工的调用超类的其他构造函数。 在构造函数参数后使用冒号 (:) 可以调用 超类构造函数。
  • import 'dart:math';
    class Point {
      final num x;
      final num y;
      final num distanceFromOrigin;
      Point(x, y)
          : x = x,
            y = y,
            distanceFromOrigin = sqrt(x * x + y * y);
    main() {
      var p = new Point(2, 3);
      print(p.distanceFromOrigin);
    
  • 重定向构造函数
    1、有时候一个构造函数会调动类中的其他构造函数。
    2、一个重定向构造函数是没有代码的,在构造函数声明后,使用 冒号调用其他构造函数。
  • class Point {
      num x;
      num y;
      // The main constructor for this class.
      Point(this.x, this.y);
      // Delegates to the main constructor.
      Point.alongXAxis(num x) : this(x, 0);
    
  • 类的继承(单继承)
    使用 extends 定义子类, supper 引用 超类
  • class Television {
      void turnOn() {
        _illuminateDisplay();
        _activateIrSensor();
      // ...
    class SmartTelevision extends Television {
      void turnOn() {
        super.turnOn();
        _bootNetworkInterface();
        _initializeMemory();
        _upgradeApps();
      // ...
    

    可以使用 @override 注解来 表明你的函数是想覆写超类的一个函数:

    class A {
      @override
      void noSuchMethod(Invocation mirror) {
        // ...
    可以通过 with 关键字来给类实现 mixin 功能,扩展类的特点:没有构造函数, 不能调用 super
    
    class Musician extends Performer with Musical {
      // ...
    class Musical {
      bool canPlayPiano = false;
      bool canCompose = false;
      bool canConduct = false;
      void entertainMe() {
        if (canPlayPiano) {
          print('Playing piano');
        } else if (canConduct) {
          print('Waving hands');
        } else {
          print('Humming to self');
    
  • 类的实例变量、方法以及静态变量和方法与JS类一致
  • 1.5、泛型

    1、使用泛型的原因是减少重复的代码。 泛型可以在多种类型之间定义同一个实现, 同时还可以继续使用检查模式和静态分析工具提供的代码分析功能。

    var names = <String>['Seth', 'Kathy', 'Lars'];
    var pages = <String, String>{
      'index.html': 'Homepage',
      'robots.txt': 'Hints for web robots',
      'humans.txt': 'We are people, not machines'
    

    2、当使用泛型类型的时候,你 可能想限制泛型的具体类型。 使用 extends 可以实现这个功能:

    // T must be SomeBaseClass or one of its descendants.
    class Foo<T extends SomeBaseClass> {...}
    

    3、泛型函数

    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;
    

    1.6、其他

    1、引用库
    使用 import 来指定一个库如何使用另外 一个库。import 必须参数为库 的 URI。 对于内置的库,URI 使用特殊的 dart: scheme。 对于其他的库,你可以使用文件系统路径或者 package: scheme。 package: scheme 指定的库通过包管理器来提供, 例如 pub 工具。
    import 'dart:io';
    import 'package:mylib/mylib.dart';
    import 'package:utils/utils.dart';
    

    2、如果你导入的两个库具有冲突的标识符, 则你可以使用库的前缀来区分。 例如,如果 library1 和 library2 都有一个名字为 Element 的类, 你可以这样使用:

    import 'package:lib1/lib1.dart';
    import 'package:lib2/lib2.dart' as lib2;
    // ...
    Element element1 = new Element();           // Uses Element from lib1.
    lib2.Element element2 = new lib2.Element(); // Uses Element from lib
    

    3、导入库的一部分

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

    4、库的延迟加载,使用 deferred as 来 导入,使用库标识符调用 loadLibrary() 函数来调用

    import 'package:deferred/hello.dart' deferred as hello;
    greet() async {
      await hello.loadLibrary();
      hello.printGreeting();
    有三个注解所有的 Dart 代码都可以使用: @deprecated、 @override、 和 @proxy
    1、Future
    
    Future.delayed(new Duration(seconds: 2),(){
       //return "hi world!";
       throw AssertionError("Error");
    }).then((data){
       //执行成功会走到这里 
       print(data);
    }).catchError((e){
       //执行失败会走到这里   
       print(e);
    }).whenComplete((){
       //无论成功或失败都会走到这里
    

    有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作

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

    2、Stream
    Stream 也是用于接收异步事件数据,和Future 不同的是,它可以接收多个异步操作的结果

    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
    

    2、最佳实践

    2.1、字符串

  • 当字符串较长,一行放不下时,不使用 + 来链接字符串:
  • raiseAlarm(
        'ERROR: Parts of the spaceship are on fire. Other '
        'parts are overrun by martians. Unclear which are which.');
    
  • 使用插值的形式来组合字符串和值
  • 'Hello, $name! You are ${year - birth} years old.';
    

    2.2、集合

  • 尽可能的使用集合字面量来定义集合
  • var points = [];
    var addresses = {};
    // bad
    var points = new List();
    var addresses = new Map();
    
  • 不要使用 .length 来判断集合是否为空
    尽管对象和数组都有length属性
  • if (lunchBox.isEmpty) return 'so hungry...';
    if (words.isNotEmpty) return words.join(' ');
    // bad
    if (lunchBox.length == 0) return 'so hungry...';
    if (!words.isEmpty) return words.join(' ');
    
  • 使用高阶(higher-order)函数来转换集合数据
    比JS多了个 where 用于筛选
  • var aquaticNames = animals
        .where((animal) => animal.isAquatic)
        .map((animal) => animal.name);
    

    2.3、变量

  • 不用显示的初始化一个变量的值为 null
  • 避免保存可以计算的结果,而是用 getter
  • class Circle {
      num radius;
      num get area => math.PI * radius * radius;
      num get circumference => math.PI * 2.0 * radius;
      Circle(this.radius);
    
  • 考虑 省略局部变量的类型
  • Map<int, List<Person>> groupByZip(Iterable<Person> people) {
      var peopleByZip = <int, List<Person>>{};
      for (var person in people) {
        peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
        peopleByZip[person.zip].add(person);
      return peopleByZip;
    // bad
    Map<int, List<Person>> groupByZip(Iterable<Person> people) {
      Map<int, List<Person>> peopleByZip = <int, List<Person>>{};
      for (Person person in people) {
        peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
        peopleByZip[person.zip].add(person);
      return peopleByZip;
    

    2.4、成员

  • 使用 final 关键字来限定只读属性
    只能读取, 而不能修改其值,最简单的做法就是使用 final 关键字来标记这个变量
  • class Box {
      final contents = [];
    
  • 用 => 来实现只有一个单一返回语句的函数
  • bool ready(num time) => minTime == null || minTime <= time;
    
  • 不要使用 this. ,除非遇到了变量冲突的情况
  • class Box {
      var value;
      void clear() {
        update(null);
      void update(value) {
        this.value = value;
    // bad
    class Box {
      var value;
      void clear() {
        this.update(null);
      void update(value) {
        this.value = value;
    
  • 要 尽可能的在定义变量的时候初始化其值
  • 2.5、构造函数

  • 要尽可能的使用初始化形式
  • class Point {
      num x, y;
      Point(this.x, this.y);
    // bad
    class Point {
      num x, y;
      Point(num x, num y) {
        this.x = x;
        this.y = y;
    
  • 不要在初始化形式上定义类型
  • // bad
    class Point {
      int x, y;
      Point(int this.x, int this.y);
    
  • 要用 ; 来替代空函数体的构造函数 {}
  • class Point {
      int x, y;
      Point(this.x, this.y);
    // bad
    class Point {
      int x, y;
      Point(this.x, this.y) {}
    
  • 要 把 super() 调用放到构造函数初始化列表之后调用
  • View(Style style, List children)
        : _children = children,
          super(style) {