本文首发于微信公众号「后厂技术官」

关联系列
ReactNative入门系列
React Native组件
Flutter基础系列

前言

Dart是Flutter SDK指定的语言,因此要学习Flutter,Dart是必须掌握的。关于Dart可以写一本书了,这里用一篇文章来介绍下Dart的精髓,带你快速入门。和Java语言类似的部分,这篇文章就尽量不再讲了。

1. Dart开发环境搭建

学习Dart语法最好需要用一个编辑器来实践,这里推荐使用IntelliJ IDEA。先下载Dart SDK,地址为:http://www.gekorm.com/dart-windows/
打开IntelliJ IDEA,菜单中点击File–>Settings–>plugins,在plugins的搜索框中搜索Dart并安装,然后重启IntelliJ IDEA。
点击File–>New Project–>Dart,按照下图配置Dart SDK。
1.png
注意要选择第三个选项Constole Application,否则会默认创建一个Web项目。点击Next然后配置项目的名称就可以创建项目了。
在项目中的bin/main.dart中加入如下测试代码:

void main() {
print("Hello World");
}

点击菜单的Run–>Run’main.dart’或者点击工具条的运行图标,就能在控制台看到输出的结果:

2. Dart概述

Dart是谷歌开发的计算机编程语言,亮相于2011年10月,最新的版本是Dart2。Dart诞生的原因是谷歌的工程师出于对JavaScript的不满,诞生的初期也赢得了部分前端开发者的青睐。但是这时JavaScript借着NodeJS火了起来,在前端、后端、移动端无孔不入,Dart就渐渐被人遗忘了,可见Dart本身是具有很强的实力的,只是不大走运。谷歌并没有放弃Dart,不遗余力的推广Dart:谷歌的Angular提供了Dart版本,指定Dart为新系统Fuchsia的官方开发语言,Dart为移动UI框架Flutter的开发语言,因此Dart又重新回到了人们的视野中。
Dart通常情况下运行在DartVM上,但是在特定情况下它也可以编译成本地代码运行在硬件上,比如Flutter会将代码编译成指定平台的本地代码来提高性能。

3. Dart特性和重要概念

Dart的特性主要有以下几点:

  1. 执行速度快,Dart是AOT(Ahead Of Time)编译的,可以编译成快速的、可预测的本地代码,这使得Flutter几乎都可以使用Dart来编写。也可以采用JIT(Just In Time)编译。
  2. 易于移植,Dart可编译成ARM和X86代码,这样Dart可以在Android、iOS和其他地方运行。
  3. 容易上手,Dart充分吸收了高级语言特性,如果你已经熟悉C++、C、Java,可以在短短几天内用Dart来开发。
  4. 易于阅读,Dart使Flutter不需要单独的声明式布局语言(XML或JSX),或者单独的可视化界面构建器,这是因为Dart的声明式编程布局易于阅读。
  5. 避免抢占式调度,Dart可以在没有锁的情况下进行对象分配和垃圾回收,和JavaScript一样,Dart避免了抢占式调度和共享内存,因此不需要锁。

Dart的重要概念有以下几点:

  1. 在Dart中,一切都是对象,每个对象都是一个类的实例,所有对象都继承自Object。
  2. Dart在运行前解析所有的代码,指定数据类型和编译时常量,可以使代码运行的更快。
  3. 与Java不同,Dart不具备关键字public、protected、private。如果一个标识符以下划线_开始,那么它和它的库都是私有的。
  4. Dart支持顶级的函数如main(),也支持类或对象的静态和实例方法,还可以在函数内部创建函数。
  5. Dart支持顶级的变量,也支持类或对象的静态变量和实例变量,实例变量有时称为字段或属性。
  6. Dart支持泛型类型,如List<int>(整数列表)或List<dynamic>(任何类型的对象列表)。
  7. Dart工具可以报告两种问题:警告和错误。警告只是说明代码可能无法正常工作,但不会阻止程序执行。错误可以是编译时或运行时的。编译时错误会阻止代码执行; 运行时错误会导致代码执行时报出异常。

4. Dart关键字

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

5. 变量

变量声明使用var关键字,未初始化的变量的初始值为null,即便是数字类型的变量也是null。

var name = 'liuwangshu';

name变量的类型被推断为String,也可以显示声明:

String name = 'liuwangshu' ; 

如果对象不限于单一类型,可以指定Object或dynamic类型。

Object name = 'liuwangshu' ; 

如果定义的变量不会变化,可以使用final或const来代替var,final变量只能设置一次。

final name = 'liuwangshu'
//name = 'zhangwuji' ; //会报错

const变量为编译时常量,如果const变量在类级别,可以使用static const。

const pi = 3.1415926;       
const area = pi * 60 * 60;

const不仅仅用来定义常量,也可以使用const来创建常量的值。

var foo = const []; final bar = const []; const baz = [];//相当于`const []` 

6. 基本数据类型

Dart的基本数据类型包括Number、String、Boolean、List、Set、Map、 Symbol、Runes。

6.1 Number

number类型为两类:

  • int:整数值不大于64位,具体取决于平台。在Dart VM上,值可以是-2 ^63到2 ^63 - 1,如果编译为JavaScript,允许值为-2^53 to 2^53 - 1。
  • double:64-bit (双精度) 浮点数,符合 IEEE 754 标准。

6.2 String

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

var s1 = '单引号适用于字符串文字';
var s2 = "双引号同样有效";

可以在字符串中使用表达式,用法是: ${expression}。如果表达式是一个标识符,可以省略 {}。

var s = '乾坤大挪移';
assert('张无忌的$s' ==
'张无忌的乾坤大挪移');

使用三个单引号或者三个双引号可以创建多行字符串对象:

var s1 = '''
第一行
第二行
''';

var s2 = """第一行
第二行""";

6.3 Boolean

Dart是强bool类型检查,只有true对象才被认为是true。

var name = '张无忌';
if (name) {
print('明教教主');
}

上面的代码编译不能通过,因为name是一个字符串,而不是bool类型。

6.4 List

下面是一个List 的示例:

var list = [1, 2, 3];

List的第一个元素的索引是0,最后一个元素的索引是 list.length - 1 。

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

6.5 Set

Dart中的Set是一组无序的集合。

var hero = ['张无忌', '风清扬', '张三丰', '独孤求败', '萧峰'];

要创建一个空集,可以在{}前面带有类型参数:

var heros= <String> {};

使用add()或addAll()方法将条目添加到现有集中:

var heros = <String>{};
heros.add('石破天');
heros.addAll(hero);

6.6 Map

Map是一个键值对相关的对象,键和值可以是任何类型的对象,每个键都是唯一的,而一个值则可以出现多次。

var player= {
// Keys Values
'20' : '斯诺',
'3': '艾弗森',
'40' : '希尔',
'8' : '麦基',
'55' : '穆托姆博'
};

使用Map构造函数也可以实现同样的功能:

var player = new Map();
player['20'] = '斯诺';
player['3'] = '艾弗森';
player['40'] = '希尔';

7. 函数

Dart是一个真正面向对象的语言,函数属于Function对象。这意味着,函数可以赋值给变量,也可以当做其他函数的参数。

void printName(String name) {
print('name is $name');
}

7.1 可选参数

可选参数可以是可选位置参数,也可以是可选命名参数,但不能同时使用。

可选命名参数
调用方法的时候,可以使用 paramName: value 的形式来指定参数的名称,这样就可以根据paramName得知参数的含义,提高代码的可读性。

coffeeFlavor (sugar :true ,sugar :false );  

定义函数时,使用{param1, param2, …}的形式来指定命名参数:

coffeeFlavor ({bool sugar , bool sugar}) {

}

可选位置参数
把函数的参数放到 [] 中就变成可选位置参数了:

String go(String to, [String who]) {
var result = 'go to the $to';
if (who != null) {
result = '$result with $who';
}
return result;
}

7. 2 默认参数值

可以使用 = 来定义可选参数的默认值, 默认值必须是编译时常量。 如果没有提供默认值,则默认值为 null。

String go(String to, [String who= 'liuwangshu']) {
var result = 'go to the $to';
if (who != null) {
result = '$result with $who';
}
return result;
}
String result= go ('beijing');

7.3 main函数

每个应用都需要有个顶级的main() 函数来作为入口才能执行。 main()函数的返回值为 void 并且有个可选的 List<String> 参数。此前我们举的例子都是在main函数中运行才能得已验证:

void main(){
void printName(String name) {
print('name is $name');
}
printName('liuwangshu');
}

7.4 匿名函数

大部分函数都有名字,例如 main() 或者 printElement()。 可以创建没有名字的匿名方法,格式如下所示。

([[Type] param1[, …]]) { 
codeBlock;
};

下面的代码定义了一个参数为i(该参数没有指定类型)的匿名函数。 list中的每个元素都会调用这个函数打印出来.

var list = ['张无忌', '风清扬', '张三丰', '独孤求败', '萧峰'];
list.forEach((i) {
print(list.indexOf(i).toString() + ': ' + i);
});

8. 流程控制语句

Dart的流程控制语句如下:

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

这些语句的大部分都和Java差不多,这里主要讲解for循环和switch语句。

8.1 for循环

标准的 for 循环:

var message = new StringBuffer("张无忌");
for (var i = 0; i < 3; i++) {
message.write('!');
}

List和Set等实现了Iterable接口的类还支持for-in形式的遍历:

var hero = ['张无忌', '风清扬', '张三丰', '独孤求败', '萧峰'];
for (var h in hero) {
print(h);
}

8.2 switch和case

Dart中Switch语句通过使用 == 来比较整型、字符串或者编译时常量。被比较的对象必须都是同一个类的实例(不能是其子类),并且这个类不允许覆写 ==。另外,枚举类型很适用于在Switch语句使用。

String today='Friday';
switch(today){
case 'Monday':
print('星期一');
break;
case 'Friday':
print('星期五');
break;
}

9.捕获异常

捕获异常可以避免异常继续传递。

try {
//...
} on OutOfLlamasException {
//...
} on Exception catch (e) {
print('Unknown exception: $e');
} catch (e) {
print('Something really unknown: $e');
}

使用on或者catch来声明捕获语句,也可以同时使用。其中on来指定异常类型,catch来捕获异常对象。
确保某些代码不管有没有出现异常都会执行,可以使用finally语句来实现。

try {
//...
} catch(e) {
print('Error: $e');
} finally {
//...
}

10.为类添加新的功能

Dart是一个面向对象编程语言,支持基于Mixin的继承机制。Mixin可以理解为多继承,在with关键字的后面为一个或者多个类。

class Person{
run(){
print('跑');
}
}

class Wushu{
use(){
print('乾坤大挪移');
}
}

class Zhangwuji extends Person with Wushu{
int age;
Zhangwuji(int age){
this.age=age;
}
}

void main() {
var zhangwuji=new Zhangwuji(30);
zhangwuji.run();
zhangwuji.use();
}

通过如上代码的验证,Zhangwuji类拥有了Person和Wushu这两个类的方法。

11.库的使用

使用import来引入一个库,对于Dart语言内置的库,使用dart: scheme。 对于第三方的库,可以使用文件系统路径或者 package: scheme。

import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

指定库前缀
如果导入的两个库具有冲突的名字, 可以使用库的前缀来进行区分。 例如,如果library1和library2 都有一个名字为Element的类,可以这样使用:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element(); //使用lib1中的Element
lib2.Element element2 = new lib2.Element(); //使用lib2中的Element

导入库的一部分
如果只使用库的一部分功能,则可以选择需要导入的部分内容。其中show代表只导入指定的部分,hide代表除了指定的部分都导入。

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

// 除了foo,其他部分都导入
import 'package:lib2/lib2.dart' hide foo;

延迟加载库
延迟加载意味着应用程序可以在需要的时候再加载库,使用延迟加载库的场景主要有以下几点:

  • 减少APP的初始启动时间。
  • 执行A/B测试,例如尝试各种算法的不同实现。
  • 加载很少使用的功能。

要延迟加载一个库,需要先使用 eferred as来导入:

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

当需要使用的时候,调用loadLibrary() 函数来加载库:

greet() async {
await hello.loadLibrary();
hello.printGreeting();
}

12.异步支持

Dart库中包含许多返回Future或Stream对象的函数。这些函数是异步的,它们在基本操作后会返回,而不等待该操作完成,例如读取一个文件,在打开文件后就返回了。
虽然看起来有点像同步代码,但是async和await的代码是的确异步的。

await readFile()

要使用await,其方法必须带有async关键字:

FileOperate() async {
var file= await readFile()
//其他处理
}

13.让类可调用

如果Dart中的类实现了call()函数,那么这个类可以当做方法来调用。

class JointFunction {
call(String a, String b, String c, String d) => '$a $b $c $d';
}

main() {
var jf = new JointFunction();
var out = jf("放","手","去","做");//1
print('$out');
}

在下面的示例中,JointFunction类定义了一个call()函数,它接收三个字符串并拼接它们。这样在注释1处就可以调用JointFunction类了。

14.创建实例

在Java中创建实例可以用new,在Dart中你可以选择用new,也可以选择不用:

Element element = Element();

对于Android开发来说用new可能更习惯一些,可读性也稍微好点,不用new的话显得更简洁,至于用不用new就看团队的要求和个人的习惯吧,没有绝对的好坏之分。

总结

Dart的知识点有很多,这里只介绍了一部分我认为需要重点掌握的部分,如果想了解更多,可以查看[官方文档][1],关于Dart的学习可以结合Flutter边写边学,不要只抠Dart的细节。
[1]: https://www.dartlang.org/guides/language/language-tour