【翻译】dart语言预览

1,130 阅读24分钟

官方文档

一个基本的程序

// 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.
}
  • //Define a function.

    单行注释块.Dart还支持多行注释块/* */

  • int

    一个类型,其他常见的list,String,bool

  • 42

    常量作为一个数字

  • print()

    一个便利的方法显示输出

  • '....'("....")

    字符串文字

  • $variableName(or $expresss)

    接受常量或者表达式,在字符串中包含变量或者表达式

  • main()

    main()是必须的,特殊的,顶级的函数,作为程序执行的入口

  • var

    一个方法声明变量没有指定他的类型

备注:这个网站代码遵循着 dart风格指南(Dart style guide)

重要的概念

当你学习dart语言,记住这些事实和概念

  • 所有可以放入变量中的都是一个对象,每一个对象都是类的实例。每一个数字,函数,甚至null都是一个对象。所有的对象都继承于对象类。

  • 虽然Dart语言是一个强类型的,但是准确定义类是可选的。因为Dart可以推断类型。在上面的代码中,number被推出是int类型,当你预料不到这个类型时,使用这个特殊的类dynamic

  • Dart支持范类型,像List<int>(整数列表)或者List<dynamic>(任何类型的列表)

  • Dart支持顶级的函数(如mian()),也支持函数关联在类或者对象上(分别时静态方法或者实例方法),你也可以创建一个函数在一个函数内(嵌套函数或局部函数)

  • 不像javaDart没有关键字 public, protected,和 private,如果一个标识符开始是下划线(_),对这个库而言它是私有的。

  • 标识符开始于一个字母或者下划线(_),接下来可以是任何字母或者数字的组合。

  • Dart语言同时具有表达式(具有运行时值)和语句(不具有运行时值),例如条件表达式condition ? expr1 : expr2 其值不是expr1就是expr2,而if-else 则没有。一个语句同时包含一个或者多个表达式,但一个表达式不能直接包含语句。

    runtime values直译来是运行时值。

    if (expression) { statement(s) } else { statement(s) }
    //这个语句没有runtime values,也就是不能复制给变量。
    //而改写
    var a=condition ? expr1 : expr2;
    // ----------------------
    var a;
    if(condition){
        a=expr1;//只能在这里写复制表达式
    } else {
        a=expr2;//只能在这里写复制表达式
    }
    
  • Dart工具可以报告出两种问题:warning(警告)和errors(错误)。警告仅仅表明你的代码可能有问题,但他们不阻止代码的执行,错误是编译错误,也可以是运行时出错,编译错误将无法运行,运行时错误,将在程序执行时发生异常

关键字

abstract 2 dynamic 2 implements 2 show 1
as 2 else import 2 static 2
assert enum in super
async 1 export 2 interface 2 switch
await 3 extends is sync 1
break external 2 library 2 this
case factory 2 mixin 2 throw
catch false new true
class final null try
const finally on 1 typedef 2
continue for operator 2 var
covariant 2 Function 2 part 2 void
default get 2 rethrow while
deferred 2 hide 1 return with
do if set 2 yield 3

避免使用这些关键字作为标识符。但是,如果需要,用上标标记的关键字是可以做标识符的。

  • 上标1是上下文关键词,只有在特殊的地方他才有意义,他们在任何地方是有效的标识符。

  • 上标2内嵌标识符,为了方便从JavaScript代码移植到Dart代码,这些关键词是有效的在大多数地方,但是他们不能用于类名或者类型名,也不能做import 前缀

  • 上标3是在Dart1.0后加的,用于支持异步的特性,你不能使用await或者yield作为一个标识符在任何一个函数体使用asyncasync或者sync**内。

表中的所有其他单词都是保留字,不能是标识符。

变量

创建并初始化一个变量

var name = 'Bob';

变量是一个引用。这个被命名为name的变量包含了一个对String对象的引用,该对象的值为“Bob”。

可以看出标识符为name的变量是一个String(字符串),但是你也可以明确指定它的类型。如果一个对象不局限于单一类型,请按设计指南(design guidelines指定Object或者dynamic类型

dyname name = 'Bob';

明确的声明这个变量

Sting name = 'Bob';

这个页面遵循 Dart语言风格指南推荐(style guide recommendation)。对局部变量使用var,而不是明确指定它的类型

默认值

未初始化的变量有一个默认的值null。甚至一个数字类型的变量也被初始化为null,因为Dart中数字和其他任何类型一样都是对象。

int lineCount;
assert(lineCount==null);  

备注:assert(condition)的只会在程序开发期间生效,如果condition是false将会抛出一个错误。正式的代码将会忽略assert()的调用。

finalconst

如果你从不想改变一个变量,使用final或者constfinal变量只能被设置一次,const变量编译运行时不变。(const变量也是final。)顶层的final变量或者类中的final在第一次使用的时候才进行初始化。

实例变量只能使用final,而不能使用const。final 实例变量必须初始化在final变量声明处(构造体)之前,可以使用构造函数传参或者使用构造函数的 初始化列表initializer list来初始化。

创建和设置final变量的例子

final name = 'Bob';   
final String nickname = 'Bobby';

你不能该改变final的变量的值

name = 'Alice';     
// Error: a final variable can only be set once.

使用const可以定义编译时常量(这里是compile-time constants:编译时常量)。如果const变量是claas级别的,标记为static const。在变量声明处,将可以将数字或者字符字面量、const变量或对常数进行算数的结果设置为编译时常量

const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

const关键字不仅仅用于声明常量(字面量、数字或者cosnt变量)变量。还可以使用它来创建常量值,以及声明创建常量值的构造器。任何变量都可以有一个常量。

var foo = const [];
final bar = const [];
const baz = []; //等效于 `const []`

像上面的baz一样,初始化表达式上省略const。详细不要使用冗余的使用const(DON’T use const redundantly)

你可以改变非final、非const的变量,即使它有一个const的值:

//foo声明处在上一个例子中
foo = [1, 2, 3]; // Was const [] 

你无法更改const声明的变量

baz = [42]; // Error: Constant variables can't be assigned a value.

dart2.5开始,你可以定义const使用 类型检测和转换(type checks and casts)isand)、集合if和集合for(使用if和for构建集合的语句)、拓展运算符(......?):

// 以下在dart2.5中有效
const Object i = 3; //i是具有int值的const对象
const list = [i as int]; // 使用类型转换。
const map = {if (i is int) i: "int"}; // 使用is和collection if.
const set = {if (list is List<int>) ...list}; // ...拓展运算符.

有关const的更多信息,参阅List,Maps和Classes。

内置类型

Dart 语言对以下类型提供了特殊的支持:

  • number
  • string
  • boolean
  • lists(也被称之为arrays)
  • maps
  • runes(用于在字符表示Unicode编码)
  • symbols.

你可以使用字面量(字面量就是输给编程语言的文字)来初始化这些特殊的类型。例如,'this is a string'是一个string的字面量,true是一个boolean的字面量

因为在dart中每一个变量都是一个对象即一个实例化的类。所以你可以使用构造函数去初始化。一些内置的类型有自己的构造函数。例如你可以使用Map()构造函数去创建map

Nubers

Dart中只有两种:

int

整形的值不大于64位,具体取决于平台。在Dart VM,值的范围为-263到263,编译成JavaScript的Dart,值的范围为 -253到253

double

64位(双倍精度)浮点数,遵行IEEE 754的标准的规定。

int和double都是num的子类。num类型包括基础的运算符,例如+,-,/和*,你也可以找到abs(),ceil(),和floor()以及其他的方法(按位运算符,如>>,是定义在int类的)如果num和他的子类没有你要的内容,dart:math可能能帮到你。

整形数字没有小数。

var x = 1;
var hex = 0xDEADBEEF;//16进制

如果一个数包括小数它是double。

var y = 1.1;
var exponents = 1.142e5;

从Dart2.1开始,必要时,整形变量自动转化为doubles。

double z = 1; // 等效于double z = 1.0.

版本:在Dart2.1之前,它是错误的。使用整形的变量储存double类型的自卖能量

类型的转化,string转化为number,number转化为string

// 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');

int 类型支持位移运算符(<<,>>) AND (&), 和 OR (|)运算符 。例如:

assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111

number字面量是编译时的常量,许多数学表达式只要操作的数是编译时常量,则结果也是编译时常量。

const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;

string

Dart 字符串时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.";

你可以通过${express}把表达式的值放入字符串中,如果表达式只是是标识符,你需要省略{}。为了获取对象相对应的字符串,dart调用该对象的方法toString()方法(如:print([1,2])先调用[1,2].toString(),再输出)。

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!');

== 运算符来测试两个对象是否相等。如果他们包含相同的代码单元序列这两个字符串相同

你可以使用+运算符连接多个字符串,将字符串放到一起也有同样的功能。

var s1 = 'String '
    'concatenation'
    " works even over line breaks.";
assert(s1 ==
    'String concatenation works even over '
        'line breaks.');

var s2 = 'The + operator ' + 'works, as well.';
assert(s2 == 'The + operator works, as well.');

另外一种创建多行字符串的方法:使用三个引号(单引号或者双引号)

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

你可以创建一个原始字符串通过前缀r

var s = r'In a raw string, not even \n gets special treatment.';

参考 Runes 来了解如何在字符串 中表达 Unicode 字符。

字符串中只要插值表达式是常量(null、数值、字符串、布尔值),则其结果也是一个常量。

// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';

使用字符串的更多信息请参考: 字符串和正则表达式

Booleans

为了表达布尔值,dart有一个类型bool,只有两个对象是bool类型,truefalse,两者都是常量。

dart的类型安全意味着,你不能使用if(nonbolleanValue)或者assert(nonbooleanValue)(不是布尔类型的值)。相反,应该像这样显示检查。

// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

lists

在几乎绝大数语言中都有数组(有序数组)这个集合类型。在dart中,数组是List。所以我们通常都称之为lists。

dart语言的数组很像JavaScript数组。这里有一些简单的例子。

var list  = [1,2,3];

dart 推断上面的list是list<int>类型。如果你尝试添加一个非整数类型的对象到这个list中,程序分析器或者运行环境会抛出一个错误。想要更多信息,阅读type inference.

列表使用从0开始的索引。0为list的第一个元素,list.length-1是list最后一个元素。你可以像JavaScript一样得到字符串的长度或者引用列表。

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

在列表之前添加const,可以创建常量列表。

var constantList = const [1, 2, 3];
// constantList[1] = 1; // Uncommenting this causes an error.

dart2.3引进拓展运算符(spread operator)(...)和空感知扩展运算符(...?) ,提供一个简明的方法在容器中插入大量的元素。

例如,你可以使用拓展运算符(...)插入列表中全部的元素到另外一个列表中。

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

如果拓展运算符右边的表达式为空,你使用空感知拓展运算符(...?)避免异常:

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

点击spread operator获取拓展运算符更多的用法和例子

Dart2.3也引入了 collection if 和**collection for ** ,你可以使用条件语句(if)和循环语句(for)在容器中添加元素。

collection if 去创建三个或四个元素

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];//promoActive是true 为四个元素,反之为三个元素。

collection for去添加大量的元素到列表中。

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

点击 control flow collections proposal.获取更多collection if和 collection for的例子。

List类有很多操作列表方便的方法,点击 范类(Generics)集合(Collections)获取更多的信息。

Sets

在dart中set是无序无重复元素的集,set字面量和 Set类型提供sets的支持。

虽然Set是核心库里的内容,但set是dart2.2才被引入的。

通过set字面量创建sets

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

dart判断上面的halogens变量是Set。如果你尝试添加一个错误的类型至set中,程序分析器或者运行环境会抛出一个错误。想要更多信息,阅读type inference.

使用类型后加{}或者将{}分配给Set类型的变量,创建空的set

var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.

是Set还是map?map的语法和set语法是相似的。因为map,{}默认为map类型。如果你忘记变量或者{}类型的注解,那么dart将创建一个Map<dynamic, dynamic>.的对象。

添加元素使用add()或者addAll()方法

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

使用 .length获取set的长度

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

在set字面量前使用const创建set常量。

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // Uncommenting this causes an error.

Dart2.3起,sets拓展运算符(......?)和collection ifs 和 fors,就像lists一样。点击 spread operator proposalcontrol flow collections proposal,获取更多拓展运算符和collection 用法和信息。

点击 Generics and Sets或者更多sets的信息。

Maps

通常,map是关联键和值的对象。键和值都可以是任何类型的对象。每个键只能出现一次,而一个值可以重复多次。Dart 通过 map 字面量 和 Map 类型支持 map。

下面是一些创建简单 map 的示例:

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

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

dart判断giftsMap<String, String> nobleGasesMap<int, String>。如果你尝试添加一个错误的类型至两者的map中,程序分析器或者运行环境会抛出一个错误。想要更多信息,阅读type inference.

你可以创建一样的对象通过Map构造器:

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';

你可能希望看到new Map()而不是Map()。dart 2起,new关键字是可选的。点击 Using constructors.获取更多的信息。

添加一个键值对到一个现存的map中和JavaScript方法一样。

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair

获取一个值的方法也和JavaScript一致

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');

如果你查找的键不在map中,你会得到null

var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

使用 .length获取map键值的个数

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

在map字面量前使用const创建map编译时的常量。

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; // Uncommenting this causes an error.

Dart2.3起,maps支持拓展运算符(......?)和collection ifs 和 fors,就像lists一样。点击 spread operator proposalcontrol flow collections proposal.,获取更多拓展运算符和collection 用法和信息。

点击 Generics and Maps或者更多maps的信息。

Runes

在Dart中,runes代表字符串的utf-32 code points。

Unicode 规定一个唯一的数字标量对世界上所有书写系统的每一个单词,数字和符号。因为dart字符串是由UTF-16编码单元组成的序列,表达32位的Unicode值需要用到特殊的语法。

通常的方法表达Unicode code points 是 \uXXXX,XXXX是4位十六进制的值。 例如,心形符号 (♥) 是 \u2665。 对于非 4 个数值的情况, 把编码值放到大括号中即可。 例如,笑脸 emoji (😆) 是 \u{1f600}

String 类型有有一系列属性你可以提取rune的信息。codeUnitAtcodeUnit属性返回一个16位的代码单元。使用 runes 属性来获取字符串的 runes 信息。

下面是一些例子

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));
}
//👏
//[55357, 56399]
//[128079]
//♥  😅  😎  👻  🖖  👍

使用 list 操作 runes 的时候请小心。你所操作的特定的语种、字符集和操作方式,可能导致你的字符串出问题。 更多信息参考 Stack Overflow 上的一个问题: 如何在 Dart 中反转一个字符串?

Symbols

Symbol 对象代表在Dart程序中声明的运算符或者操作符。你可能从来不会用到symbols,但是symbols对于名称引用的标识符的api时极有价值的,因为缩小标识符的名字不会改变标识符的symbols。

使用 # 后跟标识符,得到一个symbol字面量

#radix
#bar

symbol字面量是编译时的常量

函数

dart是面向对象的语言, Function.是一个对象,并且具有一个类型。这个意味着函数可以分配给变量,也可以作为参数传给其他函数。你也能像函数一样调用类的实例。点击 Callable classes.获得更多信息。

这里是实现函数的例子

bool isNoble(int atomicNumber) {//atomicNumber原子序数
  return _nobleGases[atomicNumber] != null;   //_nobleGases稀有气体
}

虽然Effective Dart推荐公共APIS有类型注释,但是忽略类型函数仍然是有效的。

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

对于只有一个表达式的式子,可以使用简略语法

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

=> expr语法是{ return expr; }.简略的表达式。 =>符号也被称作箭头函数。

仅仅一个表达式(非语句)可以放在箭头(=>)和分号 (;)之间。你不能将一个条件语句 (if statement)放到这里,但是可以放条件表达式(conditional expression.)

函数可以有两个参数:必选的和可选的。首先列出的是必选参数,接着是可选的参数。可选参数可以是命名可选参数或者位置可选参数。

注意:一些api尤其是 Flutter 小部件构造函数只使用命名参数,即使对于强制参数也是如此。有关详细信息,请参见下一节。

可选参数Optional parameters

可选参数可以是位置参数,也可以是命名参数。但不能是两者都是。

可选命名参数Optional named parameters

可选命名参数。调用函数时,你可以使用paramName: value指定参数。

enableFlags(bold: true, hidden: false);

当定义一个函数时,使用{param1, param2, …}指定命名参数。

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}

虽然命名参数时是可选的,你可以使用 @required来注释一个命名可选的参数是强制的,用户必须为此参数提供值。

const Scrollbar({Key key, @required Widget child})

当构建Scrollbar时,分析器会在缺少child参数时提示错误。但运行时不会抛出错误。(这个是Flutter中的内容)

要使用@required依赖于 meta库,请导入package:meta/meta.dart

位置可选参数Positional parameters

在函数参数内使用[]包裹的参数,是位置可选参数。

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

设置参数的默认值Default parameter values

你的函数可以使用=命名可选参数位置可选参数添加默认值。默认值必须是常量。如果没有定义默认值,这个默认值为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');

你也可以使用lists 或者maps作为默认值,下面定义了一个doStuff(),并为listgifts设置了默认值

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

主函数The main() function

每一个程序必须有一个顶层函数main(),作为程序的入口。main()返回void和有一个可选的参数 List<String>

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}

在上面的代码中..语法叫做[cascade](#Cascade notation (..)).使用级联,您可以对单个对象的成员执行多个操作。

main()一个命令行执行程序,它接收参数

// 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去定义和解析命令行参数。

函数作为一等对象Functions as first-class objects

你可以将函数作为参数作为一个参数传给另一个参数。

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// 将printElement作为参数传递。
list.forEach(printElement);

你也可以将函数分配给一个变量

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

这个例子使用了一个匿名函数。下一节将介绍更多信息。

匿名函数Anonymous functions

绝大数函数是命名函数,如main()或者printElement(),你也可以创建一个不需要名字的函数,称之为匿名函数。有时是lambda或者闭包函数。你分配匿名函数给一个变量。例如,你可以从一个集合中添加或者删它。

匿名函数看起来像明明函数——零个或者多个参数,括号内用逗号分隔和可选类型注释分隔。

下面注释块包含函数的主体

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

用无类型的参数item. 定义匿名函数。这个函数,为列表中的每个项调用,打印一个字符串,该字符串包含指定索引处的值。

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});
//0: apples
//1: bananas
//2: oranges

如果函数仅包含一个语句,你可以使用箭头函数简化。

list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));

静态作用域Lexical scope

Dart 是静态作用域语言,变量的作用域是由变量的位置确定的。您可以跟随大括号向外查看变量是否在作用域中。

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() 如何使用每个级别的变量。

闭包 Lexical closures

闭包是一个函数对象,不管在何处调用,该对象都可以访问其作用域内的变量。

函数可以在周围定义处封闭变量,在以下示例中,makeAdder()获得变量addBy。无论返回的函数在哪里,它都会保留addBy的值。

/// 返回一个将[addBy]添加到的函数
/// function's argument.
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);
}

相等性测试函数Testing functions for equality

下面是测试顶级函数、静态方法和实例方法是否相等的示例

void foo() {} // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}

void main() {
  var x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);

  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}

返回值Return values

所有的函数都返回一个值,如果没有返回一个指定的值,这个表达式将隐式地附加到函数体return null

foo() {}

assert(foo() == null);

Operators

在接下的表格中,你可以看到Dart定义的运算符。如[运算符重写操作Overridable operators](overridable operators).所示你可以重写这些运算符。

Description Operator
后缀unary postfix expr++ expr-- () [] . ?.
前缀unary prefix -expr !expr ~expr ++expr --expr
乘除multiplicative * / % ~/
添加additive + -
位移shift << >> >>>
按位与bitwise AND &
按位异或bitwise XOR ^
按位或bitwise OR |
关系运算符relational and type test >= > <= < as is is!
相等equality == !=
逻辑与logical AND &&
逻辑或logical OR ||
判断空if null ??
控制conditional expr1 ? expr2 : expr3
级联cascade ..
赋值assignment = *= /= += -= &= ^=

警告:运算符优先级是Dart解析器行为的近似值。要获得明确的答案,请参阅 Dart语言规范中的语法.

当你使用运算符,你创建一个表达式。这里是一些例子运算符的表达式。

a++
a + b
a = b
a == b
c ? a : b
a is T

表格中,每个操作符的优先级都高于其后行的操作符。例如,multiplicative %运算符的优先级高于equality ==(因此multiplicative 在之前进行)。equality ==的优先级高于logical AND operator &&,因此下面两行代码按照相同的方式运行

// 括号提高可读性
if ((n % i == 0) && (d % i == 0)) ...

// 等效
if (n % i == 0 && d % i == 0) ...

警告:对于一个二元运算符,使用左边的运算符操作方式。例如,你有一个Vector 的对象和一个Point 的对象相加aVector + aPoint ,使用的是Vector 版本的 +(重写运算符时)

Arithmetic operators

Dart支持常用的算术运算符,如下表所示。

Operator Meaning
+ 求和
相减
-expr 一元减号,也称为否定(反转表达式的符号)
* 相乘
/ 相除
~/ 相除,返回一个整数结果
% 获取整数除法的余数(模数)

例子:

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

Dart还支持前缀和后缀的自增自减运算符

Operator Meaning
++var var = var + 1 (表达式的值是 var + 1)
var++ var = var + 1 (表达式的值是var)
--var var = var – 1 (表达式的值是 var – 1)
var-- var = var – 1 (表达式的值是 var)

例子:

var a, b;

a = 0;
b = ++a; // a先自增再赋值给b
assert(a == b); // 1 == 1

a = 0;
b = a++; // a先赋值给b后自增
assert(a != b); // 1 != 0

a = 0;
b = --a; // a先自减再赋值给b
assert(a == b); // -1 == -1

a = 0;
b = a--; // a先赋值再自减
assert(a != b); // -1 != 0

Equality and relational operators

下表列出了相等和关系运算符的含义。

Operator Meaning
== 相等运算符
!= 不等运算符
> 大于运算符
< 小于运算符
>= 大于等于运算符
<= 小于等于运算符

为了测试两个对象x,y是同样的东西,使用==运算符(在极少数的情况下,你需要知道两个对象是否是完全相同的对象,这时使用identical()函数),以下是work的工作原理:

  1. 如果x或y不是空,如果两个都是null返回true,如果两个有一个不是null返回false
  2. 返回方法x.==(y)的结果(运算符如==是在第一个操作数上调用的方法,你甚至可以覆盖许多运算符,包括==,正如您在Overridable运算符中看到的那样。)

这里有一些使用相等运算符和关系运算符的例子:

assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

类型测试运算符Type test operators

asisis!运算符在运行时检查类型很方便。

Operator Meaning
as 类型转化 (也用于指定 库的前缀(library prefixes))
is 如果对象具有指定的类型,则为True
is! 如果对象具有指定的类型,则为False

obj is T是true,如果obj继承了由T指定的接口。如,obj is Object始终为true。

使用as运算符将对象强制转换为特定类型。通常,你应该使用as作为 is测试对象后跟使用对象表达式 的简写。例如,以下代码。

if (emp is Person) {
  // 检测类型
  emp.firstName = 'Bob';
}

你可以使用as运算符缩短代码

(emp as Person).firstName = 'Bob';

注意:代码不是等效的。如果emp是非空的或者不是Person,第一个例子(有is)不做事,第二个例子(有as)抛出一个错误

赋值运算符Assignment operators

如你所见,你可以使用=运算符赋值。要使用??=运算符,仅在是变量为null的情况下赋值。

// 值分配给a
a = value;
// 如果b为null,则将值赋给b;否则,b保持不变
b ??= value;

复合赋值运算符+=,将操作与赋值组合在一起。

= –= /= %= >>= ^=
+= *= ~/= <<= &= |=

以下是复合赋值运算符的工作原理:

符合赋值 等价表达
对于运算符op a op= b a = a op b
例子: a += b a = a + b
var a = 2; // 赋值使用 =
a *= 3; // 赋值和乘法:a = a * 3 
assert(a == 6);

逻辑运算符

可以使用逻辑运算符进行运算(反转或者结合)布尔值。

Operator Meaning
!expr 反转以下表达式(将false改为true,反之亦然)
|| 逻辑或
&& 逻辑与

以下是使用逻辑运算符的示例:

if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}

位操作与移位运算符Bitwise and shift operators

Operator Meaning
& 按位取与
| 按位取或
^ 按位异或
~expr 一元逐位补码(0s变为1s; 1s变为0s)
<< 左移
>> 右移

这是使用按位和移位运算符的示例:

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // 与
assert((value & ~bitmask) == 0x20); // 与非
assert((value | bitmask) == 0x2f); // 或
assert((value ^ bitmask) == 0x2d); // 异或
assert((value << 4) == 0x220); // 左移
assert((value >> 4) == 0x02); // 右移

条件表达式Conditional expressions

Dart有两个运算符,可以简明if-else语句的表达式

condition ? expr1 : expr2

如果condition为true,则计算expr1(并返回其值);否则,计算并返回expr2的值。

expr1 ?? expr2

如果expr1为非null,则返回其值;否则,计算并返回expr2的值。 当需要根据布尔表达式进行赋值时使用? :

var visibility = isPublic ? 'public' : 'private';

如果布尔值的表达式测试为null,考虑使用??:

String playerName(String name) => name ?? 'Guest';

前面的示例至少可以用另外两种方式编写,但没有这么简洁

// 稍微长一点的版本使用?:操作符。
String playerName(String name) => name != null ? name : 'Guest';

// 很长的版本使用if-else语句。
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}

级联运算符Cascade notation (..)

级联操作符(..)允许同一个对象进行一系列操作。除了调用函数,你还可以访问统一对象上的字段。这节省你写临时变量的步骤,是你的代码更加优美。

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

首先调用方法,querySelector(),返回一个选择器对下个。接下来是级联运算符cascade notation operates在这个选择器对象上,忽略任何其后操作可能返回的值。

上面的例子等效于

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')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.

调用sb.write()返回void,你不能使用级联运算符对void

严格来讲,级联”双点“不是一个运算符。它仅仅是dart语法的一部分。

其他运算符

在其他示例中,可以看到大多数剩余的运算符:

Operator Name Meaning
() 函数运用运算符 表示函数调用
[] 列表访问 引用列表中指定索引处的值
. 成员访问 指表达式的属性;:foo.bar从表达式foo中选择
?. 条件的成员访问 类似于.,但最左边的操作数可以为null;例如:foo?.bar从表达式foo中选择属bar,除非foonull(在这种情况下,foo?.bar的值为null)

有关.?...运算符的更多信息,查看类Classes

控制流语句

dart中控制流语句:

  • ifelse
  • for 循环
  • whiledo-while 循环
  • breakcontinue
  • switchcase
  • assert

你可以使用 try-catchthrow,影响控制流语句如异常Exceptions所述。

If 和 else

dart支持if语句和else语句(可省略else语句),如下个例子所述。

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

dart语言不像JavaScript,控制流语句必须使用布尔值,详情Booleans

For 循环

你可以使用for循环迭代:

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Dart中for循环内部的闭包获取索引的值,避免了JavaScript的陷阱

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
//改写JavaScript代码(非dart官方文档)
var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.push(() => console.log(i));
}
callbacks.forEach((c) => c());

输出是01,在JavaScript中这个例子将打印两个2

如果你正在迭代的对象是迭代器,你可以使用forEach()方面,如果不知道迭代的数量,使用forEach()是一个好选择。

candidates.forEach((candidate) => candidate.interview());

像List和Set这样的可迭代类也支持迭代的for-in形式:

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}

While 和 do-while

while循环在循环之前判断条件:

while (!isDone()) {
  doSomething();
}

do-while循环后判断条件:

do {
  printLine();
} while (!atEndOfPage());

Break 和 continue

使用break来停止循环:

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

使用continue 跳到下一个循环:

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

将上面的例子改写成迭代器Iterable(如列表或集合)

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());

Switch 和 case

Dart中的switch语句使用==比较整数,字符串或编译时常量。比较对象必须都是同一个类的实例(而不是其任何子类型),并且该类不能覆盖==枚举类型 (Enumerated types) 在switch语句中运行良好。

注意:Dart中的Switch语句适用于有限的环境,例如interpreters(解释器)或scanners(扫描仪)。

每个非空case子句以break语句结束。结束非空case子句的其他有效方法是continue,throw或return语句。

当没有case子句匹配时,执行default的代码:

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

以下示例省略了case子句中的break语句,造成了错误:

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break

  case 'CLOSED':
    executeClosed();
    break;
}

然而,Dart确实支持空的case子句,允许某种形式的省略

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // 空case语句,运行下一个case
  case 'NOW_CLOSED':
    // 运行CLOSED和NOW_CLOSED。
    executeNowClosed();
    break;
}

如果您真的想要跳转下一个case,可以使用continue语句和标签:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // 在nowClosed case上执行。

  nowClosed:
  case 'NOW_CLOSED':
    // 运行CLOSED和NOW_CLOSED。
    executeNowClosed();
    break;
}

case子句可以具有局部变量,这些变量仅在该子句的范围内有效。

Assert

在程序开发期间,使用assert语句——assert(condition,optionalMessage);如果condition的结果为false,则不会正常运行。

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

也可以自定义错误的信息,添加一个字符串作为第二个参数。

assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

assert()第一个参数可以是任意的表达式,如果表达式的结果为true,程序正常运行。反之,一个异常抛出。

在以下工具和框架的下,assert()的正常运行。

  • Flutter在debug 模式下
  • 开发者工具,例如:dartdevc默认支持。
  • 一些工具,如dartdart2js,通过命令行添加 --enable-asserts

在生产(正式运行)中,assert被忽略,assert不会评估的参数

异常

Dart代码会抛出和捕捉异常。异常是一种错误说明未知的错误发生。如果未捕获异常,异常会被抛出,导致抛出异常的代码终止执行。

与Java语言相反,Dart全部的意外都为非检查异常。methods不一定声明了它们可能抛出的异常,并且不要求捕获任何异常。

Dart提供了 ExceptionError,以及他们的子类。你可以定义自己的异常。但是,Dart程序可以抛出任何非null对象 - 不仅仅是Exception和Error对象 - 作为异常。

Throw

以下是抛出或引发异常的示例:

throw FormatException('Expected at least 1 section');

你也可以抛出任意对象:

throw 'Out of llamas!';

注意:生产代码通常会抛出错误实现Error或异常Exception的类。

因为抛出异常是一个表达式,所以可以抛出异常在=>语句中以及允许表达式的任何其他地方:

void distanceTo(Point other) => throw UnimplementedError();

Catch

捕捉异常,阻止异常继续传递(除非你重新抛出异常)。捕捉异常试你有机会处理它

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

要处理可能抛出不止一种异常类型的代码,可以指定多个catch子句。一个catch子句对应异常对象的类型之后处理异常。如果catch子句未指定类型,则该子句可以处理任何类型的抛出对象:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 一个特殊的异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 还有其他任何异常
  print('Unknown exception: $e');
} catch (e) {
  // 处理所有没有指定的类型
  print('Something really unknown: $e');
}

如前面代码所示,你可以使用oncatch或两者。需要指定异常类型时使用on。在异常处理程序需要异常对象时使用catch

你可以指定一个或者两个参数给catch(),第一个参数是抛出的异常,第二个是堆栈信息(一个 StackTrace对象)

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

使用 rethrow 关键字可以 把捕获的异常给 重新抛出。

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // 返回一个错误
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // 允许调用者看到异常。
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

Final

要确保某些代码执行,无论是否有异常都需要执行。使用 finally语句。如果没有catch 语句匹配到异常,在final子句运行之后再抛出异常。

try {
  breedMoreLlamas();
} finally {
  // 总是清理,即使抛出异常。
  cleanLlamaStalls();
}

finally 子句在catch子句之后运行:

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // 首先处理异常
} finally {
  cleanLlamaStalls(); // 之后清理
}

想了解更多信息,阅读库预览的Exceptions模块。

classes

Dart语言是一个面向对象的语言,拥有类和基于mixin的继承。每个类是类的一个实例,所有的类都来源与Object。基于mixin的继承意味着尽管每个类(除了Object)有一个额外的超类,类主体可以在多个类层次结构中重用。

类成员

Object的成员由函数和数据(方法和实例变量)组成。当你调用一个方法,可以在对象上调用它:该方法可以访问该对象的函数和数据

使用点(.)来访问实例变量和方法:

var p = Point(2, 2);

// 设置实例变量y的值。
p.y = 3;

// 获得y的值。
assert(p.y == 3);

// 调用p上的 distanceTo() 
num distance = p.distanceTo(Point(4, 4));

使用?.取代.避免因最左边的变量为空而引发的异常。

//如果p为非null,则将其y值设置为4。
p?.y = 4;

使用构造体

你可以创建对象使用构造体。构造体的名字可以是类的名字(ClassName )或者类内的方法(ClassName.identifier)。例如,下面的代码创建Point对象使用Point()Point.fromJson()构造:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

以下代码具有相同的效果,new为可选的关键字:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

版本说明:新关键字在Dart 2中变为可选。

一些方法提供常量构造器(constant constructors)。在构造函数之前放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的map:

// 这里由很多的const
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

你可以省略除第一个外的const关键词

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果常数构造函数在常数上下文之外没有const,它会创建一个**非常数(non-constant object)**对象:

var a = const ImmutablePoint(1, 1); // 创建常量
var b = ImmutablePoint(1, 1); // 不创建常量

assert(!identical(a, b)); // 不是同一个实例!

版本说明:在Dart 2的常量上下文中,const关键字变为可选。

获取对象类型

使用ObjectruntimeType的属性可以在运行时返回type对象

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

至此,您已经了解了如何使用类。本节的其余部分将展示如何实现类。

实例变量

以下是声明实例变量的方法:

class Point {
  num x; // 声明实例变量x,最初为null。
  num y; // 声明y,最初为null。
  num z = 0; // 声明y,最初为null。
}

所有为初始化的实例变量的初始值为null。

所有的实例变量隐式的定义了一个getter方法,非final的实例变量也隐式的生成了一个setter方法。详情Getters 和 setters

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // 使用x的setter方法。
  assert(point.x == 4); // 使用x的getter方法。
  assert(point.y == null); // 值的默认值为null
}

如果在变量的声明处初始化它(不是构造函数或者方法),这个值在创建实例的时候设置。创建实例的时间在构造函数和初始化列表执行之前。

class Point {
  num x, y;

  Point(num x, num y) {
    //有一个更好的方法来做到这一点,请继续关注。 
    this.x = x;
    this.y = y;
  }
}

this关键字引用当前实例。

注意:仅在存在名称冲突时使用此选项。Dart省略了this

将构造函数参数创建实例变量的模式非常常见,dart语法使其加糖,非常简单:

class Point {
  num x, y;

  // 用于设定x和y的句法糖
  // 在构造函数体运行之前。
  Point(this.x, this.y);
}

默认构造函数(Default constructors)

如果没有声明构造函数,则会有默认的构造函数。默认构造函数没有参数,在超类(被继承的类)中调用没有参数的的构造函数

构造函数不会继承(Constructors aren’t inherited)

子类不会继承超类的构造函数。子类没有定义构造函数,则会由一个只有默认(无参数,无名称)的构造函数

命名构造函数(Named constructors)

使用命名构造函数为一个类提供多种构造函数:

class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名构造函数
  Point.origin() {
    x = 0;
    y = 0;
  }
}

构造函数没有继承,这意味着超类的命名构造函数不会被子类继承。如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现超类的构造函数。

调用非默认的构造函数(Invoking a non-default superclass constructor)

默认,子类中的构造函数调用超类的未命名的无参数构造函数。超类的构造函数在构造函数体的运行之前被调用。如果使用初始化列表,则在调用超类之前执行。总之,执行顺序如下:

  1. 初始化列表
  2. 超类的无参数的构造函数
  3. 主类的无参数构造函数

如果超类不是未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。超类构造函数在冒号(:)之后、构造函数体(如果存在)之前。

在下面的例子中,Employee类的构造函数为其超类Person调用命名构造函数。

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




//console
//in Person
//in Employee

因为在调用构造函数之前会计算超类构造函数的参数,所以参数可以是一个表达式,例如函数调用:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}

警告:超类构造函数的参数不能访问this。如,参数可以调用静态方法,但不能调用实例方法。

初始化列表(Initializer list)

除了调用超类构造函数之外,还可以在构造函数体运行之前初始化实例变量。用逗号分隔初始化程序。

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

初始化列表赋值final字段时都得心应手。 下面的示例初始化在初始化列表中三个final。

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);
}
//3.605551275463989

重定向构造函数(Redirecting constructors)

有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。重定向构造函数的主体是空的,构造函数调用出现在冒号(:)之后。

class Point {
  num x, y;

  // 该类的主构造函数。
  Point(this.x, this.y);

  // 委托主构造函数。
  Point.alongXAxis(num x) : this(x, 0);
}

常量构造函数(Constant constructors)

如果生成从不不会更改的对象,则可以使这些对象成为编译时常量。为此,请定义const构造函数并确保所有实例变量都是final

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}

常量构造函数并不总是创建常量。有关详细信息,请参阅关于实例变量(using constructors)的部分。

工厂构造函数(Factory constructors)

实现一个构造函数并不总是创建其类的新实例时,使用工厂的关键字。 例如,一个工厂构造可能从缓存返回一个实例,或者它可能会返回一个子类型的实例。

下面的例子演示了一个工厂构造函数从缓存中返回的对象:

class Logger {
  final String name;
  bool mute = false;

  // _cache是库私有的
  // 在它的名字前面_
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

注意:工厂构造函数无权访问this

像调用任何其他构造函数一样调用工厂构造函数:

var logger = Logger('UI');
logger.log('Button clicked');

方法

方法是为对象提供行为的函数。

实例方法

在对象上的实例方法可以访问实例变量和thisdistanceTo()方法

下面的distanceTo()函数就是实例方法:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Getters 和 setters

Getters和Setters是对象属性的读写访问权限特殊的方法,每一个实例变量有隐式的getter方法和如果合适的话还有setter方法。你可以通过 getters 和 setters创建额外的属性,使用getset关键词。

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

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

  // 定义两个计算属性:右侧和底部。
  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);
}

借助于 getter 和 setter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。

无论是否显示定义getter,类似自增(++)的操作符都已以预期方式工作。为了避免产生任何意外的影响,操作符只要调用一次 getter ,就会把他的值存在临时变量里。

抽象方法

实例getter和setter可以是抽象的,定义接口,把实现的方法留给其他的类。抽象的方法仅存在于 抽象类(abstract classes)中。

用分号;代替方法体时方法变为抽象。

abstract class Doer {
  // 定义方法和变量

  void doSomething(); // 定义抽象方法
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 提供一个实现,这里的方法不是抽象的
  }
}

抽象类

使用abstract修饰符来定义抽象类——类不能被实例化。抽象类对定义接口是有用的,通常还有一些实现。如果你想你的抽象的类是可实例化的,请定义为工厂构造函数factory constructor

抽象类通常有抽象方法。这是一个声明具有抽象方法的抽象类的示例:

// 这个类被声明为抽象的
// 无法被实例化
abstract class AbstractContainer {
  // 定义构造函数,字段,方法......

  void updateChildren(); // 抽象方法
}

隐式接口

每个类都隐式定义了一个接口,每个类包含类的实例和他的实现的所有接口的实例。如果你想创建一个类A支持B的API,不继承B的实现,class A 应该implement B的接口。

类通过implement实现一个或者更多的接口,然后提供接口需要的API。

// 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()));
}

一个类实现多接口的实例:

class Point implements Comparable, Location {...}

继承类

使用extends创建子类,使用super来引用父级

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

重写成员

子类可以重写实例成员,方法、getters和setters。你可以使用@override注解表明你重写了一个成员

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

使用协变关键字(covariant keyword) 在类型安全的代码中缩小方法中参数的类型或者缩小实例变量的类型。

覆写运算符

可以复写以下表格的运算符,例如,如果定义Vector类,则可以定义一个+方法来添加两个向量。

< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
% >>

注意:您可能已经注意到!=不是可覆写的运算符。表达式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));
}

如果你覆写==,你应该也覆写hashCode的对象。例如重写==hashCode 实现map键(Implementing map keys)。获取更多的信息,通常,看扩展类(Extending a class)

noSuchMethod()

要在使用不存在的实例和方法时做出检测或者做出反应,可以覆盖noSuchMethod()

class A {
  // 除非你覆写 noSuchMethod
  // 不存在的成员导致NoSuchMethodError。
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

重写noSuchMethod之后,满足以下条件才能调用未写的方法:

  • 接收者有dynamic类型
  • 接收者的类型有一个定义了未实现的方法(可以是抽象方法)和重写noSuchMethod的方法。

以下是我查阅资料对官方文档的补充

// 会报错
class Person {
    @override
     noSuchMethod(Invocation msg) => "got ${msg.memberName} "
      "with arguments ${msg.positionalArguments}";
} 
void main() {
    var person = new Person();
    print(person.missing("20", "Shubham")); 
 }
class Person {
  missing(int age, String name); // 抽象方法
  @override
  noSuchMethod(Invocation msg) => "got ${msg.memberName} "
      "with arguments ${msg.positionalArguments}";
}

main(List<String> args) {
  dynamic person = new Person(); //person可以使用var、Person 或者 dynamic
  print(person.missing(20, 'shubham')); // 调用抽象方法
}

class Person {
  missing(int age,String name);  // 抽象方法

  @override // 重写noSuchMethod
    noSuchMethod(Invocation invocation) => 'Got the ${invocation.memberName} with arguments ${invocation.positionalArguments}';
}

main(List<String> args) {
  dynamic person = new Person(); //person可以使用var、Person 或者 dynamic
  print(person.missing(20,'shubham')); // 调用抽象方法
}

获取更多信息,查看 noSuchMethodui运转说明(noSuchMethod forwarding specification)

枚举类型

枚举类型是一种用于表示固定数量的常量值的一种特殊类的特殊类。

Using enums

使用enum关键字声明枚举类型:

enum Color { red, green, blue }

每一个枚举有一个indexgetter,调用它可以返回一个位置的值(0为基准)。例如第一个index是0,第二个值是1.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

使用枚举的values ,可以得到列表化的枚举。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

可以在 switch语句(switch statements)中使用枚举,如果你不处理所有的枚举值,你会得到一个警告。

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // 没有这个你将会得到一个警告
    print(aColor); // 'Color.blue'
}

枚举类型有以下限制:

  • 你不能子类化,混合或实现枚举。
  • 你无法显式实例化枚举。

有关更多信息,请参阅Dart语言规范(Dart language specification)

为类添加新功能:mixins

混入(Mixins)是一种多个类层次中重用代码的方法。

使用with关键字后跟一个或多个mixin名来使用混入。接下来这两个类显示如何使用混入:

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

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

创建一个继承Object的类和不声明构造函数来实现mixin,除非您希望mixin可用作常规类,否则使用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');
    }
  }
}

使用on指定某一种类型(超类是指定的类型)可以用mixin,这样你的mixin可以调用它未定义的方法。

mixin MusicalPerformer on Musician {
  // ···
}

版本差异:Dart2.1引入了关键词mixin,早期版本中的代码通常使用抽象类。有关2.1 mixin更改的更多信息,请参阅Dart SDK 版本日志(Dart SDK changelog)2.1 mixin规范(2.1 mixin specification)

类变量和方法

使用static关键字实现类范围的变量和方法。

静态变量

静态变量(类变量)是有用的对于类范围的状态和常量

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

静态变量不能被初始化直到他们被使用。

注意:此页面如下更喜欢lowerCamelCase(驼峰命名)为常量名的风格指南建议(style guide recommendation)

静态方法

静态方法(类方法)不对实例进行操作,因此无法访问它。例如:

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

注意:对于常用或广泛使用的实用程序和功能,考虑使用顶级函数而不是静态方法。

可以使用静态方法作为编译时常量。可以将静态方法作为参数传递给常量构造函数。

范类

如果你看了基础类型数组List的API文档,你将会看到List<E>。这个尖括号里面标记的将List制成通用的参数化的类型——具有正式类型参数的类型。按照惯例,大多数类型变量都有单字母名称,例如E,T,S,K和V.

为什么使用范类?

范类经常用作安全的类型时必须的,但是他们更有好处:

  • 正确指定泛型类型可以生成更好的代码。
  • 可以使用泛型来减少代码重复。

如果你打算让列表只包含字符串,你可以声明为的List 的(读取为“字符串列表”)。接下来的代码中,编译器可以检测到指定的添加非字符串到列表这种方式是一个错误。

//错误
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

使用范类的另一个原因是减少代码。范类可以让你有许多类型间有统的个接口和实现,同时仍然利用静态分析。例如,创建一个接口对caching对象

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

在此代码中,T是替身类型。它是一个占位符,可以将其视为开发人员稍后定义的类型。

使用集合字面量

List,set和map可以被参数化。参数化的字面量就像您已经看到的字面量一样,除了添加<type> <keyType, valueType>(对于map)在括号之前。这里是一些使用字面量类型的例子

var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <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 nameSet = Set<String>.from(names);

以下代码创建一个具有整数键和View类型值的映射:

var views = Map<int, View>();

通用集合及其包含的类型

Dart通用的类型时具体化的,这意味着它们在运行时携带其类型信息。例如,您可以测试集合的类型:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

注意:与Dart相反,Java中的泛型使用擦除,这意味着在运行时删除泛型类型参数。在Java中,您可以测试对象是否为List,但无法测试它是否为List <String>

限制参数化类型

当实现一个范类,你可能想去限制它的参数的类型。你可以使用extends

class Foo<T extends SomeBaseClass> {
  // 实现在这儿
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

可以将SomeBaseClass或其任何子类用作通用参数:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();// Extender extends SomeBaseClass

也可以不指定任何通用参数:

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'

指定任何非SomeBaseClass类型都会导致错误:

var foo = Foo<Object>();

使用范类方法

最初,Dart的范类支持仅限于类。范类方法是较新的语法,允许在方法和函数上使用类型参数:

T first<T>(List<T> ts) {
  // 做一些初步的工作或错误检查,然后...
  T tmp = ts[0];
  // 做一些额外的检查或处理...
  return tmp;
}

这里是范类参数,first (<T>) 允许在多个地方使用类型参数T

  • 函数返回值为(T
  • 参数类型中(List<T>
  • 局部变量中(T tmp

查看使用范类方法(Using Generic Methods)获取更多的信息

库和可见性

importlibrary指令可以帮助你创建一个模块化的共享的代码。库不仅仅提供API,但也有私有元素:以下划线(_)开头的标识符仅在库中可见。每个Dart应用程序都是一个库,即使它不使用库指令。

库可以分布式的使用包 packages

使用库

使用import指定在一个库命名空间来引用这个库。

例如,Dart Web应用程序通常使用dart:html库,它们可以像这样导入:

import 'dart:html';

导入所需的惟一参数是指定库的URI。对于内置库,URI具有特殊的dart: scheme。对于其他库,可以使用文件系统路径或package:schemepackage:scheme指定包管理器(如pub工具)提供的库。例如

import 'package:test/test.dart';

注意:URI代表统一资源标识符。 URL(统一资源定位符)是一种常见的URI。

指定库前缀

如果导入两个标识符冲突的库,则可以为一个或两个库指定一个前缀。例如,如果library1library2都具有Element类,那么可能具有以下代码:

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

// 使用 Element 来自于lib1
Element element1 = Element();

// 使用 Element 来自于 lib2.
lib2.Element element2 = lib2.Element();

导入库的一部分

如果只想使用一部分库,则可以有选择地导入该库。例如:

// 导入仅仅foo函数
import 'package:lib1/lib1.dart' show foo;

// 导入除foo以外iade函数
import 'package:lib2/lib2.dart' hide foo;

延迟加载库

延迟加载(也称为延迟加载)允许Web应用程序在需要库时按需加载库。以下是可能使用延迟加载的一些情况:

  • 减少web app的初始化启动时间
  • 执行A / B测试-尝试的算法的替代实施方式中;
  • 加载很少使用的功能,例如可选的屏幕和对话框。

只有dart2js支持延迟加载。 Flutter,Dart VM和dartdevc不支持延迟加载。有关更多信息,请参阅issue #33118issue #27776

要延迟加载库,必须首先使用deferred as导入它。

import 'package:greetings/hello.dart' deferred as hello;

当您需要库时,使用库的标识符调用loadLibrary()

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

在上面的代码中,await关键字暂停执行,直到加载库。有关async和await的更多信息,请参阅异步支持(asynchrony support)

可以在库上多次调用loadLibrary()而不会出现问题。该库只加载一次。

使用延迟加载时,请注意以下几点:

  • 延迟库的常量在其作为导入文件时不是常量。记住,这些常量不存在,直到迟库被加载完成。
  • 不能在导入文件中使用延迟库常量的类型。相反,考虑将接口类型移到同时由延迟库和导入文件导入的库。
  • Dart隐含调用LoadLibrary()插入到定义deferred as namespace。在调用LoadLibrary()函数返回一个Future。

实现库

查看Create Library Packages,获得如何实现一个库的包的建议,包括:

  • 如何组织库源代码。
  • 如何使用export 指令。
  • 何时使用part指令。
  • 何时使用library 指令。

异步支持

Dart库有很多返回Future或Stream对象的函数。这些函数是异步的。它们在设置可能耗时的操作(例如I / O)之后返回,而无需等待该操作完成。

asyncawait关键字支持异步编程,使可以编写看起来类似于同步代码的异步代码。

处理Futures

当你使用Futures的结果时,你有两个选项:

使用asyncawait的代码是异步的,但是看起来很像同步代码。例如,下面是一些使用await等待异步函数结果的代码:

await lookUpVersion();

要使用await,代码必须位于async函数中,该函数被标记为async

Future checkVersion() async {
  var version = await lookUpVersion();
  // 做点什么
}

注意:虽然一个async函数可能会执行耗时的操作,程序不会等待这些操作。 相反,async函数执行一直到它遇到其第一 await表达式(详细信息)。 然后它返回一个Future对象,恢复执行所述await表达完成之后。

使用try,catch,最后使用await处理代码中的错误和清除:

try {
  version = await lookUpVersion();
} catch (e) {
  // 对无法查找版本做出反应
}

可以在async函数中多次使用await。例如,以下代码等待三遍函数结果:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

await expression中,这个表达式的值通常是一个Futrue;如果他不是,然后该值将自动包装在Future中。此Future对象指示返回一个对象的承诺。await expression的值是返回的对象。await expression使执行暂停,直到该对象可用为止。

**如果在使用await时收到编译时错误,请确保await在异步函数中。**例如使用await在你的app的main()函数,这个main()的函数体必须标记成async

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

声明异步函数

异步函数是一个函数,其主体带有async的修饰符

向函数添加async关键字使其返回Future。例如,考虑以下同步函数,该函数返回一个String:

String lookUpVersion() => '1.0.0';

如果将其更改为async函数(例如,由于将来的实现会很耗时),则返回的值将为Future:

Future<String> lookUpVersion() async => '1.0.0';

请注意该函数的体并不需要使用Future 的API。 Dart如果有必要创造Future的对象。 如果你的函数不返回一个有用的值,使得它的返回类型 Future<void>

对于交互式介绍如何使用Future,asyncawait,看异步编程代码实验室(asynchronous programming codelab).

处理流

从流内得到一个值,你有两个选项

注意:在使用wait for之前,请确保它使代码更易读,并且确实需要等待所有流的结果。例如,通常不应该为UI事件侦听器使用wait For,因为UI框架会发送无穷无尽的事件流。

异步for循环具有以下形式

await for (varOrType identifier in expression) {
  // 每次流发出一个值时执行。
}

expression 表达式的值必须有 类型流,执行过程如下:

  1. 等待流发出一个值
  2. 执行循环体,包含流发出的值
  3. 循环1和2直到流关闭

要停止收听流,可以使用break或return语句,该语句会脱离for循环并注销流。

如果在实现异步for循环时收到编译时错误,请确保await for位于异步函数中。例如使用await在你的app的main()函数,这个main()的函数体必须标记成async

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

有关异步编程的更多信息,请参见库介绍的 dart:async 部分。

生成器

当需要延迟生成值序列时,请考虑使用生成器函数。 Dart具有对两种生成器功能的内置支持:

  • Synchronous 生成器:返回一个 Iterable对象
  • Asynchronous 生成器:返回一个 Stream对象

标记函数体为sync*实现一个synchronous 的生成器函数,并使用yield语句传递值:

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

标记函数主体标记为async*,实现asynchronous 生成器函数,并使用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);
  }
}

可调用类

要允许像函数一样调用Dart类的实例,实现call()方法。在下面的示例中,WannabeFunction类定义了一个call()函数,该函数接受三个字符串并将它们连接起来,每个字符串之间用空格隔开,并附加一个感叹号。单击Run执行代码。

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

// Hi there, gang!

Isolates

大多数计算机,即使在移动平台上,也有多核CPU。 为了利用所有这些核心,开发人员传统上使用并发运行的共享内存线程。 但是,共享状态并发容易出错,并且可能导致代码复杂化。

所有Dart代码都在隔离区内运行,而不是线程。 每个隔离区都有自己的内存堆,确保不会从任何其他隔离区访问隔离区的状态。

有关更多信息,请参阅以下内容:

Typedefs

在dart中,函数是对象,就像字符串和数字是对象一样。typedef或函数类型别名为函数类型提供了一个名称,可以在声明字段和返回类型时使用该名称。当函数类型被分配给变量时,typedef保留类型信息。

请考虑以下不使用typedef的代码:

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

// 初始化, 不执行。
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // 我们都知道compare 是一个函数,
  // 但是什么类型的函数呢?
  assert(coll.compare is Function); 
}

分配f进行compare时,类型信息会丢失。f的类型是 (Object, Object)int(→ 意味着返回的),然而compare 的类型是Function 。如果我们使用显式的名字更改代码并保留类型信息,则开发者和工具都可以使用这些信息。

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

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

// 初始化,不执行。
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

注意: 目前 typedefs 仅限于函数类型,我们期望这一点能有所改变。

因为 typedefs 是简单的别名,所以它提供了一种方法来检查任何函数的类型。比如:

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!
}

元数据

使用元数据在你的代码中添加一个附加的信息,元数据声明开始一个@,接着式对编译时常量的引用(例如deprecated)或对常量构造函数的调用。

所有Dart代码都可以使用两个注释:@deprecated@override。有关使用@override的示例,请参见 扩展类Extending a class。这是使用@deprecated注释的示例:

class Television {
  /// _Deprecated: 使用 [turnOn] 代替._
  @deprecated     // 标记为不推荐,直到下一个版本
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {...}
}

您可以定义自己的元数据注释。这是定义带有两个参数的@todo注释的示例:

library todo;

class Todo {
  final String who;
  final String what;

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

这是使用@todo批注的示例:

import 'todo.dart';

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

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

注释

Dart支持单行注释,多行注释和文档注释。

单行注释

单行注释以//开头。 Dart编译器会忽略//和行尾之间的所有内容。

void main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}

多行注释

多行注释以 /*开头,以*/结尾。 Dart编译器将忽略/**/之间的所有内容(除非注释为文档注释;请参阅下一节)。多行注释可以嵌套。

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

文档注释

文档注释是与 ////**开始多行或单行注释。 连续的行上使用 ///有一个多行文档注释同样的效果。

内部文档注释,Dart编译器忽略,除非它放在括号中的所有文本。 使用括号,可以参考类,方法,字段,顶级的变量,函数和参数。括号中的名称在文档程序元素的词法范围内解析

下面是与其他类和论点引用文档注释的例子:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
  String name;

  /// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

在生成的文档中, [food] 变成了指向 Food 类的 API 文档连接。

为了转换 Dart 代码并生成 HTML 文档,你可以使用 SDK 的 文档生成器。生成文档的示例,请参阅 Dart API 文档。关于如何组织你的文档,请参阅 文档注释准则

总结

此页面总结了Dart语言的常用功能。 更多功能正在实施,但我们预计,他们将不会破坏现有的代码。 欲了解更多信息,请参见 Dart language specificationEffective Dart.。

要了解更多关于Dart的核心库,看A Tour of the Dart Libraries.

参考链接

dart.dev/guides/lang…

stackoverflow.com/questions/5…