这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
let的改进
- 实际let和const出现最至关重要的作用就是块级作用域,不会向var一样因无块级作用域变成全局变量(var只有全局作用域和函数作用域)
{
let a = 1;
var b = 5;
}
a // ReferenceError: a is not defined.
b //5
这就证明let 只在块级作用域有效,从而不用担心像var一样的没有块级作用域变量变成全局变量
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
这里我们预期的输出是6,但实际是10。原因是var使i变成一个全局变量,而因为for循环,i一直在改变,但其实a[6]()调用了全局i而非我们想象的对应的i=6。由于这个i最后被赋值为10,因此它返回了10
- var允许不声明就使用,但是let不允许
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误
故这里let更适用于for循环,因为let定义的i只在一次循环中有效,从而避免了变量提示的问题最后返回6
另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
- 这里就要注意let同一个变量不能声明两次,var之前可以声明两次因为这里对应的是一个,而let是两个作用域
具体看以下代码
for (var i = 0; i < 3; i++) {
var i = 'abc';
console.log(i);
}
// abc(只输出一次,证明原变量被修改)
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc(输出了三次,证明i对应的作用域不是同一个)
// abc
// abc
以及
function func(arg) {
let arg;
}
func() // Identifier 'arg' has already been declared
- var的变量提示
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
以上代码是由于函数内层if里的tmp变量提升影响了外部的tmp造成返回undefined
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
tmp = 'hello world'; // 或者let tmp = 'hello world';
}
}
f(); // 2022-05-26T11:15:25.631Z
这样就很好的避免了var之前的缺陷,减少因编码而造成的bug
暂时性死区(temporal dead zone,简称 TDZ)
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
这里就要保证一定要先声明变量再进行使用
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
typeof x; // ReferenceError
let x;
上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError。
作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。
typeof undeclared_variable // "undefined"
const
const代表常量,这里必须要在声明时就赋值,同时不能改变
const foo;
// SyntaxError: Missing initializer in const declaration
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
暂存性死区和块级作用域,不重复声明变量与let类似
const并非真正的常量
const 的本质: const 定义的变量并非常量,并非不可变,引用内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
因此使用 const 定义的对象或者数组,其实是可变的。下面的代码并不会报错:
// 创建常量对象
const car = {type:"Fiat", model:"500", color:"white"};
// 修改属性:
car.color = "red";
注意不能整个对象重新赋值,这就像给一般常量重新赋值一样,不可以
const car = {type:"Fiat", model:"500", color:"white"};
car = {type:"Volvo", model:"EX60", color:"red"};
// TypeError: Assignment to constant variable.
修改数组:
// 创建常量数组
const cars = ["Saab", "Volvo", "BMW"];
// 修改元素
cars[0] = "Toyota";
// 添加元素
cars.push("Audi");
cars = ["Toyota", "Volvo", "Audi"]; // 错误
综上,可知在我们定义变量最好用let而非var,常量或数组就用const而非let