Dart语言基础

554 阅读5分钟

前言

通过前面的Flutter简介讲解,我们知道Flutter应用程序使用Dart语言开发,Dart由Google于2011年推出,目前最新版本是2.7。和我们熟悉的Java、ObjectC一样,Dart也属于面向对象编程语言。为了更好的使用Flutter进行应用开发,本文将详细介绍Dart语言的语法和特性。

语言特性

在学习Dart之前,我们先了解一下Dart的相关特性:

  • 能够放在变量中的所有内容都是对象,每个对象都是一个类的实例。甚至于数字、函数和null值都是对象,并且所有对象都继承自Object类。

  • Dart是强类型语言,但类型标识是可选的,因为Dart可以推断类型。如果想显式地声明一个不确定的类型,可以使用特殊类型 dynamic。。

  • Dart支持泛型,比如 List(表示一组由 int 对象组成的列表)或 List(表示一组由任何类型对象组成的列表)。

  • Dart支持顶级函数(例如 main 方法),同时还支持定义属于类或对象的函数(即 静态 和 实例方法)。你还可以在函数中定义函数(嵌套 或 局部函数)。

  • Dart支持顶级变量,以及定义属于类或对象的变量(静态和实例变量)。实例变量有时称之为域或属性。

  • 与Java不同,Dart没有关键字public、protected和private。如想设置私有变量或函数,则变量和函数名以下划线()开头。标识符可以以字母或下划线()开头,后跟这些字符加数字的任意组合。

  • Dart有两个表达式(具有运行时值)和语句(不具有)。 例如,条件表达式条件? expr1:expr2的值为expr1或expr2。 将其与if-else语句进行比较,该语句没有任何值。 语句通常包含一个或多个表达式,但表达式不能直接包含语句。

  • Dart工具可以报告两种问题:警告和错误。警告只是表明您的代码可能无法正常工作,但它们不会阻止您的程序执行。 错误可以是编译时或运行时。 编译时错误会阻止代码执行; 运行时错误导致代码执行时引发异常。

关键字

下面的表格中列出了 Dart 语言所使用的关键字。

应该避免使用这些单词作为标识符。但是,带有上标的单词可以在必要的情况下作为标识符:

  • 带有上标 1 的关键字为 上下文关键字,只有在特定的场景才有意义,它们可以在任何地方作为有效的标识符。

  • 带有上标 2 的关键字为 内置标识符,其作用只是在JavaScript代码转为Dart代码时更简单,这些关键字在大多数时候都可以作为有效的标识符,但是它们不能用作类名或者类型名或者作为导入前缀使用。

  • 带有上标 3 的关键字为 Dart1.0 发布后用于支持异步相关的特性新加的。不能在由关键字 async、async* 或 sync* 标识的方法体中使用 await 或 yield 作为标识符。

其它没有上标的关键字为 保留字,均不能用作标识符。

变量

变量的定义

变量仅存储对象的引用。通常用var关键字声明变量,编辑器会自动根据其值推断出类型;如果一个对象的引用不局限于单一的类型,可以将其指定为 Object 或 dynamic 类型;除此之外你也可以指定具体类型。三种定义变量的方式如下:

var name = 'Bob';
dynamic name = 'Bob';
String name = 'Bob';

变量默认值

在 Dart 中一切皆为对象,未初始化的变量拥有一个默认的初始化值:null。包括数字。

int age;
print('age=$age');

输出结果为

age=null

final和const

  • final

    • final变量要在构造器执行之前初始化,一般在声明时赋值,例如:

      final pi = 3.14;// 正确
      final count;// Error: The final variable ';' must be initialized. final pi;
      
    • final类型的变量值不能被修改,例如:

      pi = 3.15;// Error: 'pi', a final variable, can only be set once.
      
  • const

    • 使用关键字 const 修饰变量表示该变量为 编译时常量。如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const(注意:顺序不能颠倒)

      const bar = 1000000; // 直接赋值
      const double atm = 1.01325 * bar; // 利用其它 const 变量赋值 
      
      class Test{
        static const foo = [1, 2, 3]; 
      }
      

内置类型

Dart 语言支持下列的类型:

  • 常用
    • Numbers
    • Strings
    • Booleans
    • Lists (也被称为 arrays)
    • Sets
    • Maps
  • 不常用
    • Runes (用于在字符串中表示 Unicode 字符)
    • Symbols

常用类型介绍,不常用的暂不作讲解:

Numbers

Dart 支持两种 Number 类型。

  • int,整数值,长度不超过 64位,具体取值范围依赖于不同的平台。在 DartVM 上其取值位于 -263 至 263 - 1 之间。
  • double,64位的双精度浮点数字。

注意:int 和 double 都是 num 的子类,Dart中没有float类型。

int x = 1;//  整型
double pi = 3.14;// 浮点型

Strings

Dart 字符串是 UTF-16 编码的字符序列。

  • 可以使用单引号或者双引号来创建字符串:

    var = 'hello dart!';
    var = "dart is the best language!";
    
  • 可以在字符串中以 ${表达式} 的形式使用表达式,如果表达式是一个标识符,可以省略掉 {}。如果表达式的结果为一个对象,则 Dart 会调用该对象的 toString 方法来获取一个字符串。

    var count = 5;
    var str = 'I have $count apples.';
    

    输出结果为:

    I have 5 apples.
    
  • == 运算符判断两个对象的内容是否一样

    var str1 = 'abc';
    var str2 = 'abc';
    var str3 = 'ab';
    print(str1==str2);
    print(str1==str3);
    

    输出结果为:

      ```
      true
      false
      ```
    
  • 可以使用 + 运算符将两个字符串连接为一个,也可以将多个字符串挨着放一起变为一个:

    var str1 = 'abc''def';
    var str2 = 'abc'+'def';
    print(str1);
    print(str2);
    

    输出结果为:

    abcdef
    abcdef
    

Booleans

  • Dart 使用 bool 关键字表示布尔类型,布尔类型只有两个对象 true 和 false,两者都是编译时常量。

  • Dart 的类型安全不允许你使用类似 if (nonbooleanValue) 或者 assert (nonbooleanValue) 这样的代码检查布尔值。相反,你应该总是显示地检查布尔值,比如像下面的代码这样:

    // 检查是否为空字符串
    var name = '';
    print(name.isEmpty);
    
    // 检查是否小于等于零。
    var x = 0;
    print(x <= 0);
    
    // 检查是否为 null。
    var value;
    print(value == null);
    
    // 检查是否为 NaN。
    var nan = 0 / 0;
    print(nan.isNaN);
    

Lists

  • 数组 Array 是几乎所有编程语言中最常见的集合类型,在 Dart 中数组由 List 对象表示。

    var list = [1, 2, 3];
    
  • List 的下标索引从 0 开始,第一个元素的下标为 0,最后一个元素的下标为 list.length - 1。

    var list = ['a', 'b', 'c'];
    print(list.length);
    print(list[list.length-1]);
    

    输出结果为:

    3
    c
    
  • Dart 在 2.3 引入了扩展操作符(...),它提供了一种将多个元素插入集合的简洁方法。 例如,你可以使用扩展操作符(...)将一个 List 中的所有元素插入到另一个 List 中:

    var list1 = ['a', 'b', 'c'];
    var list2 = [...list1,'d','e'];
    print(list2);
    

    输出结果为:

    [a, b, c, d, e]
    
  • Dart 在 2.3 引入了扩展操作符(...?),如果扩展操作符右边可能为 null ,你可以使用 null-aware 扩展操作符(...?)来避免产生异常,例如:

    var list1;
    var list2 = [...?list1,'d','e'];
    print(list2);
    

Sets

  • Set的声明与初始化

    var week = {'Monday','Tuesday','Wednesday','Thursday','Friday'};
    var weekend = {'Saturday','Sunday'};
    
  • Set的常用方法

    week.addAll(weekend);
    week.add("Day");
    
  • Set的遍历

    var week = {'Monday','Tuesday','Wednesday','Thursday','Friday'}
    var weekend = {'Saturday','Sunday'};
    week.addAll(weekend);
    Iterator<String> it = week.iterator;
    while(it.moveNext()){
    print(it.current);
    }
    

    输出结果为:

    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    Sunday
    

Maps

  • Map 是用来关联 keys 和 values 的对象。 keys 和 values 可以是任何类型的对象。在一个 Map 对象中一个 key 只能出现一次。但是 value 可以出现多次。 Dart 中 Map 通过 Map 字面量和 Map 类型来实现。

  • 使用 Map 字面量创建 Map:

    var gifts = {
      // 键:    值
      'first': 'partridge',
      'second': 'turtledoves',
      'fifth': 'golden rings'
      };
    
  • 使用 Map 的构造器创建 Map:

    var gifts = Map();
    gifts['first'] = 'partridge';
    gifts['second'] = 'turtledoves';
    gifts['fifth'] = 'golden rings';
    
  • 向Map 中添加键值对

    gifts['fourth'] = 'calling birds'; // 添加键值对
    
  • 从Map中获取一个值

    print(gifts['first']);// 如果值不存在返回null
    
  • 使用.length 可以获取 Map 中键值对的数量:

    print(gifts.length);
    

函数

Dart 是一种真正面向对象的语言,所以即便函数也是对象并且类型为 Function,这意味着函数可以被赋值给变量或者作为其它函数的参数。你也可以像调用函数一样调用 Dart 类的实例。

函数的定义

int sum(int a, int b){
    return a+b;
}

箭头函数,上面的函数等价于

int sum(int a, int b) => a+b;

函数的参数

包括2种类型,必选参数和可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。

  • 可选参数分为命名参数和位置参数,可在参数列表中任选其一使用,但两者不能同时出现在参数列表中。
    • 命名参数,命名参数,定义格式如 {param1, param2, …}

      int sum({int a, int b, int c}) {
          if (a == null) {
            a = 0;
          }
          if (b == null) {
            b = 0;
          }
          if (c == null) {
            c = 0;
          }
          return a + b + c;
      }
      print(sum(a: 1, b: 2, c: 3));
      print(sum(a: 1, b: 2));
      

      输出结果为:

      6
      3
      
    • 位置参数,使用 [] 来标记可选参数,例如:

        int sum(int a, int b, [int c]) {
          if (a == null) {
            a = 0;
          }
          if (b == null) {
            b = 0;
          }
          if (c == null) {
            c = 0;
          }
          return a + b + c;
        }
      
        print(sum(1,2,3));
        print(sum(1,2));
      

      输出结果为:

      6
      3
      

参数默认值

int sum([int a = 1, int b = 2]) {
    return a + b;
  }

  print(sum(5));
  print(sum(2,3));

输出结果为:

7
5

main函数

和其他编程语言一样,Dart中每个应用程序都必须有一个顶级main()函数,该函数作为应用程序的入口点。

函数作为参数

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// 将 printElement 函数作为参数传递。
list.forEach(printElement);

匿名函数

匿名方法看起来与命名方法类似,在括号之间可以定义参数,参数之间用逗号分割。后面大括号中的内容则为函数体:

 ([[类型] 参数[, …]]) {
      函数体;
    };
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

词法作用域,下面是一个嵌套函数中变量在多个作用域中的示例:

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

注意 nestedFunction() 函数可以访问包括顶层变量在内的所有的变量。

函数闭包

闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。

函数可以封闭定义到它作用域内的变量。接下来的示例中,函数 makeAdder() 捕获了变量 addBy。无论函数在什么时候返回,它都可以使用捕获的 addBy 变量。

/// 返回一个将 [addBy] 添加到该函数参数的函数。
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // 生成加 2 的函数。
  var add2 = makeAdder(2);

  // 生成加 4 的函数。
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

返回值

所有的函数都有返回值。没有显示返回语句的函数最后一行默认为执行 return null;。

运算符

  • Dart中使用到的运算符如下表格

    描述 运算符
    一元后缀 表达式++ 表达式-- () [] . ?.
    一元前缀 -表达式 !表达式 ~表达式 ++表达式 --表达式
    乘除法 * / % ~/
    加减法 + -
    位运算 << >> >>>
    二进制与 &
    二进制异或 ^
    二进制或 |
    关系和类型测试 >= > <= < as is is!
    相等判断 == !=
    逻辑与 &&
    逻辑或 ||
    空判断 ??
    条件表达式 表达式 1 ? 表达式 2 : 表达式 3
    级联 ..
    赋值 = *= /= += -= &= ^= 等等……

    下面就对一些对于Java或Objective-C来说未使用过的运算符通过代码来做个介绍。

运算符 as、is、is!

这三个运算符是在运行时判断对象类型的运算符。

描述 运算符
as 类型转换(也用作指定类前缀))
is 如果对象是指定类型则返回 true
is! 如果对象是指定类型则返回 false

当且仅当 obj 实现了 T 的接口,obj is T 才是 true。例如 obj is Object 总为 true,因为所有类都是 Object 的子类。

仅当你确定这个对象是该类型的时候,你才可以使用 as 操作符可以把对象转换为特定的类型。例如:

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

如果你不确定这个对象类型是不是 T,请在转型前使用 is T 检查类型。

if (emp is Person) {
  // 类型检查
  emp.firstName = 'Bob';
}

运算符 ??=

使用 ??= 来为值为 null 的变量赋值。

// 将 value 赋值给 a (Assign value to a)
a = value;
// 当且仅当 b 为 null 时才赋值
b ??= value;

运算符 ?.

用于非空判断,左边的操作对象不能为 null,例如 foo?.bar,如果 foo 为 null 则返回 null ,否则返回 bar

运算符 ??

相当于java里的String nickName = name ? name : "Nick"

String nickName = name ?? "Nick"; // 如果name不为null,则nickName值为name的值,否则值为Nick.

运算符 ..

// 类定义
class Person {
  var name;
  var age;
  Person(this.name, this.age);

  void printName() {
    print("name = $name");
  }

  void printAge() {
    print("age = $age");
  }
}

void main() {
  Person('Tom', 25)
    ..printName()
    ..printAge();
}

输出结果为:

name = Tom
age = 25

流程控制语句

Dart中的流程控制语句和其他语言基本相同,这里不做详细介绍。

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

这里说一下assert。在开发过程中,可以在条件表达式为 false 时使用 - assert(条件, 可选信息); - 语句来打断代码的执行。

// 确保变量值不为 null
assert(text != null);

// 确保变量值小于 100。
assert(number < 100);

// 确保这是一个 https 地址。
assert(urlString.startsWith('https'));

异常

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

与 Java 不同的是,Dart 的所有异常都是非必检异常,方法不一定会声明其所抛出的异常并且你也不会被要求捕获任何异常。

Dart 提供了 Exception 和 Error 两种类型的异常以及它们一系列的子类,你也可以定义自己的异常类型。但是在 Dart 中可以将任何非 null 对象作为异常抛出而不局限于 Exception 或 Error 类型。

抛出异常

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

你也可以抛出任何对象

throw 'Out of llamas!';

捕获异常

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 指定异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 其它类型的异常
  print('Unknown exception: $e');
} catch (e) {
  // // 不指定类型,处理其它全部
  print('Something really unknown: $e');
}

如上述代码所示可以使用 on 或 catch 来捕获异常,使用 on 来指定异常类型,使用 catch 来捕获异常对象,两者可同时使用。

Dart 是支持基于 mixin 继承机制的面向对象语言,所有对象都是一个类的实例,而所有的类都继承自 Object 类。基于 mixin 的继承 意味着每个除 Object 类之外的类都只有一个超类,一个类的代码可以在其它多个类继承中重复使用。 Extension 方法是一种在不更改类或创建子类的情况下向类添加功能的方式。

类的定义

```
class Point {
  num x;
  num y;
}
```

抽象类(abstract)

```
// 该类被声明为抽象的,因此它不能被实例化。
abstract class AbstractContainer {
  // 定义构造函数、字段、方法等……
  void updateChildren(); // 抽象方法。
}
```

类的继承(extends)

```
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}
```

类的实现(implements)

```
// Person 接口的一个实现。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => '你好$who。你知道我是谁吗?';
}
```
实现多个接口,用‘,’分开

```
class Point implements Comparable, Location {...}
```

枚举(enum)

```
enum Color { red, green, blue }
```

使用Mixin

Mixin 是一种在多重继承中复用某个类中代码的方法模式。

```
// 类定义
class LogUtil {
  void log() {
    print("this is a log");
  }
}

class Fruit {
  Fruit() {
    print("this is Fruit constructor with no param");
  }
}

class Apple extends Fruit with LogUtil {
  Apple():super() {
    print("this is Apple constructor with no param");
  }
}

// 调用
Apple a = Apple();
a.log(); //可执行从LogUtil继承过来的方法

// 输出结果
this is Fruit constructor with no param
this is Apple constructor with no param
this is a log
```

类变量和方法

使用关键字 static 可以声明类变量或类方法。

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

泛型

泛型定义

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

在上述代码中,T 是一个替代类型。其相当于类型占位符,在开发者调用该接口的时候会指定具体类型。

泛型集合

var names = List<String>();

泛型方法

T first<T>(List<T> ts) {
  // 处理一些初始化工作或错误检测……
  T tmp = ts[0];
  // 处理一些额外的检查……
  return tmp;
}

方法 first 的泛型 T 可以在如下地方使用:

  • 函数的返回值类型 (T)。
  • 参数的类型 (List)。
  • 局部变量的类型 (T tmp)。

库和可见性

使用import关键字引入其他库。

import 'dart:html';

异步支持

async 和 await 关键字用于实现异步编程,并且让你的代码看起来就像是同步的一样。

  • 必须在带有 async 关键字的 异步函数 中使用 await:
    Future checkVersion() async {
      var version = await lookUpVersion();
      // 使用 version 继续处理逻辑
    }
    

写在最后

本文概述了Dart语言中常用的功能。相信大家通过阅读本文已经对Dart语法有个很系统的了解,这将为你后续的Flutter开发打好基础,接下来让我们开始Flutter之旅。