作为 JavaScript 开发人员学习 Dart
本指南旨在利用您在学习 Dart 时的 JavaScript 编程知识。它展示了两种语言的关键相似之处和差异,并介绍了 JavaScript 中不支持的 Dart 概念。作为一名 JavaScript 开发人员,Dart 应该感觉很熟悉,因为这两种语言共享许多概念。
与 JavaScript 一样,Dart 在事件循环上运行,因此这两种语言以类似的方式执行代码。例如,异步概念(如 JavaScript 中的 promise)和 async/await
语法非常相似。
Dart 是强类型语言,与 JavaScript 不同。如果您使用过 TypeScript 或 Flow,这将简化学习 Dart 的过程。如果您主要使用纯 JavaScript,这可能需要更多调整。通过强类型,Dart 在编译之前捕获了 JavaScript 代码中可能存在的许多错误。
Dart 默认启用空安全。JavaScript 不支持空安全。作为一名 JavaScript 开发人员,可能需要一段时间才能学习如何编写空安全代码,但权衡是更好地防止空引用异常,即使在编译 Dart 代码之前也能检测到这些异常。(从而避免在对 JavaScript 变量执行操作时发生的那些可怕的 TypeError
,这些变量结果为 null。)
约定和 linting
#JavaScript 和 Dart 都有 linting 工具来强制执行标准约定。虽然 JavaScript 提供了许多工具、标准和配置,但 Dart 有一套官方的布局和样式约定以及一个 linter 来简化合规性。Dart 分析器对代码进行 lint,并提供更多分析功能。要自定义项目的 lint 规则,请遵循 自定义静态分析 指示。
Dart 提供 dart fix
来查找和修复错误。
Dart 还提供了一个类似于 JavaScript 工具(如 Prettier)的代码格式化程序。要格式化任何 Dart 项目中的代码,请在命令行上运行 dart format
。Dart 和 Flutter 的 IDE 插件也提供了此功能。
Dart 支持在集合、参数或参数的逗号分隔列表中使用尾随逗号。当您添加尾随逗号时,格式化程序会将每个列表项放在单独的行上。当您认为您的列表将来可能会有更多项时,请添加尾随逗号。避免仅为格式化目的而添加尾随逗号。
JavaScript 仅在列表和映射文字中支持尾随逗号。
内置类型
#JavaScript 和 Dart 都将其数据分类为类型。每个变量都具有关联的类型。类型决定了变量可以存储的值的类型以及可以对这些值执行的操作。Dart 与 JavaScript 的不同之处在于它为每个表达式和变量分配了一个静态类型。静态类型预测变量的值或表达式的值的运行时类型。这意味着 Dart 应用程序具有健全的静态类型。
JavaScript 提供了原始类型 num
、string
和 boolean
以及 null
值,以及数组和 Map
类型。
Dart 支持以下内置类型
- 数字 (
num
、int
、double
) - 字符串 (
String
) - 布尔值 (
bool
) - 列表 (
List
,也称为数组) - 集合 (
Set
) - 映射 (
Map
) - 符号 (
Symbol
) - 值
null
(Null
)
要了解更多信息,请查看 内置类型 在 Dart 语言之旅 中。
Dart 中所有非 Null
类型都是 Object 的子类型。所有值也是对象。Dart 不使用像 JavaScript 这样的“原始类型”。相反,Dart 规范化或规范化数字、布尔值和 null
值。这意味着只有一个数值为 1
的 int
值存在。
例如:等于运算符 ==
和 identical()
方法对于相同数值类型的返回值为 true
。请查看以下代码中的示例。
var a = 2;
var b = 1 + 1;
print(a == b); // Prints true
print(identical(a, b)); // Prints true; only one "2" object exists
基本类型
#本节介绍 Dart 如何表示来自 JavaScript 的原始类型。
数字
#Dart 有三种数据类型用于保存数字
num
- 等效于 JavaScript 中的通用数字类型。
int
- 没有小数部分的数值。
double
- 任何 64 位(双精度)浮点数。
Dart API 将所有这些类型都包含为类。int
和 double
类型都以 num
作为父类。
由于 Dart 将数字视为对象,因此数字可以将自己的实用函数公开为对象方法。您不需要使用其他对象来将函数应用于数字。
例如,要将 double
四舍五入为整数
let rounded = Math.round(2.5);
var rounded = 2.5.round();
字符串
#Dart 中的字符串与 JavaScript 中的字符串类似。要编写字符串字面量,请将其用单引号 ('
) 或双引号 ("
) 括起来。大多数 Dart 开发人员使用单引号,但语言没有强制执行任何标准。如果您不想转义字符串中的单引号,请使用双引号。
var a = 'This is a string.';
转义特殊字符
#要在字符串中包含具有其他含义的字符,例如用于字符串插值的 $
,您必须转义该字符。在 Dart 中转义特殊字符与 JavaScript 和大多数其他语言类似。要转义特殊字符,请在该字符之前加上反斜杠字符 (\
)。
以下代码显示了一些示例。
final singleQuotes = 'I\'m learning Dart'; // I'm learning Dart
final doubleQuotes = "Escaping the \" character"; // Escaping the " character
final dollarEscape = 'The price is \$3.14.'; // The price is $3.14.
final backslashEscape = 'The Dart string escape character is \\.';
final unicode = '\u{1F60E}'; // 😎, Unicode scalar U+1F60E
字符串插值
#JavaScript 支持模板字面量。这些使用反引号 (`
) 字符分隔符,原因如下
- 允许多行字符串
- 用嵌入式表达式插值字符串
- 创建称为标记模板的特殊结构
在 Dart 中,您不需要将字符串用反引号括起来来连接字符串或在字符串字面量中使用插值。
要了解更多信息,请查看 Dart 语言之旅中的 字符串。
与 JavaScript 模板字面量一样,您可以使用 ${<expression>}
语法将表达式插入字符串字面量中。Dart 使用此语法,并允许您在表达式使用单个标识符时省略花括号。
var food = 'bread';
var str = 'I eat $food'; // I eat bread
var str = 'I eat ${food}'; // I eat bread
字符串连接和多行声明
#在 JavaScript 中,您可以使用模板字面量定义多行字符串。Dart 有两种方法可以定义多行字符串。
- 使用隐式字符串连接:Dart 会连接任何相邻的字符串字面量,即使它们跨越多行。dart
final s1 = 'String ' 'concatenation' " even works over line breaks.";
- 使用多行字符串字面量:当在字符串两侧使用三个引号(单引号或双引号)时,字面量可以跨越多行。dart
final s2 = ''' You can create multiline strings like this one. '''; final s3 = """ This is also a multiline string.""";
相等性
#当两个字符串包含相同的代码单元序列时,Dart 认为它们相等。要确定两个字符串是否具有相同的序列,请使用等于运算符 (==
)。
final s1 = 'String '
'concatenation'
" works even over line breaks.";
assert(s1 ==
'String concatenation works even over '
'line breaks.');
布尔值
#Dart 和 JavaScript 中的布尔值都表示二进制条件。这两个值表示一个值或表达式是 true
还是 false
。您可以使用字面量 true
和 false
返回这些值,或者使用表达式(如 x < 5
或 y == null
)生成它们。
let isBananaPeeled = false;
var isBananaPeeled = false;
变量
#Dart 中的变量与 JavaScript 中的变量类似,但有两个例外
- 每个变量都有一个类型。
- Dart 在块级范围内对所有变量进行作用域限定,类似于 JavaScript 中的
let
和const
变量。
Dart 变量通过以下两种方式之一获取其类型
- 声明:在声明中编写的类型。
- 推断:用于初始化变量的表达式。根据 约定,当分析器可以推断类型时,使用
var
或final
。
// Declare and initialize a variable at once
let name = "bob";
// Declare a variable with a specific type
// when you don't provide an initial value
String name;
// Declare and initialize a variable
// at the same time and Dart infers
// the type
var name = 'bob';
变量只能接受其类型的值。
var name = 'bob';
name = 5; // Forbidden, as `name` has type `String`.
如果您没有提供初始值或显式类型,Dart 会将变量的类型推断为万能类型 dynamic
。
与 JavaScript 变量类似,您可以将任何值分配给使用 dynamic
类型的 Dart 变量。
// Declare a variable
let name;
// Initialize the variable
name = "bob";
// Declare a variable without a type or assigned value
// and Dart infers the 'dynamic' type
var name;
// Initialize the variable and the type remains `dynamic`
name = 'bob';
name = 5; // Allowed, as `name` has type `dynamic`.
Final 和 const
#JavaScript 和 Dart 都使用变量修饰符。两者都使用 const
,但在 const
的工作方式上有所不同。JavaScript 使用 const
的地方,Dart 使用 final
。
当您向 Dart 变量添加 final
或向 JavaScript 变量添加 const
时,您必须在其他代码读取其值之前初始化该变量。初始化后,您无法更改这些变量的引用。
当 Dart 使用 const
时,它指的是在编译时创建的特殊值。Dart 使用有限的表达式来创建这些不可变的值。这些表达式 *不能* 产生副作用。在这些条件下,编译器可以预测常量变量或表达式的精确值,而不仅仅是其静态类型。
final String name;
// Cannot read name here, not initialized.
if (useNickname) {
name = "Bob";
} else {
name = "Robert";
}
print(name); // Properly initialized here.
在 Dart 中,*常量变量必须包含常量值*。非常量变量可以包含常量值,您也可以将这些常量值标记为 const
。
var foo = const [];
// foo is not constant, but the value it points to is.
// You can reassign foo to a different list value,
// but its current list value cannot be altered.
const baz = []; // Equivalent to `const []`
同样,类可以有自己的 const
构造函数,这些构造函数会生成不可变的实例。
您不能修改 JavaScript 或 Dart 中的 const
变量。JavaScript 允许您修改 const
对象的字段,但 Dart 不允许。
要了解更多信息,请参阅 类 部分。
空安全
#与 JavaScript 不同,Dart 支持空安全。在 Dart 中,所有类型默认情况下都是不可空的。这对 Dart 开发人员有利,因为 Dart 在编写代码时会捕获空引用异常,而不是在运行时。
可空类型与非空类型
#以下代码示例中的所有变量都不能为null
。
// In null-safe Dart, none of these can ever be null.
var i = 42; // Inferred to be an int.
String name = getFileName();
final b = Foo(); // Foo() invokes a constructor
要指示变量可能具有null
值,请在其类型声明中添加?
int? aNullableInt = null;
其他类型声明(如函数声明)也是如此
String? returnsNullable() {
return random.nextDouble() < 0.5
? 'Sometimes null!'
: null;
}
String returnsNonNullable() {
return 'Never null!';
}
空感知运算符
#Dart 支持多种运算符来处理可空性。与 JavaScript 一样,Dart 支持空赋值运算符 (??=
)、空合并运算符 (??
) 和可选链运算符 (?.
)。这些运算符的工作方式与 JavaScript 相同。
!运算符
#在可空变量或表达式可能不为空的情况下,可以使用 (!
) 运算符告诉编译器抑制任何编译时错误。将此运算符放在表达式之后。
不要将其与 Dart 的非 (!
) 运算符混淆,该运算符使用相同的符号,但放在表达式之前。
int? a = 5;
int b = a; // Not allowed.
int b = a!; // Allowed.
在运行时,如果 a 恰好为null
,则会发生运行时错误。
与?.
运算符类似,在访问对象上的属性或方法时使用!
运算符
myObject!.someProperty;
myObject!.someMethod();
如果myObject
在运行时为null
,则会发生运行时错误。
函数
#虽然 Dart 的函数与 JavaScript 中的对应函数的工作方式大致相同,但它们确实有一些额外的功能,以及在声明它们时的一些细微的语法差异。与 JavaScript 类似,您几乎可以在任何地方声明函数,无论是在顶层、作为类字段还是在局部范围内。
// On the top level
function multiply(a, b) {
return a * b;
}
// As a class field
class Multiplier {
multiply(a, b) {
return a * b;
}
}
// In a local scope
function main() {
function multiply(a, b) {
return a * b;
}
console.log(multiply(3, 4));
}
// On the top level
int multiply(a, b) {
return a * b;
}
// As a class field
class Multiplier {
multiply(a, b) {
return a * b;
}
}
// In a local scope
main() {
multiply(a, b) {
return a * b;
}
print(multiply(3, 4));
}
箭头语法
#Dart 和 JavaScript 都支持箭头语法 (=>
),但它们支持的方式不同。在 Dart 中,只有当函数包含单个表达式或 return 语句时,才能使用箭头语法。
例如,以下isNoble
函数是等效的
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
参数
#在 JavaScript 中,所有参数可以是位置参数。默认情况下,Dart要求您将所有参数作为参数传递给函数。
int multiply(int a, int b) {
return a * b;
}
main() {
multiply(3, 5); // Valid. All parameters are provided.
multiply(3); // Invalid. All parameters must be provided.
}
这可以在两种情况下改变
- 位置参数被标记为可选。
- 参数是命名的,并且没有被标记为必需的。
要定义可选位置参数,请将它们括在方括号中,紧随任何必需的位置参数之后。您不能在可选参数之后使用必需参数。
由于空安全,可选位置参数必须具有默认值或被标记为可空。要了解更多信息,请参阅前面关于空安全的部分。
以下代码包含一个有效的和两个无效的定义可选位置参数的函数示例。
// Valid: `b` has a default value of 5. `c` is marked as nullable.
multiply(int a, [int b = 5, int? c]) {
...
}
// Invalid: a required positional parameter follows an optional one.
multiply(int a, [int b = 5], int c) {
...
}
// Invalid: Neither optional positional parameter has a default
// value or has been flagged as nullable.
multiply(int a, [int b, int c]) {
...
}
以下示例展示了如何使用可选参数调用函数
multiply(int a, [int b = 5, int? c]) {
...
}
main() {
// All are valid function calls.
multiply(3);
multiply(3, 5);
multiply(3, 5, 7);
}
Dart 支持**命名参数**。与位置参数不同,命名参数不需要按照定义的顺序提供。您可以通过名称来引用它们。默认情况下,这些参数是可选的,除非它们被标记为必需。命名参数通过用大括号包围来定义。您可以将命名参数与必需的位置参数结合使用——在这种情况下,命名参数始终放在位置参数之后。当使用命名参数调用函数时,通过在传递的值前面加上参数的名称,并用冒号分隔来传递值。例如,f(namedParameter: 5)
。
同样,在空安全的情况下,未标记为必需的命名参数要么需要有默认值,要么需要被标记为可空。
以下代码定义了一个带命名参数的函数
// Valid:
// - `a` has been flagged as required
// - `b` has a default value of 5
// - `c` is marked as nullable
// - Named parameters follow the positional one
multiply(bool x, {required int a, int b = 5, int? c}) {
...
}
以下示例使用命名参数调用函数
// All are valid function calls.
// Beyond providing the required positional parameter:
multiply(false, a: 3); // Only provide required named parameters
multiply(false, a: 3, b: 9); // Override default value of `b`
multiply(false, c: 9, a: 3, b: 2); // Provide all named parameters out of order
一等函数
#JavaScript 和 Dart 将函数视为一等公民。这意味着 Dart 将函数视为任何其他对象。例如,以下代码展示了如何将函数作为参数传递给另一个函数
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);
匿名函数
#JavaScript 和 Dart 都支持匿名函数,或没有名称的函数。与命名函数一样,您可以像传递任何其他值一样传递匿名函数。例如,将匿名函数存储在变量中,将其作为参数传递给另一个函数,或从另一个函数返回它们。
JavaScript 有两种方法来声明匿名函数
- 使用标准函数表达式
- 使用箭头语法
同样,Dart 也有两种方法来声明匿名函数。两者都以类似于 JavaScript 箭头表达式的方式工作。Dart 的匿名函数不支持常规函数表达式附带的额外功能。例如,JavaScript 支持函数表达式充当构造函数,或创建对 this 的自定义绑定。
要了解更多信息,请参阅 类 部分。
// A regular function expression
// assigned to a variable
let funcExpr = function(a, b) {
return a * b;
}
// The same anonymous function
// expressed as an arrow
// function with curly braces.
let arrowFuncExpr = (a, b) => {
return a * b;
}
// An arrow function with only
// one return statement as
// its contents does not
// require a block.
let arrowFuncExpr2 = (a, b) => a * b;
// Assign an anonymous function
// to a variable.
var blockFunc =
optionalCallback ?? (int a, int b) {
return a * b;
};
// For an expression with only a return statement,
// you can use the arrow syntax:
var singleFunc = (int a, int b) => a * b;
与 JavaScript 一样,您可以将匿名函数传递给其他函数。开发人员在使用数组和列表的 map
函数时,经常会传递匿名函数。
// returns [4, 5, 6]
[1, 2, 3].map(e => e + 3);
// returns [5, 7, 9]
[1, 2, 3].map(e => {
e *= 2;
return e + 3;
});
// returns [4, 5, 6]
[1, 2, 3].map((e) => e + 3).toList();
// returns [5, 7, 9]
var list2 = [1, 2, 3].map((e) {
e *= 2;
return e + 3;
}).toList();
生成器函数
#两种语言都支持 生成器函数。这些函数返回一个可迭代的项目集合,这些项目经过计算以避免不必要的操作。
要在 Dart 中编写生成器函数,请在函数参数后添加 sync*
关键字,并返回一个 Iterable
。使用 yield
关键字将项目添加到最终的可迭代对象中,或使用 yield*
添加整个项目集。
以下示例演示了如何编写基本的生成器函数
function* naturalsTo(n) {
let k = 0;
while (k < n) {
yield k++;
}
}
// Returns [0, 1, 2, 3, 4]
for (let value of naturalsTo(5)) {
console.log(value);
}
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) {
yield k++;
}
}
// Returns an iterable with [0, 1, 2, 3, 4]
print(naturalsTo(5).toList());
function* doubleNaturalsTo(n) {
let k = 0;
while (k < n) {
yield* [k, k];
k++;
}
}
// Returns [0, 0, 1, 1, 2, 2]
for (let value of doubleNaturalsTo(3)) {
console.log(value);
}
Iterable<int> doubleNaturalsTo(int n) sync* {
int k = 0;
while (k < n) {
yield* [k, k];
k++;
}
}
// Returns an iterable with [0, 0, 1, 1, 2, 2]
print(doubleNaturalsTo(3));
您还可以定义异步生成器函数,它们返回流而不是可迭代对象。在即将到来的 异步 部分中了解更多信息。
语句
#本节介绍 JavaScript 和 Dart 之间语句的差异。
控制流(if/else、for、while、switch)
#大多数控制语句的工作方式与其 JavaScript 对应部分相同。有些语句对 集合 具有额外的用途。
迭代
#虽然 JavaScript 和 Dart 都有 for-in
循环,但它们的行为有所不同。
JavaScript 的 for-in
循环遍历对象的属性。要遍历 JavaScript 可迭代对象的元素,您必须使用 for-of
或 Array.forEach()
。Dart 的 for-in
循环的工作方式类似于 JavaScript 的 for-of
。
以下示例演示了如何遍历集合并打印出每个元素
for (const element of list) {
console.log(element);
}
for (final element in list) {
print(element);
}
开关
#在 switch
语句中使用 continue
时,您可以将其与放在 case 上的标签组合使用
switch (testEnum) {
case TestEnum.A:
print('A');
continue b;
b:
case TestEnum.B:
print('B');
break;
}
运算符
#Dart 和 JavaScript 都包含预定义的运算符。两种语言都不支持添加新的运算符。Dart 支持使用 operator
关键字重载一些现有的运算符。例如
class Vector {
final double x;
final double y;
final double z;
Vector(this.x, this.y, this.z);
Vector operator +(Vector other) => Vector(
x + other.x,
y + other.y,
z + other.z,
);
Vector operator *(double scalar) => Vector(
x * scalar,
y * scalar,
z * scalar,
);
}
算术运算符
#两种语言的相等和关系运算符几乎相同,如以下表格所示
含义 | JavaScript 运算符 | Dart 运算符 |
---|---|---|
加 | + | + |
减 | - | - |
一元减,也称为取反 | -expr | -expr |
乘 | * | * |
除 | / | / |
除法返回整数结果 | ~/ | |
获取整数除法的余数(模运算) | % | % |
x = x + 1 (表达式值为x + 1 ) | ++x | ++x |
x = x + 1 (表达式值为x ) | x++ | x++ |
x = x - 1 (表达式值为x - 1 ) | --x | --x |
x = x - 1 (表达式值为x ) | x-- | x-- |
例如
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
a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1
a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0
a = 0;
b = --a; // Decrement a before b gets its value.
assert(a == b); // -1 == -1
a = 0;
b = a--; // Decrement a AFTER b gets its value.
assert(a != b); // -1 != 0
您可能已经注意到,Dart 还包含一个~/
运算符(称为截断除法运算符),它将一个 double 除以另一个 double 并输出一个向下取整的整数
assert(25 == 50.4 ~/ 2);
assert(25 == 50.6 ~/ 2);
assert(25 == 51.6 ~/ 2);
相等和关系运算符
#两种语言的相等和关系运算符的工作方式相同
含义 | JavaScript 运算符 | Dart 运算符 |
---|---|---|
严格相等 | === | == |
抽象相等 | == | |
严格不相等 | !== | != |
抽象不相等 | != | |
大于 | > | > |
小于 | < | < |
大于或等于 | >= | >= |
小于或等于 | <= | <= |
==
和!=
JavaScript 运算符没有等效项。
例如
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);
类型测试运算符
#两种语言的测试运算符的实现略有不同
含义 | JavaScript 运算符 | Dart 运算符 |
---|---|---|
类型转换 | x as T | |
如果对象具有指定的类型,则为真 | x instanceof T | x is T |
如果对象缺少指定的类型,则为真 | !(x instanceof T) | x is! T |
如果obj
实现了T
指定的接口,则obj is T
的结果为真。例如,obj is Object?
始终为真。
使用类型转换运算符(as
)来确保一个值具有特定的类型。如果您知道该对象将具有该类型,编译器可以使用它。
例如
(person as Employee).employeeNumber = 4204583;
如果您不知道该对象是否为T
类型,则使用is T
在使用该对象之前检查类型。
在 Dart 中,局部变量的类型在 if 语句的范围内更新。实例变量并非如此。
if (person is Employee) {
person.employeeNumber = 4204583;
}
逻辑运算符
#您可以使用逻辑运算符反转或组合布尔表达式。两种语言的逻辑运算符相同。
含义 | JavaScript 运算符 | Dart 运算符 |
---|---|---|
反转下一个表达式(将 false 更改为 true,反之亦然) | !x | !x |
逻辑或 | || | || |
逻辑与 | && | && |
JavaScript 允许在需要布尔值的地方使用任何值。然后它会将这些值转换为 true
或 false
。JavaScript 将空字符串和数字 0
视为“假值”。Dart 允许在条件中以及作为逻辑运算符的操作数使用 bool
值。
例如
if (!done && (col == 0 || col == 3)) {
// ...Do something...
}
按位和移位运算符
#您可以使用按位和移位运算符对整数的各个位进行操作。两种语言的运算符几乎相同,如下表所示
含义 | JavaScript 运算符 | Dart 运算符 |
---|---|---|
按位与 | & | & |
按位或 | | | | |
按位异或 | ^ | ^ |
一元按位补码(0 变为 1;1 变为 0) | ~expr | ~expr |
左移 | << | << |
右移 | >> | >> |
无符号右移 | >>> | >>> |
例如
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
assert((-value >> 4) == -0x03); // Shift right
assert((value >>> 4) == 0x02); // Unsigned shift right
assert((-value >>> 4) > 0); // Unsigned shift right
条件运算符
#Dart 和 JavaScript 都包含一个条件运算符 (?:
) 用于评估表达式。一些开发人员将其称为三元运算符,因为它接受三个操作数。由于 Dart 还有另一个运算符 ([]=
) 接受三个操作数,因此将此运算符 (?:
) 称为条件运算符。此运算符适用于类似于 if-else 语句的表达式。
let visibility = isPublic ? "public" : "private";
final visibility = isPublic ? 'public' : 'private';
赋值运算符
#使用 (=
) 运算符赋值。
// Assign value to a
a = value;
此运算符还有一个空感知变体 (??=
)。
要了解更多信息,请参阅 空赋值 运算符部分。
JavaScript 和 Dart 包含运算符,用于计算并为表达式中的变量分配新值。这些赋值运算符使用右侧值和变量初始值作为操作数。
下表列出了这些赋值运算符
运算符 | 描述 |
---|---|
= | 赋值 |
+= | 加法赋值 |
-= | 减法赋值 |
*= | 乘法赋值 |
/= | 除法赋值 |
~/= | 截断除法赋值 |
%= | 余数(模)赋值 |
>>>= | 无符号右移赋值 |
^= | 按位异或赋值 |
<<= | 左移赋值 |
>>= | 右移赋值 |
&= | 按位与赋值 |
|= | 按位或赋值 |
JavaScript 不支持 ~/=
赋值运算符。
var a = 5;
a *= 2; // Multiply `a` by 2 and assign the result back to a.
print(a); // `a` is now 10.
级联 (..
运算符)
#Dart 允许您在一个对象上链接多个方法调用、属性赋值或两者。Dart 将此称为级联,并使用级联语法 (..
) 来执行此操作。
JavaScript 缺少此语法。
以下示例展示了使用级联语法在一个新构造的对象上链接多个方法
var animal = Animal() // Sets multiple properties and methods
..name = "Bob"
..age = 5
..feed()
..walk();
print(animal.name); // "Bob"
print(animal.age); // 5
要使第一个级联语法为空感知,请将其写为 ?..
。
var result = maybePerson
?..employment = employer
..salary = salary;
如果 maybePerson
值为 null
,Dart 会忽略整个级联。
集合
#本节介绍 Dart 中的一些集合类型,并将它们与 JavaScript 中的类似类型进行比较。
列表
#Dart 以与 JavaScript 数组相同的方式编写列表文字。Dart 将列表括在方括号中,并用逗号分隔值。
// Initialize list and specify full type
final List<String> list1 = <String>['one', 'two', 'three'];
// Initialize list using shorthand type
final list2 = <String>['one', 'two', 'three'];
// Dart can also infer the type
final list3 = ['one', 'two', 'three'];
以下代码示例概述了您可以在 Dart List
上执行的基本操作。以下示例展示了如何使用索引运算符从 List
中检索值。
final fruits = <String>['apple', 'orange', 'pear'];
final fruit = fruits[1];
使用 add
方法将值添加到 List
的末尾。使用 addAll
方法添加另一个 List
final fruits = <String>['apple', 'orange', 'pear'];
fruits.add('peach');
fruits.addAll(['kiwi', 'mango']);
使用 insert
方法在特定位置插入值。使用 insertAll
方法在特定位置插入另一个 List
final fruits = <String>['apple', 'orange', 'pear'];
fruits.insert(0, 'peach');
fruits.insertAll(0, ['kiwi', 'mango']);
使用索引和赋值运算符的组合更新 List
中的值
final fruits = <String>['apple', 'orange', 'pear'];
fruits[2] = 'peach';
使用以下方法之一从 List
中删除项目
final fruits = <String>['apple', 'orange', 'pear'];
// Remove the value 'pear' from the list.
fruits.remove('pear');
// Removes the last element from the list.
fruits.removeLast();
// Removes the element at position 1 from the list.
fruits.removeAt(1);
// Removes the elements with positions greater than
// or equal to start (1) and less than end (3) from the list.
fruits.removeRange(1, 3);
// Removes all elements from the list that match the given predicate.
fruits.removeWhere((fruit) => fruit.contains('p'));
使用 length
获取 List
中的值数量
final fruits = <String>['apple', 'orange', 'pear'];
assert(fruits.length == 3);
使用 isEmpty
检查 List
是否为空
var fruits = [];
assert(fruits.isEmpty);
使用 isNotEmpty
检查 List
是否不为空
final fruits = <String>['apple', 'orange', 'pear'];
assert(fruits.isNotEmpty);
填充
#Dart 的 List
类包含一种方法来创建一个列表,其中每个项目都具有相同的值。此 filled
构造函数创建一个大小为 n
的固定长度列表,其中包含一个默认值。以下示例创建一个包含 3 个项目的列表
final list1 = List.filled(3, 'a'); // Creates: [ 'a', 'a', 'a' ]
- 默认情况下,您无法向此列表添加或删除元素。要允许此列表添加或删除元素,请在参数列表的末尾添加
, growable: true
。 - 您可以使用索引值访问和更新此列表中的元素。
生成
#Dart List
类包含一种方法来创建一个递增值的列表。此 generate
构造函数创建一个大小为 n
的固定长度列表,其中包含一个用于构建元素值的模板。此模板将索引作为参数。
// Creates: [ 'a0', 'a1', 'a2' ]
final list1 = List.generate(3, (index) => 'a$index');
集合
#与 JavaScript 不同,Dart 支持使用字面量定义 Set
。Dart 定义集合的方式与列表相同,但使用花括号而不是方括号。集合是无序的集合,只包含唯一项。Dart 使用哈希码强制执行这些项的唯一性,这意味着对象需要哈希值才能存储在 Set
中。
以下代码片段展示了如何初始化 Set
final abc = {'a', 'b', 'c'};
创建空集合的语法乍一看可能令人困惑,因为指定空花括号 ({}
) 会导致创建空 Map
。要创建空 Set
,请在 {}
声明之前加上类型参数或将 {}
赋值给类型为 Set
的变量
final names = <String>{};
// Set<String> names = {}; // This works, too.
// final names = {}; // Creates an empty map, not a set.
以下示例概述了您可以在 Dart Set
上执行的基本操作。
使用 add
方法将值添加到 Set
。使用 addAll
方法添加多个值
final fruits = {'apple', 'orange', 'pear'};
fruits.add('peach');
fruits.addAll(['kiwi', 'mango']);
在 Set
中使用以下方法之一从集合中删除内容
final fruits = {'apple', 'orange', 'pear'};
// Remove the value 'pear' from the set.
fruits.remove('pear');
// Remove all elements in the supplied list from the set.
fruits.removeAll(['orange', 'apple']);
// Removes all elements from the list that match the given predicate.
fruits.removeWhere((fruit) => fruit.contains('p'));
使用 length
获取 Set
中的值的数量
final fruits = {'apple', 'orange', 'pear'};
assert(fruits.length == 3);
使用 isEmpty
检查 Set
是否为空
var fruits = <String>{};
assert(fruits.isEmpty);
使用 isNotEmpty
检查 Set
是否不为空
final fruits = {'apple', 'orange', 'pear'};
assert(fruits.isNotEmpty);
映射
#Dart 中的 Map
类型类似于 JavaScript 中的 Map
类型。两种类型都将键与值关联。如果所有键都具有相同的类型,则键可以是任何对象类型。此规则也适用于值。每个键最多出现一次,但您可以多次使用相同的值。
Dart 基于哈希表构建字典。这意味着键需要是可哈希的。每个 Dart 对象都包含一个哈希值。
考虑这些使用字面量创建的简单 Map
示例
final gifts = {
'first': 'partridge',
'second': 'turtle doves',
'fifth': 'golden rings'
};
final nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
以下代码示例概述了您可以在 Dart Map
上执行的基本操作。以下示例展示了如何使用索引运算符从 Map
中检索值。
final gifts = {'first': 'partridge'};
final gift = gifts['first'];
使用containsKey
方法检查Map
是否包含键。
final gifts = {'first': 'partridge'};
assert(gifts.containsKey('fifth'));
使用索引赋值运算符([]=
)在Map
中添加或更新条目。如果Map
中还没有包含该键,Dart 会添加该条目。如果键存在,Dart 会更新其值。
final gifts = {'first': 'partridge'};
gifts['second'] = 'turtle'; // Gets added
gifts['second'] = 'turtle doves'; // Gets updated
使用addAll
方法添加另一个Map
。使用addEntries
方法向Map
添加其他条目。
final gifts = {'first': 'partridge'};
gifts['second'] = 'turtle doves';
gifts.addAll({
'second': 'turtle doves',
'fifth': 'golden rings',
});
gifts.addEntries([
MapEntry('second', 'turtle doves'),
MapEntry('fifth', 'golden rings'),
]);
使用remove
方法从Map
中删除条目。使用removeWhere
方法删除所有满足给定测试的条目。
final gifts = {'first': 'partridge'};
gifts.remove('first');
gifts.removeWhere((key, value) => value == 'partridge');
使用length
获取Map
中键值对的数量。
final gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);
使用isEmpty
检查Map
是否为空。
final gifts = {};
assert(gifts.isEmpty);
使用isNotEmpty
检查Map
是否不为空。
final gifts = {'first': 'partridge'};
assert(gifts.isNotEmpty);
不可修改
#纯 JavaScript 不支持不可变性。Dart 提供了多种方法来使集合(如数组、集合或字典)不可变。
- 如果集合是编译时常量,并且不应该被修改,使用
const
关键字const fruits = <String>{'apple', 'orange', 'pear'};
- 将
Set
分配给final
字段,这意味着Set
本身不必是编译时常量。这确保了该字段不能被另一个Set
覆盖,但它仍然允许修改Set
的大小或内容final fruits = <String>{'apple', 'orange', 'pear'};
- 使用
unmodifiable
构造函数创建集合类型的最终版本(如以下示例所示)。这将创建一个无法改变其大小或内容的集合
final _set = Set<String>.unmodifiable(['a', 'b', 'c']);
final _list = List<String>.unmodifiable(['a', 'b', 'c']);
final _map = Map<String, String>.unmodifiable({'foo': 'bar'});
扩展运算符
#与 JavaScript 一样,Dart 支持使用扩展运算符(...
)和空感知扩展运算符(...?
)将列表嵌入到另一个列表中。
var list1 = [1, 2, 3];
var list2 = [0, ...list1]; // [0, 1, 2, 3]
// When the list being inserted could be null:
list1 = null;
var list2 = [0, ...?list1]; // [0]
这也适用于集合和映射
// Spread operator with maps
var map1 = {'foo': 'bar', 'key': 'value'};
var map2 = {'foo': 'baz', ...map1}; // {foo: bar, key: value}
// Spread operator with sets
var set1 = {'foo', 'bar'};
var set2 = {'foo', 'baz', ...set1}; // {foo, baz, bar}
集合 if/for
#在 Dart 中,for
和 if
关键字在处理集合时具有额外的功能。
集合if
语句仅在满足指定条件时才包含列表文字中的项目
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet',
];
它对映射和集合的工作方式类似。
集合for
语句允许将多个项目映射到另一个列表中
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i',
]; // [#0, #1, #2, #3]
这也适用于映射和集合。
异步
#与 JavaScript 一样,Dart 虚拟机 (VM) 运行一个单一的事件循环,处理所有 Dart 代码。这意味着这里也适用异步的类似规则。所有代码都是同步运行的,但您可以根据使用异步工具的方式以不同的顺序处理它。以下是一些这些构造以及它们与 JavaScript 对应部分的关系。
期货
#Future
是 Dart 中的 JavaScript Promise
的版本。两者都是异步操作的结果,该操作将在稍后某个时间点解析。
Dart 或 Dart 包中的函数可能会返回一个 Future
,而不是它们表示的值,因为该值可能在稍后才会可用。
以下示例显示了处理 future 在 Dart 中的工作方式与 promise 在 JavaScript 中的工作方式相同。
const httpResponseBody = func();
httpResponseBody.then(value => {
console.log(
`Promise resolved to a value: ${value}`
);
});
Future<String> httpResponseBody = func();
httpResponseBody.then((String value) {
print('Future resolved to a value: $value');
});
类似地,future 可能会像 promise 一样失败。捕获错误的工作方式也相同
httpResponseBody
.then(...)
.catch(err => {
console.log(
"Promise encountered an error before resolving."
);
});
httpResponseBody
.then(...)
.catchError((err) {
print(
'Future encountered an error before resolving.'
);
});
您也可以创建 future。要创建 Future
,请定义并调用 async
函数。当您需要一个需要成为 Future
的值时,请将函数转换为以下示例中的形式。
String str = 'String Value';
Future<String> strFuture = Future<String>.value(str);
Async/Await
#如果您熟悉 JavaScript 中的 promise,那么您可能也熟悉 async
/await
语法。此语法在 Dart 中是相同的:函数标记为 async
,而 async
函数始终返回 Future
。如果函数返回 String
并标记为 async
,则它返回 Future<String>
。如果它不返回任何内容,但它是 async
,则它返回 Future<void>
。
以下示例显示了如何编写 async
函数
// Returns a Promise of a string,
// as the method is async
async fetchString() {
// Typically some other async
// operations would be done here.
return "String Value";
}
// Returns a future of a string,
// as the method is async
Future<String> fetchString() async {
// Typically some other async
// operations would be done here.
return 'String Value';
}
按如下方式调用此 async
函数
Future<String> stringFuture = fetchString();
stringFuture.then((String str) {
print(str); // 'String Value'
});
使用 await
关键字获取 future 的值。与 JavaScript 中一样,这消除了在 Future
上调用 then
以获取其值的需要,并且它允许您以更类似同步的方式编写异步代码。与 JavaScript 中一样,仅在 async
上下文(例如另一个 async
函数)中才能等待 future。
以下示例显示了如何等待 future 获取其值
// We can only await futures within an async context.
Future<void> asyncFunction() async {
var str = await fetchString();
print(str); // 'String Value'
}
要了解有关 Future
和 async
/await
语法的更多信息,请参阅 异步编程 代码实验室。
流
#Dart 异步工具箱中的另一个工具是 Stream
。虽然 JavaScript 有自己的流概念,但 Dart 的流更类似于 Observable
,就像在常用的 rxjs
库中一样。如果您碰巧熟悉此库,那么 Dart 的流应该很熟悉。
对于不熟悉这些概念的人:Stream
基本上就像 Future
,但具有多个值分布在时间上,就像事件总线一样。您的代码可以监听流,并且它可以完成或达到失败状态。
监听
#要监听流,请调用其 listen
方法并提供回调方法。每当流发出值时,Dart 都会调用此方法
Stream<int> stream = ...
stream.listen((int value) {
print('A value has been emitted: $value');
});
listen
方法包含可选的回调函数,用于处理错误或流完成时的操作。
stream.listen(
(int value) { ... },
onError: (err) {
print('Stream encountered an error! $err');
},
onDone: () {
print('Stream completed!');
},
);
listen
方法返回一个 StreamSubscription
实例,您可以使用它停止监听流。
StreamSubscription subscription = stream.listen(...);
subscription.cancel();
这不是监听流的唯一方法。类似于 Future
的 async
/await
语法,您可以在 async
上下文中将流与 for-in
循环结合使用。for
循环会为每个发出的项调用回调方法,并在流完成或出错时结束。
Future<int> sumStream(Stream<int> stream) async {
var sum = 0;
await for (final value in stream) {
sum += value;
}
return sum;
}
当以这种方式监听流时发生错误时,错误将在包含 await
关键字的行中重新抛出。您可以使用 try-catch
语句处理此错误。
try {
await for (final value in stream) { ... }
} catch (err) {
print('Stream encountered an error! $err');
}
创建流
#与 Future
一样,您有几种不同的方法来创建流。Stream
类具有用于从 Future
或 Iterable
创建流的实用程序构造函数,或用于创建以定时间隔发出值的流的构造函数。要了解更多信息,请参阅 Stream
API 页面。
StreamController
#实用程序类 StreamController
可以创建和控制流。它的 stream 属性公开它控制的流。它的方法提供了向该流添加事件的方法。
例如,add
方法可以发出新项,而 close
方法可以完成流。
以下示例展示了流控制器的基本用法。
var listeners = 0;
StreamController<int>? controller;
controller = StreamController<int>(
onListen: () {
// Emit a new value every time the stream gets a new listener.
controller!.add(listeners++);
// Close the stream after the fifth listener.
if (listeners > 5) controller.close();
}
);
// Get the stream for the stream controller
var stream = controller.stream;
// Listen to the stream
stream.listen((int value) {
print('$value');
});
异步生成器
#异步生成器函数可以创建流。这些函数类似于同步生成器函数,但使用 async*
关键字并返回 Stream
。
在异步生成器函数中,yield
关键字将给定值发出到流中。但是,yield*
关键字与流而不是其他可迭代对象一起使用。这允许将来自其他流的事件发出到此流。在以下示例中,该函数在新发出的流完成时继续。
Stream<int> asynchronousNaturalsTo(int n) async* {
var k = 0;
while (k < n) yield k++;
}
Stream<int> stream = asynchronousNaturalsTo(5);
// Prints each of 0 1 2 3 4 in succession.
stream.forEach(print(value));
在 异步编程 文档中了解有关 futures、流和其他异步功能的更多信息。
类
#从表面上看,Dart 中的类类似于 JavaScript 中的类,尽管 JavaScript 类从技术上讲更像是原型包装器。在 Dart 中,类是语言的标准功能。本节介绍在 Dart 中定义和使用类以及它们与 JavaScript 的区别。
"this" 上下文
#Dart 中的 this
关键字比 JavaScript 中的更直接。在 Dart 中,您不能将函数绑定到 this
,并且 this
从不依赖于执行上下文(如 JavaScript 中那样)。在 Dart 中,this
仅在类中使用,并且始终引用当前实例。
构造函数
#本节讨论 Dart 中的构造函数与 JavaScript 中的构造函数有何不同。
标准构造函数
#标准类构造函数看起来非常像 JavaScript 构造函数。在 Dart 中,constructor
关键字被完整的类名替换,并且所有参数必须显式类型化。在 Dart 中,new
关键字曾经是创建类实例所必需的,但现在是可选的,并且不再推荐使用它。
class Point {
final double x;
final double y;
Point(double x, double y) : this.x = x, this.y = y { }
}
// Create a new instance of the Point class
Point p = Point(3, 5);
初始化列表
#使用初始化列表来编写你的构造函数。将初始化列表插入构造函数的参数和主体之间。
class Point {
...
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
...
}
构造函数参数
#在构造函数中编写代码来分配类字段可能感觉像创建样板代码,因此 Dart 有些语法糖,称为 初始化参数 来简化此操作
class Point {
double x;
double y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
// Create a new instance of the Point class
Point p = Point(3, 5);
与函数类似,构造函数可以选择接受位置参数或命名参数
class Point {
...
// With an optional positioned parameter
Point(this.x, [this.y = 5]);
// With named parameters
Point({ required this.y, this.x = 5 });
// With both positional and named parameters
Point(int x, int y, { boolean multiply }) {
...
}
...
}
命名构造函数
#与 JavaScript 不同,Dart 允许类具有多个构造函数,方法是允许你为它们命名。你可以选择只有一个未命名的构造函数,任何额外的构造函数都必须命名
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
double x = 0;
double y = 0;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
常量构造函数
#要启用不可变类实例,请使用 const
构造函数。具有 const
构造函数的类只能具有 final
实例变量。
class ImmutablePoint {
final double x, y;
const ImmutablePoint(this.x, this.y);
}
构造函数重定向
#你可以从其他构造函数调用构造函数,以防止代码重复或为参数添加额外的默认值
class Point {
double x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(double x) : this(x, 0);
}
工厂构造函数
#当你不需要创建新的类实例时,可以使用工厂构造函数。一个例子是在返回缓存实例时。
class Logger {
static final Map<String, Logger> _cache =
<String, Logger>{};
final String name;
// Factory constructor that returns a cached copy,
// or creates a new one if it is not yet available.
factory Logger(String name) {
return _cache.putIfAbsent(
name, () => _cache[name] ??= Logger._internal(name);
}
// Private constructor for internal use only
Logger._internal(this.name);
}
方法
#在 Dart 和 JavaScript 中,方法都充当为对象提供行为的函数。
function doSomething() { // This is a function
// Implementation..
}
class Example {
doSomething() { // This is a method
// Implementation..
}
}
void doSomething() { // This is a function
// Implementation..
}
class Example {
void doSomething() { // This is a method
// Implementation..
}
}
扩展类
#Dart 允许类扩展另一个类,就像 JavaScript 一样。
class Animal {
int eyes;
Animal(this.eyes);
makeNoise() {
print('???');
}
}
class Cat extends Animal {
Cat(): super(2);
@override
makeNoise() {
print('Meow');
}
}
Animal animal = Cat();
print(animal.eyes); // 2
animal.makeNoise(); // Meow
当覆盖父类中的方法时,请使用 @override
注解。虽然此注解是可选的,但它表明覆盖是故意的。如果该方法实际上没有覆盖超类方法,Dart 分析器会显示警告。
可以使用 super
关键字调用正在被覆盖的父方法
class Cat extends Animal {
...
@override
makeNoise() {
print('Meow');
super.makeNoise();
}
}
Animal animal = Cat();
animal.makeNoise(); // Meow
// ???
类作为接口
#与 JavaScript 一样,Dart 没有单独的接口定义。但是,与 JavaScript 不同,所有类定义都充当接口;你可以使用 implements
关键字将类实现为接口。
当类被实现为接口时,它的公共 API 必须由新类实现。与 extends
不同,它的方法和字段实现不会与新类共享。虽然一个类只能扩展一个类,但你可以同时实现多个接口,即使实现类已经扩展了另一个类。
class Consumer {
consume() {
print('Eating food...');
}
}
class Cat implements Consumer {
consume() {
print('Eating mice...');
}
}
Consumer consumer = Cat();
consumer.consume(); // Eating mice
当实现接口时,不能调用 super
方法,因为方法体没有被继承
class Cat implements Consumer {
@override
consume() {
print('Eating mice...');
super.consume();
// Invalid. The superclass `Object` has no `consume` method.
}
}
抽象类和方法
#要确保类只能被扩展或实现其接口,但禁止构造任何实例,请将其标记为 abstract
。
标记为 abstract
的类可以具有抽象方法,这些方法不需要主体,而是在扩展类或实现其接口时需要实现
abstract class Consumer {
consume();
}
// Extending the full class
class Dog extends Consumer {
consume() {
print('Eating cookies...');
}
}
// Just implementing the interface
class Cat implements Consumer {
consume() {
print('Eating mice...');
}
}
Consumer consumer;
consumer = Dog();
consumer.consume(); // Eating cookies...
consumer = Cat();
consumer.consume(); // Eating mice...
混入
#Mixin 用于在类之间共享功能。您可以使用 mixin 的字段和方法在类中,使用它们的功能就好像它们是类的一部分一样。一个类可以使用多个 mixin。当多个类共享相同的功能时,这很有帮助,而无需彼此继承或共享共同的祖先。
使用 with
关键字将一个或多个用逗号分隔的 mixin 添加到类中。
JavaScript 没有等效的关键字。JavaScript 可以使用 Object.assign
将其他对象合并到现有对象中,在实例化之后。
以下示例展示了 JavaScript 和 Dart 如何实现类似的行为
class Animal {}
// Defining the mixins
class Flyer {
fly = () => console.log('Flaps wings');
}
class Walker {
walk = () => console.log('Walks on legs');
}
class Bat extends Animal {}
class Goose extends Animal {}
class Dog extends Animal {}
// Composing the class instances with
// their correct functionality.
const bat =
Object.assign(
new Bat(),
new Flyer()
);
const goose =
Object.assign(
new Goose(),
new Flyer(),
new Walker()
);
const dog =
Object.assign(
new Dog(),
new Walker()
);
// Correct calls
bat.fly();
goose.fly();
goose.walk();
dog.walk();
// Incorrect calls
bat.walk(); // `bat` lacks the `walk` method
dog.fly(); // `dog` lacks the `fly` method
abstract class Animal {}
// Defining the mixins
class Flyer {
fly() => print('Flaps wings');
}
class Walker {
walk() => print('Walks on legs');
}
class Bat extends Animal with Flyer {}
class Goose extends Animal with Flyer, Walker {}
class Dog extends Animal with Walker {}
// Correct calls
Bat().fly();
Goose().fly();
Goose().walk();
Dog().walk();
// Incorrect calls
Bat().walk(); // Not using the Walker mixin
Dog().fly(); // Not using the Flyer mixin
或者,您可以用 mixin
替换 class
关键字,以防止 mixin 被用作普通类
mixin Walker {
walk() => print('Walks legs');
}
// Not possible, as Walker is no longer a class.
class Bat extends Walker {}
由于您可以使用多个 mixin,因此当它们在同一个类上使用时,它们可能具有彼此重叠的方法或字段。它们甚至可以与使用它们的类或该类的超类重叠。它们添加到类的顺序很重要。
举个例子
class Bird extends Animal with Consumer, Flyer {
当在 Bird
的实例上调用方法时,Dart 从它自己的类 Bird
开始,该类优先于其他实现。如果 Bird
没有实现,则检查 Flyer
,然后检查 Consumer
,直到找到实现。父类 Animal
最后检查。
扩展
#扩展类、实现接口或使用 mixin 都会在受影响的类可编辑时起作用。但是,有时扩展已经存在的类或作为另一个库或 Dart SDK 的一部分的类很有用。
在这些情况下,Dart 提供了为现有类编写扩展的能力。
例如,以下对 Dart SDK 中的 String
类的扩展允许解析整数
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
为了使扩展可用,它必须存在于同一个文件中,或者必须导入它的文件。
使用方法如下
import 'string_apis.dart'; // Import the file the extension is in
var age = '42'.parseInt(); // Use the extension method.
Getter 和 Setter
#Dart 中的 getter 和 setter 的工作方式与它们的 JavaScript 对应物完全相同
class Person {
_age = 0;
get age() {
return this._age;
}
set age(value) {
if (value < 0) {
throw new Error(
'age cannot be negative'
);
}
this._age = value;
}
}
var person = new Person();
person.age = 10;
console.log(person.age);
class Person {
int _age = 0;
int get age {
return _age;
}
set age(int value) {
if (value < 0) {
throw ArgumentError(
'Age cannot be negative'
);
}
_age = value;
}
}
void main() {
var person = Person();
person.age = 10;
print(person.age);
}
公有和私有成员
#与 JavaScript 一样,Dart 没有访问修饰符关键字:默认情况下所有类成员都是公共的。
JavaScript 将在 EcmaScript 标准的下一个实用修订版中包含私有类成员。因此,对此的实现已经在各种浏览器和运行时中可用了一段时间。
要在 JavaScript 中使类成员私有,请在其名称前加上一个井号(或哈希)符号 (#
)。
class Animal {
eyes; // Public field
#paws; // Private field
#printEyes() { // Private method
print(this.eyes);
}
printPaws() { // Public method
print(this.#paws);
}
}
在 Dart 中,要将类成员设为私有,请在其名称前加上下划线 (_
)。
class Animal {
int eyes; // Public field
int _paws; // Private field
void _printEyes() { // Private method
print(this.eyes);
}
void printPaws() { // Public method
print(this._paws);
}
}
JavaScript 使用井号作为约定。Dart 的编译器强制使用下划线来实现此功能。
Dart 将私有成员设为对库私有,而不是对类私有。这意味着您可以从同一库中的代码访问私有成员。默认情况下,Dart 将对私有类成员的访问限制在同一文件中的代码。要将库的范围扩展到一个文件之外,请添加 part
指令。如果可能,避免使用 part
。将使用 part
保留给代码生成器。
延迟变量
#要指示 Dart 在稍后时间初始化类字段,请将 late
关键字分配给这些类字段。这些类字段将保持不可为空。当变量不需要立即观察或访问,并且可以在以后初始化时执行此操作。这与将字段标记为可为空不同。
(不可为空)
late
字段不能在以后分配空值。(不可为空)
late
字段在初始化之前访问时会抛出运行时错误。应避免这种情况。
class PetOwner {
final String name;
late final Pet _pet;
PetOwner(this.name, String petName) {
// Cyclic object graph, cannot set _pet before owner exists.
_pet = Pet(petName, this);
}
Pet get pet => _pet;
}
class Pet {
final String name;
final PetOwner owner;
Pet(this.name, this.owner);
}
仅当不清楚的代码导致编译器无法确定代码是否已初始化变量时,才对局部变量使用 late
。
doSomething(int n, bool capture) {
late List<Foo> captures;
if (capture) captures = [];
for (var i = 0; i < n; i++) {
var foo = something(i);
if (capture) captures.add(foo);
}
}
在前面的示例中,编译器不知道如果 capture
为真,是否要分配 captures
。使用 late
会将正常的“已分配”检查延迟到运行时。
泛型
#虽然 JavaScript 不提供泛型,但 Dart 提供泛型以提高类型安全性并减少代码重复。
泛型方法
#您可以将泛型应用于方法。要定义泛型类型参数,请将其放在方法名称后的尖括号 < >
之间。然后,您可以在方法中使用此类型作为返回类型或在方法的参数中使用。
Map<Object?, Object?> _cache = {};
T cache<T>(T value) => (_cache[value] ??= value) as T;
通过用逗号分隔多个泛型类型来定义它们。
// Defining a method with multiple generics.
T transform<T, Q>(T param1, Q param2) {
...
}
// Calling the method with explicitly defined types.
transform<int, String>(5, 'string value');
// Types are optional when the analyzer can infer them.
transform(5, 'string value');
泛型类
#泛型也可以应用于类。您可以在调用构造函数时包含要使用的类型。这使您可以根据特定类型定制可重用类。
在以下示例中,Cache
类缓存特定类型。
class Cache<T> {
T getByKey(String key) {}
void setByKey(String key, T value) {}
}
// Creating a cache for strings
var stringCache = Cache<String>(); // stringCache has type Cache<String>
stringCache.setByKey('Foo', 'Bar'); // Valid, setting a string value.
stringCache.setByKey('Baz', 5); // Invalid, int type does not match generic.
如果您省略类型声明,则运行时类型将变为 Cache<dynamic>
,并且对 setByKey
的两次调用都是有效的。
限制泛型
#您可以使用泛型来使用 extends
将代码限制为一组类型。这确保您的类使用扩展特定类型的泛型类型实例化。
class NumberManager<T extends num> {
...
}
// Valid.
var manager = NumberManager<int>();
var manager = NumberManager<double>();
// Invalid, String nor its parent classes extend num.
var manager = NumberManager<String>();
泛型字面量
#Map
、Set
和 List
字面量可以接受类型参数。当 Dart 无法推断类型或无法正确推断类型时,这很有帮助。
例如,List
类有一个泛型定义:class List<E>
。类型参数 E
指的是列表内容的类型。通常,此类型会自动推断,并在一些 List
类的成员类型中使用。(例如,它的第一个 getter 返回类型为 E
的值。)在定义 List
字面量时,可以显式定义泛型类型,如下所示
// Automatic type inference
var objList = [5, 2.0]; // Type: List<num>
// Explicit type definition:
var objList = <Object>[5, 2.0]; // Type: List<Object>
// Sets work identically:
var objSet = <Object>{5, 2.0};
这对于 Map
也是如此,Map
也使用泛型定义它们的键和值类型(class Map<K, V>
)
// Automatic type inference
var map = {
'foo': 'bar'
}; // Type: Map<String, String>
// Explicit type definition:
var map = <String, Object>{
'foo': 'bar'
}; // Type: Map<String, Object>
文档注释
#常规注释在 Dart 中的工作方式与在 JavaScript 中相同。使用 //
注释掉它后面的所有内容,直到该行的末尾,可以使用 /* ... */
来注释跨越多行的内容。
除了常规注释之外,Dart 还具有 文档注释,它们与 dart doc
协同工作:一个用于生成 Dart 包的 HTML 文档的原生工具。将文档注释放在所有公共成员声明之上被认为是最佳实践。
通过使用三个斜杠而不是两个斜杠(///
)来定义文档注释
/// The number of characters in this chunk when unsplit.
int get length => ...
下一步
#本指南介绍了 Dart 和 JavaScript 之间的主要区别。此时,请考虑阅读 Dart 文档。您也可以阅读 Flutter 文档。Flutter 使用 Dart 构建,是一个开源框架,使用 Dart 从单个代码库构建原生编译的多平台应用程序。这些文档提供了有关该语言和入门实用方法的深入信息。
一些可能的下一步
- 语言之旅,以了解更多关于 Dart 语言的信息
- 核心库文档,以了解 Dart 的核心库
- Dart 代码实验室,提供动手实践,学习各种主题
- 有效 Dart,了解编写 Dart 代码时的常见约定和指南