详解JavaScript 中的变量声明与赋值
作者:晓风晓浪
在计算机编程中,使用名称(或标识符)来表示值是最基本的技术之一。将名称与值绑定为我们提供了一种在程序中引用值并利用它们的方式。当涉及到绑定名称与值时,我们通常称之为将值赋给变量。术语“变量”暗示了新的值可以被赋给它,这意味着与变量关联的值在程序执行过程中可能会改变。如果一个值被永久地分配给一个名称,那么该名称可以被视为常量而不是变量。
在 JavaScript 中使用变量或常量之前,必须先进行声明。在 ES6 版本及更高版本的 JavaScript 中,可以使用 let
和 const
关键字来实现这一点,我们将在稍后介绍。在 ES6 之前,变量使用 var
关键字进行声明。
1. 使用 let 和 const 进行声明。
在现代 JavaScript(ES6 及更高版本)中,变量使用 let
关键字进行声明:
let i; let sum;
也可以在单个 let
语句中声明多个变量:
let i, sum;
在声明时,最好将初始值赋给变量(如果可能的话):
let message = "hello"; let i = 0, j = 0, k = 0; // 初始化语句可以使用先前声明的变量 let x = 2, y = x * x;
如果在 let
语句中没有提供初始值,变量仍将被声明,但其值将是 undefined
,直到它被赋值。要声明常量而不是变量,请使用 const
关键字而不是 let
。const
类似于 let
,但关键区别在于常量在声明时必须进行初始化:
// 哈勃常数(km/s/Mpc) const H0 = 74; // 真空中的光速(km/s) const C = 299792.458; // 天文单位:地球和太阳之间的平均距离(km) const AU = 1.496E8;
正如名称所示,常量的值不能被更改,尝试重新分配常量会导致 TypeError
。声明常量的通用(尽管不是普遍的)约定是使用大写字母,例如 H0
或 HTTP_NOT_FOUND
,以将它们与变量区分开。
何时使用 const
。
在使用 const
关键字方面存在两种观点。一个观点是仅在预期值不会更改的情况下使用 const
,例如物理常数、程序版本号或用于识别文件类型的字节顺序标记。另一个观点认为,在程序中,许多所谓的变量实际上在运行时并不会改变。因此,建议是使用 const
来声明所有变量,仅在发现需要允许它们的值发生变化时将其切换为 let
。这种方法有助于避免意外的变量修改,从而导致错误。
在第一种情况下,const
仅用于必须保持恒定的值。在第二种情况下,const
用于任何不会更改的值。就个人而言,在编写代码时,我更倾向于前一种方法。
JavaScript 中的循环结构:for
、for/in
和 for/of
循环。
在 JavaScript 中,循环结构(如 for
、for/in
和 for/of
循环)包括一个在循环的每次迭代中获取新值的循环变量。JavaScript 允许在循环语法中声明这个循环变量,这是 let
的另一个常见用例:
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);
虽然可能看起来有点不寻常,但也可以在 for/in
和 for/of
循环中使用 const
来声明循环的“变量”,只要它们在循环体内部不被重新赋值。在这种情况下,const
声明表示循环变量的值在每次迭代中都是恒定的:
for (const datum of data) console.log(datum); for (const property in object) console.log(property);
变量和常量的作用域。
变量的作用域是指在源代码中定义变量的区域。使用 let
和 const
声明的变量和常量具有块级作用域。这意味着它们仅在 let
或 const
语句的代码块内部定义。JavaScript 类和函数的函数体被视为代码块,同样,if/else
语句的语句体和 while
、for
循环的循环体也被视为代码块。简单来说,如果变量或常量声明在一对花括号内部,那么这些花括号限制了变量或常量定义的代码区域(当然,在引用这些变量或常量之前的代码行中引用在 let
或 const
语句中声明它们是无效的)。作为 for
、for/in
或 for/of
循环的一部分声明的变量和常量的作用域仅限于循环体, 即使循环声明位于花括号外部。
如果声明发生在顶层,在任何代码块之外,它被称为全局变量或常量,并具有全局作用域。在 Node 和客户端 JavaScript 模块中,全局变量的作用域由声明它们的文件决定。但是,在传统的客户端 JavaScript 中,全局变量的作用域由声明它们的 HTML 文档决定。换句话说,如果 <script>
标签声明了全局变量或常量,那么该变量或常量将在同一文档中的任何 <script>
元素中定义(或者至少在所有在执行 let
和 const
语句之后执行的脚本中定义)。
重新声明。
在同一作用域内多次使用 let
或 const
语句声明相同名称是语法错误。但是,在嵌套作用域中声明相同名称的变量是允许的(尽管通常建议避免这种情况):
// 将 x 声明为全局常量 const x = 1; if (x === 1) { // 在同一代码块中,x 可以引用不同的值 let x = 2; // 输出 2 console.log(x); } // 输出 1:现在回到全局作用域 console.log(x); // 错误!重新声明 x 会导致语法错误 let x = 3;
声明和数据类型。
如果您有使用静态类型语言(例如 C 或 Java)的经验,可能会认为变量声明的主要目的是指定变量可以保存的数据类型。然而,我们还看到,在 JavaScript 中,变量声明并不与值的数据类型相关联。JavaScript 变量可以存储任何类型的值。例如,在 JavaScript 中,将一个数字赋给变量,然后将一个字符串赋给同一个变量是有效的:
let i = 10; i = "ten";
2. 使用 var 进行变量声明。
在 ES6 之前的 JavaScript 中,声明变量的唯一方式是使用 var
关键字,无法声明常量。var
的语法与 let
类似:
var x; var data = [], count = data.length; for (var i = 0; i < count; i++) console.log(data[i]);
虽然 var
和 let
具有相同的语法,但它们之间有重要的区别:
· 使用 var
声明的变量没有块级作用域。这些变量的作用域限制在包含函数的函数体内,无论它们在函数内部的嵌套有多深。
· 如果在函数体外部使用 var
,它将声明一个全局变量。然而,使用 var
声明的全局变量和使用 let
声明的全局变量之间存在重要区别。使用 var
声明的全局变量被实现为全局对象的属性。可以使用 globalThis
引用全局对象。因此,如果在函数外部编写 var x = 2;
,它等同于编写 globalThis.x = 2;
。然而,需要注意的是,这个类比并不完全准确,因为通过全局 var
创建的属性无法使用 delete
运算符删除。使用 let
和 const
声明的全局变量和常量不是全局对象的属性。
· 与使用 let
声明的变量不同,使用 var
可以多次声明具有相同名称的变量。由于 var
变量具有函数作用域而不是块级作用域,这种重新声明非常常见。变量 i
经常用于存储整数值,特别是作为 for
循环中的索引变量。在包含多个 for
循环的函数中,每个循环通常以 for (var i = 0; ...)
开始。由于 var
不限制这些变量的作用域仅限于循环体,每次迭代(无害地)重新声明和重新初始化同一个变量。
· var
最显著的特点之一是变量提升。使用 var
时,声明被提升到包含函数的顶部。然而,变量的初始化仍然发生在代码中的原始位置;只是变量的定义被移动到函数的顶部。因此,使用 var
声明的变量可以在包含函数的任何位置使用,而不会生成错误。如果尚未执行初始化代码,变量的值可能是 undefined
,但是在初始化之前仍然可以使用变量,而不会引发错误(这可能是错误的来源之一,也是 let
解决的最重要的错误倾向特性之一。如果使用 let
声明变量并尝试在 let
语句运行之前使用它,您将得到错误而不是 undefined
值)。
使用未声明的变量。
在严格模式下,尝试使用未声明的变量会在运行时触发引用错误。然而,在严格模式之外,如果将值赋给未经 let
、const
或 var
声明的名称,将创建一个新的全局变量。此外,不管赋值语句在函数或代码块内嵌套多深,它都会创建一个全局变量。这种行为当然是不可取的,因为它可能导致缺陷,是使用严格模式的一个强有力的理由。以这种意外方式创建的全局变量的行为类似于使用 var
声明的全局变量,因为它们定义了全局对象的属性。但是,与通过适当的 var
声明定义的属性不同,这些属性可以使用 delete
操作删除。
3. 解构赋值。
ES6 引入了一种复合声明和赋值语法,称为 "解构赋值"。在解构赋值中,等号右侧的值是一个数组或对象(一个 "结构化" 值),而左侧模拟了数组或对象字面语法以指定一个或多个变量。当发生解构赋值时,一个或多个值从右侧的值中提取("解构")并保存到左侧列出的变量中。解构赋值通常用于初始化 const
、let
或 var
声明中的变量,但也可以用于常规赋值表达式(用于将值赋给先前声明的变量)。此外,在定义函数参数时也可以使用解构赋值。
以下是演示数组值解构的代码片段:
// 等同于 let x = 1, y = 2 let [x, y] = [1, 2]; // 等同于 x = x + 1, y = y + 1 [x, y] = [x + 1, y + 1]; // 交换两个变量的值 [x, y] = [y, x]; // => [3, 2]:增加和交换后的值 [x, y]
解构赋值极大地简化了返回数组的函数的使用:
// 将 [x, y] 坐标转换为 [r, theta] 极坐标 function toPolar(x, y) { return [Math.sqrt(x * x + y * y), Math.atan2(y, x)]; } // 将极坐标转换回直角坐标 function toCartesian(r, theta) { return [r * Math.cos(theta), r * Math.sin(theta)]; } // r == Math.sqrt(2); theta == Math.PI/4 let [r, theta] = toPolar(1.0, 1.0); // [x, y] == [1.0, 1.0] let [x, y] = toCartesian(r, theta);
如前所述,变量和常量可以在各种 JavaScript for
循环中声明和使用。在这个情境下,变量解构也同样适用。下面的代码遍历对象的所有属性名称/值对,并使用解构赋值将一个两元素数组转换为单个变量:
// 要遍历的对象 let o = { x: 1, y: 2 }; for (const [name, value] of Object.entries(o)) { // 打印 "x 1" 和 "y 2" console.log(name, value); }
在解构赋值中,左侧的变量数目不必与右侧数组的元素数目完全匹配。左侧多余的变量将被赋值为 undefined
,而右侧多余的值将被忽略。左侧的变量列表可以包含额外的逗号,以跳过右侧的某些值:
// x == 1; y == undefined let [x, y] = [1]; // x == 1; y == 2 [x, y] = [1, 2, 3]; // x == 2; y == 4 [, x, , y] = [1, 2, 3, 4];
使用解构赋值时,如果要将所有未使用或剩余的值收集到一个变量中,可以在左侧的最后一个变量名前放置三个点(...
):
// y == [2, 3, 4] let [x, ...y] = [1, 2, 3, 4];
解构赋值也可以用于嵌套数组。在这种情况下,赋值的左侧应该类似于嵌套的数组字面值:
// a == 1; b == 2; c == 2.5 let [a, [b, c]] = [1, [2, 2.5], 3];
数组解构的一个令人印象深刻的特性是,它不仅限于数组!实际上,赋值的右侧可以是任何可迭代对象,包括可以在 for/of
循环中使用的对象:
// first == "H"; rest == ["e", "l", "l", "o"] let [first, ...rest] = "Hello";
解构赋值也适用于右侧为对象值的情况。在这种情况下,左侧类似于一个对象字面值,其中包含用花括号括起的用逗号分隔的变量名列表:
// 一个 RGBA 颜色对象 let transparent = { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }; // r == 0.0; g == 0.0; b == 0.0 let { r, g, b } = transparent;
下面的示例演示了将 Math
对象的全局函数复制到变量中,以便更轻松地处理复杂的三角计算:
// 等同于 const sin = Math.sin, cos = Math.cos, tan = Math.tan; const { sin, cos, tan } = Math;
请记住,上述代码中 Math
对象的属性远远不止三个被解构的变量。没有按名称提及的属性将被忽略。如果解构赋值的左侧包含一个不是 Math
属性的变量名,那么该变量将被赋值为 undefined
。
在上述对象解构的每个示例中,我们选择了与被解构对象的属性匹配的变量名。这保持了简单和易于理解的语法,但并不是严格必需的。对象解构赋值的左侧的每个标识符都可以是一个由冒号分隔的对,其中第一个标识符是要解构的属性名称,第二个标识符是要将值赋给的变量:
// 等同于 const cosine = Math.cos, tangent = Math.tan; const { cos: cosine, tan: tangent } = Math;
需要注意的是,变量名与属性名不同时,对象解构的语法变得过于复杂,从而使其变得不太实用。在这种情况下,我通常避免使用缩写形式。如果选择使用它,请记住属性名始终必须在冒号的左侧,无论是在对象字面值中还是在对象解构赋值中。
使用嵌套对象、对象内的数组或对象内的数组可以使解构赋值变得更加复杂,但它仍然是有效的:
// 包含两个坐标点对象的数组 let points = [{ x: 1, y: 2 }, { x: 3, y: 4 }]; // 解构为 4 个变量 let [{ x: x1, y: y1 }, { x: x2, y: y2 }] = points; // => true (x1 === 1 && y1 === 2 && x2 === 3 && y2 === 4)
如果解构的不是对象数组,还可以解构对象中的数组:
// 包含两个数组属性的对象 let points = { p1: [1, 2], p2: [3, 4] }; // 解构为 4 个变量 let { p1: [x1, y1], p2: [x2, y2] } = points; // => true (x1 === 1 && y1 === 2 && x2 === 3 && y2 === 4)
类似这样的复杂解构语法可能难以编写和理解,通常会使传统的代码(如 let x1 = points.p1[0];
)更加直观和易懂。
到此这篇关于JavaScript 中的变量声明与赋值的文章就介绍到这了,更多相关js变量声明和赋值内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!