let
let
let
语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。
语法
let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]];
参数
var1
,var2
, …,varN
变量名。可以是任意合法的标识符。value1
,value2
, …,valueN
变量的初始值。可以是任意合法的表达式。
描述
let
允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,它声明的变量只能是全局或者整个函数块的。
这里解释了我们为什么选取“let
”这个名字。
作用域规则
let
声明的变量只在其声明的块或子块中可用,这一点,与var
相似。二者之间最主要的区别在于var
声明的变量的作用域是整个封闭函数。
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x // 2
}
console.log(x // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x // 2
}
console.log(x // 1
}
简化内部函数代码
当用到内部函数的时候,let
会让你的代码更加简洁。
var list = document.getElementById('list'
for (let i = 1; i <= 5; i++) {
let item = document.createElement('li'
item.appendChild(document.createTextNode('Item ' + i)
item.onclick = function(ev) {
console.log('Item ' + i + ' is clicked.'
};
list.appendChild(item
}
// to achieve the same effect with 'var'
// you have to create a different context
// using a closure to preserve the value
for (var i = 1; i <= 5; i++) {
var item = document.createElement('li'
item.appendChild(document.createTextNode('Item ' + i)
(function(i){
item.onclick = function(ev) {
console.log('Item ' + i + ' is clicked.'
};
})(i
list.appendChild(item
}
以上示例的工作原理是因为(匿名)内部函数的五个实例引用了变量i
的五个不同实例。注意,如果你将let
替换为var
,则它将无法正常工作,因为所有内部函数都将返回相同的i
:6的最终值。此外,我们可以通过将创建新元素的代码移动到每个循环的作用域来保持循环更清晰。
在程序或者函数的顶层,let
并不会像var
一样在全局对象上创造一个属性,比如:
var x = 'global';
let y = 'global';
console.log(this.x // "global"
console.log(this.y // undefined
模仿私有接口
在处理构造函数的时候,可以通过let
绑定来共享一个或多个私有成员,而不使用闭包:
var Thing;
{
let privateScope = new WeakMap(
let counter = 0;
Thing = function() {
this.someProperty = 'foo';
privateScope.set(this, {
hidden: ++counter,
}
};
Thing.prototype.showPublic = function() {
return this.someProperty;
};
Thing.prototype.showPrivate = function() {
return privateScope.get(this).hidden;
};
}
console.log(typeof privateScope
// "undefined"
var thing = new Thing(
console.log(thing
// Thing {someProperty: "foo"}
thing.showPublic(
// "foo"
thing.showPrivate(
// 1
let暂存死区的错误
在相同的函数或块作用域内重新声明同一个变量会引发SyntaxError
。
if (x) {
let foo;
let foo; // SyntaxError thrown.
}
在 ECMAScript 2015 中,let
绑定不受变量提升
的约束,这意味着let
声明不会被提升到当前执行上下文的顶部。在块中的变量初始化之前,引用它将会导致ReferenceError
(而使用 var 声明变量则恰恰相反,该变量的值是 undefined )。该变量处于从块开始到初始化处理的“暂存死区”。
function do_something() {
console.log(bar // undefined
console.log(foo // ReferenceError
var bar = 1;
let foo = 2;
}
在 switch
声明中你可能会遇到这样的错误,因为它只有一个块.
let x = 1;
switch(x) {
case 0:
let foo;
break;
case 1:
let foo; // SyntaxError for redeclaration.
break;
}
但是,重要的是要指出嵌套在case子句内的块将创建一个新的块作用域的词法环境,这不会产生上面显示的重新声明错误。
let x = 1;
switch(x) {
case 0: {
let foo;
break;
}
case 1: {
let foo;
break;
}
}
与词法作用域结合的暂存死区
由于词法作用域,表达式(foo + 55)
内的标识符“foo
”会解析为if块的foo
,而不是
覆盖值为33的foo
。在这一行中,if块的“foo
”已经在词法环境中创建,但尚未达到(并终止)其初始化(这是语句本身的一部分):它仍处于暂存死区。
在这一行中,if 语句块的“foo”已经在词法环境中创建,但尚未到达(并终止
)它的初始化(这是语句本身的一部分):它仍然处于临时死区。
function test(){
var foo = 33;
if (true) {
let foo = (foo + 55 // ReferenceError
}
}
test(
这种现象可能会使您陷入以下情况。指令let n of n.a
已经在for循环块的私有范围内,因此标识符“n.a
”被解析为位于指令本身的第一部分(“let n”)中的'n'对象的属性'a' ,由于尚未达成和终止其声明,因此仍处于暂存死区。
function go(n) {
// n here is defined!
console.log(n // Object {a: [1,2,3]}
for (let n of n.a) { // ReferenceError
console.log(n
}
}
go{a: [1, 2, 3]}
其他情况
当在块中使用时,let
将变量的作用域限制为该块。注意var
的作用域在它被声明的函数内的区别。
var a = 1;
var b = 2;
if (a === 1) {
var a = 11; // the scope is global
let b = 22; // the scope is inside the if-block
console.log(a // 11
console.log(b // 22
}
console.log(a // 11
console.log(b // 2
规范
Specification | Status | Comment |
---|---|---|
ECMAScript 2015 (6th Edition, ECMA-262)The definition of 'Let and Const Declarations' in that specification. | Standard | Initial definition. Does not specify let expressions or let blocks. |
ECMAScript Latest Draft (ECMA-262)The definition of 'Let and Const Declarations' in that specification. | Living Standard | |
浏览器兼容性
Feature | Chrome | Edge | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|---|
Basic support | 41.0 | 12 | 44 (44) | 11 | 17 | 10.0 |
Temporal dead zone | ? | 12 | 35 (35) | 11 | ? | ? |
Allowed in sloppy mode | 49.0 | ? | 44 (44) | ? | ? | ? |
Feature | Android | Android Webview | Edge | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile | Chrome for Android |
---|---|---|---|---|---|---|---|---|
Basic support | ? | 41.0 | (Yes) | 44.0 (44) | ? | ? | 10.0 | 41.0 |
Temporal dead zone | ? | ? | (Yes) | 35.0 (35) | ? | ? | ? | ? |
Allowed in sloppy mode | No support | 49.0 | ? | 44 (44) | ? | ? | ? | 49.0 |