计算机编程中最基本的一个就是使用名字(或标识符)表示值。绑定名字和值为我们提供了一种引用值和在程序中使用值的方式,对于绑定名字和值,我们通常会说把值赋给变量。术语“变量”意味着可以为其赋予新值,也就是说与变量关联的值在程序运行时可能发生变化。如果把一个值永久地赋予给一个名字。那么可以称该名字为常量而不是变量。(来自犀牛书变量声明与赋值)
在JavaScript中使用变量或常量前,必须先声明它。在ES6之前,我们是通过 var 关键字来声明变量的,在ES6之后,我们可以通过 let 和 const 关键字来完成声明。
1️⃣ var 是 JavaScript 早期版本中最常见的变量声明方式,它可以在全局作用域和函数作用域中使用。使用 var 声明的变量具备变量提升的特性,但是在没有赋值的情况下,其默认值为 undefined。
我们看下面这段代码,会输出什么呢?没错输出的是undefined。
function getNumber(isNumber) {
if (isNumber) {
var num = "7";
} else {
var notNum = "not number!";
console.log(num);
}
}
getNumber(false);
因为在 JavaScript 中有一个提升机制,就是无论你在哪里使用 var 关键字声明变量,它都会被提升到当前作用域的顶部。在运行 getNumber 函数时,实际上执行结构是下面这个样子。
function getNumber(isNumber) {
var num;
var notNum;
if (isNumber) {
num = "7";
} else {
notNum = "not number!";
console.log(num);
}
}
getNumber(false);
因为在开头定义变量时,没有给 num 变量赋任何值,并且 getNumber 传入的是 false,导致 if 语句未执行,num 未被赋值,所以控制台输出 undefined。
2️⃣ var 声明的变量可以被重复定义,这意味着可以在同一作用域内多次声明同一个变量,而不会报错。但是,如果 var 定义的变量在全局作用域中声明,则有可能会覆盖掉某些全局变量,造成不必要的麻烦。
如下面这段代码就很麻烦了,会造成环境卡死。
function Sum(arrList) {
var sum = 0;
for (var i = 0; i < arrList.length; i++) {
var arr = arrList[i];
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
return sum;
}
var arr = [1, 2, 3, 4, 5];
document.write(Sum(arr));
这是因为在两层 for 循环中我们使用同一变量 i 进行赋值时,代码在执行过程中,第二层的 for 循环会覆盖外层变量 i 的值。
3️⃣ 由于 var 存在变量提升的问题,所以它有可能会引起一些奇怪的问题。比如,在循环体内使用 var 定义的变量(如上述代码),可能会因为变量提升而影响到其他的循环体。
补充:使用 var 关键字定义的变量只有两种作用域,全局作用域和函数作用域,两者均不是块结构,会造成变量声明的提升。这可能出现下面这种问题:
function func() {
for (var i = 0; i < 5; i++) {}
console.log(i); // 5
}
func();
运行上述代码后,你会发现页面上会显示 5。我们虽然是在 for 循环中定义的 i 变量,但由于变量被提升到 for 语句之上,所以退出循环后,变量 i 并没有被销毁,我们能够在循环外获取它的值。
1️⃣let 是 ES6 中新增的一种变量声明方式,它主要用于声明块级作用域中的变量。使用 let 声明的变量不存在变量提升的问题,因此不能在声明之前使用变量。let 声明的变量不能被重复定义,如果在同一个作用域内多次声明同一个变量,会报错。
我们看下面这段代码会输出什么呢?
function getNumber(isNumber) {
if (isNumber) {
let num = "7";
} else {
let notNum = "not number!";
console.log(num);
}
}
getNumber(false);
ReferenceError 是一个引用类型的错误,num is not defined 意思是 num 变量并不存在。
let 关键字声明变量,其作用域是一个块,如果我们是在花括号 {} 里面声明变量,那么变量会陷入暂时性死区,也就是在声明之前,变量不可以被使用。
在上面代码中,我们的 num 变量是放在 if(){} 这个块中,并没有在 else{} 块中,所以会形成暂时性死区。
虽然 let 关键字声明的变量可以重新赋值,但是它与 var 关键字有所不同,let 关键字不能在同一作用域内重新声明,而 var 可以。
let i = 5;
let i = 6;
console.log(i);
可以看到控制台会报参数错误(SyntaxError)。
如果将上面代码中的 let 关键字改为 var 关键字便不会报错了。
var i = 5;
var i = 6;
console.log(i);
2️⃣ let 声明的变量具备块级作用域,这意味着它们只在相应的代码块中存在,超出该块范围时就会被销毁。在循环体内使用 let 定义变量,则可以避免使用 var 带来的问题。
function func() {
for (let i = 0; i < 5; i++) {}
console.log(i);
}
func();
你会发现,控制台报错了。这是因为上面代码中的 i 变量只存在于 for 循环这个块中,当循环结束,i 变量就被销毁了,所以在 for 循环外访问不到。(对比看 var 的3️⃣的补充)
1️⃣ const 也是 ES6 中新增的一种变量声明方式,它用于声明常量。使用 const 声明的变量必须进行初始化操作,不能在声明后再次赋值。这意味着,const 声明的变量值是不可变的,不能被重新赋值。(除了不能被重新赋值之外,const 声明的变量与 let 声明的变量类似,也具备块级作用域。)
例如:
❗❗❗const 声明的变量值是不可变的,不能被重新赋值。否则会报如下错误。
const MaxAge = 100;
MaxAge = 10;
console.log(MaxAge);
❗❗❗声明的变量必须进行初始化操作,不能在声明后再次赋值。否则会报如下错误。
const Num;
console.log(Num);
2️⃣ const 声明的变量不是真正的常量,而是指向内存地址的指针。因此,如果 const 声明的变量是对象或数组,虽然无法重新赋值,但是可以更改其属性或元素值。
值类型是指变量直接存储的数据,例如:
const num = 23;
这里 num 变量就是值类型,我们使用的是 const 关键字来定义 num,故赋予变量 num 的值 20 是不可改变的。
引用类型是指变量存储数据的引用,而数据是放在数据堆中,比如,用 const 声明一个数组。
const arr = ["一", "二", "三"];
如果你尝试去修改数组,同样会❗报错。
const arr = ["一", "二", "三"];
arr = ["五", "六", "七"];
但是,使用 const 关键字定义的引用类型还是可以通过数组下标去修改值 。例如:
const arr = ["一", "二", "三"];
arr[0] = "四";
arr[1] = "五";
arr[2] = "六";
console.log(arr);
因为变量 arr 保存的是数组的引用,并不是数组中的值,只要引用的地址不发生改变就不会保错。这就相当于一个房子,它拥有固定的位置,但住在房子里的人不一定固定。
var、let 和 const 是 JavaScript 中的三种变量声明方式,主要区别如下:
变量作用范围不同:var 声明的变量作用域为函数作用域(全局作用域),let 和 const 声明的变量作用域为块级作用域(在花括号 {} 内)。
变量是否可以重复声明:var 可以被重复声明,后面的声明会覆盖前面的声明;而 let 和 const 同一作用域内不允许重复声明相同名称的变量。
变量初始化和赋值方式不同:var 不需要进行初始化,定义时如果没有赋值会自动赋值为 undefined;let 和 const 声明时必须赋初值或者等待第一次被赋值。同时,const 声明的变量是一个常量,不允许改变其值。而 var 和 let 声明的变量可以被重新赋值。
变量提升方式不同:var 变量会存在变量提升,即在变量声明前使用该变量会得到 undefined,因为该变量已经被声明了;而 let 和 const 声明的变量不存在变量提升,必须在声明后才能使用。
使用场景:在实际开发中,可以根据具体需求来选择使用 var、let 和 const,一般建议优先使用 const 声明常量,let 声明变量的情况下可以在需要修改变量值时使用,而 var 声明变量则可以在需要全局共享变量时使用。