犀牛书第七版学习笔记:let、const和 var 声明与赋值

本文详细介绍了JavaScript中let、const和var声明的区别,重点讲解了let和const的块级作用域、暂时性死区(Temporal Dead Zone, TDZ)、变量提升以及在循环中的应用。同时,强调了最佳实践,推荐使用const优先,let次之,避免使用var以提高代码可读性和可维护性。" 127155921,1291052,数学智力题挑战:面试必备,"['面试', '数学问题', '智力游戏', '算法设计']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

0.基本常识

0.1变量与常量

0.2 作用域scope

0.3 重复声明

1.var

1.1 var声明作用域 var Declaration Scope

函数作用域

全局var声明

1.2 重复声明

1.3 var 声明提升 var Declaration Hoisting

1.4 使用未声明的变量use an undeclared variable

2.Let

2.1 暂时性死区 Temporal Dead Zone

不同生命变量的生命周期

ECMA详解

高级案例1

高级案例2

函数的默认参数也适用于TDZ

2.2 全局声明 Global Declarations

2.3 条件声明 Conditional Declaration

2.4 for 循环中的 let 声明 let Declaration in for Loops

3. const

3.1同样的block-scoped

3.2必须初始化

3.3 const和loop

4.最佳实践

4.1.不使用 var

4.2 const 优先,let 次之

4.3 总结概览

 5. 解构赋值 Destructuring Assignment

1.数组解构赋值

2.对象解构赋值


0.基本常识

0.1变量与常量

计算机编程最基本的技术之一是使用名称names(或标识符identifiers)来表示值values。

将名称绑定到值给我们提供了一种方法来引用该值refer to that value并在我们编写的程序中使用它。当我们这样做的时候,我们通常说我们正在给一个变量赋值assigning a value to a variable

术语“变量”“variable”意味着可以分配新值new values can be assigned:与变量相关的值可能随着程序运行而变化。

如果我们将一个值永久地赋给一个名称permanently assign a value to a name,那么我们将该名称name称为常数constant,而不是变量variable。

在JavaScript程序中使用变量或常量之前,必须声明它。Before you can use a variable or constant in a JavaScript program, you must declare it.

0.2 作用域scope

变量的作用域是程序源代码中定义变量的区域。使用let和const声明的变量和常量是块作用域。这意味着它们只在let或const语句出现的代码块中定义。

当声明出现在任何代码块之外的顶层时,我们说它是一个全局变量或常量,并具有全局作用域.When a declaration appears at the top level, outside of any code blocks, we say it is a global variable or constant and has global scope

0.3 重复声明

在同一作用域中使用多个let或const声明的相同名称是语法错误 It is a syntax error to use the same name with more than one let or const declaration in the same scope

在嵌套作用域中声明具有相同名称的新变量是合法的(尽管这种做法最好避免) It is legal (though a practice best avoided) to declare a new variable with the same name in a nested scope

const x = 1; // Declare x as a global constant 
if (x === 1) { 
   let x = 2; // Inside a block x can refer to a different value 
   console.log(x); // Prints 2 
}
console.log(x); // Prints 1: we're back in the global scope now 
let x = 3; // ERROR! Syntax error trying to re- declare x

1.var

最常见的声明变量的关键字。它没有其他两个关键字的种种限制。这是因为它是传统上在 JavaScript 声明变量的唯一方法

1.1 var声明作用域 var Declaration Scope

函数作用域

使用 **var**声明的变量在它所声明的整个函数都是可见的

A variable declared with the var keyword is available from the function it is declared in

它们的作用域是包含他们的函数主体,无论它们在该函数中嵌套的深度如何

they are scoped to the body of the containing function no matter how deeply nested they are inside that function

使用 var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁

function test() {
var message = "hi"; // local variable 局部变量 message 变量是在函数内部使用 var 定义的
}
test(); //函数叫 test(),调用它会创建这个变量并给它赋值。调用之后变量随即被销毁
console.log(message); // error!

但是,在函数内定义变量时省略 var 操作符,可以创建一个全局变量, 但不建议这么做

function test() {
message = "hi"; // global variable  **去掉之前的 var 操作符之后,message 就变成了全局变量**
}
test(); //只要调用一次函数 test(),就会定义这个变量,并且可以在函数外部访问到
console.log(message); // "hi"
// myVarVariable *is* visible out here

for (var myVarVariable = 0; myVarVariable < 5; myVarVariable++) {
  // myVarVariable is visible to the whole function
}

// myVarVariable *is* visible out here

全局var声明

如果在函数体之外使用 var,它会声明一个全局变量global variable。

用 var 声明的全局变量Globals被实现为全局对象的属性implemented as properties of the global object。

全局对象可以引用为 globalThis //The global object can be referenced as globalThis。 所以如果你写 var x = 2; 在函数之外,就像你写了 globalThis.x = 2;。

使用全局 var 声明创建的属性不能使用 delete 运算符删除

用 let 和 const 声明的全局变量和常量不是全局对象的属性

1.2 重复声明

用 var 多次声明同一个变量是合法的。

因为 var 变量具有函数作用域而不是块作用域,所以这种重新声明实际上很常见。

变量 i 经常用于表示整数值,尤其是作为 for 循环的索引变量index variable。在具有多个 for 循环的函数中,通常每个循环都以 for(var i = 0; ....开始

因为var没有将这些变量限定在循环体loop body中,所以每个循环都(无害地)重新声明和初始化相同的变量

1.3 var 声明提升 var Declaration Hoisting

使用这个var声明的变量会自动提升到函数作用域顶部

变量的初始化保留在您编写它的位置,但变量的定义移动到函数的顶部

The initialization of the variable remains where you wrote it, but the definition of the variable moves to the top of the function.

如果初始化代码还没有运行,那么变量的值可能是未定义的,但是如果在变量初始化之前使用它就不会出错

If the initialization code has not run yet, then the value of the variable may be undefined, but you won’t get an error if you use the variable before it is initialized

function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined  不会报错

//因为 ECMAScript 运行时把它看成等价于如下代码:

function foo() {
var age; //把所有变量声明都拉到函数作用域的顶部
console.log(age);
age = 26;
}
foo(); // undefined

//反复多次使用 var 声明同一个变量也没有问题
function foo() {
var age = 16;
var age = 26;
var age = 36;
console.log(age);
}
foo(); // 36

1.4 使用未声明的变量use an undeclared variable

在严格模式中strict mode,如果您尝试使用未声明的变量,则在运行代码时会出现引用错误。

然而,在严格模式之外,如果你给一个没有用 let、const 或 var 声明的名字name赋值,你最终会创建一个新的全局变量

无论如何深深嵌套在函数和代码块中,它都将是一个全局变量。这几乎肯定不是您想要的,容易出错,并且是使用严格模式的最佳理由之一!

以这种偶然方式创建的全局变量就像用 var 声明的全局变量:它们定义了全局对象的属性

但与正确的 var 声明定义的属性不同,这些属性可以使用 delete 运算符删除

2.Let

**let**声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。

The **let**statement declares a block-scoped local variable, optionally initializing it to a value.

The declared variable is available from the block it is enclosed in

// myLetVariable is *not* visible out here 在这里 *不能* 被引用

for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) {
  // myLetVariable is only visible in here 只能在这里引用
}

// myLetVariable is *not* visible out here 在这里 *不能* 被引用

let 跟 var 的作用差不多。最明显的区别是,let 声明的范围是块作用域(block scoped),而 var 声明的范围是函数作用域(function scoped)

block-scoped – they only exist within the innermost block that surrounds them

if (true) {
var name = 'Matt';
console.log(name); // Matt
}
console.log(name); // Matt

if (true) {
let age = 26;
console.log(age); // 26
}
console.log(age); // ReferenceError: age is not defined
//the age variable cannot be referenced outside the if block because its scope does not extend outside the block
//age 变量之所以不能在 if 块外部被引用,是因为它的作用域仅限于该块内部

块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样也适用于 let

2.1 暂时性死区 Temporal Dead Zone

let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升

// name 会被提升
console.log(name); // undefined
var name = 'Matt';
// age 不会被提升
console.log(age); // ReferenceError:age 没有定义
let age = 26;

When parsing the code, JavaScript engines will still be aware of the let declarations that appear later in a block, but these variables will be unable to be referenced in any way before the actual declaration occurs. The segment of execution that occurs before the declaration is referred to as the “temporal dead zone,” and any attempted references to these variables will throw a ReferenceError.

在解析代码时,JavaScript 引擎也会注意出现在块后面部分的 let 声明,只不过在此之前不能以任何方式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。

What is the temporal dead zone?icon-default.png?t=M7J4https://stackoverflow.com/questions/33198849/what-is-the-temporal-dead-zone/33198850

表面上看来let 和 const 是不能被提升的,let const 是可以被提升的hoisted (like varclassand function)

但是在进入范围和声明之间有一段时间,它们不能被访问 but there is a period between entering scope and being declared where they cannot be accessed. This period is the temporal dead zone (TDZ)

当进入它的作用域时,它不能被访问(获取或设置),直到代码执行到达变量声明。

TDZ:When entering its scope, it can’t be accessed (got or set) until execution reaches the declaration

// console.log(aLet) // Would throw ReferenceError

let aLet; //TDZ已经结束了,The TDZ ends when aLet is declared, rather than assigned

console.log(aLet); // undefined
aLet = 10;
console.log(aLet); // 10

That is because let/const declarations do hoist, but they throw errors when accessed before being initialized  (instead of returning undefinedas varwould)

let x = 'outer scope';
(function() {
    console.log(x);//不会打印任何东西,整个代码会抛出ReferenceError
    let x = 'inner scope';
}());

不同生命变量的生命周期

9. Variables and scopingicon-default.png?t=M7J4https://exploringjs.com/es6/ch_variables.html#_the-temporal-dead-zone

var -declared变量的生命周期

当进入var变量的作用域(即包围这个变量的函数)时,将为变量创建存储空间(绑定)。通过将变量设置为undefined,该变量将立即初始化

  • When the scope (its surrounding function) of a var variable is entered, storage space (a binding) is created for it. The variable is immediately initialized, by setting it to undefined.

当作用域内的执行到达声明时,变量被设置为初始化器指定的值(赋值)——如果有的话。如果没有,变量的值仍然是未定义的。

  • When the execution within the scope reaches the declaration, the variable is set to the value specified by the initializer (an assignment) – if there is one. If there isn’t, the value of the variable remains undefined.

let -declared变量的生命周期

当进入let变量的作用域(即包围这个变量的块)时,将为变量创建存储空间(绑定)。该变量仍然是未初始化

  • When the scope (its surrounding block) of a let variable is entered, storage space (a binding) is created for it. The variable remains uninitialized.

获取或设置未初始化的变量会导致ReferenceError。

  • Getting or setting an uninitialized variable causes a ReferenceError.

当作用域内的执行到达声明时,变量被设置为初始化器指定的值(赋值)——如果有的话。如果没有,变量的值仍然是未定义的。

  • When the execution within the scope reaches the declaration, the variable is set to the value specified by the initializer (an assignment) – if there is one. If there isn’t then the value of the variable is set to undefined.
let tmp = true;
if (true) { // enter new scope, TDZ starts
    // Uninitialized binding for `tmp` is created
    console.log(tmp); // ReferenceError

    let tmp; // TDZ ends, `tmp` is initialized with `undefined`
    console.log(tmp); // undefined

    tmp = 123;
    console.log(tmp); // 123
}
console.log(tmp); // true

const -declared变量的生命周期

当进入const变量的作用域(即包围这个变量的块)时,将为变量创建存储空间(绑定)。该变量仍然是未初始化

  • When the scope (its surrounding block) of a const variable is entered, storage space (a binding) is created for it. The variable remains uninitialized.

获取或设置未初始化的变量会导致ReferenceError。

  • Getting or setting an uninitialized variable causes a ReferenceError.

当作用域内的执行到达声明时,变量被设置为初始化器指定的值(赋值),,此时必须赋值

  • When the execution within the scope reaches the declaration, the variable is set to the value specified by the initializer (an assignment)

TDZ是暂时的且基于位置的

let tmp = true;
if (true) { // enter new scope, TDZ starts
    // Uninitialized binding for `tmp` is created
    console.log(tmp); // ReferenceError

    let tmp; // TDZ ends, `tmp` is initialized with `undefined`
    console.log(tmp); // undefined

    tmp = 123;
    console.log(tmp); // 123
}
console.log(tmp); // true

ECMA详解

JS Rocks

The ECMAScript 2015 spec:

13.2.1 Let and Const Declarations

NOTE let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

The variables are created when their containing Lexical Environment is instantiated

这意味着每当控制流进入一个新的作用域(例如,模块、函数或块作用域),所有属于给定作用域的let/const绑定都会在给定作用域内的任何代码执行之前初始化——换句话说,let/const声明会提升!

This means whenever control flow enters a new scope (e.g. module, function or block scope), all the let/constbindings belonging to the given scope are instatiated before any code inside of the given scope is executed -- in other words, let/const declarations hoist!

[...] but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

这是TDZ。给定的let/const声明的绑定不能以任何方式(读/写)访问,直到控制流评估声明语句——这并不指向提升,而是指向代码中声明的实际位置

This is the TDZ. A given let/const-declared binding can't be acessed in any way (read/write) until control flow has evaluated the declaration statement -- that does not refer to the hoisting, but rather to where the declaration actually is in the code.

// Accessing `x` here before control flow evaluates the `let x` statement
// would throw a ReferenceError due to TDZ.
// console.log(x);

let x = 42;
// From here on, accessing `x` is perfectly fine!
console.log(x);

If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBindinf is evaluated.

//This simply means that:

let x;
//Is equivalent to:

let x = undefined;

同样,在控制流评估初始化声明之前尝试以任何方式访问将导致x将导致ReferenceError ,而在控制流评估声明之后访问它会正常工作——上面的两个样本都会返回undefined

Likewise, trying to access x in any way before control flow evaluates the initializer (or the "implicit" = undefinedinitializer) will result in a ReferenceError, while accessing it after the control flow has evaluated the declaration will work fine -- reading the x variable after the let xdeclaration in both samples above would return undefined.

高级案例1

let x = x;

记住let/const变量仅在其初始化程序已被完全评估后才算作已初始化 - 也就是说,在赋值的右侧表达式已被评估并且其结果值已分配给声明的变量之后。

let/constvariable only counts as initialized after its initializer has been fully evaluated -- that is, after the assignment's right-hand side expression has been evaluated and its resulting value has been assigned to the declared variable

右侧表达式尝试读取x变量,但x' 的初始化程序还没有被完全评估——事实上我们当时正在评估它——所以在那个时候x仍然算作未初始化,因此尝试阅读它会引发 TDZ ReferenceError

the right-hand side expression tries to read the xvariable, but x's initializer has not been fully evaluated yet -- in fact we are evaluating it at that point -- so xstill counts as uninitialized at that point and thus trying to read it throws a TDZ ReferenceError.

高级案例2

let a = f(); //the f() call makes control flow jump to and execute the f function
const b = 2;
function f() { return b; } //which in turn tries to read the b constant which, at this point in the runtime, is still uninitialized (in TDZ) and thus throws a ReferenceError.
//在第一行中,f()调用使控制流跳转到并执行该f函数
//该函数又尝试读取b常量,在运行时的这一点上该常量仍未初始化(在 TDZ 中),因此抛出一个ReferenceError.

TDZ其实非常有助于debug,因为很少有人是故意accessing a value before it has been declared

函数的默认参数也适用于TDZ

// Works fine.
(function(a, b = a) {
    a === 1;
    b === 1;
}(1, undefined));

// Default parameters are evaluated from left to right,
// so `b` is in the TDZ when `a`'s initializer tries to read it.
(function(a = b, b) {}(undefined, 1)); // ReferenceError

// `a` is still in the TDZ when its own initializer tries to read `a`.
(function(a = a) {}()); // ReferenceError

参数从左到右评估,每个参数都在TDZ中,直到它被赋值

Arguments are evaluated left to right, and each argument is in the TDZ until it is assigned

// b is in TDZ until its value is assigned.
function testDefaults(a = b, b) { }

testDefaults(undefined, 1); // Throws ReferenceError because the evaluation of a reads b before it has been evaluated.

下面这个案例仍然违反TDZ

let b = 1;
(function(a = b, b) {
    console.log(a, b);
}(undefined, 2));

默认参数是在给定函数的父范围和内部范围之间存在的中间范围内评估的。a和b参数是此(中间)范围的绑定,并且从左到右初始化,因此当a' 的初始化程序尝试读取b时,b标识符解析为当前范围(中间范围)中的b绑定,该绑定在该点未初始化并且因此由于 TDZ 语义而抛出一个ReferenceError

that is because default parameters are evaluated in an intermediate scope which exists between the parent and inner scope of the given function. The aand bparameters are bindings of this (intermediate) scope and are initialized from left to right, hence when a's initializer tries to read b , the bidentifier resolves to the bbinding in the current scope (the intermediate scope) which is uninitialized at that point and thus throws a ReferenceErrordue to the TDZ semantics.

2.2 全局声明 Global Declarations

when declaring variables using let in the global context, variables will not attach to the window object as they do with var

使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声明的变量则会)

var name = 'Matt';
console.log(window.name); // 'Matt'
let age = 26;
console.log(window.age); // undefined

let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续

let declarations will still occur inside the global block scope, which will persist for the lifetime of the page

2.3 条件声明 Conditional Declaration

在使用 var 声明变量时,由于声明会被提升(所有的自动合并到一起),JavaScript 引擎会自动将多余的声明在作用域顶部合并为一个声明。

因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同时也就不可能在没有声明的情况下声明它

Because let declarations are scoped to blocks, it’s not possible to check if a let variable has previously been declared and conditionally declare it only if it has not

<script>
var name = 'Nicholas';
let age = 26;
</script>

<script>
// Suppose this script is unsure about what has already been declared in the page.假设脚本不确定页面中是否已经声明了同名变量
// It will assume variables have not been declared.那它可以假设还没有声明过
var name = 'Matt';
// No problems here, since this will be handled as a single hoisted declaration.这里没问题,因为可以被作为一个提升声明来处理
// There is no need to check if it was previously declared.不需要检查之前是否声明过同名变量
let age = 36;
// This will throw an error when 'age' has already been declared.如果 age 之前声明过,这里会报错
</script>

使用 try/catch 语句或 typeof 操作符也不能解决,因为条件块中 let 声明的作用域仅限于该块。

<script>
let name = 'Nicholas';
let age = 36;
</script>
<script>
// Suppose this script is unsure about what has already been declared in
the page.
// It will assume variables have not been declared.
if (typeof name !== 'undefined') {
let name;
}
// 'name' is restricted to the if {} block scope, name 被限制在 if {} 块的作用域内
// so this assignment will act as a global assignment 因此这个赋值形同全局赋值
name = 'Matt';
try (age) {
// If age is not declared, this will throw an error
}
catch(error) {
let age;
}
// 'age' is restricted to the catch {} block scope, age 被限制在 catch {}块的作用域内
// so this assignment will act as a global assignment 因此这个赋值形同全局赋值
age = 26;
</script>

对于 let 这个新的 ES6 声明关键字,不能依赖条件声明模式

因为条件声明是一种反模式,它让程序变得更难理解。如果你发现自己在使用这个模式,那一定有更好的替代方式

2.4 for 循环中的 let 声明 let Declaration in for Loops

在 let 出现之前,for 循环定义的迭代变量(iterator variable)会渗透到循环体外部(bleed outside the loop body)

改成使用 let 之后,这个问题就消失了,因为迭代变量的作用域仅限于 for 循环块内部

for (var i = 0; i < 5; ++i) {
// 循环逻辑
}
console.log(i); // 5

for (let i = 0; i < 5; ++i) {
// do loop things
}
console.log(i); // ReferenceError: i is not defined

在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是同一个最终值

for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 你可能以为会输出 0、1、2、3、4
// 实际上会输出 5、5、5、5、5

在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例(references that separate instance ),所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。

for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 会输出 0、1、2、3、4

3. const

3.1同样的block-scoped

function func() {
    if (true) {
        const tmp = 123;
    }
    console.log(tmp); // ReferenceError: tmp is not defined
}

//In contrast, var-declared variables are function-scoped:

function func() {
    if (true) {
        var tmp = 123;
    }
    console.log(tmp); // 123
}

The variable is available from the block it is declared in

块作用域意味着可以在函数中覆盖变量shadow variables within a function

function func() {
  const foo = 5;
  if (···) {
     const foo = 10; // shadows outer `foo`
     console.log(foo); // 10
  }
  console.log(foo); // 5
}

3.2必须初始化

const 的行为与 let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误

const age = 26;
age = 36; // TypeError: 给常量赋值

// const 也不允许重复声明
const name = 'Matt';
const name = 'Nicholas'; // SyntaxError
// const 声明的作用域也是块
const name = 'Matt';
if (true) {
const name = 'Nicholas';
}
console.log(name); // Matt

const 声明的限制只适用于它指向的变量的引用。The const declaration is only enforced with respect to the reference to the variable that it points to

换句话说,如果 const 变量引用的是一个对象,那么修改这个对象内部的属性并不违反 const 的限制

const person = {};
person.name = 'Matt'; // ok

3.3 const和loop

JavaScript 引擎会为 for 循环中的 let 声明分别创建独立的变量实例,虽然 const 变量跟 let 变量很相似,但是不能用 const 来声明迭代变量(因为迭代变量会自增):

for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值 assignment to constant variable

因为for-of每次循环迭代都会创建一个绑定(变量的存储空间),所以可以使用const声明循环变量

Since for-ofcreates one binding(storage space for a variable) per loop iteration, it is OK to const-declare the loop variable

for (const x of ['a', 'b']) {
    console.log(x);
}
// Output:
// a
// b

关于for、for/in和for/of循环语句。每个循环都包含一个循环变量loop variable,该变量在每次循环迭代each iteration of the loop时都将获得一个新值

for(let i = 0, len = data.length; i < len; i++) console.log(data[i]); 
for(let datum of data) console.log(datum); 
for(let property in object) console.log(property);

//一般确实会使用let

你也可以使用const来声明for/in和for/of循环的循环“变量”,const声明只是说该值在一次循环迭代期间是常量

the const declaration is just saying that the value is constant for the duration of one loop iteration

for(const datum of data) console.log(datum);
for(const property in object) console.log(property);

4.最佳实践

4.1.不使用 var

限制自己只使用 let 和 const 有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。

careful management of variable scope, declaration locality, and const correctness

4.2 const 优先,let 次之

使用 const 声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。

只在提前知道未来会有修改时,再使用 let。

这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。

4.3 总结概览

Ways of declaring variables

 5. 解构赋值 Destructuring Assignment

ES6 实现了一种复合声明和赋值语法compound declaration and assignment syntax,称为解构赋值destructuring assignment。

在解构赋值中,等号右侧的值是数组或对象(“结构化”值a “structured” value),左侧使用模仿数组和对象字面量语法的语法a syntax that mimics array and object literal syntax指定一个或多个变量名称。

当发生解构赋值时,会从右侧的值中提取extracted(“解构”“destructured”)一个或多个值,并将其存储到左侧命名的变量中。

解构赋值可能最常用于将变量初始化为 const、let 或 var 声明语句的一部分initialize variables as part of a const, let, or var declaration statement,但也可以在常规赋值表达式中完成(使用已声明的变量)。

在定义函数的参数时也可以使用解构。

1.数组解构赋值

let [x,y] = [1,2]; // Same as let x=1, y=2 
[x,y] = [x+1,y+1]; // Same as x = x + 1, y = y + 1 
[x,y] = [y,x]; // Swap the value of the two variables 
[x,y] // => [3,2]: the incremented and swapped values

解构赋值让处理返回值为数组的函数变得很容易

// Convert [x,y] coordinates to [r,theta] polar coordinates 将 [x,y] 坐标转换为 [r,theta] 极坐标
function toPolar(x, y) { 
     return [Math.sqrt(x*x+y*y), Math.atan2(y,x)]; 
}
// Convert polar to Cartesian coordinates 将极坐标转换为笛卡尔坐标
function toCartesian(r, theta) {
   return [r*Math.cos(theta), r*Math.sin(theta)]; 
}

let [r,theta] = toPolar(1.0, 1.0); // r == Math.sqrt(2); theta == Math.PI/4 
let [x,y] = toCartesian(r,theta); // [x, y] == [1.0, 1,0]

这是一个循环遍历对象所有属性的名称/值对并使用解构赋值将这些名称/值对从二元数组转换为单个变量的代码

let o = { x: 1, y: 2 }; // The object we'll loop over 
for(const [name, value] of Object.entries(o)) { 
     console.log(name, value); // Prints "x 1" and "y 2" 
}
//**结合使用了for循环和数组的解构赋值**

解构赋值左侧的变量数量不必与右侧的数组元素数量相匹配。左边的额外变量被设置为未定义,右边的额外值被忽略。左边的变量列表可以包含额外的逗号,以跳过右边的某些值

let [x,y] = [1]; // x == 1; y == undefined 
[x,y] = [1,2,3]; // x == 1; y == 2 
[,x,,y] = [1,2,3,4]; // x == 2; y == 4  **注意看这里逗号的位置**

如果您想在解构数组时将所有未使用或剩余的值收集到单个变量中,请在左侧最后一个变量名称之前使用三个点 (...)

let [x, ...y] = [1,2,3,4]; // y == [2,3,4]

解构赋值可以与嵌套数组nested arrays一起使用。 在这种情况下,赋值的左侧应该看起来像一个嵌套数组字面量nested array literal

let [a, [b, c]] = [1, [2,2.5], 3]; // a == 1; b == 2; c == 2.5

数组解构的array destructuring一个强大功能是它实际上不需要数组! 您可以在赋值的右侧使用任何可迭代对象iterable object

任何可以与 for/of 循环object一起使用的对象也可以被解构

let [first, ...rest] = "Hello"; // first == "H"; rest == ["e","l","l","o"]

2.对象解构赋值

当右边是对象值object value时,也可以执行解构赋值。

在这种情况下,赋值的左边看起来有点像一个对象字面量object literal:用花括号括起来的以逗号分隔的变量名列表

let transparent = {r: 0.0, g: 0.0, b: 0.0, a: 1.0}; // A RGBA color 
let {r, g, b} = transparent; // r == 0.0; g == 0.0; b == 0.0

将Math对象Math object的全局函数复制到变量中,以简化需要处理大量三角函数的代码

// Same as const sin=Math.sin, cos=Math.cos, tan=Math.tan 
const {sin, cos, tan} = Math;

//Math 对象具有许多属性,而这三个属性被分解为单独的变量。那些未命名的将被简单地忽略。
//如果该赋值的左侧包含一个名称不是 Math 属性的变量,则该变量将被简单地赋值为 undefined。

在每一个对象解构的例子中,我们都选择了与我们要解构的对象的属性名称相匹配的变量名称。这使得语法简单易懂,但这并不是必须的。

对象解构赋值左侧的每个标识符也可以是一对冒号分隔的标识符

其中第一个是要被赋值的属性名称the name of the property whose value is to be assigned,第二个是要将其赋值的变量名称name of the variable to assign it to

// Same as const cosine = Math.cos, tangent = Math.tan; 
const { cos: cosine, tan: tangent } = Math;

无论是在对象字面量中还是在对象解构赋值的左侧, 请记住属性名称始终位于冒号左侧

nested objects arrays of objects objects of arrays都可以使用解构赋值

//arrays of objects
let points = [{x: 1, y: 2}, {x: 3, y: 4}]; // An array of two point objects 
let [{x: x1, y: y1}, {x: x2, y: y2}] = points; // destructured into 4 variables. 
(x1 === 1 && y1 === 2 && x2 === 3 && y2 === 4) // => true

//objects of arrays
let points = { p1: [1,2], p2: [3,4] }; // An object with 2 array props 
let { p1: [x1, y1], p2: [x2, y2] } = points; // destructured into 4 vars 
(x1 === 1 && y1 === 2 && x2 === 3 && y2 === 4) // => true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值