Dart指南

Dart是Google出品的语言,用于服务端,web端,移动端,物联网等多个领域,是一门面向对象的类定义的单继承的语言.Dart作为Flutter的开发语言,随着Flutter在18年年底发行的1.0正式版,也是逐渐走入大家的视野.所谓"工欲善其事,必先利其器",本篇就是我们走向Flutter开发的第一步.让我们拿起Dart这把武器,开始打怪升级,攒经验吧.

变量

创建一个变量

因为在Dart语言中,一切皆对象,一个字符串,一个数字甚至null都是一个对象.因此,变量存储的是对象的引用.

var s = 'Hello Dart';
print(s);  // Hello Dart

虽然Dart是一门强类型的语言,但它具有类型推导的功能,即我们在定义的时候,不一定非要强制定义变量的具体类型.像上面的代码中,我们使用var来声明一个变量,Dart会自行帮我们判断出这是一个String类型的变量.上面的代码我们也可以直接指定一个具体的类型:

String s = 'Hello Dart';
print(s);

但我们再次给它赋值其他类型的时候,是会报错的.我们在上面的代码后面添加上如下代码,再次运行:

s = 23;
print(s);

会出现Error: A value of type 'int' can't be assigned to a variable of type 'String'.的错误提示.它告诉我们,不能将一个int整型赋值给一个类型为String的变量.但是下面的场景中,类型是可以改变的:

var s;
print(s);  // null
s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 12

当我们用var声明一个变量,但是不给它初始化,后续的赋值操作中,可以是多种类型的.并且,我们可以看到,未被初始化的变量,它的默认值为null.
一般我们建议,在声明的时候指定一个具体的类型.如果暂时还不知道类型的话,可以使用对象类型和动态类型.

对象类型 Object

Object s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 2

动态类型 dynamic

dynamic s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 2

这两者虽然都是可以让变量接收不同的类型,但是它们是有本质的区别的.因为在Dart中,一切皆对象,所有的类型都是继承自Object,所以对象类型可以接收不同的类型.而dynamic则是让代码在编译阶段不执行类型检查.

创建一个常量

通常,创建一个常量,我们使用constfinal关键字,而不是var或者其他类型.

const

const n = 1;
n = 2;

编译器会报错:Error: Can't assign to the const variable 'n'..告诉我们无法分配给一个常量

final

final n = 1;
n = 2;

编译器会报同样的错误.
那么这两者有什么区别呢?
const是编译时常量,即它的值在编译时就已经固定了,而final则在第一次使用时被初始化(当然,const算是隐式final类型).所以实例变量可以是final类型但不能是const类型,且必须在构造函数体执行之前初始化final实例变量.

类型

Dart语言支持多种内建类型

  • Number
  • String
  • Booleab
  • List
  • Set
  • Map
  • Rune

Number

intdouble这两种类型,它们都是num的亚类型.其中 int 代表整数, double(也是采用 IEEE 754 标准)代表浮点数.num拥有各种基本运算方法: + , - , * , /,~/ , abs() , ceil() , floor()等.

int

声明一个整数

var a = 1
int b = 1;

double

声明一个浮点数

var a = 1.1;
int b = 1.1;

运算

/除法运算, %除法取余, ~/除法取整

int a = 7;
int b = 2;
print(a / b);  // 3.5
print(a % b);  // 1
print(a ~/ b);  // 3

String

可以采用单引号' , 双引号 " , 三引号 ''' 来创建字符串,其中三引号实现多行字符串对象的创建.

String a = 'Hello Dart';
String b = "Hello Dart";
String c = 'Hello I\'m Dart';
String d = '''
  Hello World
  I'm Dart
''';

字符串通过 ${expression} 内嵌表达式,如果表达式是一个标识符,则{}可以省略

String language = 'Dart';
print('Hello ${language}');  // Hello Dart
print('Hello $language');  // Hello Dart

使用r前缀,创建原始raw字符串

String s = r'Hello \n world';
print(s);  // Hello \n world

类型转换

字符串与数字之间的类型转换

// String => int
String s = '1';
int a = int.parse(s);
assert(a == 1);

// String => double
String s2 = '1.2';
double b = double.parse(s2);
assert(b == 1.2);

// int => String
int n = 1;
String c = n.toString();
assert(c == '1');

// double => String
double m = 1.2345;
String d = m.toStringAsFixed(2);
assert(d == '1.23');

其中assert是断言,在生产环境会被忽略,在开发环境下assert(condition)会在非true的情况下抛出异常.

Boolean

使用 bool 类型表示布尔值,有字面量 truefalse ,都是编译时常量.Dart的类型安全意味着不能使用if(nonbooleanValue)assert(nonbooleanValue) ,而应该明确检查.

String s = '';
assert(s.isEmpty);

int n = 0;
assert(n == 0);

var o = null;
assert(o == null);

var t = 0 / 0;
assert(t.isNaN);

在JavaScript中,因为存在隐式转换,所以我们可以有如下操作

let arr = []
if(arr){
  console.log('1')
}else{
  console.log('2')
}

结果会输出1,但是在Dart里面这样就是行不通的,if(flag)括号里面的flag必须是个很明确的bool值.

List

就是Dart中的数组类型,用List来定义.

List list = [1,2];
print(list);  // [1, 2]
list.add(3);
print(list);  // [1, 2, 3]
list.addAll([4,5]);
print(list);  // [1, 2, 3, 4, 5]
print(list.length);  // 5
list[2] = 22;
print(list);  // [1, 2, 22, 4, 5]

也可以约束List中的每一个元素的类型

List<int> list = [1,2];
list.add('2');  //Error: The argument type 'String' can't be assigned to the parameter type 'int'.

这里我们尝试将一个错误类型的值添加进去,则会提示错误.但其实如果我们将代码改成如下:

var list = [1,2];
list.add('2');

也还是会报同样的错误提示.这是因为我们之前讲到过的,有默认的类型推导,判断出list的类型为LIst<int>,而现在添加的却是一个String类型的字符串.

Set

是一个元素唯一的无序集合

var set = {'zhangsan','lisi'};  // 创建一个set

var set = {'zhangsan','lisi'}; 这里会有个默认的类型推导,判断出set的类型为 Set<String> ,如果尝试将错误类型添加进去,则会提示错误.

要创建一个空集,使用前面带有类型参数的{} 或将{}赋值给Set类型的变量

var set = <String>{};
Set<String> set2 = {}; 
var o = {};  //创建的是一个Map,而不是一个Set

使用add()addAll()添加元素,使用 .length获取Set中元素的个数

var set = <String>{};
var set2 = {'list'};
set.add('zhangsan');
set.addAll(set2);
print(set);  // {zhangsan, list}
print(set.length);  // 2

在Set字面量之前添加const,可以创建一个编译时Set常量,此后便不再可以更改set的内容.

final set = const {
  'zhangsan'
};

Map

关联keys和values的对象,keys和values可以是任何类型的对象

var map = {
  'name':'zhangsan',
  'location':'China'
};

这里会有个默认的类型推导,判断出map的类型为 Map<String,String> ,如果尝试将错误类型添加进去,则会提示错误.

使用Map构造函数创建,Dart一切皆是对象(都是类的实例),所以变量可以使用构造函数进行初始化

var map = Map();
map['name'] = 'zhangsan';
map['age'] = 18;
print(map);  // {name: zhangsan, age: 18}

在获取values值时,如不存在的返回null:

print(map['name']);
print(map['gender']);  // null

使用 .length获取键值对数量

print(map.length);  // 2

Rune

用来表示字符串中的UTF-32编码字符.由于Dart字符串是UTF-16编码单元,因此要在字符串中表示32位的Unicode需要特殊语法支持: \uXXXX用4个16进制数来表示,对于非4个数值的情况,用大括号把编码值括起来即可.

print('\u2665');  // ♥
print('\u{1f44d}');  // 👍
Runes input = new Runes('\u2665 \u{1f47b} \u{1f44d}');
print(new String.fromCharCodes(input));  // ♥ 👻 👍

函数

main() 函数

Dart支持top-level函数,就是main(),任何应用都必须拥有一个main()函数,默认返回值为空,参数为一个可选的 List<String>

void main(List<String> arguments){
  print(arguments);
}

我们在运行文件的时候,执行

dart index.dart 1 2 

这里文件名后面跟的1和2,就是两个参数

函数返回值

定义一个普通函数,下面的函数规定了参数的类型以及函数返回值的类型,都是String

String getColor(String color){
  return color;
}

所有的函数都有返回值,那么没有明确指定返回值的函数会在函数体的最后隐式的添加 return null ,则返回类型为void,表示空.

函数参数

函数除了上面提到的最普通的参数以外,还有一些其他的参数.比如:可选参数,默认参数等.其中可选参数又可以是命名参数或者位置参数,但一个参数只能选择其中一种方式修饰

命名可选参数

定义函数时,使用{param1, params2, ...}这样的形式.
调用函数时,使用指定命名参数 paramName: value 的形式.如{param1: true, param2 : 'test'},可以不按照顺序传入.

String getColor(String color,{double height}){
  print(color);
  print(height);
  return color;
}

getColor('red');
getColor('red',height:88.8);

使用 @required注解表示参数是必填的

String getColor(String color,{double height, @required double width}){
  print(color);
  print(height);
  print(width);
  return color;
}

其中 required 被定义在 meta package.所以无论是直接引入,还是引入其他package,而那个package中输出了meta都可以.
直接引用:

import 'package:meta/meta.dart';

在Flutter中引入包后也可直接使用 @required 注解

import 'package:flutter/material.dart';

位置可选参数

定义函数时,将参数放到 [] 中来标记参数是可选的,如[param1, param2]
调用函数时,就是正常的fn('必选','可选1','可选2')这样的形式.必选参数放前面,可选参数放后面,且必须按照顺序传入.若其中某个不是最后的参数不存在的话,需传入null.

String getColor(String color,[double height]){
  print(color);
  print(height);
  return color;
}

getColor('red');
getColor('red',88.8);

默认参数

在定义的时候,可以提供参数默认值,但默认值只能是编译时常量.如果没有提供默认值的话,则默认是null

void getPerson(String name, {String gender='male'}){
  print('$name $gender');
}

getPerson('zhangsan');  // zhangsan male
getPerson('lisi',gender:'female');  // lisi female

listmap也可以作为默认值传递

void foo({
  List<int> list = const [1,2],
  Map<String, double> map = const {
    'height':180,
    'width':150
  }
}){
  print('list: $list');
  print('map: $map');
}
foo();

函数是一等对象

一个函数可以作为另一个函数的参数

void printEle(Object ele){
  print(ele);
}
List<Object> list = [1,'t',true];
list.forEach(printEle);

也可以将一个函数赋值给一个变量

var printEle = (ele) => print(ele);
List<Object> list = [1,'t',true];
list.forEach(printEle);

匿名函数

又叫lambda或者closure,修改上面的例子

List<Object> list = [1,'t',true];
list.forEach((item){
  print(item);
});

运算符

算术运算符

操作符 含义
+ 加法
- 减法
* 乘法
/ 除法
~/ 除法,返回整数结果
% 除法,获取余数(取模)
++var 自增,先增再运算
var++ 自增,先运算再增
–var 自减,先减再运算
var– 自减,先运算再减
print(1+2);  // 3
print(1-2);  // -1
print(2*3);  // 6
print(7/2);  // 3.5
print(7~/2);  // 3
print(7%2);  // 1
var n = 2;
print(n++);  // 2
print(n);  // 3
print(--n);  // 2

关系运算符

操作符 含义
== 等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

要判断两个对象是否表示相同的事物,使用 ==运算符(有时,要确定两个对象是否完全相同,用identical()函数)

print(2 == 2);
print(3 != 2);
print(3 > 2);
print(2 < 3);
print(2 >= 2);
print(2 <= 2);

类型判定运算符

操作符 含义
as 类型转换
is 如果对象具有指定的类型则为true
is! 如果对象具有指定的类型则为false

这几个运算符用于在运行时处理类型检查

void main(){
  var p = new Person('zhangsan');
  assert(p is Person);
  var s = new Student(12);
  assert(s is Student);

  if(p is Person){
    print(p.name);
  }
}

class Person {
  String name;
  Person(this.name);
}
class Student {
  int age;
  Student(this.age);
}

其中

if(p is Person){
  print(p.name);
}

可以变成

print((p as Person).name);

但是这两者并不是相等的,因为在第二种情况中,如果p不是Person的实例,那么是会抛出异常的

赋值运算符

使用=为变量赋值

var a = 1;

使用??=运算符,当被赋值的变量为null时才会赋值给它,否则还是原本的值

var a = 1;
var b = 2;
var c;
b ??= a;
c ??= a;
print(b);  // 2
print(c);  // 1

逻辑运算符

操作符 含义
&& 与,两个都成立则成立
|| 或,只要有一个成立就成立
! 非,将表达式反转,true变成false, false变成true

这个和其他的语言基本一样

条件表达式

操作符 含义
condition ? expr1 : expr2 三目运算符,如果condition为true,则执行expr1,否则执行expr2
expr1 ?? expr2 如果expr1是null,则返回expr2的值,否则返回expr1的值
var f = false;
var a = f ? 'Yes' : 'No';
print(a);  // No

var n = null;
print(n ?? 666);  // 666

级联运算符

实现对同一个对象进行一系列操作.除了调用函数,还能访问字段属性,有点像jQuery的链式调用.好处是可以节省创建临时变量的步骤,使编写出来的代码更加的流畅.

List<int> list = [1,2]
  ..add(3)
  ..add(4);
print(list);  // [1, 2, 3, 4]

其他运算符

?. 当运算符左边不为null的时候,获取其运算符右边的属性值.当为null时,返回null

var p = new Person('zhangsan');
var p2 = null;
print(p?.name);  // zhangsan
print(p2?.name);  // null

控制流程语句

顾名思义,就是用来控制dart程序流程走向的语句

if和else

var a = 2;
if(a == 1){
  print(1);
}else if(a == 2){
   print(2);
}else{
  print(3);
}

for循环

var list = ['zhangsan','lis'];
for(var i = 0; i < list.length; i++) {
  print(list[i]);
}

如果要迭代一个实现了 Iterable 接口的对象,可以使用forEach()方法 ,例如 List, Set 等

var list = ['zhangsan','lis'];
list.forEach((item) => print(item));

var s = {'zhangsan','lisi'};
s.forEach((item) => print(item));

同样的,它们也支持使用 for-in 来进行迭代

var s = {'zhangsan','lisi'};
for(var p in s){
  print(p);
}

while和do-while

和其他语言的基本一样,while就是先判断再执行, do-while就是先执行再判断

var a = 1;
while(a < 5){
  print(a++);  // 1 2 3 4
}

var b = 1;
do{
  print(b++);  // 1
}while(b < 1);

break和continue

和其他语言的基本一样,break停止循环,continue跳转到下次迭代

for(var i = 0; i < 5; i++){
  if(i == 2){
    break;
  }
  print(i);  // 0 1
}

for(var i = 0; i < 5; i++){
  if(i == 2){
    continue;
  }
  print(i);  // 0 1 3 4
}

switch和case

每个非空的case语句结尾必须要跟一个break语句.除break以外还有continue, throw , return,否则会报错

var a = 1;
switch(a){
  case 1:
    print(1);
    break;
  case 2:
    print(2);
  default:
    print('other');
}

上面的代码会报错:Error: Switch case may fall through to the next case.
若匹配到某一个为空的case语句,则顺延执行下一个case语句,下面的代码打印出other

var a = 2;
switch(a){
  case 1:
    print(1);
    break;
  case 2:
  default:
    print('other');
}

可以使用continue语句跳转到具体的一个标签处

var a = 2;
switch(a){
  label:
  case 1:
    print(1);
    break;
  case 2:
    print(2);
    continue label;
  default:
    print('other');
}

assert

断言语句,生产环境无效,只在开发环境中有效
可以有两个参数:
第一个参数是一个解析成bool类型的任何表达式.若为true,断言成功,程序继续执行,若为false,断言失败,抛出异常
第二个参数就是断言失败抛出异常时的自定义提示

var a = 1;
assert(a == 1);
assert(a != 1,'a的值为1,所以此次断言失败');

Dart是一种基于类和mixin继承机制的面向对象的语言.每个对象都是一个类的实例,所有的类都继承于Object

定义和实例化

定义一个类

class Person {}

实例化一个对象

var p = new Person();
print(p);  // Instance of 'Person'

所有的实例变量都生成隐式的getter方法,非final的实例变量同样会生成隐式setter方法

void main() {
  var p = new Person();
  p.name = 'zhangsan';
  print(p.name);
}
class Person {
  String name;
}

这里在 p.name = 'zhangsan'的时候,就调用了setter方法, print(p.name)调用了getter方法

构造函数

在没有声明构造函数的情况下,会有一个默认的构造函数, 默认构造函数没有参数并会调用父类的无参构造函数
通过创建一个与其类名相同的函数来声明构造函数或者附加一个额外标识符(即命名构造函数)

void main() {
  var p = new Person('zhangsan');
  print(p.name);  // zhangsan
}
class Person {
  String name;
  Person(String name){
    this.name = name;  // 这里的this指的是当前实例
  }
}

Dart具有精简构造函数写法的语法糖.通常情况下,我们都会这么做,只有当存在命名冲突的时候,才建议使用this.一般采用下面的写法

void main() {
  var p = new Person('zhangsan');
  print(p.name);
}
class Person {
  String name;
  Person(this.name);
}

构造函数不会被继承,即子类不会继承父类的构造函数.所以若子类不声明构造函数,那么它就只有默认构造函数(匿名,无参)

命名构造函数

使用命名构造函数可为一个类实现多个构造函数,同时也可以更清晰的表明函数意图

void main() {
  var p = new Person('zhangsan');
  var p2 = new Person.nickname('xiaozhang');  //通过命名构造函数实例化一个对象
  print(p.name);  // zhangsan
  print(p2.name);  // xiaozhang
}
class  Person {
  String name;
  Person(this.name);
  Person.nickname(this.name);  // 命名构造函数
}

调用父类构造函数

默认情况下,子类的构造函数会自动调用父类的默认构造函数(匿名,无参).父类的构造函数在子类构造函数体开始执行的位置被调用.若有初始化参数列表,则初始化参数列表在父类构造函数之前执行

初始化参数列表 => 父类默认构造函数 => 主类的无名构造函数

如果父类没有默认构造函数,那么就需要子类手动调用父类的其他构造函数,在当前构造函数冒号:之后,函数体之前,声明调用父类构造函数

void main(){
  var s = new Student('zhangsan','math');
  print(s);
  print(s.name);
  print(s.job);
}

class Person {
  String name;
  Person(this.name);
}

class  Student extends Person {
  String job;
  Student(String name,String job):super(name){
    this.job = job;
  }
}

初始化列表

可以初始化实例变量,位于函数体之前,各参数用逗号隔开

void main(){
  var p = new Person('zhangsan',12);
  print(p.name);
  print(p.age);
}

class Person {
  String name;
  int age;
  Person(String name, int age)
      :name = name + ' shuai',
       age = age;
}

在开发阶段,使用assert来验证输入的初始化列表

class Person {
  String name;
  int age;
  Person(String name, int age)
    :assert(age >= 18);
}

使用初始化列表设置final修饰的常量

class Person {
  final String name;
  final String sex;
  Person(name, sex)
    :name = name,
     sex = sex;
}

重定向构造函数

重定向到同一个类的其他构造函数去,重定向构造函数体为空,构造函数的调用是在冒号(:)之后

void main(){
  var p = new Person.nav('zhangsan');
  print(p.name);
  print(p.age);
}

class Person {
  String name;
  int age;
  Person(this.name, this.age);
  Person.nav(name): this(name, 12);
}

常量构造函数

如果该类实例化的对象是不变的,可以把这些对象定义为编译时常量
定义一个Point类,包含一个常量构造函数,注意其成员都是final类型,且构造函数用const修饰

class Point {
  final int x;
  const Point(this.x);
}

构造两个相同的编译时常量会产生一个唯一的标准的实例

void main(){
  var p1  = const Point(2);
  var p2  = const Point(2);
  print(p1);
  print(p2);
  print(identical(p1,p2));  // true 说明这俩是同一个实例
}
void main(){
  var p1  = Point(2);
  var p2  = const Point(2);
  print(p1);
  print(p2);
  print(identical(p1,p2));  // false  说明这俩不是同一个实例
}

获取对象类型
runtimeType 属性,可以在运行时获取对象的类型

var p = new Person();
print(p.runtimeType); 

工厂构造函数

当执行构造函数并不总是创建这个类的一个新实例时,则使用factory 关键字。 例如,一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。

void main(){
  var m = new Machine('test');
  m.produce('computer');
}

class Machine {
  final String name;
  static final Map<String , Machine> _cache = <String , Machine>{};
  factory Machine(String name) {
    if(_cache.containsKey(name)){
      return _cache[name];
    }else{
      final machine = Machine._internal(name);
      _cache[name] = machine;
      return machine;
    }
  }
  Machine._internal(this.name);
  void produce(String sample){
    print(sample);
  }
}

实例方法

对象的实例方法可以访问this和实例变量

void main(){
  var p = new Person('zhangsan');
  p.say();
}

class Person {
  String name;
  Person(this.name);
  say(){
    print(name);
  }
}

Getter 和 Setter

用于对象属性的读和写,使用get和set关键字实现Getter和Setter

void main(){
  var t = new T(1,2);
  print(t.z);  // 3
  t.z = 4;
  print(t.x);  // 2
}

class T {
  int x,y;
  T(this.x, this.y);
  int get z => x + y;
  set z(int value) => x = value - y;
}

抽象类

修饰符 abstract 来定义一个抽象类,抽象类不能被实例化,只有继承它的子类可以,通常用来定义接口,以及部分实现,下面是一个具有抽象方法的抽象类

void main(){
  Dog dog = new Dog();
  dog.eat();
}

abstract class Animal {
  // 抽象方法 
  void eat();
}
class Dog extends Animal {
  eat(){
    print('dog eat');
  }
}

没有方法体的方法我们称为抽象方法.如果子类继承抽象类必须得实现里面的抽象方法.抽象类主要是用来约束子类.下面的例子中,抽象类中的普通方法是不需要被重写的.

void main(){
  Dog dog = new Dog();
  dog.eat();
  dog.fn();
}

abstract class Animal {
  void eat();  // 抽象方法 
  fn(){  // 普通方法 
    print('la la la');
  }
}
class Dog extends Animal {
  eat(){
    print('dog eat');
  }
}

extends抽象类 和 implements的区别:

  • 如果要复用抽象类里面的方法,并且要用抽象方法约束子类的话我们就用extends继承抽象类
  • 如果只是把抽象类当作标准的话我们就用implements实现抽象类

一个类实现多个接口

class Dog implements Animal,Animal2 {
  ...
}

extends关键字创建子类, super关键字引用父类

class Test1 {
  fn(){
    print('fn');
  }
}

class Test2 extends Test1 {
  fn(){
    super.fn();
    print('test2-fn');
  }
}

枚举类型

枚举类型是一种特殊的类,用于表示数量固定的常量值
定义一个枚举类型

enum Animal { dog, cat, fish}

返回枚举值的索引

print(Animal.fish.index);

使用枚举的values常量,获取所有枚举值列表

List<Animal> animal = Animal.values;

mixin混合

复用类代码的一种方式,复用的类在不同层级之间,不存在继承关系.通过创建一个继承自Object且没有构造函数的类来实现一个mixin.
用法: with后跟一个或多个mixin的名字

void main(){
  var s = new Student('zhangsan', 12);
  print(s.age);
  print(s.isBoy);
  s.fn();
}

mixin M {
  bool isBoy = true;
}
mixin M2 {
  void fn(){
    print('M2 - fn');
  }
}

class Person {
  String name;
  Person(this.name);
}

class Student extends Person with M,M2 {
  int age;
  Student(String name, int age):super(name){
    this.age = age;
  }
}

类静态变量和方法

它们不能被实例使用,因此也不能访问this

void main(){
  Person.fn2();
  print(Person.a);
}

class Person {
  static const a = 2;
  static fn2(){
    print('fn2');
  }
}

call()方法

通过实现类的call()方法,让类像函数一样被调用

void main(){
  var t = new Test();
  print(t('aa','bb'));
}
class Test {
  call(String a, String b) => '$a$b';
}

泛型

泛型的好处是正确指定泛型可以提高代码质量,减少重复的代码
运行时中的泛型集合,Dart中泛型类型是固化的,即它们在运行时是携带类型信息的.

var list = List<int>();
list.addAll([2,3]);
assert(list is List<int>);

库不仅提供API,还封装代码,下划线(_)开头的标识符只能在库中可见,库可通过包来分发

引用库

引用内置库

import 'dart:html';

引用其他库

import 'package:pages/index.dart';

指定库前缀

如果导入两个存在冲突标识符的库,可为其制定库前缀

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

Element ele1 = Element();  // 使用 lib1 中的 Element。
lib2.Element ele2 = lib2.Element();  // 使用 lib2 中的 Element。

导入部分库

只导入fn

import 'package:pages/index.dart' show fn;

除了fn都导入

import 'package:pages/index.dart' hide fn;

延迟加载

在需要库的时候再加载

import 'package:pages/index.dart' deferred as lib1;

当需要使用的时候

Future getLib() async {
  await lib1.loadLibrary();
  lib1.fn();
}

一个库可以多次调用loadLibrary,但只会载入一次

异步编程

Future getLib() async {
  await lib1.loadLibrary();
  lib1.fn();
}

其中async 表明这是一个异步函数,await等待其完成后,再执行下面的lib1.fn(),这看起来很像同步代码
假如我们要在主函数main中使用await,那么main()的函数体就必须被标记

Future main() async {
  await xx();
}

在一个异步函数中可以使用多个await
在await表达式中,其值通常是一个Future对象,有点类似于JS中的Promise

声明异步函数

同步写法

String fn() => 'zhangsan';

异步写法

Future<String> fn() async => 'zhangsan';

若函数没有返回值,则返回类型声明为Future<void>

typedef

因为在Dart中一切皆对象,函数也是,我们可以使用typedef为函数起一个别名,用来声明字段及返回值类型
未使用typedef

void main(){
  Test t = Test(foo);
  print(t.fn is Function);
}
class Test {
  Function fn;
  Test(bool f(String a, int b)){
    fn = f;
  }
}
bool foo(String a, int b) => true;

上面的代码在将f赋值给fn时,类型信息就丢失了,但是如果我们使用显式的名字保留类型信息,这样开发者和工具都可以使用它们.

使用typedef

void main(){
  var t = new Test(foo);
  print(t.fn is Function);
  print(t.fn is Fn);
}
typedef Fn = bool Function(String a, int b);
class Test {
  Fn fn;
  Test(this.fn);
}
bool foo(String a, int b) => true;

元数据

元数据注释以@开头,Dart中有两种可用注解: @deprecated@override
下面是@deprecated 的用法

void main(){
  var t = new Test();
  t.oldFn();  // 在vscode中,这里会有一条删除线提示我们这个方法是不推荐使用的,但它仍是能够执行的
  t.newFn();
}

class Test {
  @deprecated
  void oldFn(){
    newFn();
  }
  
  newFn(){
    print(1);
  }
}

注释

普通注释

单行注释

// print(1);

多行注释

/*
print(1);
print(1);
*/

文档注释

单行注释

/// 继承自[Test2]

多行注释

/** 
 * 继承自[Test2]
 * 有个参数
**/

[Test2]会成为一个链接,指向Test2类的API文档

总结: 以上便是Dart的基本语法.相信大家看着这篇指南,应该能在Dart的世界里面入个门吧.Dart作为Flutter的开发语言,随着Flutter应用的越来越流行,相信在不久的将来也会有Dart的一席之地.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章