Hexo创建文章和草稿

JavaScript语法let以及let与var的区别

let 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。

语法

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

参数

var1, var2, …, varN

变量名。可以是任意合法的标识符。

value1, value2, …, valueN

变量的初始值。可以是任意合法的表达式。

描述

let允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,它声明的变量只能是全局或者整个函数块的。

这里解释了我们为什么选取“let”这个名字。

1
2
3
4
5
6
7
8
Let is a mathematical statement that was adopted by early programming languages like Scheme and Basic. 
Variables are considered low level entities not suitable for higher levels of abstraction,
thus the desire of many language designers to introduce similar but more powerful concepts like in Clojure, F#, Scala,
where let might mean a value, or a variable that can be assigned, but not changed,
which in turn lets the compiler catch more programming errors and optimize code better.
JavaScript has had var from the beginning, so they just needed another keyword,
and just borrowed from dozens of other languages that use let already as a traditional keyword as close to var as possible,
although in JavaScript let creates block scope local variable instead.

answered Jun 20 ‘16 at 7:47

exebook

1
2
3
4
5
6
Let是一个数学声明,是采用于早期的编程语言如Scheme和Basic。
变量被认为是不适合更高层次抽象的低级实体,因此许多语言设计者希望引入类似但更强大的概念,
如在Clojure、f#、Scala,let可能意味着一个值,或者一个变量可以赋值,但不能被更改,
这反过来使编译器能够捕获更多的编程错误和优化代码更好。
javascript从一开始就有var,所以他们只是需要另一个关键字,并只是借用了其他数十种语言,
使用let已经作为一个传统的尽可能接近var的关键字,虽然在javascript 中 let只创建块范围局部变量而已。

作用域规则

let声明的变量只在其声明的块或子块中可用,这一点,与var相似。二者之间最主要的区别在于var声明的变量的作用域是整个封闭函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function varTest() {
var x = 1;
if (true) {
var x = 2; // 同样的变量!
console.log(x); // 2
}
console.log(x); // 2
}

function letTest() {
let x = 1;
if (true) {
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
}

简化内部函数代码

当用到内部函数的时候,let会让你的代码更加简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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**一样在全局对象上创造一个属性,比如:

1
2
3
4
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined

模仿私有接口

在处理构造函数的时候,可以通过let绑定来共享一个或多个私有成员,而不使用闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

1
2
3
4
if (x) {
let foo;
let foo; // TypeError thrown.
}

在 ECMAScript 2015 中,**let**绑定不受变量提升的约束,这意味着**let**声明不会被提升到当前执行上下文的顶部。在块中的变量初始化之前,引用它将会导致 [ReferenceError](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError)(而使用 var 声明变量则恰恰相反,该变量的值是 undefined )。该变量处于从块开始到初始化处理的“暂存死区”。

1
2
3
4
5
6
function do_something() {
console.log(bar); // undefined
console.log(foo); // ReferenceError: foo is not defined
var bar = 1;
let foo = 2;
}

switch 声明中你可能会遇到这样的错误,因为它只有一个块.

1
2
3
4
5
6
7
8
9
switch (x) {
case 0:
let foo;
break;

case 1:
let foo; // TypeError for redeclaration.
break;
}

但是,重要的是要指出嵌套在case子句内的块将创建一个新的块作用域的词法环境,这不会产生上面显示的重新声明错误。

1
2
3
4
5
6
7
8
9
10
11
12
let x = 1;

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

与词法作用域结合的暂存死区

由于词法作用域,表达式(foo + 55)内的标识符“foo”会解析为if块的foo,而不是覆盖值为33的foo。在这一行中,if块的“foo”已经在词法环境中创建,但尚未达到(并终止)其初始化(这是语句本身的一部分):它仍处于暂存死区。

1
2
3
4
5
6
7
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’ ,由于尚未达成和终止其声明,因此仍处于暂存死区。

1
2
3
4
5
6
7
8
9
10
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**的作用域在它被声明的函数内的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
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) Let and Const Declarations | Standard | Initial definition. Does not specify let expressions or let blocks. |
| ECMAScript Latest Draft (ECMA-262) Let and Const Declarations | Living Standard | |

浏览器兼容

  • Desktop
  • Mobile
Feature Chrome Edge Firefox Internet Explorer Opera Safari
Basic support 41 12 441 2 3 11 17 10

1. Prior to Firefox 44, let is only available to code blocks in HTML wrapped in a <script type="application/javascript;version=1.7"> block (or higher version) and has different semantics (e.g. no temporal dead zone).

2. Prior to Firefox 46, a TypeError is thrown on redeclaration instead of a SyntaxError.

3. Firefox 54 adds support of let in workers.

相关链接

文档标签和贡献者

标签:

参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let

Fork me on GitHub