let 和 const
let 声明
(一)基本用法
let 声明的变量只在块级作用域内有效,出了该块则报错,最常见且适合的地方在 for 循环中:
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
上面的 i 通过 var 方式声明,(循环次数内)全局有效且只有一个,故每次循环结束后, i 的值会被更新,从而导致在完成所有循环后, a 数组中所有的成员中的 i 均指向最后一次更新的值, 即 10,而使用 let 声明:
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
在每次循环时,该次循环体(代码块)中的 i 只对当前代码块有效,简单而言即:每次循环的 i 相当于一个新的变量,故最终结果是 6 。
(二)var 的变量提升
先看一个例子:
console.log(foo); // 输出undefined var foo = 2;
按照正常的逻辑,执行 console 时,变量 foo 在内存中并不存在,但是此时会输出 undefined(未赋值) 错误,这就是 var 声明的变量提升:脚本运行时,变量已经存在,但是没有值,最终输出 undefined。使用 let 声明可以避免变量提升。
(三)暂时性死区
只要块级作用域内存在 let 指令,它所声明的变量就 “绑定” 在这个区域,不在受外部影响。
if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 }
如上例所示,在 let 命令之前, tmp 变量都不可用, 这块区域都属于 tmp 的 “死区”,语法上称这种现象为 “暂时性死区”。
注意:1.使用 let 命令时,typeof 不在是一个百分百安全操作;
2.一个比较隐蔽的死区:
function bar(x = y, y = 2) { return [x, y]; } bar(); // 报错
调用 bar() 报错的原因在于 x = y 时,y 并没有声明,属于 “死区”,故报错。解决办法是将函数中参数位置对调即可。
同理:
// 不报错 var x = x; // 报错 let x = x; // ReferenceError: x is not defined
总之:暂时性锁区的本质是,只要一进入当前作用域,所要使用的变量就已经存在,但是处于不可获取的状态,只有等到声明语句出现,才可以获取和使用。
(四)块级作用域
es 5 只有全局和函数作用域两种,接下来看两个 es5 的例子:
1.内层变量覆盖外层变量
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
在 fun f() 中,因为存在 var 指令,故可以说在 f() 中就会存在变量提升的现象(而且优先级貌似蛮高的), 所以这就会导致 console undefined 错误,而不是 console 时间;
2.用来计数的循环变量泄露成全局变量:
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
上例可以看出,在循环结束后,作为控制循环的 i 变量并没有消失,这样就很容易泄露,可能会被恶意利用。
而 let 的使用为 es 6 新増了块级作用域。
const 声明
效果跟 java 中的 static final 差不多;
需要注意:
1.const 的变量声明时必须赋值,否则报错;
2.const 的变量一旦声明,其值只读,不可改变;
3.const 的作用域与 let 相同: 只在声明所在的块级作用域内有效;
4.const 的变量不存在变量提升现象,同样存在暂时性死区;
5.const 的变量,不可重复声明;
6.关于引用类型的对象,一个例子:
const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错
常量 a 中存储了一个数组对象的地址,然后这个数组对象中的内容是可变的,但是,最后一行很明显是把一个新的数组对象的地址赋值给 a,这就导致了报错。