ECMAScript6 let 详解

let 语句声明一个块级作用域变量,它的初始化值为可选的.

语法

let var1 [= value1] [, var2 [= value2]] [, …, varN [= valueN]];

参数

var1, var2, …, varN
变量名.可以是任何合法的标识符.

value1, value2, …, valueN
变量的初始值.可以是任何合法的表达式.

描述

let 声明变量的作用域为语句或表达式所在的块.这和使用 var 语句不同, var 声明的变量是全局变量,或者整个函数范围里的变量,而不是所在的代码块作用域.

块级作用域和 let

在一个代码块里使用 let 语句定义变量

if (x > y) {
  let gamma = 12.7 + y;
  i = gamma * x;
}

通常, let 语句用在函数里嵌套函数的时候能使得代码更清晰.

var list = document.getElementById("list");

for (var i = 1; i <= 5; i++) {
  var item = document.createElement("LI");
  item.appendChild(document.createTextNode("Item " + i));

  let j = i;
  item.onclick = function (ev) {
    console.log("Item " + j + " is clicked.");
  };
  list.appendChild(item);
}

上面的这个例子能够按照所希望的那样运行,因为五个内嵌的匿名函数的实例里分别使用五个不同的变量 j 的实例.注意,如果你把 let 换成 var 或者移除变量 j 或者在内嵌的函数里使用 i ,是不行的.

作用域规则

通过 let 声明的变量它的作用域是声明时所在的块级作用域,以及块级作用域的子块级作用域.就这一点来说, let 的工作方式和 var 很相似. 他们两者间最主要的区别是: var 声明的变量所在的作用域是整个闭包函数.

function varTest() {
  var x = 31;
  if (true) {
    var x = 71;  // x是同一个变量!
    console.log(x);  // 71
  }
  console.log(x);  // 71
}

function letTest() {
  let x = 31;
  if (true) {
    let x = 71;  // x是两个不同的变量
    console.log(x);  // 71
  }
  console.log(x);  // 31
}

不同于使用 var , 如果在程序或者函数的最外层使用 let 语句,它不会给全局对象创建一个新的属性.举个栗子:

var x = 'global';
let y = 'global';
console.log(this.x);   //全局对象下有x属性
console.log(this.y);   //全局对象下没有y属性

这段代码运行的结果: this.x 的输出为 "global" ,但 this.y 的输出为 undefined.

暂死区(temporal dead zone)和let相关报错

在同一个函数或者代码块里使用 let 声明两个同样的变量会导致 TypeError.

if (x) {
  let foo;
  let foo; // TypeError thrown.
}

ECMAScript 2015 会把通过 let 语句声明的变量提升到代码块的开头(类似于var声明变量的预解析). 然而,在变量声明之前访问这个变量会导致ReferenceError.
因为存在于代码块开头的变量还处在”暂死区”里,直到程序走到声明变量那里.

补充说明一下:众所周知,var 声明的变量会被预声明,所以在 var 之前访问变量,不会报错,而是得到 undefined. 同样, let 声明的变量也会被提升到区块的顶部,但是却不能访问它,因为变量虽然声明了,但还处在 “暂死区” 里,直到程序走到 let 声明变量的语句,变量才从暂死区里被释放出来. 大致就是这样,点击了解更多关于暂死区的概念.

function do_something() {
  console.log(foo); // ReferenceError
  let foo = 2;
}

如果你在 switch 语句中使用 let ,可能会遇到错误,因为在 switch 语法里,它只有一个底层代码块

switch (x) {
  case 0:
    let foo;
    break;

  case 1:
    let foo; // 由于重复声明导致 TypeError .
    break;
}

for 循环里使用 let 时的作用域

你可以在 for 循环里使用 let 语句来创建一个对应当前作用域的变量.这和在 for 循环头部使用 var 语句是不同的, var 声明的变量在不仅在循环里可用,在整个所在的函数里都可用.

var i=0;
for ( let i=i ; i < 10 ; i++ ) {
  console.log(i);
}

注意这个例子是错的,它会抛出 ReferenceError 错误,在介绍 暂死区 的文章里有类似的例子…大致解释一下:变量 i 一开始就被预提升到整个块作用域了,但是还处在暂死区,然后当执行 let i=i 的时候,要给变量 i 进行词法绑定,但是绑定的值又是 i, 而 i 又处在暂死区,无法访问,所以就报错了.但是需要注意的是,由于 i 变量已经被预提升了,它不是一个不存在的变量,而是一个处在暂死区的变量,所以,它不会沿着作用域链向上寻找到外层 var 声明的 i,而是直接报错了.
然后这个例子正确的写法应该是:

var a=0;
for ( let i=a ; i < 10 ; i++ ) {
  console.log(i);
}

作用域规则

for (let expr1; expr2; expr3) statement

在这个例子里, expr2,expr3 以及 statement 语句,都会被隐式的包含到一个代码块里. let 声明的 expr1 变量就处在这个隐藏的代码块里. 这在上一个循环的例子里已经阐述过了.

栗子

let vs var

在代码块里使用 let 语句,变量的作用域就是所在的代码块. 而 var 声明的变量,所在的作用域是声明时所在的整个函数.

var a = 5;
var b = 10;

if (a === 5) {
  let a = 4; // 作用域是在 if 代码块里
  var b = 1; // 作用域是在整个函数里

  console.log(a);  // 4
  console.log(b);  // 1
} 

console.log(a); // 5
console.log(b); // 1

循环里的 let

建议在循环里使用 let 来创建一个作用于循环代码块下的变量,而不是使用 var 来创建一个全局的变量用于循环.

for (let i = 0; i<10; i++) {
  console.log(i); // 0, 1, 2, 3, 4 ... 9
}

console.log(i); // i is not defined

非标准的 let 扩展

非标准的 let 扩展基本没有得到支持,而且很多已经被废弃,就不翻译了…..((☄⊙ω⊙)☄…好吧,因为快要下班了…)

原文地址