一篇文章了解 Dart 语言

移动开发 DartFlutter
Zyao89 2019年2月21日星期四 19:41

# Dart 语言

Dart 是谷歌在011 年推出的编程语言,是一种结构化 Web 编程语言,允许用户通过 Chromium 中所整合的虚拟机(Dart VM)直接运行 Dart 语言编写的程序,免去了单独编译的步骤。以后这些程序将从 Dart VM 更快的性能与较低的启动延迟中受益。Dart 从设计之初就为配合现代 web 整体运作而考虑,开发团队也同时在持续改进 Dart 向 JavaScript 转换的快速编译器。Dart VM 以及现代 JavaScript 引擎(V8 等)都是 Dart 语言的首选目标平台。
https://webdev.dartlang.org/api

# 一个基础的 Dart 程序

下面的代码运用了 Dart 的许多基础功能:

//定义一个函数
printNumber(num aNumber) {
  print('The number is $aNumber.'); // 输出至控制台.
}

// 此处是APP开始执行的地方
main() {
  var number = 42; // 声明并初始化变量
  printNumber(number); // 调用函数
}
1
2
3
4
5
6
7
8
9
10

注释:

使用 // 来表示剩下的行是一个注释,也可以使用 /*...*/ 来注释

num
一种变量类型。其他一些内置类型有 String, int 和 bool;

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

print()
一个手动的方式来展示输出。

'...'(或者"...")
一个字符串。

variableName(或者{expression})
字符串插值:包括一个变量或表达式的字符串,相当于一个字符串内。

main()
是一个特殊的、必要、顶级的函数,应用程序开始执行的地方。

var 一种特殊的方式来声明变量,不用特指变量类型。

# 重要的概念

当学习Dart语言的时候,把这些事实和概念记在脑子里:

  • 每个变量都是一个对象,每个对象是一个类的实例。甚至数字,函数,和null都是对象。所有对象都继承自Object
  • 虽然Dart是强类型语言,但变量类型是可选的因为Dart可以自动推断变量类型。
  • Dart支持顶级函数(如main())也支持类或者对象(静态和实例方法分别支持)里的函数。还可以在函数里创建函数(嵌套或局部功能)。
  • 类似的,Dart支持顶级变量,以及依赖于类或对象(静态变量和实例变量)变量。实例变量有时被称为域或属性。
  • 与Java不同,Dart不具备关键字public,protected和private。如果一个标识符以下划线(_)开始,那么它和它的库都是私有的。
  • Dart中变量可以以字母或下划线开头,后面跟着任意组合的字符或数字。

# 关键字

下面表格列出了 Dart 语言的关键字:

关键字 关键字 关键字 关键字 关键字
abstract continue false new this
as default final null throw
assert deferred finally operator true
async do for part try
async* dynamic get rethrow typedef
await else if return var
break enum implements set void
case export import static while
catch external in super with
class extends is switch yield
const factory1 library sync* yield*
  • 在关键字表中的所有单词都是保留字。不能使用保留字作为标识符。

# 变量

下面是创建变量并对其赋值的一个例子:

var name = 'Bob';
1

变量都是引用,变量name包含对一个 String 对象值是 “Bob” 的引用。

# 默认值

未初始化的变量具有 null 的初始值。即使数字类型变量最初为 null ,因为数字是对象。

# final 和 const

如果从不打算改变一个变量,使用 final 或者 const 代替 var 或者其他类型。一个 final 变量只能被设置一次;一个 const 变量是一个编译时常数。

被声明为 final 的顶层或类变量第一次使用时被初始化:

final name = 'Bob'; // Or: final String name = 'Bob';
// name = 'Alice';  // 取消注释会产生一个错误
1
2

注:延迟初始化变量最终有助于应用程序启动更快。

使用常量作为要编译的常数变量。如果 const 的变量是在类级别,将其标记为静态常量。 (实例变量不能是const。)如果你声明的变量,设置该值为编译时常数设置,如文字,一个 const 变量,或常数算术运算的结果:

const bar = 1000000;       //压力单位(dynes/cm2)
const atm = 1.01325 * bar; // 标准大气压
1
2

# 内建数据类型

Dart有如下几种内建的数据类型:

  • number

  • string

  • boolean

  • list(或者是array)

  • map

  • rune(UTF-32字符集的字符)

  • symbol

  • dynamic 和 Object

    下面用一段代码来演示以上各类数据类型:


  numbers() {
    // numbers
    var a = 0;
    int b = 1;
    double c = 0.1;

    output.print('a=$a, b=$b, c=$c');
  }

  strings() {
    // strings
    var s1 = 'hello';
    String s2 = "world";

    output.print('s1=$s1, s2=$s2');
  }

  booleans() {
    // booleans
    var real = true;
    bool isReal = false;

    output.print('real=$real, isReal=$isReal');
  }

  lists() {
    // lists
    var arr = [1, 2, 3, 4, 5];
    List<String> arr2 = ['hello', 'world', "123", "456"];
    List<dynamic> arr3 = [1, true, 'haha', 1.0];

    output.print('arr=$arr, arr2=$arr2, arr3=$arr3');
  }

  maps() {
    // maps
    var map = new Map();
    map['name'] = 'zhangsan';
    map['age'] = 10;
    Map m = new Map();
    m['a'] = 'a';

    output.print('map=$map, m=$m');
  }

  runes() {
    //runes,Dart 中 使用runes 来获取UTF-32字符集的字符。String的 codeUnitAt and codeUnit属性可以获取UTF-16字符集的字符
    var clapping = '\u{1f44f}';

    output.print('clapping=$clapping');
  }

  symbols() {
    // symbols
    final b = (#s == new Symbol("s")); // true

    output.print('#s == new Symbol("s")=$b');
  }

dynamics() {
    Object o = 'string';
    o = 42;
    o.toString();   // 我们只能调用Object 支持的方法

    // dynamic 是告诉编译器,我们知道自己在做什么,不用做类型检测。
    dynamic obj = 'string';
    obj['foo'] = 4;  // 可以编译通过,但在运行时会抛出 NoSuchMethodError
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# 函数

Dart是一个面向对象的编程语言,所以即使是函数也是一个对象,也有一种类型Function,这就意味着函数可以赋值给某个变量或者作为参数传给另外的函数。虽然Dart推荐你给函数加上返回值,但是不加返回值的函数同样可以正常工作,另外你还可以用=>代替return语句,比如下面的代码:

// 声明返回值
int add(int a, int b) {
  return a + b;
}

// 不声明返回值
add2(int a, int b) {
  return a + b;
}

// =>是return语句的简写
add3(a, b) => a + b;

main() {
  print(add(1, 2)); // 3
  print(add2(2, 3)); // 5
  print(add3(1, 2)); // 3
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 命名参数、位置参数、参数默认值、可选参数

# 命名参数

sayHello({String name}) {
  print("hello, my name is $name");
}

sayHello2({name: String}) {
  print("hello, my name is $name");
}

main() {
  // 打印 hello, my name is zhangsan
  sayHello(name: 'zhangsan');

  // 打印 hello, my name is wangwu
  sayHello2(name: 'wangwu');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以看到,定义命名参数时,你可以以 {type paramName} 或者 {paramName: type} 两种方式声明参数,而调用命名参数时,需要以 funcName(paramName: paramValue) 的形式调用。

命名参数的参数并不是必须的,所以上面的代码中,如果调用sayHello()不带任何参数,也是可以的,只不过最后打印出来的结果是:hello, my name is null,在Flutter开发中,你可以使用@required注解来标识一个命名参数,这代表该参数是必须的,你不传则会报错,比如下面的代码:

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

# 位置参数 (可选参数)

使用中括号[]括起来的参数是函数的位置参数,代表该参数可传可不传,位置参数只能放在函数的参数列表的最后面,如下代码所示:

sayHello(String name, int age, [String hobby]) { // 位置参数可以有多个,比如[String a, int b]
  StringBuffer sb = new StringBuffer();
  sb.write("hello, this is $name and I am $age years old");
  if (hobby != null) {
    sb.write(", my hobby is $hobby");
  }
  print(sb.toString());
}

main() {
  // hello, this is zhangsan and I am 20 years old
  sayHello("zhangsan", 20);
  // hello, this is zhangsan and I am 20 years old, my hobby is play football
  sayHello("zhangsan", 20, "play football");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 参数默认值

你可以为命名参数或者位置参数设置默认值,如下代码所示:

// 命名参数的默认值
int add({int a, int b = 3}) { // 不能写成:int add({a: int, b: int = 3})
  return a + b;
}

// 位置参数的默认值
int sum(int a, int b, [int c = 3]) {
  return a + b + c;
}
1
2
3
4
5
6
7
8
9

# main() 函数

不论在Dart还是Flutter中,必须都需要一个顶层的main()函数,它是整个应用的入口函数,main()函数的返回值是void,还有一个可选的参数,参数类型是List<String>

# 匿名函数

大多数函数都是有名称的,比如main() printName()等,但是你也可以写匿名函数,如果你对Java比较熟悉,那下面的Dart代码你肯定也不会陌生:

test(Function callback) {
  callback("hello");
}

main() {
  test((param) {
    // 打印hello
    print(param);
  });
}
1
2
3
4
5
6
7
8
9
10

匿名函数类似于Java中的接口,往往在某个函数的参数为函数时使用到。

# 封闭函数

封闭指的是一个函数可以访问其语法作用域内的变量,即使这个函数是在变量本身的作用域之外被调用的。

函数内部会包含在临近作用域内所定义的变量。在下一个示例中,makeAdder()捕获了变量addBy。不管返回的函数在哪里被调用,它都可以使用addBy

/// 返回一个把 addBy 作为参数的函数
Function makeAdder(num addBy) {
     return (num i) => addBy + 1;
}

main() {
     // 创建一个加2的函数
     var add2 = makeAdder(2);
     // 创建一个加4的函数
     var add4 = makeAdder(4);

     assert(add2(3) == 5);
     assert(add4(3) == 7);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 函数返回值

所有的函数都有返回值,如果没有指定return语句,那么该函数的返回值为null。

Dart 不支持函数的重载。

# 运算符

操作符 含义
+
-
-expr 一元减号,也被命名为负号(使后面表达式的值反过来)
*
/
~/ 返回一个整数值的除法
% 取余,除法剩下的余数

# 类型测试操作符

asisis! 操作符在运行时用于检查类型非常便捷。

运算符 含义
as 类型转换
is 当对象是相应类型时返回 true
as 当对象不是相应类型时返回 true

Dart中的运算符与Java中的类似,下面用代码说明:

main() {
  // 与Java相同的运算符操作

  int a = 1;
  ++a;
  a++;
  var b = 1;
  print(a == b);  // false
  print(a * b); // 3
  bool real = false;
  real ? print('real') : print('not real'); // not real
  print(real && a == b); // false
  print(real || a == 3); // true
  print(a != 2); // true
  print(a <= b); // false
  var c = 9;
  c += 10;
  print("c = $c"); // c = 19
  print(1<<2); // 4

  // 与Java不太一样的运算符操作

  // is运算符用于判断一个变量是不是某个类型的数据
  // is!则是判断变量不是某个类型的数据
  var s = "hello";
  print(s is String); // true
  var num = 6;
  print(num is! String); // true

  // ~/才是取整运算符,如果使用/则是除法运算,不取整
  int k = 1;
  int j = 2;
  print(k / j); // 0.5
  print(k ~/ j); // 0

  // as运算符类似于Java中的cast操作,将一个对象强制类型转换
  (emp as Person).teach();

  // ??=运算符 如果 ??= 运算符前面的变量为null,则赋值,否则不赋值
  var param1 = "hello", param2 = null;
  param1 ??= "world";
  param2 ??= "world";
  print("param1 = $param1"); // param1 = hello
  print("param2 = $param2"); // param2 = world

  // ?.运算符
  var str1 = "hello world";
  var str2 = null;
  print(str1?.length); // 11
  print(str2?.length); // null
  print(str2.length); // 报错
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 级联操作符

class Person {
  eat() {
    print("I am eating...");
  }

  sleep() {
    print("I am sleeping...");
  }

  study() {
    print("I am studying...");
  }
}

main() {
  // 依次打印
  //  I am eating...
  //  I am sleeping...
  //  I am studying...
  new Person()..eat()
      ..sleep()
      ..study();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

可以看到,使用..调用某个对象的方法(或者成员变量)时,返回值是这个对象本身,所以你可以接着使用..调用这个对象的其他方法.

# 控制流程 (与其他语言差不多)

你可以使用以下任何一种方式来控制你的Dart代码流:

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

你也可以通过使用try-catch和throw来改变控制流的异常部分。

下面用一段代码说明:

main() {
  // if else语句
  int score = 80;
  if (score < 60) {
    print("so bad!");
  } else if (score >= 60 && score < 80) {
    print("just so so!");
  } else if (score >= 80) {
    print("good job!");
  }

  // switch语句
  String a = "hello";
  // case语句中的数据类型必须是跟switch中的类型一致
  switch (a) {
    case "hello":
      print("haha");
      break;
    case "world":
      print("heihei");
      break;
    default:
      print("WTF");
  }

  // for语句
  List<String> list = ["a", "b", "c"];
  for (int i = 0; i < list.length; i++) {
    print(list[i]);
  }
  for (var i in list) {
    print(i);
  }
  // 这里的箭头函数参数必须用圆括号扩起来
  list.forEach((item) => print(item));

  // while语句
  int start = 1;
  int sum = 0;
  while (start <= 100) {
    sum += start;
    start++;
  }
  print(sum);

  // try catch语句
  try {
    print(1 ~/ 0);
  } catch (e) {
    // IntegerDivisionByZeroException
    print(e);
  }
  try {
    1 ~/ 0;
  } on IntegerDivisionByZeroException { // 捕获指定类型的异常
    print("error"); // 打印出error
  } finally {
    print("over"); // 打印出over
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 异常

抛出异常:

throw Exception('put your error message here');
1

捕获异常:

try {
  // ...
// 捕获特定类型的异常
} on FormatException catch (e) {
  // ...
// 捕获特定类型的异常,但不需要这个对象
} on Exception {
  // ..
// 捕获所有异常
} catch (e) {
  // ...
} finally {
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

跟 Java 不同的是,Dart 可以抛出任意类型的对象:

throw 42;
1

# 类 ( class )

Dart中的类没有访问控制,所以你不需要用private, protected, public等修饰成员变量或成员函数,一个简单的类如下代码所示:

class Person {
  String name;
  int age;
  String gender;
  Person(this.name, this.age, this.gender);
  sayHello() {
    print("hello, this is $name, I am $age years old, I am a $gender");
  }
}
1
2
3
4
5
6
7
8
9

构造方法等同于:

Person(String name, int age, String gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}
1
2
3
4
5

使用如下:

var p = new Person("zhangsan", 20, "male");
  p.sayHello(); // hello, this is zhangsan, I am 20 years old, I am a male
  p.age = 50;
  p.gender = "female";
  p.sayHello(); // hello, this is zhangsan, I am 50 years old, I am a female
1
2
3
4
5

# 命名构造方法

class Point {
  num x, y;
  Point(this.x, this.y);
  // 类的命名构造方法
  Point.origin() {
    x = 0;
    y = 0;
  }
}

main() {
  // 调用Point类的命名构造方法origin()
  var p = new Point.origin();
  var p2 = new Point(1, 2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

命名构造方法继承, 需注意, 如下

class Human {
  String name;
  Human.fromJson(Map data) {
    print("Human's fromJson constructor");
  }
}

class Man extends Human {
  Man.fromJson(Map data) : super.fromJson(data) {
    print("Man's fromJson constructor");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

由于Human类没有默认构造方法,只有一个命名构造方法fromJson,所以在Man类继承Human类时,需要调用父类的fromJson方法做初始化,而且必须使用Man.fromJson(Map data) : super.fromJson(data)这种写法,而不是像Java那样将super写到花括号中。

有时候你仅仅只是在某个类的构造方法中,调用这个类的另一个构造方法,你可以这么写:

class Point {
  num x, y;
  Point(this.x, this.y);
  // 命名构造方法调用了默认的构造方法
  Point.alongXAxis(num x) : this(x, 0);
}
1
2
3
4
5
6

# 抽象类和抽象方法

使用abstract修饰一个类,则这个类是抽象类,抽象类中可以有抽象方法和非抽象方法,抽象方法没有方法体,需要子类去实现,如下代码:

abstract class Doer {
  // 抽象方法,没有方法体,需要子类去实现
  void doSomething();
  // 普通的方法
  void greet() {
    print("hello world!");
  }
}

class EffectiveDoer extends Doer {
  // 实现了父类的抽象方法
  void doSomething() {
    print("I'm doing something...");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 运算符重载

class Vector {
  num x, y;
  Vector(this.x, this.y);
  Vector operator +(Vector v) => new Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => new Vector(x - v.x, y - v.y);
  printVec() {
    print("x: $x, y: $y");
  }
}

main() {
  Vector v1 = new Vector(1, 2);
  Vector v2 = new Vector(3, 4);
  (v1 - v2).printVec(); // -2, -2
  (v1 + v2).printVec(); // 4, 6
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 枚举类

使用enum关键字定义一个枚举类,这个语法跟Java类似,如下代码:

enum Color { red, green, blue }
1

# mixins

mixins是一个重复使用类中代码的方式,比如下面的代码:

class A {
  a() {
    print("A's a()");
  }
}

class B {
  b() {
    print("B's b()");
  }
}

// 使用with关键字,表示类C是由类A和类B混合而构成
class C = A with B;

main() {
  C c = new C();
  c.a(); // A's a()
  c.b(); // B's b()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 静态成员变量和静态成员方法

// 类的静态成员变量和静态成员方法
class Cons {
  static const name = "zhangsan";
  static sayHello() {
    print("hello, this is ${Cons.name}");
  }
}

main() {
  Cons.sayHello(); // hello, this is zhangsan
  print(Cons.name); // zhangsan
}
1
2
3
4
5
6
7
8
9
10
11
12

# 泛型

如果你在API文档寻找基本数组类型或者 List 类型,你将会看到该类型实际上为List<E>,其中<...>标记表示此表为一个泛型类型(或为参数化结构)—— 一种含有正规类型参数的类型。按照惯例,类型变量通常为单字符名称,例如E,T,S,K,以及V。

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
//...
names.add(42); //在调试模式中失败 (在生产模式中成功).
1
2
3
4

# 异步

Dart 是单线程的,主线程由一个事件循环来执行(类似 Android 的主线程)。对于异步代码,我们通过 Future 来获取结果.

Dart提供了类似ES7中的async await等异步操作,这种异步操作在Flutter开发中会经常遇到,比如网络或其他IO操作,文件选择等都需要用到异步的知识。
async和await往往是成对出现的,如果一个方法中有耗时的操作,你需要将这个方法设置成async,并给其中的耗时操作加上await关键字,如果这个方法有返回值,你需要将返回值塞到Future中并返回,如下代码所示:

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
1
2
3
4

下面的代码使用Dart从网络获取数据并打印出来:

import 'dart:async';
import 'package:http/http.dart' as http;

Future<String> getNetData() async{
  http.Response res = await http.get("http://www.baidu.com");
  return res.body;
}

main() {
  getNetData().then((str) {
    print(str);
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Dart库(Libraries)

Dart目前已经有很多的库提供给开发者,许多功能不需要开发者自己去实现,只需要导入对应的包即可,使用import语句来导入某个包,比如下面的代码:

import 'dart:html';
1

如果你想导入自己写的某个代码文件,使用相对路径即可,例如当前有一个demo.dart文件,跟该文件同级目录下有个util.dart文件,文件代码如下:

// util.dart文件内容

int add(int a, int b) {
  return a + b;
}
1
2
3
4
5

在demo.dart文件中如果要引用util.dart文件,使用下面的方式导入:

// demo.dart

import './util.dart';

main() {
  print(add(1, 2));
}
1
2
3
4
5
6
7

你可以使用as关键字为导入的某个包设置一个前缀,或者说别名,比如下面的代码:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
1
2
3
4
5
6
7
8

你也可以在导入包时使用show hide关键字来导入某个包中的部分功能,比如下面的代码:

// 只导入foo
import 'package:lib1/lib1.dart' show foo;

// 导入除了foo的所有其他部分
import 'package:lib2/lib2.dart' hide foo;
1
2
3
4
5

导入包时使用deferred as可以让这个包懒加载,懒加载的包只会在该包被使用时得到加载,而不是一开始就加载,比如下面的代码:

import 'package:greetings/hello.dart' deferred as hello;
1

# dart:html

用于连接 Dart 和 HTML

import 'dart:html';

class OutPut {
    Element _$codeEl;
    Element _$resultEl;

    OutPut(String codeId, String resultId) {
        _$codeEl = querySelector('#$codeId');
        _$resultEl = querySelector('#$resultId');

        _init();
    }

    _init() {
        _$codeEl.style.border = 'solid 1px grey';
        _$codeEl.style.padding = '5px';
        _$codeEl.style.margin = '5px 0';

        _$resultEl.style.border = 'solid 1px green';
        _$resultEl.style.padding = '5px';
        _$resultEl.style.margin = '5px 0';
    }

    code(List cs) {
        var c = cs.join('<br>');
        _$codeEl.innerHtml = c;
    }

    print(String text) {
        _$resultEl.appendHtml('<p>$text</p>');
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# dart:js

用于调用原生 JavaScript


# Demo链接

我是链接

作者: Zyao89; 转载请保留
版权声明: 自由转载-非商用-非衍生-保持署名
上次编辑时间: 2023年11月27日星期一 11:18