Let 知识梳理
变量提升问题
从上图的中得出结论:
- let 的【创建】过程被提升了(在执行之前就创建了变量),但是【初始化】和【赋值】并没有被提升。
- var 的【创建】,【初始化】被提升了。
- function 的【创建】,【初始化】,和【赋值】均被提升了。
- const 和 let 的区别在于,它只有【创建】和【初始化】,并没有赋值因为用它声明的变量不能被赋值。
因此,暂时死区:其实就是不能在初始化之前使用变量
let a = 1
{
a = 2
// 对于 let 在执行之前只会创建变量
// 初始化和赋值是发生在开始执行之后
let a
}
let 详解
变量封禁
在控制台做如下操作:
x
VM51:1 Uncaught ReferenceError: x is not defined
at <anonymous>:1:1
(anonymous) @ VM51:1
let x = x
VM64:1 Uncaught ReferenceError: x is not defined
at <anonymous>:1:9
(anonymous) @ VM64:1
x
VM70:1 Uncaught ReferenceError: x is not defined
at <anonymous>:1:1
(anonymous) @ VM70:1
let x
VM79:1 Uncaught SyntaxError: Identifier 'x' has already been declared
at <anonymous>:1:1
(anonymous) @ VM79:1
var x
VM87:1 Uncaught SyntaxError: Identifier 'x' has already been declared
at <anonymous>:1:1
(anonymous) @ VM87:1
x
VM91:1 Uncaught ReferenceError: x is not defined
at <anonymous>:1:1
(anonymous) @ VM91:1
y
VM95:1 Uncaught ReferenceError: y is not defined
at <anonymous>:1:1
(anonymous) @ VM95:1
var y = y
undefined
y
undefined
从控制台输出看出,在经过 let x = x
之后就无法再对 x
进行任何操作(声明,赋值,取值等等),甚至用
var
都无法再次声明
这个问题说明:如果 let x
的初始化过程失败了,那么会发生以下结果:
x
变量将永远处于created
状态。- 你将无法再次对
x
进行初始化(因为初始化只有一次机会,但是却失败了)。 - 由于
x
无法进行初始化,将永远处在“暂时死区,TDZ”,也就无法使用它。
let 是否提升问题
let 和 var 一样会发生变量提升,但是 let 提升之后,从 let 的提升后的位置开始直到遇到该变量第一次初始化语句之间这一段区域被称作为“临时死区(Temporal Dead Zone, TDZ, 即你不能在该区域之内使用该变量,否则会报错)”。
示例:
let a = 3
{
// 如果不存在提升那么这里操作的应该是区块外面的 let a = 3
// 那个 a,因此这代码是可以正常运行的,但是实际上运行时会报错:
a = 2
let a
}
ReferenceError: a is not defined
at /private/var/folders/q5/0qbdnm9s76lbl83y1kykyy_m0000gn/T/babel-70903Rwg/js-script-70903tIl:7:5
at Object.<anonymous> (/private/var/folders/q5/0qbdnm9s76lbl83y1kykyy_m0000gn/T/babel-70903Rwg/js-script-70903tIl:10:2)
at Module._compile (module.js:569:30)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:503:32)
at tryModuleLoad (module.js:466:12)
at Function.Module._load (module.js:458:3)
at Function.Module.runMain (module.js:605:10)
at startup (bootstrap_node.js:158:16)
at bootstrap_node.js:575:3
说明区块内的 a = 2
所使用的 a
压根不是区块外声明的 let a = 3
中的 a
,唯一能解释的只有区块内部声明的 let a
被提升了,而根据临时死区(Temporal Dead Zone, TDZ)的概念,这个区间内去使用该变量是会发生错误的。
示例一(let + 立即执行函数):
var a = 6
;(function() {
// undefined, 因为 var a = 20 的创建和初始化undefined提升了
console.log(a)
a = 5
console.log(a) // 5
a = 10
// 这里取到的window a 即 var a = 6 中的a
console.log(window.a) // 6
// 赋值
var a = 20
console.log(a) // 20
}())
全局使用 var
和局部使用 let
结果:
var b = 7
;(function() {
// let b = 20 的创建提升了,但是从此处开始形成了“暂时死区”
// 但是由于是没有初始化的,因此这里会 ReferenceError 报错
console.log(b)
b = 5
console.log(b)
b = 10
// 一直到此处前面使用 b 的地方都会引发 ReferenceError 报错
console.log(window.b) // 正常值为 7 全局下的 var b = 7 的值
let b = 20
console.log(b) // 正常到这里的话,值为20,let b = 20初始化并赋值的结果
}())
全局使用 let
局部使用 var
结果:
let c = 7
;(function() {
// var c = 20创建,初始化提升
console.log(c) // undefined
c = 5
console.log(c) // 5
c = 10
// 使用 let 创建的变量是局部变量,并非存在于全局环境下
console.log(window.c) // undefined
var c = 20
console.log(b) // 20
}())
针对该例中,让人困惑的地方在于 let c = 7
然后 window.c
直接看控制台:
使用 let bar = 4
创建的变量作用域:
使用 var foo = 3
创建的变量作用域
由此可见 let
在全局作用域下面创建的变量作用域并非属于全局作用域。
搞清楚了上面的,那下面的
全局使用 let
局部也使用 let
的分析就显而易见了
let d = 7
;(function() {
// ReferenceError 错误,因为 let d = 20 只有创建被提升
// 形成暂时死区
console.log(d)
// 同上,触发 ReferenceError错误
d = 5
// 同上,触发 ReferenceError错误
console.log(d)
// 同上,触发 ReferenceError错误
d = 10
// 使用 let 创建的变量是局部变量,并非存在于全局环境下
console.log(window.d) // undefined
let d = 20
console.log(d) // 20
}())
示例二(let + for 循环):
let i = 0, arr = []
for (; i < 3; i++) {
arr.push(function() {
console.log(i)
})
}
arr.map(item => {
item()
})
上面的代码不管你怎么改输出的都是同一个值 3,再看下面的修改:
let i = 0, arr = []
for (; i < 3; i++) {
var j = i
arr.push(function() {
console.log('var j = i ? ' + j)
})
}
arr.map(item => item())
最终还是2,因为这里使用的是 var j
而 var
声明只会有一次,即3次循环使用的都是同一个 j
所指的空间,因此最后执行的时候输出的还是 j
最后的那个值
再看,如果用 let 来声明 j 呢?
let i = 0, arr = []
for (; i < 3; i++) {
let j = i
arr.push(function() {
console.log('let j = i ? ' + j)
})
}
arr.map(item => item())
是不是得到想要的结果了,这说明在使用 var
时候发生了变量声明提升,接下来每次循环使用的都是同一个 j
指向的空间,然后使用 let
的时候,每次遍历都会重新为 j
分配空间,从而 push
进到 arr
里的函数里面调用的 j
都是不同的空间里的值,最后调用的时候值自然不同了。
有了上述结论,那么将 var i = 0
放到 for
和在 for
外面无论用 let i = 0
还是 var i = 0
结果都是一样的
如果在不增加额外变量的情况下如何修改?
let arr = []
for (let i = 0; i < 3; i++) {
arr.push(function() {
console.log('for(let i = 0; ...) ? ' + i)
})
}
arr.map(item => item())
使用 for(let i = 0; ...)
其实就相当于每次遍历都使用 let
重新声明了一遍,因此得到的是 i
存储的不同空间内的值传入到了回调函数中,此时每个回调函数的作用域内都保留了一份该空间。
闭包方式(为代码创建一个特定的作用域,然后将当前 i
的值传入到该作用域中):
let arr = []
for (var i = 0; i < 3; i++) {
(function(j) {
arr.push(function() {
console.log('closure var i = 0 ? ' + j)
})
}(i))
}
arr.map(item => item())
总结:因此要解决 for(...)
循环中 i 的值的问题有以下几种方式:
- 将
var i = 0
改成let i = 0
并且放到for(...)
中去声明 - 在
for(...)
循环体内,使用let j = i
重新增加一个变量用来保存当前 i 的值 - 闭包的方式