Skip to content
大纲

变量提升(Hoisting)

定义

js 在执行上下文其中的一种工作方式,js 引擎在代码执行的过程中将变量声明部分和函数声明提取到开头一种行为。与直觉不相符,算是一种设计缺陷。在 es6 引入这个问题,但为了向下兼容,目前还存在这种情况。

  • 变量
js
// 源代码
console.log(num);
var num = 1;

// 变量提升代码
var num;
console.log(num);
num = 1;
  • 函数
js
// 源代码
add();
function add() {
  console.log("add");
}

// 变量提升
function add() {
  console.log("add");
}

add();

js 在编译阶段,会去收集所有的变量声明,提前让声明生效。剩下的在执行阶段生效。

为什么会有变量提升这种特性

在 es6 之前,js 里作用域分两种全局作用域函数作用域,不支持块级作用域。这种情况下,变量统一提升时最简单有效的设计。

作用域。控制着变量和函数的可访问范围(可见性和生命周期)

好处

  1. 提高性能 js 执行代码前,会去语法检查和预编译,并且只操作一次。这样每次执行代码的时候就不用再去解析一边这些变量和函数,直接分配执行空间就行

    预编译时会去统计哪些变量、函数,压缩代码,去除注释、空格等操作

  2. 容错性好

问题点

  1. 变量会被覆盖 函数内部同名变量会去覆盖全局的

  2. 变量未被销毁 经典问题,for 循环的变量外部获取它时等于++的最终结果

注意点

  1. 函数内部不用var声明,会被自动提升到全局做声明

解决方式

为解决上述变量提升问题,es6 引入的letconst两个关键字,来创建块级作用域

如何支持块级作用域的

通过词法环境的栈结构来实现(变量提升是通过变量环境实现)

  1. 在创建执行上下文时,会创建两个环境变量环境词法环境
  2. 如果解析当前作用域是,遇到 var,则将该变量放置变量环境,let、const 放置词法环境栈底
  3. 如果该作用域内部还有其他函数作用域,其内部遇到 var,则将该变量放置变量环境,let、const 不处理
  4. 执行上下文时,遇到内部的函数作用域,再在词法环境内单独创建一个作用域收集 let、const 的变量,加入到栈顶
  5. 函数作用域内部查找时,先从栈顶到栈低,到变量环境中查找。
  6. 当函数作用域执行完,函数作用域就会从栈顶弹出

暂示性死区

ES6 规定:如果区块中存在 let 和 const,这个区块对这两个关键字声明的变量,从一开始就形成了封闭作用域。假如尝试在声明前去使用这类变量,就会报错。这一段会报错的区域就是暂时性死区。

js
var name = "JavaScript";
const temp = () => {
  name = "CSS";
  // Error:Cannot access 'name' before initialization
  let name;
};
temp();

上方 88 行以上就是暂时性死区,去掉 89 行就正常了

练习

js
temp();
let a = 1;
function temp() {
  asd();
  let a = 2;
  b = 111;
  function asd() {
    console.log("inner", a);
  }
}
console.log("out", a, b);
console.log("asd", asd);

Released under the MIT License.