目的

计算机语言需要使用变量。为什么呢?在大多数情况下,程序并不是解决单一问题,比如:一个半径为 5 厘米的圆的周长是多少?这样一个具体的问题是可以不使用变量解决的:alert(2 * 5 * 3.14);。然而,大多数问题是更一般化的:一个任意半径的圆的周长是多少?你不想为 5 厘米的半径写一个程序,为 6 厘米的半径再写一个程序,为 7 厘米的半径再写一个程序,等等。你希望编写一个程序,它可以计算所有可能半径的周长。这个程序需要输入(来自用户、另一个程序、数据库等),用来告诉它应该为哪个值运行。例如:let r = prompt("你的圆的半径是多少?"); alert(2 * r * 3.14);,或者更好地:let r = prompt("你的圆的半径是多少?"); alert(2 * r * Math.PI);

这两个例子具有灵活性。它们询问用户所需的半径,将给定值存储在名为 r 的变量中,并使用该变量计算周长。变量 r 是通过 let 关键字引入的。而且还有第二个变量,Math 模块预定义了一个名为 PI 的常量:const PI = 3.141592653589793;

在 JavaScript 中,变量可以像数学公式中的变量一样使用。在运行时,变量的值会存储在计算机的主内存(RAM)中,并且可以在程序的生命周期中的稍后时刻使用。你可以把变量想象成一个小盒子,你可以把某个值放进去,并在需要时取出来。

变量是从单个问题求解到策略和算法过渡的基石。

声明和初始化

如果你想使用一个变量,建议明确声明它们。这不是强制性的,但有很大的好处。

在许多情况下,声明伴随初始化,例如:let x = 0;。声明是 let x;,而初始化是 x = 0;。但也可以省略初始化部分:let x;,这会使得变量的值变成 undefined

关键字 let

let 关键字用于引入一个可以多次更改值的变量。

let x = 0;
// ...
x = x + 5;
// ...
x = -4;
// ...

关键字 const

const 关键字用于引入一个必须立即初始化的变量。此外,这个初始值永远不能更改。这有助于 JavaScript 引擎优化代码以提高性能。尽可能使用 const

const maxCapacity = 100;
// ...
maxCapacity = maxCapacity + 10; // 不可能
let maxCapacity = 110;          // 不可能

当你与对象(如数组)结合使用 const 时,它的行为是相同的:你不能给变量赋一个新的值(对象、数组、数字或其他任何东西)。然而,变量的元素是可以更改的。

const arr = [1, 2, 3];
arr = [1, 2, 3, 4];     // 不可能
arr = new Array(10);    // 不可能

arr[0] = 5;             // 可以:5, 2, 3
alert(arr);

arr.push(42);           // 可以:5, 2, 3, 42
alert(arr);

在某些情况下,const 变量会以大写字母书写,例如 PI。这是一种约定,并不是强制要求。

关键字 var

乍一看,varlet 相同。但 var 声明的变量作用域范围与 let 声明的变量不同;请参见下面的作用域章节。

省略声明

你可以在不先声明变量的情况下直接赋值给它。“JavaScript 曾允许给未声明的变量赋值,这会创建一个未声明的全局变量。在严格模式下这是一个错误,应该完全避免。”[1] 换句话说,变量会进入全局作用域,见下文。除非你有充分的理由,否则应该避免使用全局作用域,因为它的使用容易引发不必要的副作用。

// 直接使用 'radius',没有声明关键字
/* 1 */ radius = 5;
/* 2 */ alert(2 * radius * 3.14);

有时这种情况是偶然发生的。如果源代码中有拼写错误,JavaScript 会使用两个不同的变量:原始变量和一个拼写错误的新变量——即使你使用了 letconstvar 关键字。

let radius = 5;  // 或者没有 'let'
alert("Test 1");
// ... 后面的代码
radus = 1; // 拼写错误不会被检测到
alert("Test 2");

你可以通过在脚本的第一行插入 "use strict"; 来指示 JavaScript 检查拼写错误。

"use strict";
let radius = 5;
alert("Test 1");
// ... 后面的代码
radus = 1;       // 拼写错误会被检测到,并给出错误信息
alert("Test 2"); // 不会执行

数据类型

熟悉(严格)类型语言如 Java 的程序员可能会在上面的章节中感到缺少变量类型定义的功能。JavaScript 确实知道许多不同的数据类型。但是它们的处理和行为与 Java 中的不同。在下一章中,你将了解更多关于这方面的内容。

作用域

作用域是指一系列连续的 JavaScript 语句,具有明确的起始和结束。JavaScript 知道四种类型的作用域:块作用域、函数作用域、模块作用域和全局作用域。根据声明的类型和声明的位置,变量处于这些作用域中。它们只有在其作用域内“可见”或“可访问”,如果你从外部访问它们,会发生错误。

块作用域

一对花括号 {} 创建了一个块作用域。通过 letconst 在块内部声明的变量绑定到该块,无法在块外部访问。

"use strict";
let a = 0;
// ...
if (a == 0) {
  let x = 5;
  alert(x);   // 显示数字 5
} else {
  alert(x);   // 引用错误(因为 'x' 不在此作用域中)
}
alert(x);     // 引用错误

变量 x 在块内声明(在这个简单的例子中,块只有两行)。它在块结束后不可访问,块结束是 else 行的右花括号 }。同样的情况,如果变量 xconst 声明,也会出现同样的情况。

注意 var 关键字,它的语义是不同的!首先,var 不是块级作用域。其次,它会导致一种被称为“提升”(hoisting)的技术,这是 JavaScript 从诞生之初就使用的技术。提升会在幕后改变不同声明的语义。对于 var,它将声明和初始化分为两个独立的语句,并将声明部分提升到当前作用域的顶部。因此,如果你在声明行之前使用变量,它会被声明,但不会初始化。

"use strict";
alert(x);   // undefined,而不是 ReferenceError!
x = 1;      // 正确,尽管启用了 "use strict"
alert(x);   // 显示 1
var x = 0;

上述脚本被改为:

"use strict";
var x;
alert(x);   // undefined,而不是 ReferenceError!
x = 1;      
alert(x);   // 显示 1
x = 0;

另一方面,let 保持声明在它被写入的行。

"use strict";
alert(x);     // ReferenceError
x = 1;        // ReferenceError
alert(x);     // ReferenceError
let x = 0;

还有更多的差异。以下是将 let 替换为 var 的第一个示例:

"use strict";
let a = 0;
// ...
if (a == 0) {
  var x = 5;  // 用 'var' 替换 'let'
  alert(x);   // 显示数字 5
} else {
  alert(x);   // 引用错误(因为 'x' 在另一个作用域中)
}
alert(x);     // 显示数字 5 !!

由于以下两个原因,强烈建议完全避免使用 var

  1. JavaScript 的提升技术不容易理解。
  2. C 系列语言的其他成员并不支持此特性。

因此,应该使用 let 替代 var

函数作用域

函数创建自己的作用域。在函数作用域内声明的变量无法从外部访问。

"use strict";
function func_1() {
  let x = 5; // x 只能在 func_1 内使用
  alert("函数内: " + x);
}
func_1();
alert(x); // 触发错误

函数作用域有时被称为局部作用域,因为在旧版 ECMAScript 中,它使用的是这个名称。

另请参见:闭包(Closures)是反向工作——访问函数内部的外部变量。

模块作用域

可以将巨大的脚本分成多个文件,并让函数和变量彼此通信。每个文件创建自己的作用域,即模块作用域。有关更多信息,请参见 JavaScript/Modules 章节。

全局作用域

如果变量或函数在脚本的顶层声明(即不在任何块或函数内),则它们属于全局作用域。

"use strict";
let x = 42;   // 'x' 属于全局作用域

// 定义一个函数
function func_1() {
  // 使用全局上下文中的变量
  alert("函数内: " + x);
}

// 调用函数
func_1();   // 显示 "函数内: 42"
alert(x);   // 显示 "42"

在下一个示例中,变量 x 的声明被包裹在 {} 中,因此它不再是全局作用域的一部分。

"use strict";
{
  let x = 42;   // 'x' 不再在全局作用域中
}
alert(x);       // 引用错误

提示:使用全局作用域通常不是好习惯。它往往会引起不必要的副作用。相反,应该尝试将代码模块化,并通过接口让不同部分进行通信。

练习

… 练习可以在另一个页面中找到(点击这里)。

另请参见

  • 闭包(Closures)

参考资料

  • MDN: 变量声明
最后修改: 2025年01月13日 星期一 15:02