一、let
1.1 let 申明的变量,只能在 let 所在的代码块内有效;
例:
{ let a = 10; var b = 1; }
a // ReferenceError: a is not defined.
b // 1
b 是 var 声明的变量,所以是全局的,在代码块外也能有效;
1.2 let 在 for 循环中很适用;
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i); // ReferenceError: i is not defined
二、var 和 let 在 for 循环中声明变量的区别
var a = [];
for (var i = 0; i < 10; i++)
{
a[i] = function ()
{
console.log(i);
};
}
a[6](); // 10
上面代码中,变量 i 是 var 命令声明的,在全局范围内都有效,所以全局只有一个变量 i 。每一次循环,变量 i 的值都会发生改变,而循环内被赋给数组a的函数内部的 console.log(i) ,里面的 i 指向的就是全局的 i 。也就是说,所有数组 a 的成员里面的 i ,指向的都是同一个 i ,导致运行时输出的是最后一轮的 i 的值,也就是 10。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () { console.log(i);};
}
a[6](); // 6
上面的代码中,i 是 let 声明的,只能在每次循环中有效,每次循环 i 都是一个新的变量;
2.1 for 循环的特别之处
设置循环的部分是一个父作用域,而循环体内部是一个单独的子作用域;
for (let i = 0; i < 3; i++) {
let i = 'abc'; console.log(i);
}
// abc
// abc
// abc
三次都是输出 abc ,let 变量在不同的作用域中是独立的;
三、不存在变量提升
3.1
var 变量在声明之前使用,值为 undefined;
let 变量在声明之前使用,直接报错;
// var 的情况
console.log(foo);// 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
四、暂时性死区(TDZ)
4.1 let 变量绑定在块级区域内,不受外部变量的影响;
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
以上代码虽然已在代码块外面声明了变量 tmp,但是在代码块内部,使用 let 重新声明了变量 tmp,所有在声明之前使用 tmp 就直接报错,这里就被成为暂时性死区;
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
在上面代码块中,在声明 tmp之前,都属于 tmp 的 TDZ;
4.2 typeof 不再百分百安全
typeof x; // ReferenceError
let x;
在使用 let 声明 变量 x 之前,x 属于 TDZ 中
typeof x // "undefined"
在不使用 let 声明 x 时,不会报错;
4.3 比较隐蔽的 TDZ
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
在声明 y 之前, x 的默认值 等于 y ,此时 y 处于 TDZ。所以报错;
// 不报错
var x = x;
// 报错
let x = x; // ReferenceError: x is not defined
在 x 变量完成声明前使用 x ,x 仍处于 TDZ 中;
五、不允许重复声明变量
let 不允许在同一个代码块中,重复声明同一个变量
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
以上两种情况都会报错;
在函数内部也不能重复声明变量;
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
六、块级作用域
6.1 需要块级作用域的原因
场景一:
var tmp = new Date();
function f() { console.log(tmp);
if (false) { var tmp = 'hello world'; } }
f(); // undefined
上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。
场景二:
var s = 'hello';
for (var i = 0; i < s.length; i++) { console.log(s[i]); }
console.log(i); // 5
i 变量本来只是作用在循环中,最后却泄漏成全局变量;
6.2 块级作用域的使用
function f1() { let n = 5;
if (true) { let n = 10;}
console.log(n); // 5
}
以上 变量 n 在代码块内声明,并不会影响代码块外的使用;
6.3 块级作用域与函数声明;
避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句
// 函数声明语句
{
let a = 'secret';
function f() { return a; }
}
// 函数表达式
{
let a = 'secret';
let f = function () { return a; };
}
另外,还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
七、do表达式
在块级作用域前加上 do ,可以得到块级作用域中的值
let x = do {
let t = f(); t * t + 1;
};
八、const
1.const 声明的是一个只读常量;不能改变,类似 java 中的 带 final 的变量;
2.const 声明的变量必须赋值,否则会报错
3.作用域 与 let 相同,只能在块级作用域中
4.同样有暂时性死区(TDZ)
5.在同一块级作用域中不能重复声明;
6.Object.freeze 冻结对象
九、声明变量的方法
es5中只能使用 var 和 function 命令;
es6 中新加了 let 、const、import、class四种方法;
十、顶层对象的属性;
与全局变量是等价的;
ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
网友评论