作为 JavaScript 开发人员学习 Dart
本指南旨在利用您在学习 Dart 时所掌握的 JavaScript 编程知识。它展示了两种语言的主要相似之处和差异,并介绍了 JavaScript 不支持的 Dart 概念。作为一名 JavaScript 开发人员,Dart 应该会感觉非常熟悉,因为这两种语言都共享许多概念。
与 JavaScript 一样,Dart 也在事件循环上运行,因此两种语言以类似的方式执行代码。例如,像 Future(JavaScript 中的 Promise)和 async/await
语法这样的异步概念非常相似。
与 JavaScript 不同,Dart 是强类型语言。如果您使用过 TypeScript 或 Flow,这将简化学习 Dart 的过程。如果您主要使用纯 JavaScript,则可能需要更多调整。通过强类型,Dart 可以捕获许多 JavaScript 代码中可能存在的编译前错误。
默认情况下,Dart 启用空安全。JavaScript 不支持空安全。作为一名 JavaScript 开发人员,可能需要一段时间才能学会如何编写空安全代码,但这样做的好处是可以更好地防止在编译 Dart 代码之前就检测到的空引用异常。(从而避免了在对事实证明为空的 JavaScript 变量执行操作时发生的那些可怕的 TypeError
。)
约定和 lint
#JavaScript 和 Dart 都具有 lint 工具来强制执行标准约定。虽然 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 中所有非 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 中,只有当函数包含单个表达式或返回语句时,才能使用箭头语法。
例如,以下 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 对充当构造函数的函数表达式或创建对此的自定义绑定的支持。
要了解更多信息,请参阅类部分。
// 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
#在 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 还包含一个 ~/
运算符(称为截断除法运算符),它将一个双精度浮点数相除并输出一个向下取整的整数。
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 | |
如果对象具有指定的类型,则为 true | x instanceof T | x is T |
如果对象缺少指定的类型,则为 true | !(x instanceof T) | x is! T |
如果 obj
实现了 T
指定的接口,则 obj is T
的结果为 true。例如,obj is Object?
始终为 true。
使用类型转换运算符 (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
视为“falsy”值。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
#Future
是 Dart 中 JavaScript Promise
的版本。两者都是稍后解析的异步操作的结果。
Dart 或 Dart 包中的函数可能会返回 Future
,而不是它们表示的值,因为该值可能要到稍后才能使用。
以下示例表明,在 Dart 中处理 future 与在 JavaScript 中处理 promise 的方式相同。
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 中一样,等待 future 仅在 async
上下文(例如另一个 async
函数)中才可能。
以下示例演示如何等待 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));
在异步编程文档中了解有关 future、流和其他异步功能的更多信息。
类
#从表面上看,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
构造函数。具有 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...
混入
#混入用于在类之间共享功能。你可以在类中使用混入的字段和方法,就像它们是该类的一部分一样使用它们的功能。一个类可以使用多个混入。当多个类共享相同的功能时,这会有所帮助,而无需彼此继承或共享共同的祖先。
使用 with
关键字向类添加一个或多个逗号分隔的混入。
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 Walker {
walk() => print('Walks legs');
}
// Not possible, as Walker is no longer a class.
class Bat extends Walker {}
由于你可以使用多个混入,因此当在同一类上使用时,它们可以彼此具有重叠的方法或字段。它们甚至可以与使用它们的类或该类的超类重叠。将它们添加到类中的顺序很重要。
举例说明
class Bird extends Animal with Consumer, Flyer {
当在 Bird
的实例上调用方法时,Dart 会首先从其自身的类 Bird
开始查找,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 字段不能在稍后分配 null 值。
(不可为空) 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
为 true,编译器不知道是否分配 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
也是如此,它也使用泛型定义其键和值类型 (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 教程,用于涵盖各种主题的应用实践
- Effective Dart,以了解编写 Dart 代码时常用的约定和指南
除非另有说明,否则本网站上的文档反映的是 Dart 3.6.0。页面最后更新于 2024-12-16。 查看源代码 或 报告问题。