重要的概念
- 任何你可以放在一个变量中的都是一个object,并且每个object都是一个class的实例,甚至于数字,functions,null都是对象,所有的对象继承自Object类。
- 即使Dart是强类型的,但是类型说明是可选的,因为Dart可以推断类型;当你要显式说明没有指定任何类型的时候,可以用dynamic
- Dart也支持泛型,比如List<int>(整数列表)或者List<dynamic>(一个任意类型的对象列表)
- Dart支持顶层函数(如main()),以及绑定到一个类或者对象的方法(静态方法或者实例方法);
- 同样的,Dart支持顶层变量;
- 不像Java,Dart中没有public,private,protected这些关键字,如果一个标识符以“_”(下划线)符号开头,则对其库来说是私有的。
- 标识符可以以字母或者下划线开头,后加字母或者数字组合
- Dart有表达式(有运行时值)和语句(没有运行时值), 如condition ? exp1:exp2,与if else 语句相比,前则有值,后者没有值。一个语句经常包含一个或者多个表达式,但是一个表达式不能直接包含一个语句。
- Dart工具可以报告两种问题:errors和warnings。
变量
var name = 'Bob';
dynamic name = 'Bob';//后面可以改变类型
String name = 'Bob';
默认值
没有初始化的变量有一个初始值null,即使是数字类型也一样,因为数字类型也是一个对象。
int lineCount;
assert(lineCount == null);
final 和 const
如果你有一个变量不会改变,应该使用final或者const。
一个const变量是一个编译时常量,而一个final 顶层或者类变量当第一次使用的时候初始化。
注意:实例变量可以是final,但不是const。final型实例变量必须在构造函数体执行之前(变量声明的时候)就初始化。
在你需要一个编译时常量的时候可以使用const,如果const常量在Class层级,则用static const,在声明的地方将变量设置为编译时常量,比如一个数字,一个字符串,一个const常量,或者一系列常量运算 的结果。
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere
Const关键字不仅可以用于声明常量,还可以用它创建常量值,以及声明创建常量值的构造器,任何变量可以有一个常量。
var foo = const []; //你后面可以修改foo,如foo = [1, 2, 3];
final bar = const [];
const baz = []; // Equivalent to `const []`,后面不能修改baz
内建类型
numbers
有两种形式,int(64位,依赖于平台,在Dart VM中,为64位)和double(64位)
strings
一个Dart String是一系列UTF-16代码单元,可以使用单引号和双引号。
可以使用${expression}在字符串中表示动态内容,大括号可以省略,对于对象,会使用其toString()方法;
可以用三个引号创建多行文本:
var s1 = '''
You can create
multi-line strings like this one.
''';
var s2 = """This is also a
multi-line string.""";
可以用一个r创建一个raw string,比如Unicode字符串
var s = r'In a raw string, not even \n gets special treatment.';
booleans
为了表示布尔值,Dart有一个叫做bool的类型。
list/arrays
在Dart中,arrays是List对象,这里就直接叫做lists了。
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 1;
assert(list[1] == 1);
为了创建一个编译时常量的list,在list 字面量前加const,如:
var constantList = const [1, 2, 3];
maps
键值对,不用多说,上代码:
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';
//添加一个新的键值对
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair
//取值
var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');
//如果不存在,会返回null
var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);
//编译时常量
final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
// constantMap[2] = 'Helium'; // Uncommenting this causes an error.
runes(用于在字符串中标识Unicode字符)
Dart中, runes是字符串的UTF-32代码点。
由于Dart使用的是UTF-16,因此在字符串中表示32位字符,需要特殊的语法。
一般表示方式是\uXXXX,XXXX是一个4位16禁止数
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));
}
symbols
一个Symbol对象表示一个Dart程序中声明的运算符或者标识符。你可能永远都不会使用这个。不多说。
Function
Dart是一个真正面向对象的语言,即使是function都有一个叫做Function的对象。也就是说,Function可以赋值给一个变量,或者作为参数传递给其他functions,你也可以像调用函数一样调用Dart实例,如Callable Classes。(后面又说,暂时忽略即可)
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
//如果函数只是包含了一个简单的表达式,可以用速记方法表示
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
=> expr 是 { return expr; }的速写方式。值得注意的是,这里只可以用表达式的速写,而不可以用语句的速写,比如不可以使用if语句。
一个function有两种类型的参数,required和optional,必须的参数放在最前面,可选参数放在后面。命名的可选参数也可以用@required标记。
可选参数可以是位置性的,也可以是命名的,但是不能包含这两种情况。
可选命名参数
//定义
void enableFlags({bool bold, bool hidden}) {...}
enableFlags(bold: true, hidden: false);
Flutter中创建实例的参数很多,因此主要使用可选命名参数这种方式,便于阅读。
你也可以用@required注解表示这是个必须的参数,如:
const Scrollbar({Key key, @required Widget child})
Required 是在meta 包下面定义的,因此你需要import package:meta/meta.dart 或者,import那些输出meta包的包,如Flutter下的package:flutter/material.dart.
可选位置参数
用[] 包裹着的参数:
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');
默认参数值
你的function可以用=为可选位置参数或者可选命名参数定义默认值,而且必须使用编译时常量值,如果没有提供默认值,那么默认值为null。
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold will be true; hidden will be false.
enableFlags(bold: true);
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');
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');
}
main()
每一个APP都必须有一个main()方法,作为app的入口点。main()方法返回void,可以有一个List<String> 作为参数。
void main() {
querySelector('#sample_text_id')
..text = 'Click me!'
..onClick.listen(reverseText);
}
这种..的语法叫做级联。通过级联,你可以对单个对象的成员执行多个操作。
// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}
你也可以用args库,来定义和解析命令行参数。
作为第一类对象的functions
你可以将一个function作为参数传递给另一个function:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);
也可以将一个function赋值给一个变量:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
匿名函数
你可以创建一个没有命名的函数,如匿名函数,lambda,闭包等。
下面的代码块就包含了一个函数体:
([[Type] param1[, …]]) {
codeBlock;
};
如下面这个方法,使用了没有指定类型的参数:
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
如果函数只包含一个语句,可以用=> 箭头速写方式。
list.forEach(
(item) => print('${list.indexOf(item)}: $item'));
嵌套函数变量作用域问题
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}
nestedFunction()可以使用每一个层级的变量。
闭包
参考JavaScript中的闭包
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
返回值
所有function都会返回一个值,如果没有指定,则返回null:
foo() {}
assert(foo() == null);
操作符
基本的就不说了,和其他语言的大同小异,如Java,JavaScript等。
不同的是:
?? 代表 if null
?. 表示 如果不为null才执行(参考kotlin)
~/ 代表 整除(返回值为int)
/ 返回值 为double
as 用来类型转换(和kotlin一样)
is 判断是否属于否个类型
..(级联操作符)
级联操作符允许你在相同的对象上执行一系列操作。除了函数调用,你还可以通过级联访问同一个对象上的多个字段(field)。
querySelector('#confirm') // Get an object.
..text = 'Confirm' // Use its members.
..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();
在返回实际对象的function上运用级联要非常小心,
var sb = StringBuffer();
sb.write('foo')
..write('bar'); // Error: method 'write' isn't defined for 'void'.
Sb.write()返回void,你不能在void上构造级联。
控制流/异常
和其他语言大同小异,不多说。
Class
创建对象
var p1 = Point(2, 2);
var p2 = 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)); // They are the same instance!
子类不从父类继承构造函数,如果子类没有声明构造函数,则是默认的午餐构造函数。
获取一个对象的类型
可以使用runtimeType属性获取一个对象的类型,这个属性返回一个Type类型的对象。
print('The type of a is ${a.runtimeType}');
命名构造函数
class Point {
num x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
调用一个非默认的超类构造函数
默认情况下,一个子类的构造函数调用超类的没有命名的无参构造函数。超类的构造函数在构造函数体的开始被调用,如果还是用了初始化列表,则初始化列表先于超类被调用。
执行顺序如下:
- 初始化列表
- 超类的无参构造函数
- main class的无参构造函数
如果超类没有无命名的无参构造函数,你必须手动调用它的一个构造函数:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
初始化列表
除了调用超类构造函数之外,还可以在构造函数体执行之前初始化实例变量。
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
初始化程序的右边部分无法访问this。
开发过程中也可以使用assert进行输入验证:
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
重定向构造函数
class Point {
num x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(num x) : this(x, 0);
}
常量构造函数
如果你的类提供用不改变的对象,那么你可以将那些对象设置为编译时常量。为了实现这种效果,你需要定义个常量构造函数,并且确认所有的实例变量都是final类型的。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
常量构造函数也不总是创建常量。
工厂构造函数
借助factory关键字,你不用每次创建对象的时候都创建一个新的对象,而可以从缓存中返回,或者返回一个子类型的对象。
下面是一个从缓存中返回对象的例子:
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
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);
}
}
值得注意的是工厂构造函数无法访问this。
调用工厂构造方法和调用其他构造方法的方式是一样的:
var logger = Logger('UI');
logger.log('Button clicked');
Method/方法
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);
}
抽象方法
抽象方法仅存在于抽象类中;
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
抽象类
抽象类是不可以实例化的。如果你想要你的抽象类看起来是实例化的,可以定义一个工厂构造函数。
抽象类通常都有抽象方法。
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
隐式接口
每个类都隐式定义一个接口,该接口包含该类的所有实例成员,以及其实现的任何接口;如果你想要在不继承B类实现的情况下创建支持B类API的A类,则A类应该实现B接口。
类可以通过 implements 实现一个或者多个接口:
// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
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 {...}
拓展一个类
使用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() {...}
// ···
}
重写操作符
你可以重写操作符,比如你创建了一个Vector类,可以重写+操作符进行向量的加法:
!=不可以重写,因为这只是一个语法糖。
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));
}
如果你重写 ==那么你也得重写hashCode
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}');
}
}
枚举类型
enum Color { red, green, blue }
每个枚举类型中的值都有一个index getter,
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
获取枚举中所有值的列表:
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
用在switch流程中:
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
向class中添加特征:mixins
Mixins是一种在多个类层次中重用类代码的方式。
可以用with 关键字使用mixins:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
为了实现一个mixin,创建一个继承Object的类,不要声明构造函数,不要调用super,如:
abstract 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');
}
}
}
Class 变量和方法
和Java中的静态方法一样,用static表示:
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);
}
考虑用顶层函数替代静态方法。
泛型
和Java一样,也都是List<E>形式的。
使用集合字面量
List和map字面量都可以泛化:
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'
};
在构造函数中使用泛型
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set<String>.from(names);
var views = Map<int, View>();
泛型集合和它们包含的类型
与Java不同,Dart中的泛型在运行时是可以携带类型信息的。
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
限定泛型
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
//使用子类作为泛型参数
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
//不指定泛型也可以
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
泛型方法
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;
}
Librarys 和它的可见性
可见性: _ 表示仅仅对该Library可见。
每一个Dart App都是一个Library。
import 'dart:html';
import 'package:test/test.dart';
import 'package:lib1/lib1.dart';
//指定别名
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
Element element1 = Element();
// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
库的懒加载
懒加载(延迟加载)允许app按需加载一个库。
下面是用到懒加载的一些场景:
为了减少一个app的初始化启动时间
进行A/B测试
加载很少被使用的库
使用方式:必须首先用deferred as
import 'package:greetings/hello.dart' deferred as hello;
要使用的时候,调用库的loadLibrary()方法:
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
以上代码中,await表示等待hello库加载完成才继续执行。
你可以多次调用一个库的loadLibrary()方法,但是它只会被加载一次。
当你使用库的懒加载的时候,需要注意下面这些:
- 一个懒加载库中的常量不是导入文件中的常量,在库加载之前,这些常量都不存在;
- 你不能在导入文件中使用懒加载库中的类型,但是,你可以考虑将接口类型移动到由懒加载库和导入文件导入的库。
- Dart隐式的将loadLibrary()插入到使用deferred as namespace 定义的命名空间中,loadLibray()返回Future类型。
注意:即使是在loadLibrary()执行之前,Dart VM也允许访问懒加载库中的成员,这个特征在后面可能会变更,应该避免使用。
异步支持
Dart中很多函数都返回Future和Stream对象。 这些函数都是异步的。
async/await 支持你在Dart中进行异步编程。
当你使用await的时候,必须在方法外面用async包裹。
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
}
在异步方法中,你可以多次使用await
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
声明异步函数
Future<String> lookUpVersion() async => '1.0.0';
如果不返回一个有用的值,可以用Future<void>
处理Streams
可以从Stream中获取值。
你需要用async和一个异步循环(await for)
注意:在使用await for之前,确信你知道要一直等待循环获取到所有结果。比如在处理UI事件的时候,就不应该使用这个,因为UI事件是无尽的。
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
你也可以用break或者return终止循环。
生成器
当你需要laily生成一系列值,你可以使用生成器。
目前有两种生成器:
同步生成器: 返回Iterable对象
异步生成器: 返回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*提升性能:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
网友评论