本文的内容都来自对A Tour of the Dart Language的学习理解。
你可以使用DartPad来测试dart程序,注意需要翻墙。
一个基本的Dart程序
// Define a function.
printInteger(int aNumber) {
print('The number is $aNumber.'); // Print to console.
}
// This is where the app starts executing.
main() {
var number = 42; // Declare and initialize a variable.
printInteger(number); // Call a function.
}
相信不对上面代码中的语法特性作解释,有一定语言基础的人都能看懂。这里暂时不作解释。
一些重要的概念
- 一切都是对象(object), 每个对象都是一个类(class)的实例。所有对象都继承自Object。
- Dart是一个强类型的语言,但是类型声明是可选的,Dart可以推断出变量的类型。
- Dart支持泛型,形式上和Java是相似的,如List<int>, List<dynamic>。
- Dart支持一等函数,例如main(),函数也是对象。你可以在函数内再创建函数,即嵌套函数。
- Dart支持一等变量。
- Dart没有public,protect,private访问权限的关键字,如果一个标志符以_下划线开始,那它就是库私有的。
- Dart支持表达式和语句,语句可包括多个表达式。
- Dart支持报告两种类型的问题:warnings和errors。警告表示程序可能存在问题,但是不会中断程序的执行,Errors可以是编译时的,也可以是运行时的。运行时错误会导致一个exception异常。
关键字
需要注意的是,有上标的关键字,在一些特定场景下,可以作为标志符使用:
- 有上标为1的是上下文相关的关键字,它们仅在特定的地方才有意义,在其他地方都是合法的标志符。
- 有上标为2的是内建标志符,从JavaScript移植过来的,它们除了不可以作为类或者类型,以及作为import导入的前缀,在其他地方都是合法的标志符。
- 有上标为3的是Dart 1.0后新增的。你不可以在有async,async*,或者sync*标志的函数里使用await或者yield作为标识符。
- 剩下的所有关键字都是保留字,不可以作为标识符。
变量
可以使用var声明变量:
var name = 'Bob';
使用var声明的变量会被推断为一个String类型,但是你不可以再改变name的类型。如果想要一个变量的类型不受限制,可以使用object 或者 dynamic类型来声明:
dynamic name = 'Bob';
var,dynamic和object的区别请参考这篇文章:var, object, dynamic的区别以及使用
最后,显示的类型指定:
String name = 'Bob';
但是显示类型声明并不被规范推荐,推荐使用var。
默认值
任何未初始化的变量的默认值为null,包括数值类型的变量,因为数值也是一个对象。
Final 和 cosnt
final和Java一样,是不可变变量,只能被赋值一次。const代表的是编译时常量,在编译时即确定值的。一个变量被const修饰,它一定是final不可变的,但是反过来并不一样。和Java不同的是,final的实例变量必须要在构造函数体执行之前被初始化——你需要在变量声明的地方由构造函数参数初始化,或者用构造函数的初始化列表初始化。
final使用示例:
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
name = 'Alice'; // Error: a final variable can only be set once.
const示例:
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere
这里再说说const关键字,const不仅可以用来声明常量,你也可以用它来创建不可变的值:
var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`
在const声明的初始化的表达式中,你可以省略const,比如上面的baz。
你可以改变foo的值,既使它曾经有一个不可变的值,因为它并没有使用final或者const修饰:
foo = [1, 2, 3]; // Was const []
但是,你不能修改baz的值,因为它是const的:
baz = [42]; // Error: Constant variables can't be assigned a value.
内建类型
Dart支持以下内建类型:
- numbers
- strings
- booleans
- lists
- maps
- runes (用于表示string中的Unicode字符)
- symbols
以上这些内建类型的变量都可以使用字面量来初始化,例如'this is a string'是一个string字面量,true是一个boolean字面量。
因为在Dart中,一切都是对象,通常你也可以使用内建类型提供的构造函数来初始化变量。例如,你可以使用Map()来创建一个map。
Numbers
不超过64位的整数,在Dart VM中,值可以介于到
。
IEEE 754的双精度浮点数,
int 和 double都是num的子类。除了支持常规的+,-, /,*外,还支持abs(),ceil(),floor()等方法,更多的操作见dart:math库。
Strings
Dart string是一序列UTF-16代码单元。你可以使用单引号或双引号来创建字符串。
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";
Dart支持模版字符串,通过${expression}来指定,如果expression是一个变量标识符,则可以省略{}。
var s = 'string interpolation';
assert('Dart has $s, which is very handy.' ==
'Dart has string interpolation, ' +
'which is very handy.');
assert('That deserves all caps. ' +
'${s.toUpperCase()} is very handy!' ==
'That deserves all caps. ' +
'STRING INTERPOLATION is very handy!');
除了可以像Java一样使用+操作符拼接字符串外,dart还支持直接拼接相邻字符串。
var s1 = 'String '
'concatenation'
" works even over line breaks.";
assert(s1 ==
'String concatenation works even over '
'line breaks.');
另外,使用三引号创建多行文本:
var s1 = '''
You can create
multi-line strings like this one.
''';
var s2 = """This is also a
multi-line string.""";
Booleans
Dart同样使用bool来表示boolean值,只有true和false两个值。
List
初始化一个List:
var list = [1, 2, 3];
dart的解析器会推断出list的类型是List<int>,所以如果在运行时插入一个非int型的值,会异常。
Lists基于0开始定位元素位置。list有一个length的成员变量表示List的size。
var list = [1, 2, 3]; //字面量初始化方式
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 1;
assert(list[1] == 1);
Maps
初始化一个map:
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
同样,分析器会推断出gitfs的类型为Map<String, String> ,nobleGases的类型是Map<int, String>。
你也可以使用构造函数:
var gifts = Map();
gifts['first'] = 'partridge'; //增加一个key-value
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
创建一个map常量:
final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
Runes(符文)
在Dart中,runes(符文)是string中的UTF-32代码点。因为dart中string是UTF-16的代码单元序列,所以要表示32位的Unicode就必须要使用一种特殊的语法。
通常,使用\uXXXX的格式表示一个Unicode,XXXX是4位16进制值,例如♥的unicode值是\u2665,对于表示超过或少于4个16进制表示的Unicode符号,我们把值放在花括号中,你如,?的unicode值表示为\u{1f600}。
String类有几个属性,可以用于从string对象中抽取rune信息。例如codeUnitAt和codeUint返回16位的代码单元。runes属性用于获取string的runes。
下面的例子展示了runes,16位的代码单元和32位的代码点之间的关系:
main() {
var clapping = '\u{1f44f}';
print(clapping); //?
print(clapping.codeUnits); //[55357, 56399]
print(clapping.runes.toList()); //[128079]
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input)); //♥ ? ? ? ? ?
}
Symbols
Symbol表示Dart程序中的一个操作符或者标识符,一般用不上。
函数
函数也是对象,所以函数也有自己的类型,Function。所以你可以把函数赋值给变量或者作为参数传递。
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
你可以省略函数的类型声明,但是并不推荐:
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
如果函数体仅仅只包括一个表达式,那么可以采用箭头函数的写法:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
=> expr 语法是{return expr}的简写形式,是等效的。
一个函数包括两种类型的参数:必要的和可选的。必要参数列在函数参数列表的第一个,后面跟着任意个可选参数。命名的可选参数也可以被@required注解。
可选参数
可选参数可以是位置参数或者命名参数,但是不能同时都是。
可选命名参数
使用{param1, param2,...}的形式定义函数,在{}里的参数都是可选参数:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}
通过指定paramName: value来调用函数,例如:
enableFlags(bold: true, hidden: false);
当一个命名可选参数被@required注解,那么它也不可以被省略:
const Scrollbar({Key key, @required Widget child})
当ScrollBar被构造的时候,如果你没有传递child参数,分析器会提示你。
可选位置参数
使用[]将一组参数包装起来,[]内的参数就是可选位置参数。
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');
参数的默认值
你可以使用=来为可选参数定义默认值。默认值必须是编译时常量,如果没有显示指定默认值,系统会指定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);
为位置参数设置默认参数形式也是一样的。
main()函数
每个app都有一个main()函数,作为程序的入口点。main()函数返回void并有一个可选的List<String>参数。
下面是一个打印命令行参数的app示例:
// 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-library库来定义和解析命令行参数。
函数作为一等对象
将函数作为参数传递给另一个函数:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);
你也可以将函数赋值给一个变量:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
匿名函数
匿名函数形式:
([[Type] param1[, …]]) {
codeBlock;
};
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
(item) {
print('${list.indexOf(item)}: $item');
}
既是一个匿名函数。如果匿名函数的函数体只有一条语句,你也可以改写为箭头函数:
list.forEach(
(item) => print('${list.indexOf(item)}: $item'));
词法作用域
和Java一样,变量作用域基于代码块,很好理解。
词法闭包
闭包是一个可以访问定义它所在的作用域的变量的函数。看下面的例子:
/// 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);
}
在上面的例子中,makeAdder()函数捕获了变量addBy,无论makeAdder返回的函数何时执行,它总是能访问addBy。
函数返回值
所有函数都有返回值。如果函数未指定返回值,函数最终将返回null。
foo() {}
assert(foo() == null);