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 模块预定义了一个名为 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
乍一看,var
与 let
相同。但 var
声明的变量作用域范围与 let
声明的变量不同;请参见下面的作用域章节。
省略声明
你可以在不先声明变量的情况下直接赋值给它。“JavaScript 曾允许给未声明的变量赋值,这会创建一个未声明的全局变量。在严格模式下这是一个错误,应该完全避免。”[1] 换句话说,变量会进入全局作用域,见下文。除非你有充分的理由,否则应该避免使用全局作用域,因为它的使用容易引发不必要的副作用。
// 直接使用 'radius',没有声明关键字
/* 1 */ radius = 5;
/* 2 */ alert(2 * radius * 3.14);
有时这种情况是偶然发生的。如果源代码中有拼写错误,JavaScript 会使用两个不同的变量:原始变量和一个拼写错误的新变量——即使你使用了 let
、const
或 var
关键字。
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 知道四种类型的作用域:块作用域、函数作用域、模块作用域和全局作用域。根据声明的类型和声明的位置,变量处于这些作用域中。它们只有在其作用域内“可见”或“可访问”,如果你从外部访问它们,会发生错误。
块作用域
一对花括号 {}
创建了一个块作用域。通过 let
或 const
在块内部声明的变量绑定到该块,无法在块外部访问。
"use strict";
let a = 0;
// ...
if (a == 0) {
let x = 5;
alert(x); // 显示数字 5
} else {
alert(x); // 引用错误(因为 'x' 不在此作用域中)
}
alert(x); // 引用错误
变量 x
在块内声明(在这个简单的例子中,块只有两行)。它在块结束后不可访问,块结束是 else
行的右花括号 }
。同样的情况,如果变量 x
用 const
声明,也会出现同样的情况。
注意 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
:
- JavaScript 的提升技术不容易理解。
- 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: 变量声明