一、什么是变量提升
JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的行为,叫做变量提升
变量被提升后,会给变量设置默认值 undefined
变量的声明部分提升:
console.log(a) // undefined
var a = 1
复制代码
函数的声明部分提升:
console.log(add(1, 2)) // 3
function add(x, y) {
return x + y
}
复制代码
二、变量提升的原理
变量提升 = 物理移动?
不,变量和函数声明在代码中的位置是不会改变的,它们在编译阶段被 JavaScript 引擎放入内存中
一段代码,经过编译后,会生成两部分内容:
- 执行上下文:JavaScript 执行一段代码时的运行环境
- 变量环境
- 词法环境
- 可执行代码
先编译,再执行:
- 编译阶段:变量和函数的声明会被存放到变量环境中,变量的默认值会被设置为
undefined
- 执行阶段:JavaScript 引擎会从变量环境中去查找自定义的变量和函数
console.log(a)
console.log(add(1, 2))
var a = 1
function add(x, y) {
return x + y
}
复制代码
编译后:
// 执行上下文的变量环境
a = undefined
add = address1 -> function (x, y) { return x + y }
复制代码
// 可执行代码
console.log(a) // undefined
console.log(add(1, 2)) // 3
a = 1
复制代码
三、变量提升的缺陷
导致很多代码与直觉不符
1、变量容易在不被察觉的情况下被覆盖掉
function fn() {
var a = 1
if (true) {
var a = 2
console.log(a)
}
console.log(a)
}
fn()
复制代码
编译后:
// fn 的执行上下文的变量环境
a = undefined
复制代码
// fn 的可执行代码
a = 1
if (true) {
a = 2
console.log(a) // 2
}
console.log(a) // 2
复制代码
2、本应销毁的变量没有被销毁
function fn() {
for (var i = 0; i < 5; i++) {
console.log(i)
}
console.log(i)
}
fn()
复制代码
编译后:
// fn 的执行上下文的变量环境
i = undefined
复制代码
// fn 的可执行代码
for (i = 0; i < 5; i++) {
console.log(i) // 0 1 2 3 4
}
console.log(i) // 5
复制代码
四、ES6 如何解决变量提升带来的问题
作用域:变量和函数的可访问范围
ES6 之前:
- 全局作用域
- 函数作用域
ES6 之后:
- 块级作用域
{}
ES6 通过使用 let
或者 const
关键字来实现块级作用域
function fn() {
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
fn()
复制代码
第一步:编译并创建执行上下文
函数内部:
- 通过
var
声明的变量,在编译阶段全都被存放到变量环境中 - 通过
let
声明的变量,在编译阶段全都被存放到词法环境中 - 作用域块内部,通过
let
声明的变量并没有被存放到词法环境中
// fn 的执行上下文
// 变量环境
a = undefined
c = undefined
// 词法环境
b = undefined
复制代码
第二步:继续执行代码
当进入作用域块时,通过 let
声明的变量,会被存放在词法环境的一个单独的区域中,这个区域的变量并不影响作用域块外面的变量
在词法环境内部,维护了一个小型的栈结构:
- 栈底是函数最外层的变量
- 进入一个作用域块后,就会把该作用域块内部的变量压到栈顶
- 当作用域执行完成之后,该作用域的信息就会从栈顶弹出
执行 ing…
// fn 的可执行代码
a = 1
b = 2
复制代码
// fn 的执行上下文
// 变量环境
a = 1
c = undefined
// 词法环境
b = 2
复制代码
执行 ing…
// fn 的可执行代码
{
// 进入作用域块
}
复制代码
// fn 的执行上下文
// 变量环境
a = 1
c = undefined
// 词法环境
---
b = undefined
d = undefined
---
b = 2
复制代码
执行 ing…
// fn 的可执行代码
{
b = 3
c = 4
d = 5
}
复制代码
// fn 的执行上下文
// 变量环境
a = 1
c = 4
// 词法环境
---
b = 3
d = 5
---
b = 2
复制代码
执行 ing…
// fn 的可执行代码
{
// 沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中找到了,就直接返回给 JavaScript 引擎,如果没有找到,那么继续在变量环境中查找
console.log(a) // 1
console.log(b) // 3
}
console.log(b) // 2
console.log(c) // 4
console.log(d) // Uncaught ReferenceError: d is not defined
复制代码
五、总结
- 由于 JavaScript 的变量提升存在着变量覆盖、变量污染等设计缺陷,所以 ES6 引入了块级作用域关键字
let
和const
来解决这些问题 - 变量提升是通过变量环境来实现的,块级作用域是通过词法环境的栈结构来实现的
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END