1.let命令
基本用法
ES6新增了一个let命令,用于声明变量,与var命令用法类似,但是用let声明的变量只在let命令所在的代码块有效。
{ let name = "abc"; var age = 18 } console.log(name); console.log(age);
上面的代码在代码块中,分别用let和var定义了两个变量,在代码块外,可以打印出用var声明的age,但是打印不出用let声明的name。这表明,用let声明的变量只在其所在的代码块中有效。
for循环现在就很适合使用let命令了。
{ let arr = []; for (let i = 0, len = arr.length; i < len; i++) { } }
另外,使用let命令可以解决之前的闭包问题。
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
上面代码用var声明了变量i后,是在全局范围内都有效的。所以每一次循环,新的i都会覆盖之前的旧值,导致最后输出的是最后一个i的值。
在ES6以前,解决方式通常是采用闭包的方式。
var a = []; for (var i = 0; i < 10; i++) { (function (i) { a[i] = function () { console.log(i); } })(i); } a[6](); // 6
现在有了let命令,可以不再使用闭包方式解决该问题,直接通过let声明变量i就可以达到该效果。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
上面的代码用let声明了i变量,当前的i只在本轮循环有效,所以每一次循环的i都是一个新的变量,最后输出的是6。
不存在变量提升
let命令声明的变量不会像用var声明的一样,发生“变量提升”的现象。所以,变量一定要在声明后使用。
console.log(name); // abc var name = 'abc'; console.log(name); // ReferenceError: can't access lexical declaration `name' before initialization let name = 'abc';
暂时性死区(temporal dead zone, TDZ)
只要块作用于中存在let命令声明的变量,该变量就“绑定”在这个块级作用域中,不再受外部声明的变量影响。
var name = 'abc'; { name = 'jack'; let name; } // ReferenceError: can't access lexical declaration `name' before initialization
上面的代码先声明了name,又在块级作用域中用let声明了name,导致后者绑定了这个作用域,所以在let声明name前,对name赋值会报错。
不允许重复声明
let不允许在相同的作用域内重复声明一个变量。
function test() { let a = 10; let a = 20; // SyntaxError: redeclaration of let a } function test1() { let a = 10; var a = 30; // SyntaxError: redeclaration of let a } function test2(arg) { let arg; // SyntaxError: redeclaration of formal parameter arg } function test3(arg) { { let arg; // ok } }
上面代码中,前三个函数都报错了,因为在同一个作用域下存在对let声明的变量进行了重复用声明,而test3函数没有报错,因为不在同一个作用域中。
2.const命令
const用来声明常量。一旦声明,其值就不可以再改变。
const PI = 3.14; PI = 3; // TypeError: invalid assignment to const `PI'
const与let一样,只在声明所在的作用域有效,也同样存在TDZ,只能在声明后使用,也不可以重复声明。
对于对象类型的变量,其声明不指向对象的数据,而是指向对象所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址下的数据不变。
const user = {}; user.name = 'jack'; console.log(user.name); // jack user = {}; // TypeError: invalid assignment to const `user'
上面的代码中,常量user保存着一个对象地址,该对象本身是可变的,可以添加name属性,但是地址不可变,将user重新复制给一个地址会报错。
如果想使const声明的对象数据也不可变,可以使用Object.freeze方法冻结对象。
const user = Object.freeze({ name: 'jack' }); console.log(user.name); // jack user.name = 'mark'; console.log(user.name); // jack
上面的代码中,常量user所指向的对象被冻结,修改name属性无效,但是如果name属性指向的不是字符串,而是一个对象,该处理方式则无效。
let role = {name: 'admin'}; const user = Object.freeze({ name: 'jack', role: role }); console.log(user.name); // jack console.log(user.role.name); // admin user.name = 'mark'; user.role.name = 'qa'; console.log(user.name); // jack console.log(user.role.name); // qa
可以看到,user的role属性是个对象,而role的name属性仍然可以修改,想要解决这种情况,需要将对象的属性也冻结。
let role = {name: 'admin'}; const user = { name: 'jack', role: role }; let constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach((key, value) => { if (typeof obj[key] === 'object') { constantize(obj[key]); } }); }; constantize(user); console.log(user.name); // jack console.log(user.role.name); // admin user.name = "mark"; user.role.name = "qa"; console.log(user.name); // jack console.log(user.role.name); // admin
上面代码中,constantize是一个将对象彻底冻结的函数,调用该函数后,则user对象就彻底冻结了。