详解JavaScript中的变量、范围和提升
导读
变量是许多编程语言的基本组成部分,也是新手需要学习的第一个也是最重要的概念。JavaScript中有许多不同的变量属性,以及命名变量时必须遵循的一些规则。在JavaScript中,有三个关键字用于声明变量——var、let和const——每个关键字都会影响代码对变量的不同解释。
本教程将介绍什么是变量,如何声明和命名变量,并进一步研究var、let和const之间的区别。我们还将回顾提升的影响以及全局和局部作用域对变量行为的重要性。
理解变量
变量是用于存储值的命名容器。我们可能多次引用的一条信息可以存储在一个变量中,供以后使用或修改。在JavaScript中,变量中包含的值可以是任何JavaScript数据类型,包括数字、字符串或对象。
在今天的JavaScript所基于的ECMAScript 2015 (ES6)语言规范之前,只有一种方法来声明变量——使用var关键字。因此,大多数较老的代码和学习资源将只对变量使用var。我们将在下面单独一节讨论var、let和const关键字之间的区别。
我们可以使用var来演示变量本身的概念。在下面的示例中,我们将声明一个变量,并为其赋值。
// Assign the string value Sammy to the username identifier var username = "sammy_shark";
本声明由以下几部分组成:
使用var关键字声明变量
变量名(或标识符),用户名
赋值操作,由=语法表示
分配的值“sammy_shark”
现在我们可以在代码中使用username。JavaScript将记住username表示字符串值sammy_shark。
// Check if variable is equal to value if (username === "sammy_shark") { console.log(true); }
输出:
true
如前所述,变量可以用来表示任何JavaScript数据类型。在本例中,我们将使用字符串、数字、对象、布尔值和null值声明变量。
// Assignment of various variables var name = "Sammy"; var spartans = 300; var kingdoms = [ "mammals", "birds", "fish" ]; var poem = { roses: "red", violets: "blue" }; var success = true; var nothing = null;
使用console.log,我们可以看到特定变量中包含的值。
// Send spartans variable to the console console.log(spartans);
输出:300
变量将数据存储在内存中,稍后可以访问和修改这些数据。变量也可以重新分配,并给定一个新值。下面的简化示例演示了如何将密码存储到变量中,然后进行更新。
//为password变量赋值 var password = "hunter2"; //用一个新值重新分配变量值 password = "hunter3"; console.log(password);
输出:
'hunter3'
在实际的程序中,密码很可能安全地存储在数据库中。然而,这个例子说明了一种情况,在这种情况下,我们可能需要更新变量的值。password的值是hunter2,但是我们将其重新分配给了hunter3,这是JavaScript从那时起可以识别的值。
命名变量
变量名在JavaScript中称为标识符。我们讨论了在理解JavaScript语法和代码结构时命名标识符的一些规则,总结如下:
变量名只能由字母(a-z)、数字(0-9)、美元符号($)和下划线(_)组成
变量名不能包含任何空白字符(制表符或空格)
数字不能是任何变量的名称开头
保留的关键字不能用作变量的名称
变量名区分大小写
JavaScript还习惯在使用var或let声明的函数和变量的名称中使用驼峰大小写(有时作为驼峰大小写进行样式化)。这是一种将第一个单词小写,然后将后面每个单词的第一个字母大写,中间没有空格的做法。除了一些例外,大多数非常量的变量都遵循这种约定。使用const关键字声明的常量变量的名称通常都是大写的。
这可能看起来像要学习很多规则,但很快就会成为编写有效和常规变量名称的第二天性。
var、let和const之间的区别
JavaScript有三个不同的关键字来声明变量,这给语言增加了额外的复杂性。三者之间的区别是基于范围、提升和重新分配。
关键字 | 范围 | 变量提升 | 可以重新分配 | 可以重新定义 |
---|---|---|---|---|
var | 功能范围 | Yes | Yes | Yes |
let | 阻止范围 | No | Yes | No |
const | 阻止范围 | No | No | No |
您可能想知道应该在自己的程序中使用这三种方法中的哪一种。一个普遍接受的做法是尽可能多地使用const,并在循环和重新分配的情况下使用let。通常,在处理遗留代码之外可以避免var。
变量作用域
JavaScript中的作用域是指代码的当前上下文,它决定了变量对JavaScript的可访问性。范围的两种类型是局部的和全局的:
全局变量是在块之外声明的变量
局部变量是在块内声明的变量
在下面的示例中,我们将创建一个全局变量。
//初始化一个全局变量 var creature = "wolf";
我们知道变量可以重新分配。使用局部作用域,我们实际上可以创建与外部作用域中的变量同名的新变量,而无需更改或重新分配原始值。
在下面的示例中,我们将创建一个全局species变量。函数内部是一个具有相同名称的局部变量。通过将它们发送到控制台,我们可以看到变量的值如何根据范围而不同,并且原始值不会更改。
//初始化一个全局变量 var species = "human"; function transform() { //初始化一个局部的、函数作用域的变量 var species = "werewolf"; console.log(species); } //记录全局和局部变量 console.log(species); transform(); console.log(species);
输出:
human werewolf human
在本例中,局部变量是函数作用域的。使用var关键字声明的变量总是函数作用域,这意味着它们将函数识别为具有独立作用域。因此,这个局部作用域的变量不能从全局作用域访问。
然而,新的关键字let和const是块范围的。这意味着从任何类型的块(包括函数块、if语句、for和while循环)创建一个新的本地范围。
为了说明函数作用域变量和块作用域变量之间的区别,我们将使用let在if块中分配一个新变量。
var fullMoon = true; //初始化一个全局变量 let species = "human"; if (fullMoon) { //初始化一个块范围的变量 let species = "werewolf"; console.log(`It is a full moon. Lupin is currently a ${species}.`); } console.log(`It is not a full moon. Lupin is currently a ${species}.`);
输出:
It is a full moon. Lupin is currently a werewolf. It is not a full moon. Lupin is currently a human.
在此示例中,species变量具有一个值global(human),另一个值local(werewolf)。var但是,如果我们使用,则会有不同的结果。
//使用var初始化一个变量 var species = "human"; if (fullMoon) { //尝试在一个块中创建一个新变量 var species = "werewolf"; console.log(`It is a full moon. Lupin is currently a ${species}.`); } console.log(`It is not a full moon. Lupin is currently a ${species}.`);
输出:
It is a full moon. Lupin is currently a werewolf. It is not a full moon. Lupin is currently a werewolf.
在这个例子的结果中,全局变量和块范围的变量都以相同的值结束。这是因为您不是使用var创建一个新的本地变量,而是在相同的范围内重新分配相同的变量。var不能识别是否属于不同的新范围。通常建议声明块范围的变量,因为它们生成的代码不太可能无意中覆盖变量值。
变量提升
到目前为止,在大多数示例中,我们已经使用var声明了一个变量,并使用一个值初始化了它。在声明和初始化之后,我们可以访问或重新分配变量。
如果我们试图在变量被声明和初始化之前使用它,它将返回undefined。
//在声明变量之前尝试使用它 console.log(x); / /变量赋值 var x = 100;
输出:
undefined
但是,如果省略var关键字,就不再声明变量,而是初始化它。它将返回一个ReferenceError并停止脚本的执行。
//在声明变量之前尝试使用它 console.log(x); //没有var的变量赋值 x = 100;
输出:
ReferenceError: x is not defined
原因在于提升,这是JavaScript的一种行为,其中变量和函数声明被移到它们作用域的顶部。由于只挂起实际声明,而没有初始化,因此第一个示例中的值返回未定义的值。
为了更清楚地演示这个概念,下面是我们编写的代码以及JavaScript如何解释它。
// The code we wrote console.log(x); var x = 100; // How JavaScript interpreted it var x; console.log(x); x = 100;
JavaScript在执行脚本之前将x保存为内存作为变量。 由于它在定义之前仍然被调用,因此结果是未定义的而不是100.但是,它不会导致ReferenceError并停止脚本。
尽管var关键字实际上并未更改var的位置,但这有助于表示提升的工作原理。 但是,这种行为可能会导致问题,因为编写此代码的程序员可能希望x的输出为true,而不是undefined。
在下一个例子中,我们还可以看到提升是如何导致不可预测的结果的:
//在全局范围内初始化x var x = 100; function hoist() { //不应影响编码结果的条件 if (false) { var x = 200; } console.log(x); } hoist();
输出:
undefined
在本例中,我们声明x全局为100。根据if语句,x可以更改为200,但是由于条件为false,所以它不应该影响x的值。
这种不可预测的行为可能会在程序中引起bug。由于let和const是块范围的,所以它们不会以这种方式提升,如下所示。
//在全局范围内初始化x let x = true;function hoist() { //在函数作用域中初始化x if (3 === 4) { let x = false; } console.log(x); } hoist();
输出:
true
变量的重复声明(这在var中是可能的)将在let和const中抛出一个错误。
//试图覆盖用var声明的变量 var x = 1; var x = 2; console.log(x);
输出:2
//试图覆盖用let声明的变量 let y = 1; let y = 2; console.log(y);
输出:
Uncaught SyntaxError: Identifier 'y' has already been declared
总之,使用var引入的变量有可能受到提升的影响,提升是JavaScript中的一种机制,其中变量声明被保存到内存中。这可能导致代码中出现未定义的变量。let和const的引入解决了这个问题,它在试图在声明变量之前使用该变量或多次声明该变量时抛出一个错误。
常量
许多编程语言都有常量,这些常量是不能修改或更改的值。在JavaScript中,const标识符是根据常量建模的,不能重新分配分配给const的值。
将所有const标识符都写成大写是常见的约定。这将它们与其他变量值区分开来。
在下面的示例中,我们使用const关键字将变量SPECIES初始化为常量。试图重新分配变量将导致错误。
//给const赋值 const SPECIES = "human"; //尝试重新分配值 SPECIES = "werewolf"; console.log(SPECIES);
输出:
Uncaught TypeError: Assignment to constant variable.
因为不能重新分配const值,所以需要同时声明和初始化它们,否则也会抛出错误。
//声明,但不初始化const const TODO; console.log(TODO);
输出:
Uncaught SyntaxError: Missing initializer in const declaration
不能在编程中更改的值称为不可变值,而可以更改的值是可变的。虽然const值不能重新分配,但是它们是可变的,因为可以修改用const声明的对象的属性。
//创建一个具有两个属性的CAR对象 const CAR = { color: "blue", price: 15000} //修改CAR的属性 CAR.price = 20000; console.log(CAR);
输出:
{ color: 'blue', price: 20000 }
常量非常有用,可以让将来的自己和其他程序员清楚地认识到,不应该重新分配预期的变量。如果您希望将来修改某个变量,那么您可能希望使用let来声明该变量。