【ES6】你真的了解let/const吗

我心飞翔 分类:javascript

在循环初始化语句中声明变量

今天看书看到下面几句话:

《深入理解ES6》 第一章,块级作用域绑定,第9页循环中的let声明一节中提到

每次循环迭代都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。

《JavaScript高级程序设计(第4版)》 第三章,语言基础,3.3.2节,第28页中也提到

在使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。

最佳实践

  1. for循环中推荐使用let来声明迭代变量
  2. for...infor...of推荐使用const来声明迭代变量。

光看有点不好理解为什么这么设计,写点代码来实践一下。

代码实践

1. 异步打印

for (var i = 0; i < 3; ++i) {
  setTimeout(() => {console.log(i)})
}
 

输出 3 3 3

原因:由于var声明会进行变量提升。函数指向全局作用域的i,打印i的时候,i的值已经变成3了。

改成let试试

for (let i = 0; i < 3; ++i) {
  setTimeout(() => {console.log(i)})
}
 

输出 0 1 2

原因:每次循环迭代都会在其块级作用域中创建一个新变量i

异曲同工的ES5写法

for (var i = 0; i < 3; ++i) {
  (function (i){
    setTimeout(() => {console.log(i)})
  }(i))
}
 

输出 0 1 2

原因:利用闭包,人为做了一次i的拷贝。

2. 在循环中创建函数

var fns = []
for (var i = 0; i < 3; ++i) {
  fns.push(() => { console.log(i) })
}
fns.forEach(fn => fn())
 

输出 3 3 3

原因:由于var声明会进行变量提升。函数使用全局作用域的同一个i,打印i的时候,i的值已经变成3了。

改成let试试

var fns = []
for (let i = 0; i < 3; ++i) {
  fns.push(() => { console.log(i) })
}
fns.forEach(fn => fn())
 

输出 0 1 2

原因:let每轮循环都创建了一个新的变量i,三个函数指向了三个不同的i

异曲同工的ES5写法

var fns = []
for (var i = 0; i < 3; ++i) {
  (function (i) {
    fns.push(() => { console.log(i) })
  }(i))
}
fns.forEach(fn => fn())
 

输出 0 1 2

原因:利用函数作用域的特点,每次循环都人为拷贝了变量i

使用引用类型变量进行循环

for (const i = {a: 0}; i.a < 3; i.a = i.a + 1) {
  i[`${i.a}`] = i.a
  setTimeout(() => console.log(i), 1000 * i.a)
}
// { '0': 0, '1': 1, '2': 2, a: 3 }
// { '0': 0, '1': 1, '2': 2, a: 3 }
// { '0': 0, '1': 1, '2': 2, a: 3 }
 

原因:每轮循环中,JavaScript引擎拷贝的i实际上是一个指向堆内存对象的地址。

因为这里只是改变了堆内存对象属性的值,并没有改变i的值(实际上i是一个指向堆内存对象的指针),可以使用const来声明i,这与使用letvar结果相同。

如果JavaScript提供了查看变量栈内存地址的API,那么验证这种说法就轻而易举了。

回复

我来回复
  • 暂无回复内容