Dart 语言集合了 Java、JavaScript、C、TypeScript 等语言的优势,可以说是集百家之长的一门编程语言,可以用与 web 编程、服务端编程以及 app 编程(Flutter),目前主要用于 Flutter 开发。以下是几点重要的概念:
- Dart 是强类型语言,但是又兼有 JS 的动态性
- 类型声明是可选的,可通过类型推导而来
- 语句以分号结尾,不能省略
1、基本语法
1.1、变量
- var 关键字
可以用var 声明一个变量,一旦赋值,类型就不能再改变,但值可以变化
var str = 'abc';
str = 'def'; // ok
str = 123; // error
- 变量声明时,若未指定初始值,则默认值都是null,而不是JS 中的 undefined
- 类型可选,类型推断的存在使得声明变量时,变量类型时可选的
String str = 'abc' // 与 var str='abc' 等价
- dynamic和Object
1、Object 是dart所有对象的根基类,也就是说所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象.
2、dynamic与var一样都是关键词,声明的变量可以赋值任意对象. 而dynamic与Object相同之处在于,他们声明的变量可以在后期改变赋值类型.
dynamic t;
Object x;
t = "hi world";
x = 'Hello Object';
//下面代码没有问题
t = 1000;
x = 1000;
dynamic与Object不同的是,dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错.
- final & const 关键字
1、声明常量可用 final 或者 const , const 属于编译时常量,而 final 属于运行时常量;
2、const 还可以用来创建不变的值
var foo = const [1]; // 表示这个数组的值不能变化,而不是数组的地址
foo[0]=1; // error
- num 数字类型
包含两个子类型 int & double。以下是字符串类型和数字类型的互转
// 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
可用单双三引号来创建string,字符串中支持变量插值
String a='123';
var b='123'; // 等价于 var b='$a';
- bool
Dart 中只有 true 对象才被认为是 true。 所有其他的值都是 flase。这点和 JavaScript 不一样, 像 1、 "aString"、 以及 someObject 等值都被认为是 false。
var name = 'Bob';
if (name) {
// Prints in JavaScript, not in Dart.
print('You have a name!');
}
- List
就是JS中的 Array,在 list 字面量之前添加 const 关键字,可以 定义一个不变的 list 对象(编译时常量)
var list = [1, 2, 3];
assert(list.length == 3);
var constantList = const [1, 2, 3];
constantList[1] = 1; // causes an error.
- Map
键和值可以是任何类型的对象,比JS中的Object强大一点儿(key不能为对象),
如果所查找的键不存在,则返回 null,而不是 undefined
var gifts = {
// Keys Values
'first' : 'partridge',
'second': 'turtledoves',
'fifth' : 'golden rings'
};
var gifts = new Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
- Symbol & Runes 暂时略过
- enum 枚举
enum Color {
red,
green,
blue
}
枚举类型中的每个值都有一个 index getter 函数, 该函数返回该值在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的位置为 0, 第二个为 1.
1.2、函数 Function
- 声明
函数如果没有返回值,则一律返回 null
bool isNoble(int atomicNumber) {
...
}
- 箭头函数
1、对于只有一个表达式的方法,你可以选择 使用缩写语法来定义。
2、箭头函数即使只有一个参数,也不能省略括号
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
[1,2,3].forEach(a=>print(a)); // err
[1,2,3].forEach((a)=>print(a)); // ok
- 可选参数(必须放在必须参数后面)
1、可选命名参数
定义方法的时候,使用 {param1, param2, …} 的形式来指定命名参数
enableFlags({bool bold, bool hidden}) {
// ...
}
调用方法的时候,你可以使用这种形式 paramName: value 来指定命名参数
enableFlags(bold: true, hidden: false);
2、可选位置参数
把一些方法的参数放到 [] 中就变成可选 位置参数了:
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
// 不使用可选参数调用
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
// 使用可选参数
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
- 默认参数
函数可选参数可以指定默认值,必须参数不能指定
void enableFlags({bool bold = false, bool hidden = false}) {
// ...
}
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');
- main 入口函数
每个应用都需要有个顶级的 main() 入口方法才能执行。 main() 方法的返回值为 void 并且有个可选的 List<String> 参数。
void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}
- 匿名函数
就是无函数名的函数,一般用于迭代器
var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
list.forEach((i) {
print(list.indexOf(i).toString() + ': ' + i);
});
- 如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断
1.3、操作符
只记录与JS语言不一致的操作符
- 算术操作符
除法结果取整: ~/
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2);
- 相等操作符
无三等号操作符,双等号操作符比较的是内容是否相同,而不是内存地址,如果要比较内存地址是否相同,请用 identical
void main() {
const c = [1,2];
var a = c;
var b = c;
print(a == b);
print(identical(a,b));
}
- 类型判定操作符
as:类型转换;is 相当于 instanceof;is!
if (emp is Person) { // Type check
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
- 赋值操作符
使用 = 操作符来赋值。 但是还有一个 ??= 操作符用来指定 值为 null 的变量的值
a = value; // 给 a 变量赋值
b ??= value; // 如果 b 是 null,则赋值给 b;
// 如果不是 null,则 b 的值保持不变
- 条件表达式
除了? : ,多了一个双问号 ??,相当于JS里短路运算符 ||
expr1 ?? expr2 // expr1 为 null 时执行 expr2
- 级联操作符
级联操作符 (..) 可以在同一个对象上 连续调用多个函数以及访问成员变量。 使用级联操作符可以避免创建 临时变量, 并且写出来的代码看起来 更加流畅,相当于JS中的链式调用:
querySelector('#button') // Get an object.
..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
- 对象访问操作符
新增了一个 .?
左边的操作对象不能为 null,例如 foo?.bar 如果 foo 为 null 则返回 null,否则返回 bar 成员
1.4、类
- 构造函数
与JS的 constructor 不同,把类中同名函数作为构造函数
class Point {
num x;
num y;
Point(this.x, this.y);
}
- 命名构造函数
使用命名构造函数可以为一个类实现多个构造函数
class Point {
num x;
num y;
Point(this.x, this.y);
// Named constructor 只有当名字冲突的时候才使用 this。否则的话, Dart 代码风格样式推荐忽略 this。
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
注意:构造函数不能继承,所以超类的命名构造函数 也不会被继承。如果你希望 子类也有超类一样的命名构造函数, 你必须在子类中自己实现该构造函数。
- 调用超类构造函数
1、默认情况下,子类的构造函数会自动调用超类的 无名无参数的默认构造函数,
2、若提供了初始化参数列表,则初始化参数列表在超类构造函数执行之前执行。
3、构造函数执行顺序:a、initializer list(初始化参数列表)b、superclass’s no-arg constructor(超类的无名构造函数)c、main class’s no-arg constructor(主类的无名构造函数)
4、如果超类没有无命名无参数构造函数, 则你需要手工的调用超类的其他构造函数。 在构造函数参数后使用冒号 (:) 可以调用 超类构造函数。
import 'dart:math';
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);
}
- 重定向构造函数
1、有时候一个构造函数会调动类中的其他构造函数。
2、一个重定向构造函数是没有代码的,在构造函数声明后,使用 冒号调用其他构造函数。
class Point {
num x;
num y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(num x) : this(x, 0);
}
- 类的继承(单继承)
使用 extends 定义子类, supper 引用 超类
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ...
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ...
}
可以使用 @override 注解来 表明你的函数是想覆写超类的一个函数:
class A {
@override
void noSuchMethod(Invocation mirror) {
// ...
}
}
- 扩展类
可以通过 with 关键字来给类实现 mixin 功能,扩展类的特点:没有构造函数, 不能调用 super
class Musician extends Performer with Musical {
// ...
}
class 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');
}
}
}
- 类的实例变量、方法以及静态变量和方法与JS类一致
1.5、泛型
1、使用泛型的原因是减少重复的代码。 泛型可以在多种类型之间定义同一个实现, 同时还可以继续使用检查模式和静态分析工具提供的代码分析功能。
var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
2、当使用泛型类型的时候,你 可能想限制泛型的具体类型。 使用 extends 可以实现这个功能:
// T must be SomeBaseClass or one of its descendants.
class Foo<T extends SomeBaseClass> {...}
3、泛型函数
T first<T>(List<T> ts) {
// ...Do some initial work or error checking, then...
T tmp ?= ts[0];
// ...Do some additional checking or processing...
return tmp;
}
1.6、其他
- 模块系统
1、引用库
使用 import 来指定一个库如何使用另外 一个库。import 必须参数为库 的 URI。 对于内置的库,URI 使用特殊的 dart: scheme。 对于其他的库,你可以使用文件系统路径或者 package: scheme。 package: scheme 指定的库通过包管理器来提供, 例如 pub 工具。
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';
2、如果你导入的两个库具有冲突的标识符, 则你可以使用库的前缀来区分。 例如,如果 library1 和 library2 都有一个名字为 Element 的类, 你可以这样使用:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element(); // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib
3、导入库的一部分
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
4、库的延迟加载,使用 deferred as 来 导入,使用库标识符调用 loadLibrary() 函数来调用
import 'package:deferred/hello.dart' deferred as hello;
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
- 注解
有三个注解所有的 Dart 代码都可以使用: @deprecated、 @override、 和 @proxy - 异步支持
1、Future
Future.delayed(new Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print(data);
}).catchError((e){
//执行失败会走到这里
print(e);
}).whenComplete((){
//无论成功或失败都会走到这里
});
有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作
Future.wait([
// 2秒后返回结果
Future.delayed(new Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回结果
Future.delayed(new Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
2、Stream
Stream 也是用于接收异步事件数据,和Future 不同的是,它可以接收多个异步操作的结果
Stream.fromFutures([
// 1秒后返回结果
Future.delayed(new Duration(seconds: 1), () {
return "hello 1";
}),
// 抛出一个异常
Future.delayed(new Duration(seconds: 2),(){
throw AssertionError("Error");
}),
// 3秒后返回结果
Future.delayed(new Duration(seconds: 3), () {
return "hello 3";
})
]).listen((data){
print(data);
}, onError: (e){
print(e.message);
},onDone: (){
});
// 依次输出
hello 1
Error
hello 3
2、最佳实践
2.1、字符串
- 当字符串较长,一行放不下时,不使用 + 来链接字符串:
raiseAlarm(
'ERROR: Parts of the spaceship are on fire. Other '
'parts are overrun by martians. Unclear which are which.');
- 使用插值的形式来组合字符串和值
'Hello, $name! You are ${year - birth} years old.';
2.2、集合
- 尽可能的使用集合字面量来定义集合
var points = [];
var addresses = {};
// bad
var points = new List();
var addresses = new Map();
- 不要使用 .length 来判断集合是否为空
尽管对象和数组都有length属性
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');
// bad
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');
- 使用高阶(higher-order)函数来转换集合数据
比JS多了个 where 用于筛选
var aquaticNames = animals
.where((animal) => animal.isAquatic)
.map((animal) => animal.name);
2.3、变量
- 不用显示的初始化一个变量的值为 null
- 避免保存可以计算的结果,而是用 getter
class Circle {
num radius;
num get area => math.PI * radius * radius;
num get circumference => math.PI * 2.0 * radius;
Circle(this.radius);
}
- 考虑 省略局部变量的类型
Map<int, List<Person>> groupByZip(Iterable<Person> people) {
var peopleByZip = <int, List<Person>>{};
for (var person in people) {
peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
peopleByZip[person.zip].add(person);
}
return peopleByZip;
}
// bad
Map<int, List<Person>> groupByZip(Iterable<Person> people) {
Map<int, List<Person>> peopleByZip = <int, List<Person>>{};
for (Person person in people) {
peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
peopleByZip[person.zip].add(person);
}
return peopleByZip;
}
2.4、成员
- 使用 final 关键字来限定只读属性
只能读取, 而不能修改其值,最简单的做法就是使用 final 关键字来标记这个变量
class Box {
final contents = [];
}
- 用 => 来实现只有一个单一返回语句的函数
bool ready(num time) => minTime == null || minTime <= time;
- 不要使用 this. ,除非遇到了变量冲突的情况
class Box {
var value;
void clear() {
update(null);
}
void update(value) {
this.value = value;
}
}
// bad
class Box {
var value;
void clear() {
this.update(null);
}
void update(value) {
this.value = value;
}
}
- 要 尽可能的在定义变量的时候初始化其值
2.5、构造函数
- 要尽可能的使用初始化形式
class Point {
num x, y;
Point(this.x, this.y);
}
// bad
class Point {
num x, y;
Point(num x, num y) {
this.x = x;
this.y = y;
}
}
- 不要在初始化形式上定义类型
// bad
class Point {
int x, y;
Point(int this.x, int this.y);
}
- 要用 ; 来替代空函数体的构造函数 {}
class Point {
int x, y;
Point(this.x, this.y);
}
// bad
class Point {
int x, y;
Point(this.x, this.y) {}
}
- 要 把 super() 调用放到构造函数初始化列表之后调用
View(Style style, List children)
: _children = children,
super(style) {
// bad
View(Style style, List children)
: super(style),
_children = children {