From c8891b4e501f35cef406b3ce938c2c7cfadd66bd Mon Sep 17 00:00:00 2001 From: hanyujie2002 Date: Sun, 4 Aug 2024 10:45:14 +0800 Subject: [PATCH] init translation of variable declaration (#86) --- .../zh/reference/Variable Declarations.md | 600 ++++++++++++------ 1 file changed, 392 insertions(+), 208 deletions(-) diff --git a/docs/documentation/zh/reference/Variable Declarations.md b/docs/documentation/zh/reference/Variable Declarations.md index ee0d045..d901fd4 100644 --- a/docs/documentation/zh/reference/Variable Declarations.md +++ b/docs/documentation/zh/reference/Variable Declarations.md @@ -1,33 +1,30 @@ --- -title: Variable Declaration +title: 变量声明 layout: docs permalink: /zh/docs/handbook/variable-declarations.html -oneline: How TypeScript handles variable declaration +oneline: TypeScript 如何处理变量声明 translatable: true --- -`let` and `const` are two relatively new concepts for variable declarations in JavaScript. -[As we mentioned earlier](/zh/docs/handbook/basic-types.html#a-note-about-let), `let` is similar to `var` in some respects, but allows users to avoid some of the common "gotchas" that users run into in JavaScript. +`let` 和 `const` 是 JavaScript 中用于变量声明的两个相对较新的概念。[正如我们之前提到的](/zh/docs/handbook/basic-types.html#a-note-about-let),`let` 在某些方面与 `var` 类似,但使用户能够避免在 JavaScript 中常见的一些“陷阱”。 -`const` is an augmentation of `let` in that it prevents re-assignment to a variable. +`const` 是对 `let` 的一种增强,它防止对变量进行重新赋值。 -With TypeScript being an extension of JavaScript, the language naturally supports `let` and `const`. -Here we'll elaborate more on these new declarations and why they're preferable to `var`. +作为 JavaScript 的扩展,TypeScript 自然支持 `let` 和 `const`。在这里,我们将更详细地介绍这些新的声明方式以及为什么它们比 `var` 更优秀。 -If you've used JavaScript offhandedly, the next section might be a good way to refresh your memory. -If you're intimately familiar with all the quirks of `var` declarations in JavaScript, you might find it easier to skip ahead. +如果你曾经不经意间使用过 JavaScript,下一节可能是一个很好的回顾方式。如果你熟悉 JavaScript 中 `var` 声明的所有怪异之处,你可以直接跳过这一部分。 -## `var` declarations +## `var` 声明 -Declaring a variable in JavaScript has always traditionally been done with the `var` keyword. +在 JavaScript 中,变量声明传统上是使用 `var` 关键字完成的。 ```ts var a = 10; ``` -As you might've figured out, we just declared a variable named `a` with the value `10`. +我们刚刚声明了一个名为 `a` 的变量,并赋予它值 `10`。 -We can also declare a variable inside of a function: +我们也可以在函数内部声明变量: ```ts function f() { @@ -37,7 +34,7 @@ function f() { } ``` -and we can also access those same variables within other functions: +我们还可以在其他函数中访问这些变量: ```ts function f() { @@ -49,12 +46,10 @@ function f() { } var g = f(); -g(); // returns '11' +g(); // 返回‘11’ ``` -In this above example, `g` captured the variable `a` declared in `f`. -At any point that `g` gets called, the value of `a` will be tied to the value of `a` in `f`. -Even if `g` is called once `f` is done running, it will be able to access and modify `a`. +在上面的示例中,`g` 捕获了在 `f` 中声明的变量 `a`。每当调用 `g` 时,`a` 的值都会与 `f` 中 `a` 的值相关联。即使在 `f` 执行完毕后调用 `g`,它仍然能够访问和修改 `a`。 ```ts function f() { @@ -67,17 +62,17 @@ function f() { return b; function g() { + a += 1; return a; } } -f(); // returns '2' +f(); // 返回‘2’ ``` -### Scoping rules +### 作用域规则 -`var` declarations have some odd scoping rules for those used to other languages. -Take the following example: +`var` 声明在作用域规则上有一些古怪的地方,对于那些习惯了其他语言的人来说可能会感到困惑。让我们来看一个示例: ```ts function f(shouldInitialize: boolean) { @@ -88,18 +83,13 @@ function f(shouldInitialize: boolean) { return x; } -f(true); // returns '10' -f(false); // returns 'undefined' +f(true); // 返回‘10’ +f(false); // 返回‘undefined’ ``` -Some readers might do a double-take at this example. -The variable `x` was declared _within the `if` block_, and yet we were able to access it from outside that block. -That's because `var` declarations are accessible anywhere within their containing function, module, namespace, or global scope - all which we'll go over later on - regardless of the containing block. -Some people call this _`var`-scoping_ or _function-scoping_. -Parameters are also function scoped. +有些读者可能会对这个例子感到困惑。变量 `x` 是在 `if` 块内部声明的,但我们却能从块外访问到它。这是因为 `var` 声明的作用域是在它们所在的函数、模块、命名空间或全局作用域内(我们稍后会讨论这些),而不受包含它的块的影响。有些人将这种行为称为 `var` 作用域或函数作用域。函数参数也遵循函数作用域的规则。 -These scoping rules can cause several types of mistakes. -One problem they exacerbate is the fact that it is not an error to declare the same variable multiple times: +这些作用域规则可能会导致多种类型的错误。其中一个问题是,多次声明同一个变量并不会报错: ```ts function sumMatrix(matrix: number[][]) { @@ -115,12 +105,11 @@ function sumMatrix(matrix: number[][]) { } ``` -Maybe it was easy to spot out for some experienced JavaScript developers, but the inner `for`-loop will accidentally overwrite the variable `i` because `i` refers to the same function-scoped variable. -As experienced developers know by now, similar sorts of bugs slip through code reviews and can be an endless source of frustration. +尽管对于一些经验丰富的 JavaScript 开发人员来说,这个问题可能很容易发现,内部的 `for` 循环意外地覆盖了变量 `i`,因为 `i` 是在同一个函数作用域内被引用的。正如经验丰富的开发人员所知,这种类型的 bug 很容易在代码审查时被忽视,并且可能成为一个持续的挫败感来源。 -### Variable capturing quirks +### 变量捕获的怪现象 -Take a quick second to guess what the output of the following snippet is: +让我们来猜猜下面这段代码的输出结果: ```ts for (var i = 0; i < 10; i++) { @@ -130,9 +119,9 @@ for (var i = 0; i < 10; i++) { } ``` -For those unfamiliar, `setTimeout` will try to execute a function after a certain number of milliseconds (though waiting for anything else to stop running). +对于不熟悉的人来说,`setTimeout` 会尝试在经过指定的毫秒数后执行一个函数(当然,要等待其他所有代码执行完毕)。 -Ready? Take a look: +准备好了吗?让我们看一下结果: ``` 10 @@ -147,8 +136,7 @@ Ready? Take a look: 10 ``` -Many JavaScript developers are intimately familiar with this behavior, but if you're surprised, you're certainly not alone. -Most people expect the output to be +许多 JavaScript 开发人员都对这种行为非常熟悉,但如果你感到惊讶,那你并不孤单。大多数人期望的输出应该是: ``` 0 @@ -163,20 +151,15 @@ Most people expect the output to be 9 ``` -Remember what we mentioned earlier about variable capturing? -Every function expression we pass to `setTimeout` actually refers to the same `i` from the same scope. +还记得我们之前提到的变量捕获吗?我们传递给 `setTimeout` 的每个函数表达式实际上都引用了同一个作用域内的同一个 `i`。 -Let's take a minute to consider what that means. -`setTimeout` will run a function after some number of milliseconds, _but only_ after the `for` loop has stopped executing; -By the time the `for` loop has stopped executing, the value of `i` is `10`. -So each time the given function gets called, it will print out `10`! +让我们花点时间考虑一下这意味着什么。`setTimeout` 将在经过一些毫秒数后运行一个函数,*但是只有在* `for` 循环停止执行之后;当 `for` 循环停止执行时,`i` 的值为 `10`。所以每次函数被调用时都会打印出 `10`! -A common work around is to use an IIFE - an Immediately Invoked Function Expression - to capture `i` at each iteration: +一个常见的解决方法是使用立即调用函数表达式(IIFE)来在每次迭代时捕获 `i`: ```ts for (var i = 0; i < 10; i++) { - // capture the current state of 'i' - // by invoking a function with its current value + // 通过调用一个带有当前值的函数来捕获‘i’的当前状态 (function (i) { setTimeout(function () { console.log(i); @@ -185,44 +168,40 @@ for (var i = 0; i < 10; i++) { } ``` -This odd-looking pattern is actually pretty common. -The `i` in the parameter list actually shadows the `i` declared in the `for` loop, but since we named them the same, we didn't have to modify the loop body too much. +这种看起来很奇怪的模式实际上是相当常见的。参数列表中的 `i` 确实遮蔽了在 `for` 循环中声明的 `i`,但由于我们把它们命名为相同的名称,我们就不需要太多地修改循环体了。 -## `let` declarations +## `let` 声明 -By now you've figured out that `var` has some problems, which is precisely why `let` statements were introduced. -Apart from the keyword used, `let` statements are written the same way `var` statements are. +你已经了解到 `var` 存在一些问题,这正是为什么引入了 `let` 语句。除了使用的关键字不同,`let` 语句的编写方式与 `var` 语句是一样的。 ```ts let hello = "Hello!"; ``` -The key difference is not in the syntax, but in the semantics, which we'll now dive into. +关键的区别不在于语法,而在于语义,我们现在就来探讨这个问题。 -### Block-scoping +### 块作用域 -When a variable is declared using `let`, it uses what some call _lexical-scoping_ or _block-scoping_. -Unlike variables declared with `var` whose scopes leak out to their containing function, block-scoped variables are not visible outside of their nearest containing block or `for`-loop. +当使用 `let` 声明一个变量时,它使用的是所谓的*词法作用域*或*块作用域*。与使用 `var` 声明的变量其作用域会扩散到它们所在的函数中不同,块作用域变量的作用域仅限于它们最近的包含块或 `for` 循环内部。 ```ts function f(input: boolean) { let a = 100; if (input) { - // Still okay to reference 'a' + // 仍然可以引用‘a’ let b = a + 1; return b; } - // Error: 'b' doesn't exist here + // 错误:这里‘b’不存在 return b; } ``` -Here, we have two local variables `a` and `b`. -`a`'s scope is limited to the body of `f` while `b`'s scope is limited to the containing `if` statement's block. +在这里,我们有两个局部变量 `a` 和 `b`。`a` 的作用域仅限于 `f` 的函数体,而 `b` 的作用域仅限于包含它的 `if` 语句块。 -Variables declared in a `catch` clause also have similar scoping rules. +在 `catch` 子句中声明的变量也有类似的作用域规则。 ```ts try { @@ -231,46 +210,41 @@ try { console.log("Oh well."); } -// Error: 'e' doesn't exist here +// 错误:这里‘e’不存在 console.log(e); ``` -Another property of block-scoped variables is that they can't be read or written to before they're actually declared. -While these variables are "present" throughout their scope, all points up until their declaration are part of their _temporal dead zone_. -This is just a sophisticated way of saying you can't access them before the `let` statement, and luckily TypeScript will let you know that. +块作用域变量的另一个特性是,在实际声明它们之前,你不能读写它们。虽然这些变量在整个作用域中都“存在”,但在声明之前的所有点都属于它们的*暂时性死区*。这只是一个复杂的方式来说明你不能在 `let` 语句之前访问它们,幸运的是,TypeScript 会提示你这种错误。 ```ts -a++; // illegal to use 'a' before it's declared; +a++; // 在声明之前使用‘a’是非法的; let a; ``` -Something to note is that you can still _capture_ a block-scoped variable before it's declared. -The only catch is that it's illegal to call that function before the declaration. -If targeting ES2015, a modern runtime will throw an error; however, right now TypeScript is permissive and won't report this as an error. +需要注意的是,你仍然可以在声明之前*捕获*一个块作用域变量。唯一的限制是在声明之前调用那个函数是非法的。如果目标是 ES2015,现代运行时会抛出一个错误;然而,现在 TypeScript 比较宽容,不会报告这种错误。 ```ts function foo() { - // okay to capture 'a' + // 捕获‘a’是可以的 return a; } -// illegal call 'foo' before 'a' is declared -// runtimes should throw an error here +// 在‘a’声明之前调用‘foo’是非法的 +// 运行时应该在这里抛出一个错误 foo(); let a; ``` -For more information on temporal dead zones, see relevant content on the [Mozilla Developer Network](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone_and_errors_with_let). +关于暂时性死区的更多信息,请参阅 [Mozilla 开发者网络](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let#暂时性死区)上的相关内容。 -### Re-declarations and Shadowing +### 重新声明和变量屏蔽 -With `var` declarations, we mentioned that it didn't matter how many times you declared your variables; you just got one. +在使用 `var` 声明时,我们提到无论你声明变量多少次,你只会得到一个变量。 ```ts function f(x) { var x; - var x; if (true) { var x; @@ -278,30 +252,27 @@ function f(x) { } ``` -In the above example, all declarations of `x` actually refer to the _same_ `x`, and this is perfectly valid. -This often ends up being a source of bugs. -Thankfully, `let` declarations are not as forgiving. +在上面的例子中,所有对 `x` 的声明实际上都指向*同一个*变量 `x`,而且这是完全有效的。这经常会导致 bug。幸运的是,`let` 声明不太宽容。 ```ts let x = 10; -let x = 20; // error: can't re-declare 'x' in the same scope +let x = 20; // 错误:无法在同一作用域内重新声明‘x’ ``` -The variables don't necessarily need to both be block-scoped for TypeScript to tell us that there's a problem. +这两个变量不一定都需要块作用域,TypeScript 也会告诉我们存在问题。 ```ts function f(x) { - let x = 100; // error: interferes with parameter declaration + let x = 100; // 错误:与参数声明冲突 } function g() { let x = 100; - var x = 100; // error: can't have both declarations of 'x' + var x = 100; // 错误:无法有两个‘x’的声明 } ``` -That's not to say that a block-scoped variable can never be declared with a function-scoped variable. -The block-scoped variable just needs to be declared within a distinctly different block. +这并不意味着块作用域变量永远不能与函数作用域变量同时声明。块作用域变量只需要在一个明显不同的块中声明即可。 ```ts function f(condition, x) { @@ -313,13 +284,11 @@ function f(condition, x) { return x; } -f(false, 0); // returns '0' -f(true, 0); // returns '100' +f(false, 0); // 返回‘0’ +f(true, 0); // 返回‘100’ ``` -The act of introducing a new name in a more nested scope is called _shadowing_. -It is a bit of a double-edged sword in that it can introduce certain bugs on its own in the event of accidental shadowing, while also preventing certain bugs. -For instance, imagine we had written our earlier `sumMatrix` function using `let` variables. +在更嵌套的作用域中引入一个新名称的行为被称为*变量屏蔽*。这是一把双刃剑,因为它既可能在发生意外变量屏蔽时引入某些 bug,同时也可以防止某些 bug。例如,想象一下,如果我们使用 `let` 变量来编写之前的 `sumMatrix` 函数。 ```ts function sumMatrix(matrix: number[][]) { @@ -335,16 +304,13 @@ function sumMatrix(matrix: number[][]) { } ``` -This version of the loop will actually perform the summation correctly because the inner loop's `i` shadows `i` from the outer loop. +这个循环的版本将正确执行求和,因为内部循环的 `i` 屏蔽了外部循环的 `i`。 -Shadowing should _usually_ be avoided in the interest of writing clearer code. -While there are some scenarios where it may be fitting to take advantage of it, you should use your best judgement. +为了编写更清晰的代码,*通常*应避免使用变量屏蔽。虽然有些情况下可能适合利用它,但你应该根据实际情况判断是否使用。 -### Block-scoped variable capturing +### 块作用域变量的捕获 -When we first touched on the idea of variable capturing with `var` declaration, we briefly went into how variables act once captured. -To give a better intuition of this, each time a scope is run, it creates an "environment" of variables. -That environment and its captured variables can exist even after everything within its scope has finished executing. +当我们首次接触使用 `var` 声明时,我们简要介绍了变量捕获的概念,以及变量在捕获后的行为。为了更好地理解,每当一个作用域运行时,它都会创建一个变量的“环境”。即使在作用域内的所有代码执行完毕后,这个环境和其中捕获的变量仍然存在。 ```ts function theCityThatAlwaysSleeps() { @@ -361,15 +327,11 @@ function theCityThatAlwaysSleeps() { } ``` -Because we've captured `city` from within its environment, we're still able to access it despite the fact that the `if` block finished executing. +因为我们从该环境中捕获了 `city`,所以尽管 `if` 块已经执行完毕,我们仍然能够访问它。 -Recall that with our earlier `setTimeout` example, we ended up needing to use an IIFE to capture the state of a variable for every iteration of the `for` loop. -In effect, what we were doing was creating a new variable environment for our captured variables. -That was a bit of a pain, but luckily, you'll never have to do that again in TypeScript. +回想一下之前的 `setTimeout` 示例,我们最终需要使用 IIFE 来捕获每次 `for` 循环迭代的变量状态。实际上,我们正在为捕获的变量创建一个新的变量环境。这有点麻烦,但幸运的是,在 TypeScript 中你将不再需要这样做。 -`let` declarations have drastically different behavior when declared as part of a loop. -Rather than just introducing a new environment to the loop itself, these declarations sort of create a new scope _per iteration_. -Since this is what we were doing anyway with our IIFE, we can change our old `setTimeout` example to just use a `let` declaration. +当 `let` 声明作为循环的一部分时,其行为与之前截然不同。它们不仅仅为循环本身引入一个新的环境,而是*在每次迭代中*创建一个新的作用域。由于这正是我们使用 IIFE 做的事情,我们可以将之前的 `setTimeout` 示例更改为只使用 `let` 声明。 ```ts for (let i = 0; i < 10; i++) { @@ -379,7 +341,7 @@ for (let i = 0; i < 10; i++) { } ``` -and as expected, this will print out +如预期,这将打印出: ``` 0 @@ -394,18 +356,17 @@ and as expected, this will print out 9 ``` -## `const` declarations +## `const` 声明 -`const` declarations are another way of declaring variables. +`const` 声明是声明变量的另一种方式。 ```ts const numLivesForCat = 9; ``` -They are like `let` declarations but, as their name implies, their value cannot be changed once they are bound. -In other words, they have the same scoping rules as `let`, but you can't re-assign to them. +它们与 `let` 声明类似,但是正如它们的名字所暗示的那样,一旦绑定了值,就无法更改它们。换句话说,它们具有与 `let` 相同的作用域规则,但不能对其重新赋值。 -This should not be confused with the idea that the values they refer to are _immutable_. +这不应与它们所引用的值是*不可变*的概念混淆。 ```ts const numLivesForCat = 9; @@ -414,69 +375,61 @@ const kitty = { numLives: numLivesForCat, }; -// Error +// 错误 kitty = { name: "Danielle", numLives: numLivesForCat, }; -// all "okay" +// 所有这些都是合法的 kitty.name = "Rory"; kitty.name = "Kitty"; kitty.name = "Cat"; kitty.numLives--; ``` -Unless you take specific measures to avoid it, the internal state of a `const` variable is still modifiable. -Fortunately, TypeScript allows you to specify that members of an object are `readonly`. -The [chapter on Interfaces](/zh/docs/handbook/interfaces.html) has the details. +除非你采取特定措施来避免,否则 `const` 变量的内部状态仍然是可修改的。幸运的是,TypeScript 允许你指定对象成员为 `readonly`。关于此内容的详细信息可以在[接口章节](/zh/docs/handbook/interfaces.html)中找到。 ## `let` vs. `const` -Given that we have two types of declarations with similar scoping semantics, it's natural to find ourselves asking which one to use. -Like most broad questions, the answer is: it depends. +考虑到我们有两种具有相似作用域语义的声明方式,很自然地会想知道该使用哪一种。像大多数广泛的问题一样,答案是:取决于情况。 -Applying the [principle of least privilege](https://wikipedia.org/wiki/Principle_of_least_privilege), all declarations other than those you plan to modify should use `const`. -The rationale is that if a variable didn't need to get written to, others working on the same codebase shouldn't automatically be able to write to the object, and will need to consider whether they really need to reassign to the variable. -Using `const` also makes code more predictable when reasoning about flow of data. +根据[最小权限原则](https://zh.wikipedia.org/zh-cn/最小权限原则),除了计划修改的声明之外,所有其他声明都应该使用 `const`。其理由是,如果一个变量不需要被写入,那么其他在同一代码库上工作的人不应该自动具有对该对象的写入权限,并且他们需要考虑是否真的需要重新分配变量。使用 `const` 还可以使代码在推理数据流时更加可预测。 -Use your best judgement, and if applicable, consult the matter with the rest of your team. +请根据自己的判断力进行选择,并在适当的情况下与团队中的其他成员进行讨论。 -The majority of this handbook uses `let` declarations. +本手册的大部分内容使用 `let` 声明。 -## Destructuring +## 解构 -Another ECMAScript 2015 feature that TypeScript has is destructuring. -For a complete reference, see [the article on the Mozilla Developer Network](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment). -In this section, we'll give a short overview. +TypeScript 还支持 ECMAScript 2015 的另一个特性,即解构。关于解构的完整参考,请参阅 [MDN 上的文章](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)。在本节中,我们将进行简要概述。 -### Array destructuring +### 数组解构 -The simplest form of destructuring is array destructuring assignment: +解构的最简单形式是数组解构赋值: ```ts let input = [1, 2]; let [first, second] = input; -console.log(first); // outputs 1 -console.log(second); // outputs 2 +console.log(first); // 输出 1 +console.log(second); // 输出 2 ``` -This creates two new variables named `first` and `second`. -This is equivalent to using indexing, but is much more convenient: +这将创建两个名为 `first` 和 `second` 的新变量。这相当于使用索引访问数组元素,但更加方便: ```ts first = input[0]; second = input[1]; ``` -Destructuring works with already-declared variables as well: +解构赋值也适用于已经声明的变量: ```ts -// swap variables +// 交换变量的值 [first, second] = [second, first]; ``` -And with parameters to a function: +还可以在函数参数中使用解构赋值: ```ts function f([first, second]: [number, number]) { @@ -486,32 +439,32 @@ function f([first, second]: [number, number]) { f([1, 2]); ``` -You can create a variable for the remaining items in a list using the syntax `...`: +使用 `...` 语法可以创建一个变量来存储列表中的剩余项: ```ts let [first, ...rest] = [1, 2, 3, 4]; -console.log(first); // outputs 1 -console.log(rest); // outputs [ 2, 3, 4 ] +console.log(first); // 输出 1 +console.log(rest); // 输出 [ 2, 3, 4 ] ``` -Of course, since this is JavaScript, you can just ignore trailing elements you don't care about: +当然,由于这是 JavaScript,你也可以忽略不需要的尾部元素: ```ts let [first] = [1, 2, 3, 4]; -console.log(first); // outputs 1 +console.log(first); // 输出 1 ``` -Or other elements: +或者忽略其他元素: ```ts let [, second, , fourth] = [1, 2, 3, 4]; -console.log(second); // outputs 2 -console.log(fourth); // outputs 4 +console.log(second); // 输出 2 +console.log(fourth); // 输出 4 ``` -### Tuple destructuring +### 元组解构 -Tuples may be destructured like arrays; the destructuring variables get the types of the corresponding tuple elements: +元组可以像数组一样进行解构;解构的变量会获得相应元组元素的类型: ```ts let tuple: [number, string, boolean] = [7, "hello", true]; @@ -519,29 +472,29 @@ let tuple: [number, string, boolean] = [7, "hello", true]; let [a, b, c] = tuple; // a: number, b: string, c: boolean ``` -It's an error to destructure a tuple beyond the range of its elements: +在解构时超出元组元素范围会导致错误: ```ts -let [a, b, c, d] = tuple; // Error, no element at index 3 +let [a, b, c, d] = tuple; // 错误,索引 3 处没有元素 ``` -As with arrays, you can destructure the rest of the tuple with `...`, to get a shorter tuple: +与数组一样,可以使用 `...` 解构剩余的元组部分,以获得较短的元组: ```ts let [a, ...bc] = tuple; // bc: [string, boolean] -let [a, b, c, ...d] = tuple; // d: [], the empty tuple +let [a, b, c, ...d] = tuple; // d: [],空元组 ``` -Or ignore trailing elements, or other elements: +也可以忽略尾随元素或其他元素: ```ts let [a] = tuple; // a: number let [, b] = tuple; // b: string ``` -### Object destructuring +### 对象解构 -You can also destructure objects: +你也可以对对象进行解构: ```ts let o = { @@ -552,52 +505,47 @@ let o = { let { a, b } = o; ``` -This creates new variables `a` and `b` from `o.a` and `o.b`. -Notice that you can skip `c` if you don't need it. +这将用 `o.a` 和 `o.b` 创建新的变量 `a` 和 `b`。如果你不需要 `c`,可以忽略它。 -Like array destructuring, you can have assignment without declaration: +与数组解构类似,你可以进行赋值而无需声明: ```ts ({ a, b } = { a: "baz", b: 101 }); ``` -Notice that we had to surround this statement with parentheses. -JavaScript normally parses a `{` as the start of block. +请注意,我们必须用括号将该语句括起来。JavaScript 通常将 `{` 解析为块的开始。 -You can create a variable for the remaining items in an object using the syntax `...`: +你可以使用 `...` 语法为对象中的剩余项创建一个变量: ```ts let { a, ...passthrough } = o; let total = passthrough.b + passthrough.c.length; ``` -#### Property renaming +#### 属性重命名 -You can also give different names to properties: +你也可以给属性指定不同的名称: ```ts let { a: newName1, b: newName2 } = o; ``` -Here the syntax starts to get confusing. -You can read `a: newName1` as "`a` as `newName1`". -The direction is left-to-right, as if you had written: +这里的语法开始变得混乱。你可以将 `a: newName1` 理解为“把 `a` 当作 `newName1`”。解构的方向是从左到右的,就好像你写成了: ```ts let newName1 = o.a; let newName2 = o.b; ``` -Confusingly, the colon here does _not_ indicate the type. -The type, if you specify it, still needs to be written after the entire destructuring: +令人困惑的是,这里的冒号*不*表示类型。如果你指定了类型的话,仍然需要将其写在整个解构之后: ```ts let { a: newName1, b: newName2 }: { a: string; b: number } = o; ``` -#### Default values +#### 默认值 -Default values let you specify a default value in case a property is undefined: +默认值允许你在属性为 `undefined` 时指定一个默认值: ```ts function keepWholeObject(wholeObject: { a: string; b?: number }) { @@ -605,13 +553,11 @@ function keepWholeObject(wholeObject: { a: string; b?: number }) { } ``` -In this example the `b?` indicates that `b` is optional, so it may be `undefined`. -`keepWholeObject` now has a variable for `wholeObject` as well as the properties `a` and `b`, even if `b` is undefined. +在这个例子中,`b?` 表示 `b` 是可选的,因此它可能是 `undefined`。`keepWholeObject` 现在有了一个包含属性 `a` 和 `b` 的变量 `wholeObject`,即使 `b` 是 `undefined`。 -## Function declarations +## 函数声明 -Destructuring also works in function declarations. -For simple cases this is straightforward: +解构也适用于函数声明。对于简单的情况,这很直观: ```ts type C = { a: string; b?: number }; @@ -620,8 +566,7 @@ function f({ a, b }: C): void { } ``` -But specifying defaults is more common for parameters, and getting defaults right with destructuring can be tricky. -First of all, you need to remember to put the pattern before the default value. +但是,在参数中指定默认值更常见,而使用解构来正确设置默认值可能会有些棘手。首先,你需要记住在默认值之前放置解构模式。 ```ts function f({ a = "", b = 0 } = {}): void { @@ -630,31 +575,24 @@ function f({ a = "", b = 0 } = {}): void { f(); ``` -> The snippet above is an example of type inference, explained earlier in the handbook. +> 上述片段是类型推断的示例,在本手册前面有解释。 -Then, you need to remember to give a default for optional properties on the destructured property instead of the main initializer. -Remember that `C` was defined with `b` optional: +然后,你需要记住在解构的属性(而不是主初始化器)上给出可选属性的默认值。请记住,`C` 被定义为 `b` 是可选的: ```ts function f({ a, b = 0 } = { a: "" }): void { // ... } -f({ a: "yes" }); // ok, default b = 0 -f(); // ok, default to { a: "" }, which then defaults b = 0 -f({}); // error, 'a' is required if you supply an argument +f({ a: "yes" }); // 可行,b 默认为 0 +f(); // 可行,默认为 { a: "" },然后 b 默认为 0 +f({}); // 错误,如果提供参数,则 'a' 是必需的 ``` -Use destructuring with care. -As the previous example demonstrates, anything but the simplest destructuring expression is confusing. -This is especially true with deeply nested destructuring, which gets _really_ hard to understand even without piling on renaming, default values, and type annotations. -Try to keep destructuring expressions small and simple. -You can always write the assignments that destructuring would generate yourself. +使用解构时请小心。正如前面的示例所示,除了最简单的解构表达式之外,其他任何表达式都会让人感到困惑。尤其是在深层嵌套的解构中,即使没有重命名、默认值和类型注解,也会变得非常难以理解。尽量保持解构表达式的简短和简单。你总是可以自己编写解构生成的赋值语句。 -## Spread +## 展开运算符 -The spread operator is the opposite of destructuring. -It allows you to spread an array into another array, or an object into another object. -For example: +展开运算符是解构的相反操作。它允许你将一个数组展开到另一个数组中,或将一个对象展开到另一个对象中。例如: ```ts let first = [1, 2]; @@ -662,34 +600,25 @@ let second = [3, 4]; let bothPlus = [0, ...first, ...second, 5]; ``` -This gives bothPlus the value `[0, 1, 2, 3, 4, 5]`. -Spreading creates a shallow copy of `first` and `second`. -They are not changed by the spread. +这将使 `bothPlus` 的值变为 `[0, 1, 2, 3, 4, 5]`。展开操作创建了 `first` 和 `second` 的浅拷贝。原始数组不会被改变。 -You can also spread objects: +你也可以展开对象: ```ts let defaults = { food: "spicy", price: "$$", ambiance: "noisy" }; let search = { ...defaults, food: "rich" }; ``` -Now `search` is `{ food: "rich", price: "$$", ambiance: "noisy" }`. -Object spreading is more complex than array spreading. -Like array spreading, it proceeds from left-to-right, but the result is still an object. -This means that properties that come later in the spread object overwrite properties that come earlier. -So if we modify the previous example to spread at the end: +现在 `search` 的值是 `{ food: "rich", price: "$$", ambiance: "noisy" }`。对象展开比数组展开更复杂。与数组展开类似,它从左到右进行展开,但结果仍然是一个对象。这意味着后面的属性会覆盖前面的属性。因此,如果我们修改上面的示例,在最后进行展开: ```ts let defaults = { food: "spicy", price: "$$", ambiance: "noisy" }; let search = { food: "rich", ...defaults }; ``` -Then the `food` property in `defaults` overwrites `food: "rich"`, which is not what we want in this case. +那么 `defaults` 中的 `food` 属性会覆盖 `food: "rich"`,这在这种情况下并不是我们想要的。 -Object spread also has a couple of other surprising limits. -First, it only includes an objects' -[own, enumerable properties](https://developer.mozilla.org/docs/Web/JavaScript/Enumerability_and_ownership_of_properties). -Basically, that means you lose methods when you spread instances of an object: +对象展开还有一些其他的限制。首先,它只包括对象[自身的可枚举属性](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)。基本上,这意味着在展开对象实例时会丢失方法: ```ts class C { @@ -698,9 +627,264 @@ class C { } let c = new C(); let clone = { ...c }; -clone.p; // ok -clone.m(); // error! +clone.p; // 正常 +clone.m(); // 报错! +``` + +其次,TypeScript 编译器不允许从泛型函数中展开类型参数。这个特性在未来的语言版本中有望实现。 + +## `using` 声明 + +`using` 声明是 JavaScript 即将推出的一个特性,它是 [Stage 3 显式资源管理](https://github.com/tc39/proposal-explicit-resource-management)提案的一部分。`using` 声明类似于 `const` 声明,但是它将绑定到声明的值的*生命周期*与变量的*作用域*结合起来。 + +当控制流离开包含 `using` 声明的块时,将执行声明值的 `[Symbol.dispose]()` 方法,从而允许该值执行清理操作: + +```ts +function f() { + using x = new C(); + doSomethingWith(x); +} // 调用 `x[Symbol.dispose]()` 方法 +``` + +在运行时,这大致相当于以下操作: + +```ts +function f() { + const x = new C(); + try { + doSomethingWith(x); + } + finally { + x[Symbol.dispose](); + } +} +``` + +`using` 声明在处理保存原生引用(如文件句柄)的 JavaScript 对象时非常有用,可以帮助避免内存泄漏。 + +```ts +{ + using file = await openFile(); + file.write(text); + doSomethingThatMayThrow(); +} // 即使抛出错误,`file` 也会被处理 +``` + +或者在跟踪等作用域操作中, + +```ts +function f() { + using activity = new TraceActivity("f"); // 跟踪函数入口 + // ... +} // 跟踪函数退出 + +``` + +与 `var`、`let` 和 `const` 不同,`using` 声明不支持解构。 + +### `null` 和 `undefined` + +需要注意的是,值可以是 `null` 或 `undefined`,在这种情况下,在块的末尾不会处理任何内容: + +```ts +{ + using x = b ? new C() : null; + // ... +} +``` + +大致相当于: + +```ts +{ + const x = b ? new C() : null; + try { + // ... + } + finally { + x?.[Symbol.dispose](); + } +} +``` + +这允许你在声明 `using` 声明时有条件地获取资源,而无需复杂的分支或重复。 + +### 定义可处理资源 + +你可以通过实现 `Disposable` 接口来指示你生成的类或对象是可处理的: + +```ts +// 来自默认库: +interface Disposable { + [Symbol.dispose](): void; +} + +// 用法: +class TraceActivity implements Disposable { + readonly name: string; + constructor(name: string) { + this.name = name; + console.log(`进入: ${name}`); + } + + [Symbol.dispose](): void { + console.log(`退出: ${name}`); + } +} + +function f() { + using _activity = new TraceActivity("f"); + console.log("Hello world!"); +} + +f(); +// 输出: +// 进入: f +// Hello world! +// 退出: f +``` + +## `await using` 声明 + +某些资源或操作可能需要以异步方式执行清理工作。为了适应这一点,[明确资源管理](https://github.com/tc39/proposal-explicit-resource-management)提案还引入了 `await using` 声明: + +```ts +async function f() { + await using x = new C(); +} // 调用 `await x[Symbol.asyncDispose]()` 方法 ``` -Second, the TypeScript compiler doesn't allow spreads of type parameters from generic functions. -That feature is expected in future versions of the language. +`await using` 声明在控制流离开包含块时调用并*等待*其值的 `[Symbol.asyncDispose]()` 方法。这允许进行异步清理,例如数据库事务执行回滚或提交,或文件流在关闭之前刷新任何待定写入存储的操作。 + +与 `await` 一样,`await using` 只能在 `async` 函数或方法中使用,或者在模块的顶层使用。 + +### 定义一个可异步处理的资源 + +就像 `using` 依赖于 `Disposable` 对象一样,`await using` 依赖于 `AsyncDisposable` 对象: + +```ts +// 来自默认库: +interface AsyncDisposable { + [Symbol.asyncDispose]: PromiseLike; +} + +// 用法: +class DatabaseTransaction implements AsyncDisposable { + public success = false; + private db: Database | undefined; + + private constructor(db: Database) { + this.db = db; + } + + static async create(db: Database) { + await db.execAsync("BEGIN TRANSACTION"); + return new DatabaseTransaction(db); + } + + async [Symbol.asyncDispose]() { + if (this.db) { + const db = this.db; + this.db = undefined; + if (this.success) { + await db.execAsync("COMMIT TRANSACTION"); + } + else { + await db.execAsync("ROLLBACK TRANSACTION"); + } + } + } +} + +async function transfer(db: Database, account1: Account, account2: Account, amount: number) { + using tx = await DatabaseTransaction.create(db); + if (await debitAccount(db, account1, amount)) { + await creditAccount(db, account2, amount); + } + // 如果在此行之前抛出异常,事务将回滚 + tx.success = true; + // 现在事务将提交 +} +``` + +### `await using` 与 `await` 的区别 + +`await using` 声明中的 `await` 关键字仅表示资源的*处理*是被 `await` 的。它*不会* `await` 值本身: + +```ts +{ + await using x = getResourceSynchronously(); +} // 执行 `await x[Symbol.asyncDispose]()` + +{ + await using y = await getResourceAsynchronously(); +} // 执行 `await y[Symbol.asyncDispose]()` +``` + +### `await using` 和 `return` + +需要注意的是,如果你在返回 `Promise` 的 `async` 函数中使用 `await using` 声明,并且没有首先 `await` 它,这种行为可能会出现一个小问题: + +```ts +function g() { + return Promise.reject("error!"); +} + +async function f() { + await using x = new C(); + return g(); // 缺少一个 `await` +} +``` + +因为返回的 promise 没有被 `await`,在 `await` 异步处理 `x` 的同时,可能会导致 JavaScript 运行时报告未处理的拒绝,因为没有订阅返回的 promise。这并不是 `await using` 才有的问题,同样的情况也会发生在使用 `try..finally` 的 `async` 函数中: + +```ts +async function f() { + try { + return g(); // 也会报告未处理的拒绝 + } + finally { + await somethingElse(); + } +} +``` + +为了避免这种情况,建议在可能返回 `Promise` 的情况下 `await` 返回值: + +```ts +async function f() { + await using x = new C(); + return await g(); +} +``` + +## `for` 和 `for..of` 语句中的 `using` 和 `await using` + +`using` 和 `await using` 都可以在 `for` 语句中使用: + +```ts +for (using x = getReader(); !x.eof; x.next()) { + // ... +} +``` + +在这种情况下,`x` 的生命周期仅限于整个 `for` 语句,并且只有在由于 `break`、`return`、`throw` 或循环条件为 false 而控制流离开循环时才会进行处理。 + +除了 `for` 语句之外,这两种声明也可以在 `for..of` 语句中使用: + +```ts +function * g() { + yield createResource1(); + yield createResource2(); +} + +for (using x of g()) { + // ... +} +``` + +在这里,`x` 在*每次循环迭代*结束时被处理,然后用下一个值重新初始化。这在通过生成器逐个产生资源时特别有用。 + +## 在旧版运行时中使用 `using` 和 `await using` + +只要你使用与 `Symbol.dispose`/`Symbol.asyncDispose` 兼容的 polyfill,比如最近的 NodeJS 版本中默认提供的 polyfill,就可以在目标是旧版 ECMAScript 版本时使用 `using` 和 `await using` 声明。