Dart编程语言概览
一个简单的Dart程序:
- 注释,单行、多行
- 数据类型、字面量、输出方式
- 字符串插值
- main()函数:特定的顶级函数
- 定义变量var:通过这种方式定义变量不需要指定变量类型
// 定义一个函数
printInteger(int aNumber) {
// 打印
print('The number is $aNumber)');
}
// 应用从这里开始
main() {
var number = 42;
printInteger(number);
}
重要的概念:
- 一切皆对象,所有对象都有对应的一个
类
的实例;无论数字、函数和null
都是对象;所有对象都继承自Object
类; - Dart是强类型语言,但可以推断类型;如果要明确说明不需要任何类型,需要使用特殊类型
dynamic
动态类型; - Dart支持泛型,如
List<int>
整数列表、List<dynamic>
任何类型的对象列表; - Dart对函数的支持:
- 支持顶级函数main()
- 绑定在类上——静态函数
- 绑定在对象上——实例函数
- 支持函数内创建函数(嵌套或 局部函数)
- Dart对变量的支持:
- 支持顶级变量
- 绑定在类上——静态变量
- 绑定在对象上——实例变量(字段/属性)
- Dart没有关键字
public/protected/private
,如果标识符以下划线_
开头,则它相对于库是私有的; - Dart表达式(运行时有值),语句(运行时无值);
condition?expr1:expr2
值可能是二者之一,if-else
语句没有值;语句可以包含表达式,但是表达式不能直接包含语句; - Dart工具提示两种类型问题:警告 和 错误(编译时错误会阻止代码执行 或 运行时错误会导致代码在执行过程中引发异常);
Dart关键字解析:
abstract
:定义 抽象类 — 抽象类不能实例化;抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现;
// 这个类被定义为抽象类,
// 所以不能被实例化。
abstract class AbstractContainer {
// 定义构造行数,字段,方法...
void updateChildren(); // 抽象方法。
}
as
、as
、is
、is!
运算符用于在运行时处理类型检查;- 例如,
obj is Object
总是 true。 但是只有 obj 实现了 T 的接口时,obj is T
才是 true。 - 使用 as 运算符将对象强制转换为特定类型;
- 例如,
if (emp is Person) {
(emp as Person).firstName = 'Bob';
}
-
assert
:如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断- 例如,
assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
- assert 语句只在开发环境中有效, 在生产环境是无效的;
- 断言失败,会抛出异常 (
AssertionError
);
- 例如,
-
async
和await
:- Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。
- 要使用 await , 代码必须在 异步函数(使用 async 标记的函数)中;
- 使用 try, catch, 和 finally 来处理代码中使用 await 导致的错误。
- 在一个异步函数中可以多次使用 await
- 在 await 表达式 中, 表达式 的值通常是一个 Future 对象; 如果不是,这是表达式的值会被自动包装成一个 Future 对象。
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
break
和continue
:- 使用 break 停止程序循环,使用 continue 跳转到下一次迭代;
- 如果对象实现了 Iterable 接口 (例如,list 或者 set)。 那么示例完全可以用另一种方式来实现:
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());
case
、switch
、default
:- 在Dart中switch语句使用
==
比较比较整数,字符串,或者编译时常量 - 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。
- 枚举类型 可以用于 switch 语句
- 在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句;除 break 以外,还有可以使用 continue, throw,者 return。
- Dart 支持空 case 语句, 允许程序以
fall-through
的形式执行。 - 在非空 case 中实现
fall-through
形式, 可以使用 continue 语句结合 lable 的方式实现 - case 语句可以拥有局部变量, 这些局部变量只能在这个语句的作用域中可见。
- 在Dart中switch语句使用
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
// break; // 缺省break会报错
case 'PENDING':
// executePending(); //但支持空case语句
// break;
case 'APPROVED':
executeApproved();
continue open;
case 'DENIED':
executeDenied();
break;
open:
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
catch
、finally
、rethrow
:捕获异常可以避免异常继续传递(除非重新抛出(rethrow
)异常)。 可以通过捕获异常的机会来处理该异常- 通过指定多个 catch 语句,可以处理可能抛出多种类型异常的代码。
- catch 语句未指定类型, 则该语句可以处理任何类型的抛出对象
catch()
函数可以指定1到2个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 ( 一个StackTrace
对象 )。- 如果仅需要部分处理异常, 那么可以使用关键字 rethrow 将异常重新抛出。
- 不管是否抛出异常, finally 中的代码都会被执行。 如果 没有用catch 匹配异常, 异常会在 finally 执行完成后,再次被抛出;任何匹配的 catch 执行完成后,再执行 finally ;
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 一个特殊的异常
buyMoreLlamas();
} on Exception catch (e) {
// 其他任何异常
print('Unknown exception: $e');
} catch (e, s) {
// 没有指定的类型,处理所有异常
print('Something really unknown: $e');
rethrow;
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
// 抛出异常
throw FormatException('Expected at least 1 section');
// 抛出任意对象
throw 'Out of llamas!';
// 因为抛出异常是一个表达式, 所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();
class
、this
:class 用于声明类;- 所有实例变量都生成隐式 getter 方法。 非 final 的实例变量同样会生成隐式 setter 方法
- 构造函数中,使用
this
关键字引用当前实例;仅当存在命名冲突时,使用this
关键字。 否则,按照 Dart 风格应该省略 this ;(通常模式下,会将构造函数传入的参数的值赋值给对应的实例变量)
class Point {
num x; // 声明示例变量 x,初始值为 null 。
num y; // 声明示例变量 y,初始值为 null 。
num z = 0; // 声明示例变量 z,初始值为 0 。
// 生成构造函数
Point(num x, num y) {
// 还有更好的方式来实现下面代码,敬请关注。
this.x = x;
this.y = y;
}
}
const
和final
:- 使用过程中从来不会被修改的变量, 可以使用 final 或 const, 而不是 var 或者其他类型
- Final 变量的值只能被设置一次;Const 变量在编译时就已经固定 (Const 变量 是隐式 Final 的类型.)
- 实例变量可以是 final 类型但不能是 const 类型。
- 如果 Const 变量是类级别的,需要标记为 static const
- Const 关键字不仅可以用于声明常量变量,还可以用来创建常量值(
const
关键字在声明常量构造函数时
还有应用,参考关键字new
的描述)
// 声明常量变量
const bar = 1000000;
// 创建常量值
var foo = const [];
deferred
:- Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再加载库
- 常用场景:减少 APP 的启动时间。执行 A/B 测试,例如 尝试各种算法的 不同实现。加载很少使用的功能,例如可选的屏幕和对话框。
- 延迟加载库的常量在导入的时候是不可用的,在导入文件的时候也无法使用延迟库中的类型;
// 要延迟加载一个库,需要先使用 deferred as 来导入
import 'package:greetings/hello.dart' deferred as hello;
// 当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库:
Future greet() async {
// 可以多次调用 loadLibrary() 函数。但是该库只是载入一次
await hello.loadLibrary();
hello.printGreeting();
}
do
和do-while
while (!isDone()) {
doSomething();
}
do {
printLine();
} while (!atEndOfPage());
dynamic
:动态的数据类型else
if
:和 JavaScript 不同, Dart 的判断条件必须是布尔值,不能是其他类型
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
enum
:枚举类型也称为 enumerations 或 enums , 是一种特殊的类,用于表示数量固定的常量值- 枚举中的每个值都有一个 index getter 方法, 该方法返回值所在枚举类型定义中的位置(从 0 开始)
- 使用枚举的 values 常量, 获取所有枚举值列表( list )
- 可以在 switch 语句 中使用枚举, 如果不处理所有枚举值,会收到警告
- 枚举不能被子类化,混合或实现
- 枚举不能被显式实例化
enum Color { red, green, blue }
assert(Color.red.index == 0);
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
export
:- 库代码位于lib目录下,对其他包是公开的。您可以根据需要在lib下创建任何层次结构。按照惯例,实现代码放在lib/src下。lib/src下的代码被认为是私有的;其他包永远不需要导入src/…要使lib/src下的api公开,可以从直接位于lib下的文件导出lib/src文件;
// 目录结构
- src
- cascade.dart
- ...
- shelf.dart
- shelf_io.dart
// shelf.dart, exports several files from lib/src:
export 'src/cascade.dart';
export ...
extends
和super
:使用 extends 关键字来创建子类, 使用 super 关键字来引用父类
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
factory
:工厂构造函数- 当执行构造函数并不总是创建这个类的一个新实例时,则使用 factory 关键字。
- 一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。
- 工厂构造函数无法访问 this。
class Logger {
final String name;
bool mute = false;
// 从命名的 _ 可以知,
// _cache 是私有属性。
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;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
// 工厂构造函的调用方式与其他构造函数一样
var logger = Logger('UI');
logger.log('Button clicked');
false
和true
for
:- 闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱
- 如果要迭代一个实现了 Iterable 接口的对象, 可以使用 forEach() 方法
- 实现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作 iteration
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
var callbacks = [];
for (var i = 0; i < 2; i++) {
// 输出的是 0 和 1。 但是示例中的代码在 JavaScript 中会连续输出两个 2
callbacks.add(() => print(i));
}
// 如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择;
callbacks.forEach((c) => c());
var collection = [0, 1, 2];
for (var x in collection) {
print(x); // 0 1 2
}
-
Function
:- Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function;
- 这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。
箭头 语法
:=> expr;
语法是{ return expr; }
的简写;- 在箭头 (
=>
) 和分号 (;
) 之间只能使用一个 表达式 ,不能是 语句 - 函数有两种参数类型: required 和 optional。 required 类型参数在参数最前面, 随后是 optional 类型参数。 命名的可选参数也可以标记为 “
@required
” - 可选参数可以是
命名参数
或者位置参数
,但一个参数只能选择其中一种方式修饰。
-
命名参数 & 位置参数:
- 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。
- list 或 map 可以作为默认值传递;
// 位置参数声明方式
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
// 位置参数的调用方式
isNoble(100);
// 命名参数声明方式
void enableFlags({bool bold, bool hidden= false}) {...}
// 命名参数的调用方式
enableFlags(bold: true, hidden: false);
// Flutter 创建实例的表达式可能很复杂, 因此窗口小部件构造函数仅使用命名参数
const Scrollbar({Key key, @required Widget child})
// 位置可选参数
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
// list 或 map 可以作为默认值传递
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'second': 'cotton',
'third': 'leather'
}}) {
print('list: $list');
print('gifts: $gifts');
}
Required 被定义在 meta package。 无论是直接引入(import)
package:meta/meta.dart
,或者引入了其他 package,而这个 package 输出(export)了 meta,比如 Flutter 的package:flutter/material.dart
。
implements
:隐式接口- 每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。
- 一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口要求的 API。
// 一个类对应一个 隐式的接口Person
class Person {
// 包含在接口里,但只在当前库中可见。
final _name;
// 不包含在接口里,因为这是一个构造函数。
Person(this._name);
// 包含在接口里。
String greet(String who) => 'Hello, $who. I am $_name.';
}
// person 接口的实现。
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
// 调用
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
// 实现多个接口
class Point implements Comparable, Location {...}
get
和set
:- Getter 和 Setter 是用于对象属性读和写的特殊方法
- 使用 get 和 set 关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算属性: right 和 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);
}
import
:通过 import 指定一个库命名空间中的内如如何在另一个库中使用- import 参数只需要一个指向库的 URI(URI 代表统一资源标识符)
- 对于内置库,URI 拥有自己特殊的dart: 方案
- 对于其他的库,使用系统文件路径或者 package: 方案
- package: 方案指定由包管理器(如 pub 工具)提供的库
- 如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀
// Dart Web应用程序通常使用 dart:html 库,它们可以像这样导入:
import 'dart:html';
// 指定库前缀
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// 使用 lib1 中的 Element。
Element element1 = Element();
// 使用 lib2 中的 Element。
lib2.Element element2 = lib2.Element();
hide
和show
:如果你只使用库的一部分功能,则可以选择需要导入的 内容
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
-
in
:- for-in 进行迭代操作
-
interface
:Yes. The interface keyword was removed from Dart. Instead all classes have implicit interfaces. So if you want to define an interface you can use an abstract class instead.
-
library
:库和可见性- import 和 library 指令可以用来创建一个模块化的,可共享的代码库。
- 库不仅提供了 API ,而且对代码起到了封装的作用: 以下划线 (_) 开头的标识符仅在库内可见。
- 每个 Dart 应用程序都是一个库 ,虽然没有使用 library 指令
- 库可以通过包(Package)来分发,pub(集成在SDK中的包管理器)
-
mixin
、with
:- Dart 是一种基于类和 mixin 继承机制的面向对象的语言;
- Mixin 提供了复用类代码的一种途径, 复用的类可以在不同层级,之间可以不存在继承关系。
- 通过
with
后面跟一个或多个混入的名称,来 使用 Mixin - 通过创建一个继承自 Object 且没有构造函数的类,来 实现 一个 Mixin 。如果 Mixin 不希望作为常规类被使用,使用关键字
mixin
替换 class; - 指定只有某些类型可以使用的 Mixin - 比如, Mixin 可以调用 Mixin 自身没有定义的方法 - 使用
on
来指定可以使用 Mixin 的父类类型;
// 通过 with 后面跟一个或多个混入的名称,来 使用 Mixin
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
// Mixin
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');
}
}
}
// Musician是一个类, MusicalPerformer这个mixin 通过on关键字 就可以调用类的方法
mixin MusicalPerformer on Musician {
// ···
}
mixin 关键字在 Dart 2.1 中被引用支持
new
:- 构造函数 创建对象。 构造函数的名字可以是
ClassName
或者ClassName.identifier
(这可能是一个工厂函数); - 构造函数前面的的
new
关键字是可选的 - 一些类提供了常量构造函数。 使用常量构造函数,在构造函数名之前加
const
关键字,来创建编译时常量; - 在 常量上下文 中, 构造函数或者字面量前的
const
可以省略;
- 构造函数 创建对象。 构造函数的名字可以是
// 例如, 以下代码使用 Point 和 Point.fromJson() 构造函数创建 Point 对象:
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
// 常量构造函数
var p = const ImmutablePoint(2, 2);
// 构造两个相同的编译时常量会产生一个唯一的, 标准的实例
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 它们是同一个实例。
// 这里有很多的 const 关键字。
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
// 保留第一个 const 关键字,其余的全部省略:
// 仅有一个 const ,由该 const 建立常量上下文。
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
null
:- 未初始化的变量默认值是
null
,即使变量是数字 类型默认值也是null
,因为在 Dart 中一切都是对象,数字类型 也不例外;
- 未初始化的变量默认值是
int lineCount;
// 在生产环境代码中 assert() 函数会被忽略,不会被调用。 在开发过程中, assert(condition) 会在非 true 的条件下抛出异常
assert(lineCount == null);
-
on
:- 在捕获异常
try-catch
语句中,使用on
来指定异常类型,使用catch
来捕获异常对象; - 在
mixin
声明中,指定只有某些类型可以使用这个mixin
;
- 在捕获异常
-
operator
:操作符- 可以被重写的操作运算符:
<
、+
、|
、[]
、>
、/
、^
、[]=
、<=
、~/
、&
、~
、>=
、*
、<<
、==
、–
、%
、>>
- 如果要重写 == 操作符,需要重写对象的 hashCode getter 方法。
- 可以被重写的操作运算符:
你可能会被提示 != 运算符为非可重载运算符。 因为 e1 != e2 表达式仅仅是 !(e1 == e2) 的语法糖。
// 重写 + -
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);
// 运算符 == 和 hashCode 部分没有列出。 有关详情,请参考下面的代码段。
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
// 重写 == 操作符
class Person {
final String firstName, lastName;
Person(this.firstName, this.lastName);
// 重写 hashCode,实现策略源于 Effective Java,
// 第11章。
@override
int get hashCode {
int result = 17;
result = 37 * result + firstName.hashCode;
result = 37 * result + lastName.hashCode;
return result;
}
// 如果重写了 hashCode,通常应该从新实现 == 操作符。
@override
bool operator ==(dynamic other) {
if (other is! Person) return false;
Person person = other;
return (person.firstName == firstName &&
person.lastName == lastName);
}
}
void main() {
var p1 = Person('Bob', 'Smith');
var p2 = Person('Bob', 'Smith');
var p3 = 'not a person';
assert(p1.hashCode == p2.hashCode);
assert(p1 == p2);
assert(p1 != p3);
}
-
part
:You may have heard of the part directive, which allows you to split a library into multiple Dart files. We recommend that you avoid using part and create mini libraries instead.
-
return
:返回函数返回值; -
static
:使用static
关键字实现类范围的变量和方法- 静态变量(类变量)对于类级别的状态是非常有用的;
- 静态变量只到它们被使用的时候才会初始化;
- 静态方法(类方法)不能在实例上使用,因此它们不能访问
this
; - 静态函数可以当做编译时常量使用。 例如,可以将静态方法作为参数传递给常量构造函数。
// 静态变量
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
// 静态方法
import 'dart:math';
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);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
对于常见或广泛使用的工具和函数, 应该考虑使用顶级函数而不是静态方法;
代码准守风格推荐指南中的命名规则, 使用 lowerCamelCase 来命名常量。
sync
yield
(sync*
、async*
):- 当您需要延迟生成( lazily produce )一系列值时, 可以考虑使用_生成器函数_。 Dart 内置支持两种生成器函数:
- Synchronous 生成器: 返回一个 Iterable 对象
- Asynchronous 生成器: 返回一个 Stream 对象
- 当您需要延迟生成( lazily produce )一系列值时, 可以考虑使用_生成器函数_。 Dart 内置支持两种生成器函数:
// 通过在函数体标记 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* 来提高其性能:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield* naturalsDownFrom(n - 1);
} else {
yield n;
}
}
typedef
:- 在 Dart 中,函数也是对象,就想字符和数字对象一样。 使用
typedef
,或者function-type alias
为函数起一个别名, 别名可以用来声明字段及返回值类型。 当函数类型分配给变量时,typedef会保留类型信息。
- 在 Dart 中,函数也是对象,就想字符和数字对象一样。 使用
// 未使用typedef
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation. // broken ?
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
// 虽然知道 compare 是函数,
// 但是函数是什么类型 ?
// 当把 f 赋值给 compare 的时候,类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 但是 compare 得到的类型是 Function
assert(coll.compare is Function);
}
目前,typedefs 只能使用在函数类型上
// 使用typedef为函数起一个别名
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
// 判断任意函数的类型
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True!
}
-
var
:创建一个变量,初始化之后,变量仅存储对象引用; -
void
:函数无返回值的类型描述;如果不是void声明的函数返回值类型,那么函数就一定有返回值,实际上所有函数都会返回一个值。 如果没有明确指定返回值, 函数体会被隐式的添加 return null; 语句;
Dart 内建数据类型
支持的内建类型:
- Number
- String
- Boolean
- List(也被称为Array)
- Map
- Set
- Rune(用于在字符串中表示Unicode字符)
- Symbol
这些类型都可以被初始化为字面量;
因为在 Dart 所有的变量终究是一个对象(一个类的实例), 所以变量可以使用 构造涵数 进行初始化。 一些内建类型拥有自己的构造函数。 例如, 通过 Map()
来构造一个 map 变量。
几个参考链接,便于使用查阅:
Number
Dart 语言的 Number 有两种类型:
int
:整数值不大于64位, 具体取决于平台。double
:64位(双精度)浮点数,依据 IEEE 754 标准
int
和 double
都是 num
. 的亚类型
num
类型包括基本运算+
,-
,/
, 和*
, 以及abs()
,ceil()
, 和floor()
, 等函数方法。- 如果
num
及其亚类型找不到你想要的方法, 尝试查找使用dart:math
库。 - 按位运算符,例如
»
,定义在int
类中。int 特有的传统按位运算操作,移位(<<, >>),按位与(&)以及 按位或(|)。
var x = 1;
var hex = 0xDEADBEEF;
var y = 1.1;
var exponents = 1.42e5;
// 从 Dart 2.1 开始,必要的时候 int 字面量会自动转换成 double 类型
double z = 1; // 相当于 double z = 1.0
assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111
// 数字类型字面量是编译时常量。 在算术表达式中,只要参与计算的因子是编译时常量, 那么算术表达式的结果也是编译时常量
const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;
字符串 与 数字 的相互转换:
// String -> int
var one = int.parse('1');
assert(one == 1);
// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);
// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
String
Dart 字符串是一组 UTF-16
单元序列:
- 字符串通过单引号或者双引号创建。
- 字符串可以通过
${expression}
的方式内嵌表达式,如果表达式是一个标识符,则 {} 可以省略,如$var
。 - 在 Dart 中通过调用就对象的 toString() 方法来得到对象相应的字符串
- 可以使用 + 运算符来把多个字符串连接为一个(把多个字面量字符串写在一起也可以实现字符串连接);
- 使用连续三个单引号或者三个双引号实现多行字符串对象的创建:
- 使用 r 前缀,可以创建 “原始 raw” 字符串:
== 运算符用来测试两个对象是否相等。 在字符串中,如果两个字符串包含了相同的编码序列,那么这两个字符串相等
一个编译时常量的字面量字符串中,如果存在插值表达式,表达式内容也是编译时常量, 那么该字符串依旧是编译时常量。 插入的常量值类型可以是 null,数值,字符串或布尔值:
// const 类型数据
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';
// 非 const 类型数据
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];
const validConstString = '$aConstNum $aConstBool $aConstString'; //const 类型数据
// const invalidConstString = '$aNum $aBool $aString $aConstList'; //非 const 类型数据
字符常用方法和正则表达式:
- 使用正则表达式 (RegExp 对象) 可以在字符串内搜索和替换部分字符串
- String 定义了例如
split()
,contains()
,startsWith()
,endsWith()
等方法 - 字符串是不可变的对象,也就是说字符串可以创建但是不能被修改,例如,方法 replaceAll() 返回一个新字符串, 并没有改变原始字符串;
- 具体参看如下代码示例;
// 检查一个字符串是否包含另一个字符串。
assert('Never odd or even'.contains('odd'));
// 一个字符串是否以另一个字符串为开头?
assert('Never odd or even'.startsWith('Never'));
// 一个字符串是否以另一个字符串为结尾?
assert('Never odd or even'.endsWith('even'));
// 查找一个字符串在另一个字符串中的位置。
assert('Never odd or even'.indexOf('odd') == 6);
// 抓取一个子字符串。
assert('Never odd or even'.substring(6, 9) == 'odd');
// 使用字符串模式分割字符串。
var parts = 'structured web apps'.split(' ');
assert(parts.length == 3);
assert(parts[0] == 'structured');
// 通过下标获取 UTF-16 编码单元(编码单元作为字符串)。
assert('Never odd or even'[0] == 'N');
// 使用 split() 传入一个空字符串参数,
// 得到一个所有字符的 list 集合;
// 有助于字符迭代。
for (var char in 'hello'.split('')) {
print(char);
}
// 获取一个字符串的所有 UTF-16 编码单元。
var codeUnitList =
'Never odd or even'.codeUnits.toList();
assert(codeUnitList[0] == 78);
// 转换为首字母大写。
assert('structured web apps'.toUpperCase() ==
'STRUCTURED WEB APPS');
// 转换为首字母小写。
assert('STRUCTURED WEB APPS'.toLowerCase() ==
'structured web apps');
// Trim a string.
assert(' hello '.trim() == 'hello');
// 检查字符串是否为空。
assert(''.isEmpty);
// 空格字符串不是空字符串。
assert(' '.isNotEmpty);
// 替换部分字符串
var greetingTemplate = 'Hello, NAME!';
var greeting =
greetingTemplate.replaceAll(RegExp('NAME'), 'Bob');
// greetingTemplate 没有改变。
assert(greeting != greetingTemplate);
// 要以代码方式生成字符串,可以使用 StringBuffer
// 在调用 toString() 之前, StringBuffer 不会生成新字符串对象。 writeAll() 的第二个参数为可选参数,用来指定分隔符, 本例中使用空格作为分隔符。
var sb = StringBuffer();
sb
..write('Use a StringBuffer for ')
..writeAll(['efficient', 'string', 'creation'], ' ')
..write('.');
// .. 是 级联运算符
var fullString = sb.toString();
assert(fullString ==
'Use a StringBuffer for efficient string creation.');
// RegExp类提供与JavaScript正则表达式相同的功能。 使用正则表达式可以对字符串进行高效搜索和模式匹配。
// 下面正则表达式用于匹配一个或多个数字。
var numbers = RegExp(r'\d+');
var allCharacters = 'llamas live fifteen to twenty years';
var someDigits = 'llamas live 15 to 20 years';
// contains() 能够使用正则表达式。
assert(!allCharacters.contains(numbers));
assert(someDigits.contains(numbers));
// 替换所有匹配对象为另一个字符串。
var exedOut = someDigits.replaceAll(numbers, 'XX');
assert(exedOut == 'llamas live XX to XX years');
// 你也可以直接使用RegExp类。 Match 类提供对正则表达式匹配对象的访问。
var numbers = RegExp(r'\d+');
var someDigits = 'llamas live 15 to 20 years';
// 检查正则表达式是否在字符串中匹配到对象。
assert(numbers.hasMatch(someDigits));
// 迭代所有匹配对象
for (var match in numbers.allMatches(someDigits)) {
print(match.group(0)); // 15, then 20
}
Boolean
Dart 使用 bool
类型表示布尔值。 Dart 只有字面量 true
and false
是布尔类型, 这两个对象都是编译时常量。
Dart 的类型安全意味着不能使用 if (nonbooleanValue)
或者 assert (nonbooleanValue)
。 而是应该像下面这样,明确的进行值检查:
// 检查空字符串。
var fullName = '';
assert(fullName.isEmpty);
// 检查 0 值。
var hitPoints = 0;
assert(hitPoints <= 0);
// 检查 null 值。
var unicorn;
assert(unicorn == null);
// 检查 NaN 。
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
List
在 Dart 中的 Array 就是 List 对象, 通常称之为 List 。
- Dart 集合 API
var list = [1, 2, 3];
- Lists 的下标索引从 0 开始,第一个元素的索引是 0。 list.length - 1 是最后一个元素的索引
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 1;
assert(list[1] == 1);
// 在 List 字面量之前添加 const 关键字,可以定义 List 类型的编译时常量
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 取消注释会引起错误。
// 使用 List 构造函数。
var vegetables = List();
// 或者仅使用一个 list 字面量。
var fruits = ['apples', 'oranges'];
// 添加一个元素到 list 对象。
fruits.add('kiwis');
// 添加多个元素到 list 对象。
fruits.addAll(['grapes', 'bananas']);
// 获取 list 长度。
assert(fruits.length == 5);
// 移除一个元素到 list 对象。
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 4);
// 移除多个元素到 list 对象。
fruits.clear();
assert(fruits.length == 0);
// 使用 sort() 方法排序一个 list
// 下面示例中使用 compareTo() 函数, 该函数在 Comparable 中定义, 并被 String 类实现
var fruits = ['bananas', 'apples', 'oranges'];
// 排序一个 list 。
fruits.sort((a, b) => a.compareTo(b));
assert(fruits[0] == 'apples');
// list 是参数化类型, 因此可以指定 list 应该包含的元素类型
// 这个 list 只能包含字符串类型。
var fruits = List<String>();
fruits.add('apples');
var fruit = fruits[0];
assert(fruit is String);
// 产生静态分析警告,num 不是字符串类型。
fruits.add(5); // BAD: Throws exception in checked mode.
Set
在 Dart 中 Set 是一个元素唯一且无需的集合。
- Dart 为 Set 提供了 Set 字面量和 Set 类型
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
,Dart 推断 halogens 类型为 Set 。如果尝试为它添加一个 错误类型的值,分析器或执行时会抛出错误;- 要创建一个空集,使用前面带有类型参数的 {} ,或者将 {} 赋值给 Set 类型的变量;
虽然 Set 类型 一直是 Dart 的核心部分, 但在 Dart2.2 中才引入了 Set 字面量 。
var names = <String>{};
// Set<String> names = {}; // 这样也是可以的。
// var names = {}; // 这样会创建一个 Map ,而不是 Set 这会创建一个类型为 Map<dynamic, dynamic> 的对象;
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
assert(ingredients.length == 3);
// 添加一个重复的元素是无效的。
ingredients.add('gold');
assert(ingredients.length == 3);
// 从 set 中移除一个元素。
ingredients.remove('gold');
assert(ingredients.length == 2);
// 在 Set 字面量前增加 const ,来创建一个编译时 Set 常量:
final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// 使用 contains() 和 containsAll() 来检查一个或多个元素是否在 set 中。
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
// 检查一个元素是否在该 set 中。
assert(ingredients.contains('titanium'));
// 检查多个元素是否在该 set 中。
assert(ingredients.containsAll(['titanium', 'xenon']));
// 交集是另外两个 set 中的公共元素组成的 set
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
// 创建两个 set 的交集。
var nobleGases = Set.from(['xenon', 'argon']);
var intersection = ingredients.intersection(nobleGases);
assert(intersection.length == 1);
assert(intersection.contains('xenon'));
Map
通常来说, Map 是用来关联 keys 和 values 的对象:
- keys 和 values 可以是任何类型的对象。
- 在一个 Map 对象中一个 key 只能出现一次。 但是 value 可以出现多次。
- Dart 中 Map 通过 Map 字面量 和 Map 类型来实现。
- 如果 Map 中不包含所要查找的 key,那么 Map 返回
null
- 使用
.length
函数获取当前 Map 中的 key-value 对数量 - 创建 Map 类型运行时常量,要在 Map 字面量前加上关键字 const
- 使用 remove() 方法从 map 中移除键值对
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
上面代码,Dart 会将 gifts 的类型推断为 Map<String, String>, nobleGases 的类型推断为 Map<int, String> 。 如果尝试在上面的 map 中添加错误类型,那么分析器或者运行时会引发错误
// map 是参数化类型;
// 可以指定一个 map 中 key 和 value 的类型。
var nobleGases = Map<int, String>();
//
var nobleGases = {54: 'xenon'};
// 使用 key 检索 value 。
assert(nobleGases[54] == 'xenon');
// 检查 map 是否包含 key 。
assert(nobleGases.containsKey(54));
// 移除一个 key 及其 value。
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));
var hawaiianBeaches = {
'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
'Big Island': ['Wailea Bay', 'Pololu Beach'],
'Kauai': ['Hanalei', 'Poipu']
};
// 获取的所有的 key 是一个无序集合
// (可迭代 list 对象)。
var keys = hawaiianBeaches.keys;
assert(keys.length == 3);
assert(Set.from(keys).contains('Oahu'));
// 获取的所有的 value 是一个无序集合
// (可迭代 list 对象).
var values = hawaiianBeaches.values;
assert(values.length == 3);
assert(values.any((v) => v.contains('Waikiki')));
assert(hawaiianBeaches.containsKey('Oahu'));
assert(!hawaiianBeaches.containsKey('Florida'));
公共集合方法
List, Set, 和 Map 共享许多集合中的常用功能。 其中一些常见功能由 Iterable 类定义, 这些函数由 List 和 Set 实现。
虽然Map没有实现 Iterable, 但可以使用 Map keys 和 values 属性从中获取 Iterable 对象。
var coffees = [];
var teas = ['green', 'black', 'chamomile', 'earl grey'];
assert(coffees.isEmpty);
assert(teas.isNotEmpty);
var teas = ['green', 'black', 'chamomile', 'earl grey'];
teas.forEach((tea) => print('I drink $tea'));
// 当在 map 对象上调用 `forEach() 方法时,函数必须带两个参数(key 和 value)
hawaiianBeaches.forEach((k, v) {
print('I want to visit $k and swim at $v');
// 我想去瓦胡岛并且在
// [Waikiki, Kailua, Waimanalo]游泳, 等等。
});
var teas = ['green', 'black', 'chamomile', 'earl grey'];
// map() 方法返回的对象是一个 懒求值(lazily evaluated)对象: 只有当访问对象里面的元素时,函数才会被调用。
var loudTeas = teas.map((tea) => tea.toUpperCase());
loudTeas.forEach(print);
// 使用 map().toList() 或 map().toSet() , 可以强制在每个项目上立即调用函数。
var loudTeas =
teas.map((tea) => tea.toUpperCase()).toList();
// 使用 Iterable 的 where() 方法可以获取所有匹配条件的元素。 使用 Iterable 的 any() 和 every() 方法可以检查部分或者所有元素是否匹配某个条件。
var teas = ['green', 'black', 'chamomile', 'earl grey'];
// 洋甘菊不含咖啡因。
bool isDecaffeinated(String teaName) =>
teaName == 'chamomile';
// 使用 where() 来查找元素,
// 这些元素在给定的函数中返回 true 。
var decaffeinatedTeas =
teas.where((tea) => isDecaffeinated(tea));
// 或者 teas.where(isDecaffeinated)
// 使用 any() 来检查集合中是否至少有一个元素满足条件。
assert(teas.any(isDecaffeinated));
// 使用 every() 来检查集合中是否所有元素满足条件。
assert(!teas.every(isDecaffeinated));
// 如果当且仅当该 key 不存在于 map 中,且要为这个 key 赋值, 可使用putIfAbsent()方法。 该方法需要一个方法返回这个 value
var teamAssignments = {};
teamAssignments.putIfAbsent(
'Catcher', () => pickToughestKid());
assert(teamAssignments['Catcher'] != null);
Rune
Rune 用来表示字符串中的 UTF-32 编码字符
Unicode 定义了一个全球的书写系统编码, 系统中使用的所有字母,数字和符号都对应唯一的数值编码。 由于 Dart 字符串是一系列 UTF-16 编码单元, 因此要在字符串中表示32位 Unicode 值需要特殊语法支持。
表示 Unicode 编码的常用方法是,
\uXXXX
, 这里 XXXX 是一个4位的16进制数。 例如,心形符号 (♥
) 是\u2665
。 对于特殊的非 4 个数值的情况, 把编码值放到大括号中即可。 例如,emoji 的笑脸 (�
) 是\u{1f600}
。
String 类有一些属性可以获得 rune 数据。 属性 codeUnitAt 和 codeUnit 返回16位编码数据。 属性 runes 获取字符串中的 Rune 。
下面是示例演示了 Rune 、 16-bit code units、 和 32-bit code points 之间的关系:
main() {
var clapping = '\u{1f44f}';
print(clapping);
print(clapping.codeUnits);
print(clapping.runes.toList());
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input));
}
// console
👏
[55357, 56399]
[128079]
♥ 😅 😎 👻 🖖 👍
谨慎使用 list 方式操作 Rune 。 这种方法很容易引发崩溃, 具体原因取决于特定的语言,字符集和操作;
Symbol
一个 Symbol 对象表示 Dart 程序中声明的运算符或者标识符。
- 你也许永远都不需要使用 Symbol ,但要按名称引用标识符的 API 时, Symbol 就非常有用了。
- 因为代码压缩后会改变标识符的名称,但不会改变标识符的符号。
- 通过字面量 Symbol ,也就是标识符前面添加一个
#
号,来获取标识符的 Symbol 。 - Symbol 字面量是编译时常量
Symbol obj = new Symbol('name');
Symbol obj = #radix
import 'dart:mirrors';
void main(){
Symbol lib = new Symbol("foo_lib");
String name_of_lib = MirrorSystem.getName(lib);
print(lib);
print(name_of_lib);
}
// log:
// Symbol("foo_lib")
// foo_lib
函数——一等对象
Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function
// 虽然在 Effective Dart 中推荐 公共API中声明类型, 但是省略了类型声明,函数依旧是可以正常使用的
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
// 箭头 语法:=> expr 语法是 { return expr; } 的简写
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
// 命名可选参数:使用 {param1, param2, …} 来指定命名参数
void enableFlags({bool bold, bool hidden}) {...}
// 使用 @required 注释表示参数是 required 性质的命名参数
const Scrollbar({Key key, @required Widget child})
// 位置可选参数:将参数放到 [] 中来标记参数是可选的
String say(String from, String msg, [String device]) {...}
// 默认参数值
void enableFlags({bool bold = false, bool hidden = false}) {...}
// 为位置参数设置默认值
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {...}
// list 或 map 可以作为默认值传递
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'second': 'cotton',
'third': 'leather'
}}) {...}
// main() 函数
// 任何应用都必须有一个顶级 main() 函数,作为应用服务的入口。 main() 函数返回值为空,参数为一个可选的 List<String>
void main(List<String> arguments){...}
// 匿名函数
// 有时候也被称为 lambda 或者 closure
([[Type] param1[, …]]) {
codeBlock;
};
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
// 如果函数只有一条语句, 可以使用箭头简写
list.forEach(
(item) => print('${list.indexOf(item)}: $item'));
测试函数是否相等:
void foo() {} // 顶级函数
class A {
static void bar() {} // 静态方法
void baz() {} // 示例方法
}
void main() {
var x;
// 比较顶级函数。
x = foo;
assert(foo == x);
// 比较静态方法。
x = A.bar;
assert(A.bar == x);
// 比较实例方法。
var v = A(); // A的1号实例
var w = A(); // A的2号实例
var y = w;
x = w.baz;
// 两个闭包引用的同一实例(2号),
// 所以它们相等。
assert(y.baz == x);
// 两个闭包引用的非同一个实例,
// 所以它们不相等。
assert(v.baz != w.baz);
}
词法作用域和词法闭包
词法作用域:
- Dart 是一门词法作用域的编程语言,就意味着变量的作用域是固定的, 简单说变量的作用域在编写代码的时候就已经确定了。 花括号内的是变量可见的作用域。
词法闭包:
- 闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外, 依然能够访问在它词法作用域内的变量
// makeAdder() 捕获了变量 addBy
/// 返回一个函数,返回的函数参数与 [addBy] 相加。
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);
}
运算符
熟悉常用运算符即可:
- 运算符
- 算数运算符
- 关系运算符
- 类型判定运算符(参考关键字
as
、is
、is!
) - 赋值运算符
- 复合赋值运算符((如 += )将算术运算符和赋值运算符组合在了一起)
=
、–=
、/=
、%=
、>>=
、^=
、+=
、*=
、~/=
、<<=
、&=
、|=
- 逻辑运算符:
!done && (col == 0 || col == 3)
- 按位和移位运算符
- 条件表达式
- 级联运算符(
..
):..
语法为 级联调用 (cascade)。 使用级联调用, 可以简化在一个对象上执行的多个操作。 - 其他运算符
对于有两个操作数的运算符,运算符的功能由左边的操作数决定。 例如, 如果有两个操作数 Vector 和 Point, aVector + aPoint 使用的是 Vector 中定义的 + 运算符。
算数运算符使用举例:
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // 结果是双浮点型
assert(5 ~/ 2 == 2); // 结果是整型
assert(5 % 2 == 1); // 余数
var a, b;
a = 0;
b = ++a; // a自加后赋值给b。
assert(a == b); // 1 == 1
a = 0;
b = a++; // a先赋值给b后,a自加。
assert(a != b); // 1 != 0
a = 0;
b = --a; // a自减后赋值给b。
assert(a == b); // -1 == -1
a = 0;
b = a--; // a先赋值给b后,a自减。
assert(a != b); // -1 != 0
关系运算符-判等:
- 要测试两个对象x和y是否表示相同的事物, 使用
==
运算符 - 在极少数情况下, 要确定两个对象是否完全相同,需要使用 identical() 函数
==
运算符的工作原理:- 如果 x 或 y 可以 null,都为 null 时返回 true ,其中一个为 null 时返回 false。
- 结果为函数
x.==(y)
的返回值,==
运算符执行的是第一个运算符的函数。 我们甚至可以重写很多运算符;
赋值运算符:
- 使用 = 为变量赋值。 使用 ??= 运算符时,只有当被赋值的变量为 null 时才会赋值给它
// 将值赋值给变量a
a = value;
// 如果b为空时,将变量赋值给b,否则,b的值保持不变。
b ??= value;
// 使用赋值和复合赋值运算符
var a = 2; // 使用 = 复制
a *= 3; // 复制并做乘法运算: a = a * 3
assert(a == 6);
按位和移位运算符:
final value = 0x22;
final bitmask = 0x0f;
assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
条件表达式:
// 如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值
condition ? expr1 : expr2
// 如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值
expr1 ?? expr2
级联运算符(..
):
- 级联运算符 (
..
) 可以实现对同一个对像进行一系列的操作 - 除了调用函数, 还可以访问同一对象上的字段属性。 这通常可以节省创建临时变量的步骤, 同时编写出更流畅的代码。
querySelector('#confirm') // 获取对象 获取的对象依次执行级联运算符后面的代码 代码执行后的返回值会被忽略
..text = 'Confirm' // 调用成员变量。
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
// 代码等价于
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
// 级联运算符可以嵌套
final addressBook = (AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
// 在返回对象的函数中谨慎使用级联操作符
var sb = StringBuffer();
sb.write('foo') // sb.write() 函数调用返回 void, 不能在 void 对象上创建级联操作
..write('bar'); // Error: 'void' 没哟定义 'write' 函数。
严格的来讲, “两个点” 的级联语法不是一个运算符。 它只是一个 Dart 的特殊语法。
其他运算符:
()
:函数调用[]
:获取List指定索引的值.
:对象成员获取,如foo.bar
?.
:带条件的对象成员获取,如foo?.bar
foo?.bar
selects propertybar
from expression foo unlessfoo
isnull
;
控制流程语句
参考相关关键字:if
、else
、for
、while
、do-while
、break
、continue
、switch
、case
、assert
,使用try-catch
和throw
也可以改变程序流程;
异常
Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行:
- 和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。
- Dart 提供了
Exception
和Error
类型, 以及一些子类型;当然也可以定义自己的异常类型。 - 此外 Dart 程序可以抛出任何
非 null 对象
, 不仅限Exception
和Error
对象。 - 抛出异常是一个表达式, 所以可以在 => 语句中使用
参考相关关键字:try
、catch
、rethrow
、on
、finally
- 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象;
- 不管是否抛出异常, finally 中的代码都会被执行;
- 如果 catch 没有匹配到异常, 异常会在 finally 执行完成后,再次被抛出;
- 任何匹配的 catch 执行完成后,再执行 finally
try {
} on OutOfLlamasException {
// 指定具体异常
} on Exception catch (e) {
// 指定异常
} catch (e) {
// 没有指定的类型
}finally {
// Always clean up, even if an exception is thrown.
}
try {
// ···
} on Exception catch (e) {
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
rethrow;
}
类
Dart 是一种基于类和 mixin 继承机制的面向对象的语言。 每个对象都是一个类的实例,所有的类都继承于 Object.
类的基本使用
var p = Point(2, 2);
p.y = 3;
num distance = p.distanceTo(Point(4, 4));
p?.y = 4;
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
// Dart 2 中 new是可选的
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
// 常量构造函数:在构造函数名之前加 const 关键字,来创建编译时常量时
var p = const ImmutablePoint(2, 2);
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 它们是同一个实例
// 这里有很多的 const 关键字。
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
// 保留第一个 const 关键字,其余的全部省略:在 常量上下文 中, 构造函数或者字面量前的 const 可以省略
// 仅有一个 const ,由该 const 建立常量上下文。
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
获取对象的类型
使用对象的 runtimeType
属性, 可以在运行时获取对象的类型, runtimeType
属性回返回一个 Type
对象。
a.runtimeType
print('The type of a is ${a.runtimeType}');
类的结构
class Point {
num x; // 声明示例变量 x,初始值为 null 。
num y; // 声明示例变量 y,初始值为 null 。
num z = 0; // 声明示例变量 z,初始值为 0 。
Point(num x, num y) {
// 还有更好的方式来实现下面代码。
this.x = x;
this.y = y;
}
}
class Point {
num x, y;
// 在构造函数体执行前,
// 语法糖已经设置了变量 x 和 y。
Point(this.x, this.y);
}
默认构造函数:
- 在没有声明构造函数的情况下, Dart 会提供一个默认的构造函数。 默认构造函数没有参数并会调用父类的无参构造函数。
- 子类不会继承父类的构造函数。 子类不声明构造函数,那么它就只有默认构造函数 (匿名,没有参数) ,构造函数不会被继承;
命名构造函数:
- 使用命名构造函数可为一个类实现多个构造函数;
- 构造函数不能够被继承, 这意味着父类的命名构造函数不会被子类继承。 如果希望使用父类中定义的命名构造函数创建子类, 就必须在子类中实现该构造函数;
class Point {
num x, y;
Point(this.x, this.y);
// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
}
调用父类非默认构造函数:
- 默认情况下,子类的构造函数会自动调用父类的默认构造函数(匿名,无参数)。 父类的构造函数在子类构造函数体开始执行的位置被调用。 如果提供了一个 initializer list (初始化参数列表), 则初始化参数列表在父类构造函数执行之前执行。
- initializer list (
初始化参数列表
) - superclass’s no-arg constructor (父类的无名构造函数)
- main class’s no-arg constructor (主类的无名构造函数)
- initializer list (
- 如果父类中没有匿名无参的构造函数, 则需要手工调用父类的其他构造函数。 在当前构造函数冒号 (
:
) 之后,函数体之前,声明调用父类构造函数。- 调用父类构造函数的参数无法访问 this
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson(data).
// super.fromJson(data)的参数data可以是一个表达式或者一个方法调用
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
// Prints:
// in Person
// in Employee
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
}
初始化列表:
- 除了调用超类构造函数之外, 还可以在构造函数体执行之前初始化实例变量
- 各参数的初始化用逗号分隔。
- 在开发期间, 可以使用 assert 来验证输入的初始化列表。
- 使用初始化列表可以很方便的设置 final 字段
// 在构造函数体执行之前,
// 通过初始列表设置实例变量。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
// 语法糖已经设置了变量 x 和 y。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
// 使用初始化列表可以很方便的设置 final 字段
class Point {
final num x;
final num y;
final num distanceFromOrigin;
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin);
}
其他构造函数:
- 重定向构造函数:有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的函数体为空, 构造函数的调用在冒号 (
:
) 之后。 - 常量构造函数:如果该类生成的对象是固定不变的, 那么就可以把这些对象定义为编译时常量。 为此,需要定义一个
const
构造函数, 并且声明所有实例变量为final
- 工厂构造函数:当执行构造函数并不总是创建这个类的一个新实例时,则使用
factory
关键字(工厂构造函数无法访问this
);
// 重定向构造函数
class Point {
num x, y;
// 类的主构造函数。
Point(this.x, this.y);
// 指向主构造函数
Point.alongXAxis(num x) : this(x, 0);
}
// 常量构造函数
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
// 工厂构造函数
class Logger {
final String name;
bool mute = false;
// 从命名的 _ 可以知,
// _cache 是私有属性。
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;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
抽象类:
- 使用
abstract
修饰符来定义 抽象类 — 抽象类不能实例化。 抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。
方法
- 对象的实例方法可以访问 this 和实例变量
- Getter 和 Setter,使用 get 和 set 关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。
- 最开始实现 Getter 和 Setter 也许是直接返回成员变量; 随着需求变化, Getter 和 Setter 可能需要进行计算处理而使用方法来实现;
- 抽象方法:只定义接口不进行实现,而是留给其他类去实现。 抽象方法只存在于 抽象类 中
- 调用抽象方法会导致运行时错误
abstract class Doer {
// 定义实例变量和方法 ...
void doSomething(); // 定义一个抽象方法。
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供方法实现,所以这里的方法就不是抽象方法了...
}
}
隐式接口:
- 每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。
- 一个类可以通过
implements
关键字来实现一个或者多个接口, 并实现每个接口要求的 API。如class Point implements Comparable, Location {...}
;
// person 类。 隐式接口里面包含了 greet() 方法声明。
class Person {
// 包含在接口里,但只在当前库中可见。
final _name;
// 不包含在接口里,因为这是一个构造函数。
Person(this._name);
// 包含在接口里。
String greet(String who) => 'Hello, $who. I am $_name.';
}
// person 接口的实现。
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
扩展类——继承:
- 使用
extends
关键字来创建子类, 使用super
关键字来引用父类
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重写类成员:
- 子类可以重写实例方法,getter 和 setter。 可以使用
@override
注解指出想要重写的成员
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
重写运算符:参考关键字 operator
noSuchMethod()
:
- 当代码尝试使用不存在的方法或实例变量时, 通过重写 noSuchMethod() 方法,来实现检测和应对处理
class A {
// 如果不重写 noSuchMethod,访问
// 不存在的实例变量时会导致 NoSuchMethodError 错误。
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
类变量和方法
参考相关关键字:static
枚举
参考相关关键字:enum
为类添加功能
参考相关关键字:mixin
泛型
在 API 文档中你会发现基础数组类型 List 的实际类型是List<E>
。 <…>
符号将 List 标记为 泛型 (或 参数化) 类型。 这种类型具有形式化的参数。 通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等
- 正确指定泛型类型可以提高代码质量。
- 使用泛型可以减少重复的代码。
// 一个用于缓存对象的接口
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
// 一个相同功能的字符串类型接口
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
// 泛型可以省去创建所有这些接口的麻烦
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
使用集合字面量:
- List , Set 和 Map 字面量也是可以参数化的。 参数化字面量和之前的字面量定义类似, 对于 List 或 Set 只需要在声明语句前加
<type>
前缀, 对于 Map 只需要在声明语句前加<keyType, valueType>
前缀 var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{};
使用泛型类型的构造函数:
- 在类名字后面使用尖括号(
<...>
)来指定泛型类型;Set<String>.from(names);
Map<int, View>();
运行时中的泛型集合:
- Dart 中泛型类型是 固化的,也就是说它们在运行时是携带着类型信息的。 例如, 在运行时检测集合的类型(Java中的泛型会被 擦除);
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
限制泛型类型:
- 可以使用 extends 实现参数类型的限制
class Foo<T extends SomeBaseClass> {...}
,如果Foo()
时不指定泛型,那么T
就是SomeBaseClass
;
使用泛型函数:
T first<T>(List<T> ts) {};
库和可见性
参考相关关键字:import
、library
、as
、show
、hide
、deferred as
实现库
- 如何组织库的源文件。
- 如何使用
export
命令。 - 何时使用
part
命令。 - 何时使用
library
命令。
异步支持
Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程;
- Future处理
- 声明异步函数
- 处理Stream
- 生成器:参考相关关键字
sync
和yield
Future处理:
- 使用
async
和await
;(使用try
,catch
, 和finally
来处理代码中使用 await 导致的错误) - 使用 Future API;
声明异步函数:
- 函数体被
async
标示符标记的函数,即是一个_异步函数_; - 将
async
关键字添加到函数使其返回Future; Future<String> lookUpVersion() async => '1.0.0';
注意,函数体不需要使用Future API。 如有必要, Dart 会创建 Future 对象。如果函数没有返回有效值, 需要设置其返回类型为
Future<void>
;
处理Stream:
- 从 Stream 中获取数据值的两种方式;
- 使用
async
和 一个 异步循环 (await for
) - 使用 Stream API;
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
上面 表达式 返回的值必须是 Stream 类型。 执行流程如下:
- 等待,直到流发出一个值。
- 执行 for 循环体,将变量设置为该发出的值
- 重复1和2,直到关闭流。
- 使用 break 或者 return 语句可以停止接收 stream 的数据, 这样就跳出了 for 循环, 并且从 stream 上取消注册
提示: 在使用 await for 前,确保代码清晰, 并且确实希望等待所有流的结果。 例如,通常不应该使用 await for 的UI事件侦听器, 因为UI框架会发送无穷无尽的事件流。
可调用类
通过实现类的 call() 方法, 能够让类像函数一样被调用
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c!';
}
main() {
var wf = new WannabeFunction();
var out = wf("Hi","there,","gang");
print('$out');
}
Isolates
大多数计算机中,甚至在移动平台上,都在使用多核CPU。 为了有效利用多核性能,开发者一般使用共享内存数据来保证多线程的正确执行。 然而, 多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。
所有 Dart 代码都在隔离区( isolates )内运行,而不是线程。 每个隔离区都有自己的内存堆,确保每个隔离区的状态都不会被其他隔离区访问
元数据
使用元数据可以提供有关代码的其他信息。 元数据注释以字符 @
开头, 后跟对编译时常量 (如 deprecated
) 的引用或对常量构造函数的调用
对于所有 Dart 代码有两种可用注解:
@deprecated
@override
可以自定义元数据注解
元数据可以在 library、 class、 typedef、 type parameter、 constructor、 factory、 function、 field、 parameter 或者 variable 声明之前使用,也可以在 import 或者 export 指令之前使用。
使用反射
可以在运行时获取元数据信息
注释
- 单行注释
- 多上注释
- 文档注释
文档注释以 ///
或者 /**
开始。 在连续行上使用 ///
与多行文档注释具有相同的效果
/// A domesticated South American camelid (Lama glama).
///
/// 自从西班牙时代以来,
/// 安第斯文化就将骆驼当做肉食类和运输类动物。
class Llama {
String name;
/// 喂养骆驼 [Food].
///
/// 典型的美洲驼每周吃一捆干草。
void feed(Food food) {
// ...
}
/// 使用 [activity] 训练骆驼
/// [timeLimit] 分钟。
void exercise(Activity activity, int timeLimit) {
// ...
}
}
解析 Dart 代码并生成 HTML 文档,可以使用 SDK 中的 documentation generation tool.
总结
不清晰的地方:
- 库、包的开发;
- 元数据编程;
- 异步Stream编程;
- 文档生成;
- 隔离区isolates的使用;
- 生成器
Dart语言概述 over!