ES6 let和const

1. let

let 是一种变量声明方式,它绑定于块级作用域,有效地防止之前因var声明方式导致的变量泄露,变量覆盖,重复声明问题

变量泄露

var s = 'hello';

for (var i = 0; i < s.length; i++) {
 console.log(s[i]);
}

console.log(i); // 5 

使用let就不会有这样的问题

var s = 'hello';

for (let i = 0; i < s.length; i++) {
 console.log(s[i]);
}

console.log(i); // undefined 

变量覆盖

var tmp = new Date();

function f() {
 console.log(tmp);
 if (false) {
   var tmp = 'hello world';
 }
}

f(); // undefined 

这是由于if代码块的tmp变量发生了变量提升,实际引擎翻译时会将这段代码翻译成

var tmp = new Date();

function f() {
 var tmp;
 console.log(tmp);
 if (false) {
   tmp = 'hello world';
 }
}

f(); // undefined 

将变量提升到该函数作用域的顶层,所以tmp是undefined的。

使用let变量就不会有这样的问题

var tmp = new Date();

function f() {
 console.log(tmp);
 if (false) {
   let tmp = 'hello world';
 }
}

f(); // 2017-03-10T01:37:32.427Z 

重复声明问题

var 重复声明完全没问题

var a = 1;
var a = "a"; 

但是let不允许重复声明

// 报错
function () {
 let a = 10;
 var a = 1;
} 
  • 块级作用域

let实际上带来的是块级作用域

往常在Javascript中只有全局作用域和函数作用域,所以会引发上述问题,虽然我们通过IIFE(立即执行函数表达式)来防止这些问题,但是let让IIFE失去了原来的地位,我们现在可以轻松借助let来实现变量的封装了。

let也不存在变量提升,在let声明之前会存在暂时性死区(TDZ)即let所声明的变量名,不能在它之前进行声明相同的变量名

function bar(x = y, y = 2) {
 return [x, y];
}

bar(); // 报错 

y = 2是一句let声明,在它之前使用y会报错

// 报错
let x = x; 

在x变量由let声明,x不能提前使用

  • 函数声明和块级作用域

ES6规定可以在块级作用域中声明函数,声明方式类似于let,不过,由于要兼容旧的代码,它同时还提出实际浏览器实现规范的时候,可以将函数声明等同于var声明,所以函数会进行变量提升

function f() { console.log('I am outside!'); }

(function () {
 if (false) {
   // 重复声明一次函数f
   function f() { console.log('I am inside!'); }
 }

 f();
}()); 

这段代码会被翻译成

function f() { console.log('I am outside!'); }

(function () {
 var f;
 if (false) {
   // 重复声明一次函数f
   function f() { console.log('I am inside!'); }
 }

 f();
}()); 

所以在ES6浏览器中上述代码是会报错的

2. const

const声明一个只读的变量,一旦声明,值不可更改,必须在声明的同时初始化赋值,同时也不会发生常量提升,同时和let一样存在TDZ,一样绑定于块级作用域,不过const其实是保证变量指向的内存地址不得更改,如果const声明的是对象,数组等引用类型,是可以对其进行读写。如果需要对对象进行冻结,可以使用object.freeze()方法

const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123; 

如果还想连属性也冻结

var constantize = (obj) => {
 Object.freeze(obj);
 Object.keys(obj).forEach( (key, i) => {
   if ( typeof obj[key] === 'object' ) {
     constantize( obj[key] );
   }
 });
}; 

3. 总结

  • letconst都不绑定与顶层对象

顶层对象在各环境下不尽相同,我们很难在同一段代码中统一获得各个环境的顶层变量,垫片库system.global是的每个环境都能获得global对象

  • letconst引进了块级作用域,使得javascript原来的语言上的缺陷得以弥补
  • letconst倡导了很多良好的编程方式:使用变量前,先声明;不重复声明变量;不在块作用域中声明函数等等。

参考文献


write by jeffwang 2017/03/10