【ES6系列】let-var-function 声明和作用域问题

Let 知识梳理

  1. 变量提升问题
  2. let 详解
    1. 变量封禁
    2. let 是否提升问题
    3. 示例一(let + 立即执行函数):
    4. 示例二(let + for 循环):

变量提升问题

es6-let-var-function-hoist

img

从上图的中得出结论:

  1. let 的【创建】过程被提升了(在执行之前就创建了变量),但是【初始化】和【赋值】并没有被提升。
  2. var 的【创建】,【初始化】被提升了。
  3. function 的【创建】,【初始化】,和【赋值】均被提升了。
  4. 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 的初始化过程失败了,那么会发生以下结果:

  1. x 变量将永远处于 created 状态。
  2. 你将无法再次对 x 进行初始化(因为初始化只有一次机会,但是却失败了)。
  3. 由于 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 创建的变量作用域:

img

使用 var foo = 3 创建的变量作用域

img

由此可见 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 jvar 声明只会有一次,即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 的值的问题有以下几种方式:

  1. var i = 0 改成 let i = 0 并且放到 for(...) 中去声明
  2. for(...) 循环体内,使用 let j = i 重新增加一个变量用来保存当前 i 的值
  3. 闭包的方式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若叶岂知秋vip

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值