JavaScript/变量
计算机语言需要使用变量。为什么呢?在大多数情况下,程序不会解决像“半径为 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 用关键字 const
预定义了一个名为 PI 的变量:const PI = 3.141592653589793;
。
在 JavaScript 中,变量的使用方式类似于数学公式中的变量。在运行时,变量的值存储在计算机的主内存(RAM)中,可以稍后在程序的生命周期中使用。你可以将变量想象成一个小盒子,你在里面放一些值,并在需要时取出来。
变量是将单个问题解决转变为策略或 **算法** 的基石。
如果你想使用变量,我们建议你明确地进行 声明。这不是强制性的,但有很大的好处。
在许多情况下,声明伴随着初始化,例如 let x = 0;
。声明是 let x;
,初始化是 x = 0;
。但也可以省略初始化部分:let x;
,这将导致变量的值为 undefined
。
关键字 let
引入一个变量,其值可以多次更改。
let x = 0;
// ...
x = x + 5;
// ...
x = -4;
// ...
关键字 const
引入一个变量,该变量 **必须** 立即初始化。此外,这个第一个值永远不能改变。这有助于 JavaScript 引擎优化代码,以提高性能。尽可能使用 const
。
const maxCapacity = 100;
// ...
maxCapacity = maxCapacity + 10; // not possible
let maxCapacity = 110; // not possible
当你使用对象(例如,数组)并结合关键字 const
时,情况相同:你不能将另一个值(对象、数组、数字或其他任何值)赋给变量。但是,它的元素可以更改。
const arr = [1, 2, 3];
arr = [1, 2, 3, 4]; // not possible
arr = new Array(10); // not possible
arr[0] = 5; // ok: 5, 2, 3
alert(arr);
arr.push(42); // ok: 5, 2, 3, 42
alert(arr);
在某些情况下,const
变量以大写形式编写,例如 PI
。这是一种约定,不是强制性的。
乍一看,var
与 let
相同。但是,这些变量的有效范围不同于由 var
声明的变量;请参见下面的 作用域 一章。
你可以将值赋给变量,而无需事先声明该变量。“JavaScript 以前允许将值赋给未声明的变量,这会创建一个 未声明的全局变量。这是 严格模式 中的错误,应该完全避免。”[1] 换句话说:变量进入全局作用域,请参见下面的 内容。除非你有充分的理由,否则应该避免使用全局作用域,因为它的使用往往会产生意想不到的副作用。
// direct usage of 'radius' without any keyword for its declaration
/* 1 */ radius = 5;
/* 2 */ alert (2 * radius * 3.14);
有时这种情况是意外发生的。如果源代码中存在打字错误,JavaScript 会使用两个不同的变量:原始变量和一个使用错误类型名称的新变量——即使你使用 let
、const
或 var
中的任何一个关键字。
let radius = 5; // or without 'let'
alert("Test 1");
// ... later in the code
radus = 1; // typo will not be detected
alert("Test 2");
你可以通过在脚本的第一行插入命令 "use strict";
来指示 JavaScript 搜索此类打字错误。
"use strict";
let radius = 5;
alert("Test 1");
// ... later in the code
radus = 1; // typo will be detected and an error message given
alert("Test 2"); // will never execute
熟悉(严格)类型语言(如 Java)的程序员可能会在上一章中错过定义变量类型的可能性。JavaScript 了解许多不同的数据类型。但它们的处理和行为与 Java 中的处理和行为非常不同。在 下一章 中,你将了解这一点。
作用域 是一个连续的 JavaScript 语句范围,具有明确定义的开始和结束。JavaScript 了解四种作用域:块级作用域、函数作用域、模块作用域和全局作用域。根据声明的类型和声明的位置,变量位于这些作用域中。它们仅在其作用域内是“可见的”或“可访问的”。如果你尝试从外部访问它们,则会发生错误。
一对花括号 {}
创建一个 块。在块中由 let
或 const
声明的变量绑定到此块,并且无法从外部访问。
"use strict";
let a = 0;
// ...
if (a == 0) {
let x = 5;
alert(x); // shows the number 5
} else {
alert(x); // ReferenceError (with a different 'a')
}
alert(x); // ReferenceError
变量 x
在一个块中声明(在这个简单的例子中,块只包含两行)。它在块的末尾(即 else
行中的右花括号 }
)之后无法访问。如果变量 x
用 const
而不是 let
声明,也是如此。
小心使用关键字 var
;它的语义不同! 首先,var
不是块级作用域的。其次,它会导致一种称为 提升 的技术,该技术从 JavaScript 的最初版本开始就被使用。提升会改变不同声明的“幕后”语义。关于 var
,它将声明和初始化分成两个独立的语句,并将声明部分移到当前作用域的顶部。因此,如果你在 **源代码中** 声明该变量之前使用它,则该变量将被声明,但不会被初始化。
脚本
"use strict";
alert(x); // undefined, not ReferenceError !
x = 1; // correct, despite of "use strict"
alert(x); // shows 1
var x = 0;
将变为
"use strict";
var x;
alert(x); // undefined, not ReferenceError !
x = 1;
alert(x); // shows 1
x = 0;
另一方面,关键字 let
会将声明保留在它被写入的行中。
"use strict";
alert(x); // ReferenceError
x = 1; // ReferenceError
alert(x); // ReferenceError
let x = 0;
还有一些其他差异。以下是用 var
替换本章第一个例子中 let
的版本
"use strict";
let a = 0;
// ...
if (a == 0) {
var x = 5; // 'var' instead of 'let'
alert(x); // shows the number 5
} else {
alert(x); // ReferenceError (with a different 'a')
}
alert(x); // shows the number 5 !!
我们建议完全避免使用 var
,因为有两个原因
- JavaScript 的提升技术不容易理解。
- C 家族的其他语言成员不知道它。
不要使用 var
,而应该使用关键字 let
。
函数作用域
[edit | edit source]函数创建自己的作用域。在函数作用域中声明的变量无法从外部访问。
"use strict";
function func_1() {
let x = 5; // x can only be used in func_1
alert("Inside function: " + x);
}
func_1();
alert(x); // Causes an error
函数作用域有时被称为局部作用域,因为这是旧版本 ECMAScript 中的名称。
另请参阅:闭包 的工作方式相反——在函数内部访问外部变量。
模块作用域
[edit | edit source]可以将大型脚本分成多个文件,并让函数和变量相互通信。每个文件创建自己的作用域,即模块作用域。章节JavaScript/Modules 对此进行了更多解释。
全局作用域
[edit | edit source]如果变量或函数在脚本的顶层(在所有块和函数之外)声明,则它们位于全局作用域中。
"use strict";
let x = 42; // 'x' belongs to global scope
// define a function
function func_1() {
// use variable of the global context
alert("In function: " + x);
}
// start the function
func_1(); // shows "In function: 42"
alert(x); // shows "42"
x
在顶层声明,因此它位于全局作用域中,可以在任何地方使用。但在下一个示例中,x
的声明被 { }
包裹。因此它不再位于全局作用域中。
"use strict";
{
let x = 42; // 'x' is not in global scope
}
alert(x); // ReferenceError
提示:使用全局作用域不被认为是好的做法。它往往会产生不必要的副作用。相反,尝试将代码模块化,并让各个部分通过接口进行通信。