跳转到内容

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 引入一个变量,其值可以多次更改。

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

关键字 const

[编辑 | 编辑源代码]

关键字 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

[编辑 | 编辑源代码]

乍一看,varlet 相同。但是,这些变量的有效范围不同于由 var 声明的变量;请参见下面的 作用域 一章。

省略声明

[编辑 | 编辑源代码]

你可以将值赋给变量,而无需事先声明该变量。“JavaScript 以前允许将值赋给未声明的变量,这会创建一个 未声明的全局变量。这是 严格模式 中的错误,应该完全避免。”[1] 换句话说:变量进入全局作用域,请参见下面的 内容。除非你有充分的理由,否则应该避免使用全局作用域,因为它的使用往往会产生意想不到的副作用。

// direct usage of 'radius' without any keyword for its declaration
/* 1 */ radius = 5;
/* 2 */ alert (2 * radius * 3.14);

有时这种情况是意外发生的。如果源代码中存在打字错误,JavaScript 会使用两个不同的变量:原始变量和一个使用错误类型名称的新变量——即使你使用 letconstvar 中的任何一个关键字。

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 了解四种作用域:块级作用域、函数作用域、模块作用域和全局作用域。根据声明的类型和声明的位置,变量位于这些作用域中。它们仅在其作用域内是“可见的”或“可访问的”。如果你尝试从外部访问它们,则会发生错误。

块级作用域

[编辑 | 编辑源代码]

一对花括号 {} 创建一个 。在块中由 letconst 声明的变量绑定到此块,并且无法从外部访问。

"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 行中的右花括号 })之后无法访问。如果变量 xconst 而不是 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

提示:使用全局作用域不被认为是好的做法。它往往会产生不必要的副作用。相反,尝试将代码模块化,并让各个部分通过接口进行通信。

练习

[edit | edit source]
... 可以在另一个页面上找到(点击这里)。

另请参阅

[edit | edit source]

参考资料

[edit | edit source]
华夏公益教科书