Dart之旅

最近看见同学都这么努力,而我每天就是上班撸码,下班玩游戏,感觉自己也应该努力一点,做为一个大龄程序员,为了不必淘汰,必须要加强学习。Google出了个Flutter,像ReactNative一样,可以编写移动应用程序,于是决定学习一波,看了一天Dart官网的文档,决定写一篇博客加强记忆,这篇博文就是自己对官网文档的一个翻译。

基本的Dart程序

一个基本的Dart程序如下所示:

从以上代码,我们可以知道以下几点:

  • Dart的入口也是main函数,写法和C/C++类似,但是可以省略返回值,返回值由最后的返回类型进行推导。
  • Dart使用var关键字定义变量,可以省略变量类型
  • 在字符串中可以使用$变量名或${变量名}来引用该变量

重要概念

  • 一切能赋给一个变量的值都是一个对象,而每一个对象都是一个类的实例,不管是数值,函数,或者是null对象,所有对象继承自Object类
  • 尽管Dart是一种强类型语言,但是类型声明是可选的,Dart语言可以自动推导变量类型,如果你的确想声明这个变量无类型,请使用dynamic
  • Dart也支持泛型,如List或者List等
  • Dart支持顶级的函数,如main函数,也有类函数和实例函数,还有嵌套函数
  • 同样的,Dart也支持顶层的变量,也支持类变量和实例变量,就像Objc中的@property
  • 不像C++或Java,Dart没有public, protected, private等关键字,如果一个函数以下划线(_)开头,那么这是一个私有函数
  • 变量同样可以使用下划线(_)开头,表示这是一个私有变量
  • Dart同样有表达式和变量声明,比如说三目运算符condition ? expr1 : expr2
  • Dart工具能够指示警告和错误两种问题,警告只是提醒你,你的代码可能无法正常工作,错误分成编译时错误和运行时错误,编译时错误会阻止程序运行,运行时错误则会出现异常。

Dart的关键字

Dart的关键字如下所示,共有60个

其中:

  • 角标为1的是上下文关键字,只在特定的地方有意义,他们在其他地方都可以用做变量
  • 角标为2的是内建变量,他们可以在大多数地方被用做变量,但不能用于类或类型名称,或者在import声明
  • 角标为3的是较新的关键字,这些关键字在Dart 1.0之后用于支持异步,当一个函数被标记为async或async或sync时,你不能使用await或者yield作为变量名
    既然如此,还有个解决问题的更好的方法,就是不用任何关键字做变量名

变量

变量的声明方式如下:

var name = 'Bob';

变量以引用的形式存在,变量名’name‘是一个值为’Bob’的String类型的引用,但是你可以通过指定类型来修改,如果一个变量不仅仅指定一个类型,请指定类型为Object或dynamic,如下:

dynamic name = 'Bob';

另外还有一种显式声明的方法来定义变量,如下:

String name = 'Bob';

默认值

未初始化的变量都会有一个初始化的值null,甚至是一个数值类型,因为在Dart中,就算是一个普通的数值类型,也是一个对象。

Final/Const

如果你不想去修改一个变量,请使用final或const来代替var,一个final变量只能被设置一次,一个const变量是一个编译时常量,final顶层变量或类变量将会在第一次使用时被初始化。

内建类型

Dart语言的内建类型如下所示:

  • numbers: int, double
  • stirngs:String 表示字符串,
  • booleans:bool 表示布尔值
  • lists:List 表示数组
  • sets:Set 集合
  • maps:Map 表示字典,键值对
  • runes: 用于描述Unicode字符串
  • symbols

Strings

  1. UTF-16字符串,可以使用单引号或双引号定义
  2. 使用+号连接字符串,使用’’’(三个单引号)或"""(三个双引号)可定义多行字符串
  3. 可以使用toString方法,创建字符串,使用类型的parse方法可以解析字符串
  4. 可以使用r‘’或r""定义原生字符串,原生字符串中的不会存在转义

List

list也许是几乎所有语言中最常用的集合类型,在Dart中,数组使用List对象,定义如下,类似JavaScript

var list = [1, 2, 3];

List支持泛型,你可以使用List,这样,如果你在列表中加入一个非整型对象,则会得到一个错误。List索引从0开始,-1表示最后一个元素,你可以使用list.length方法获得数组的长度。

Sets

Dart中的一个set是一个无序惟一容器,set的定义方式如下:

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

Set也支持泛型,上述定义也可以使用Set

Maps

map是一个关键键值对的集合,map的键值对可以是任何类型,每个key只能出现一次,但值可以出现任意多次,其定义方式如下:

var gifts = {
    //Key:          Value:
    'first'     :     'partridge',
    'second'  :  'turtledoves',
    'fifth'     :    'golden rings'
    };

同样的, Map也支持泛型,上述定义也可以使用Map<String, String>,另外,以上类型也可以使用new关键字创建,如new Map(),但Dart 2 中,new关键字可以省略。通过键可以获取到值,访问方式如下:

var value = gifts['first'];

同时,你也可以直接给对应的键赋值,使用length获取长度。

Runes

runes表示UTF-32字符,比如\u{1f600}代表emoji表情(?) 。String类也有一些方法使你可以获得rune信息,codeUnitAt和codeUnit属性返回一个16位编码单位,使用runes属性可以获得一个runes字符串。

Symbols

一个Symbol类型表示一个操作符或Dart程序中的一个标识符定义,你可能永远也不会使用symbols,但是对于按名称引用标识符的API来说,它们是有用的,因为缩小更改了标识符名称,而不是标识符符号。要想拿到一个标识符的symbol,只需要使用#号即可。

Functions

Dart是一门真正的面向对象的语言,所以即使是一个函数也有一个类型Function,这意味着一个函数可以被赋值或用做参数传递到另外一个函数,你也可以像调用函数一样调用一个类,类似C++中的 仿函数。函数的定义如下所示:

bool isNoble(int atomicNumber) {
    return _nobleGases[atomicNumber] != null;
}

尽管Dart推荐为公用Api声明类型,但你确实可以省略类型,如下所示:

isNoble(atomicNumber) {
   return _nobleGases[atomicNumber] != null;
}

对于那些仅包含一句表达式的函数来说,你甚至可以按如下写法:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

这种写法就是一个闭包或Lambda函数,你可以使用@required来标记一个参数,表示这个参数必须要传值。

可选参数

可选参数可能是位置型或者是命名型,但不能同时存在

可选命名参数

当你在调用一个方法时,你可以使用paramName:value的方式给指定的参数赋值。当你定义一个函数时,使用{param1, param2, …}的方式定义如下所示:

enableFlags(bold : true, hidden : false);

可选位置参数

参数使用[]包裹即可被定义为一个位置参数,如下所示:

String say(String from, String msg, [String device]) {
    var result = '$from says $msg';
    if(device != null) {
        result = '$result with a $device';
    }
    return result;
}

这样一来,在调用参数的时候,你可以根据需要传递两个或三个参数。

参数默认值

你的函数可以使用=来为位置参数和命名参数定义一个默认值,如果没有指定默认值,默认值为null。如下所示:

void enableFlags({bool bold = false, bool hidden = false}){...}

main()函数

每一个app都必须有一个顶级的main函数,作为整个app的入口,main函数返回值为void,标准的C/C++都是返回int,main函数的参数列表可以为空,也可以是一个List类型的参数列表。

匿名函数

大多数函数都是命名函数,你也能创建一个匿名函数,你可以将匿名函数赋值给一个变量,比如说你可以将它添加到集合或者从中移除。一个匿名函数看起来和命名函数差不多,定义方法如下:

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

从上述定义可以看出,匿名函数,可以省略返回类型和参数。

Opeartor

Dart的操作符类型如下表所示,你可以像C++一样,重写操作符。

数学运算符

Dart的数学运算符如下表所示:

Dart操作符和运算法与其他的语言几乎一模一样,~/是整除的意思,只返回一个整型,比如:

5 ~/ 2 = 2

类似于C++中的

(int)(5 / 2)

另外Dart也支持++, – 这些操作符。还有各种等于,不等于,大于,小于操作符。

类型测试

as: 用于类型转换,is 测试该实例是否是一个类的实例,is!与is结果相反。

赋值操作符

Dart中有一个赋值操作符??=,当要赋值的变量(左值)是null时,则赋值,否则保持原值。
另外还有逻辑运算符,位移运算符,条件运算符等。其中条件运算符除了?:这个三目运算符之外,还有一个expr1 ?? expr2运算符,如果expr1不为null, 返回expr1的值,否则返回expr2的值

级联运算

级联(…)允许实现类型链式的调用方法,它让你额外的访问同一个类型的一些属性,如下所示:

querySelector('#confir')
..text = 'Confirm'
..classes.add('important')
..onClick.listen((e)=>window.alert('Confirmed!'));

以上代码可以分解为以下代码

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('import');
button.onClick.listen((e) => window.alert('Confirmed!'));

其他运算符

Dart中有一个?.运算符,称做条件成员访问,例如foo?.bar,当foo为null的时候选择访问bar,否则选择访问foo。

流程控制

Dart中的流程控制和其他语言类似,共有如下几种

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

其中,大部分的使用方法和C/C++一致,只有switch有区别,区别如下:

  1. dart中的case 语句必须以break结尾,否则会报错,但C/C++中并未有此规定
  2. 当且仅当一个case 为空时,可以继续往下执行,但C++中case可包含代码块,如下所示:

Dart:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

C++:

int command = 0;
switch(command) {
 case 0:
       printf("0");
 case 1:
       printf("1");
       break;
}
  1. C++中的switch类型只能为整型,但是Dart中可以为任意类型
  2. 如果想实现C++的方法,可以使用continue label的方式,跳转到指定标签继续执行case

异常

Dart代码可以抛出和捕获异常,异常是一种指示一些意外情况发生的错误,如果异常没有捕获,那么抛出的异常可能导致程序挂起或终止。Dart提供Exception和Error两种类型,除了预定义的异常类型,你还可以定义自己的异常类型。然而Dart程序可以抛出任何非空对象,不仅仅是Exception或Error。

Throw & Catch

Dart使用throw关键字来抛出异常,在Java中,使用catch来捕获异常,但是在Dart中,则使用on关键字来捕获,使用cache来传递异常参数,单独的catch会捕获所有异常,代码如下所示:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

其中,catch不单只有一个异常参数,还有一个CallStack参数,表示程序的调用栈,类似于Java中的printStackTrace()方法。同时,你还可以使用rethrow关键字将正在处理的异常进行再抛出,由调用的函数继续捕获进行处理。

Finally

类似于Java中的finally,不管最后发生何种异常,finally中的代码都会执行。

Classes

Dart 是一门面向对象语言,每个对象都是一个类的实例,而每一个类都是由Object类扩展下来,除了Object之外,每个类都有一个父类。每个类也包含一个this,类似C++。

访问类成员

对象由成员和函数组成,当你调用一个方法时,该方法就可以访问该对象中的函数和数据。使用点(.)来访问成员变量或成员函数。使用?.来避免空指针异常。

成员变量

成员变量的定义如下:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

可以在里面进行初始化,如果未初始化,则默认初始化为null。所有的成员变量会生成一个隐式的getter方法,非final实例变量也会隐式生成一个setter方法。

构造函数

你可以通过构造函数来创建一个对象,构造函数通常以类名或类名.标识符命名,你还可以使用const 构造函数来创建一个const对象。在C++中,使用构造函数,如果不使用初始化列表,我们应该写如下代码:
C++:

Point(int x, int y) { this->x = x; this->y = y};

但是在Dart中,你可以使用类似如上的写法,还有另外一种更简便的方法:

Point(this.x, this.y);

如果你不为当前类声明一个构造函数,Dart将为你提供一个默认的构造函数,默认的构造函数没有参数,并且会调用父类的无参构造方法,代码可编写如下:

Point():super() {}

初始化列表

我们知道,在C++中,构造函数有一个初始化列表,如下所示:

Point(int x, int y) : x(x), y(y) {}

在Dart中,同样可以使用初始化列表,使用方法如下:

Point(x, y) : x = x, y = y {}

构造重定向

有时候一个构造函数只有一个目的,就是重定向到该类的另一个构造函数中,重定义构造函数的函数体为空,定义如下:

Point.alongXAxis(num x) : this(x, 0);

常构造

如果你产生的对象不会被修改,你可以使这些对象保持编译时常量,要这样做,定义一个const构造函数,确保所有变量为final类型。

Factory构造函数

通常情况下,构造函数会创建一个新的实例,但有时候你需要复用对象而不是创建新对象,则可以使用factor构造函数,如下代码所示:

class Logger {
  final String name;
  bool mute = false;
  
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }
}

Getter & Setter

Getter和Setter指定用于读取和写入权限的方法,每个实例变量都会有一个隐式的getter方法,你可以通过实现自己的getter或setter创建额外的属性。如下所示:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

对象类型

要想获得一个对象的类型,可以使用对象的runtimeType属性,该属性返回一个Type对象,如下所示:

print('The type of a is ${a.runtimeType}');

抽象类

在C++中,也有抽象类的概念,我们把包含有纯虚函数的类称做抽象类,并没有任何关键字标记,C++中的定义如下所示:

class Test {
    virtual void test(void) = 0;
};

抽象类不能实例化,子类继承自抽象类,必须实现纯虚函数,否则该子类也不能实例化。在Dart中,使用abstract来定义一个抽象类。其中包含的方法是抽象函数,也必须在子类中实现。

隐式接口

每个类隐式的定义了一个接口,这个接口包含所有的实倒方法,如果你需要创建一个类B但不从A继承,可以使用implements关键字实现该接口,并实现其中的方法。

继承与覆写

和Java一样,Dart使用extends来继承一个类,如果需要覆盖父类的方法,需要使用@override,Dart也可以像C++一样重写操作符函数, 如下所示:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

noSuchMethod()

当检测或者尝试使用一个不存在的方法或变量时,你可以覆写noSuchMethod方法,否则将会抛出一个异常。如下所示:

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

除了以下几种情况,否则你不能调用一个未实现的方法:

  • 接收者有一个dynamic的类型定义
  • 接收者有一个定义为未实现方法的类型,并且实现了不同于Object类中的noSuchMethod()方法

枚举类型

枚举类型是一个特别的类,通常用于表示一个固定的数值或常量。使用方式如下:

enum Color { red, green, blue }

每一个枚举类型都有一个称为index的getter方法,返回一个从0开始的对应的值在枚举中的索引。枚举类型可以在switch语句中使用,如果没有将所有的情况都处理完整,你将会得到一个警告。使用枚举类型还有如下限制:

  • 你不能继承,混入或实现一个枚举
  • 你不能显式的实例化一个枚举

特征添加:混入

混入是一种在多个类继承中重用类代码的方式,要想使用混入,在with关键字后添加一个或多个混入类名称,示例如下:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

要实现混入,创建一个继承自Object的没有构造函数的类,除非你想要你的混入用起来不像一个常规的类,使用mixin关键字替代class,示例如下:

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

指定一个仅有确定类型

import和library能帮助你创建一个模块并且分享出去,库不仅仅提供API,更是一个以下划线开头的可视类型。每一个Dart app都是一个库,即使并没有使用library指令。库可以以包的形式被使用。通常使用import来引用一个库中的命名空间。import惟一要求的参数就是一个指定库的URI,对于内建的库来说,你需要指定dart:,如果该库是由包管理器提供的,则应该使用package:。

如果你引入了两个命名有冲突的库,你可以使用as关键字为其中一个或多个同时指定一个前缀,示例如下:

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

你也可以引用库中的一部分,使用show关键字,可以指定从库中仅引入某个类,使用hide则可以引用除该类中的其他类,如下所示:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

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

延迟加载允许应用在使用到一个库的时候进行加载,以下是一些你可能需要延迟加载的情况:

  • 需要减少app的初动速度
  • 执行A/B测试-尝试交替实现一个算法
  • 加载很少使用的功能,比如一个可选的屏幕和对话框

要延迟加载一个库,你必须道先使用deferred as来引入一个库,之后在使用的时候调用loadLibrary()方法,如下所示:

import 'package:greetings/hello.dart' deferred as hello;
Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

你可以多次调用loadLibrary(),但是这个类仅会被加载一次。使用延迟加载时,你必须要谨记以下几点:

  • 使用延迟加载的库中的常量引入时不再是常量。记住,这些常量在加载之前是不存在的。
  • 你不能使用导入文件中的类型,相反,考虑将接口类型移动到由延迟库和导入文件导入的库中。
  • Dart会在你使用deferred as定义的命名空间中隐藏的插入一个loadLibrary()调用,loadLibrary()返回一个Feature对象。

异步支持

Dart库是一个充满返回一个Feature或Stream方法的库。这些方法都是异步的,他们可能在设置某些耗时的操作之后就返回而不等待他们完成。async和await关键字支持异步编程,可以让你像编写同步代码一样的编写异步代码。

处理Features

当你需要使用完整的Feature时,你有两个选择:

  • 使用async 和 await
  • 使用Feature API,如从库中的引入

使用async和await的代码是异步的,但是它看起来会像同步的代码一样。要使用await,代码必须在一个async方法中,使用try,catch,finally来处理错误和清理操作,如下所示:

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

在一个异步方法中,你可以多次使用await,在一个await表达式中,它的值通常是一个Feature,如果不是一个Feature,这个值也是一个原子值,Feature对象指定一个promise返回一个对象。await表达式的值就是这个返回的对象,await表达式会中断执行直接这个对象可以使用。如果你使用await的时候获得一个编译时错误,确保await是在async方法当中

处理Streams

当你需要从Stream中获取一个值,你有两个选择:

  • 使用async和一个异步循环(await for)
  • 使用Stream API

如下所示:

await for (varOrType identifier in expression) {}

这个表达式的值是一个Stream,执行进程如下所示:

  • 等待直到Stream发射一个值
  • 执行for循环的body,变量设置为发射的值
  • 重复1和2直接到Stream关闭

如果需要停止监听Stream,你可以使用break或return来中断循环。

生成器

当你需要延迟生成一个值的序列时,可以考虑使用一个生成器函数,Dart内建支持两类生成器

  • 同步生成器(Synchronous generator): 返回一个Iterable对象
  • 异步生成器(Asynchronous generator): 返回一个Stream对象

要实现一个同步生成器函数,你需要使用sync*标记,然后使用yield语句传送该值。如下所示:

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

要实现异步生成器函数,你需要使用async*标记,然后使用yield语句传送该值,如下所示:

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

如果你的生成器是一个递归函数,你可以使用yield*来提高性能。

可调用类

如果你要使你的类能像函数一样被调用,类似C++中的仿函数,请实现call()方法

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

Typedefs

在Dart中,函数是一个对象,就像String和Number对象一样。一个typedef或者函数类型别名,为函数类型提供一个在声明字段和返回类型时可以使用的名称,当一个函数类型赋值给一个变量的时候,typedef会维持原有的类型信息。使用方法如下:

typedef Compare = int Function(Object a, Object b);

Metadata

使用Metadata给你的代码添加额外的信息。一个Metadata标注以字符@开始,跟着是一个编译时常值,例如deprecated或者调用一个常量构造函数。有两个标注在所有Dart代码中都可以使用,@deprecated和@override。你可以定义自己的metadata标注,只需要类定义了const构造函数。如下所示:

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

使用方式如下:

import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

Metadata可以出现在库、类、typedef、类型参数、构造函数、工厂、函数、字段、参数或变量声明之前,也可以出现在导入或导出指令之前。您可以使用反射在运行时检索元数据。

注释

Dart中的注释也分单行注释//和多行注释/**/, 其中还有一种文档注释类型,以/**或///开始,同样以**/或///结束。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值