浅谈JS变量提升

1,084 阅读3分钟

一、什么是变量提升?

  JavaScript中,函数和变量的声明总是会被悄悄地“提升”到各自作用域的最顶部。

二、为什么会出现“提升”现象?

  我们直觉上会认为JavaScript代码在执行时期是由上到下一行一行地执行,实际上并不完全正确。
  先来看个demo,验证下这一说法:

a = 2;
var a;
console.log(a);

  很多开发会认为输出是undefined。但是,真正代码执行的输出结果是2。上述情况推翻了JavaScript代码由上到下逐行执行的说法。
  为了搞明白这个问题,我们需要了解JavaScript在运行时经历了哪些阶段?
  任何JavaScript代码片段主要经历了2个阶段:预编译和执行。

1、预编译阶段:

  任何JavaScript代码片段在执行前都要进行预编译(通常就在执行前),预编译所占用的时间仅仅是几微秒(甚至更短),所以我们开发人员基本感知不到这一阶段。

  预编译阶段会经历三个步骤:

  • 分词/词法分析(Tokenizing/Lexing)

  这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代 码块被称为词法单元(token)。例如,考虑程序var a = 2;。这段程序通常会被分解成 为下面这些词法单元:var、a、=、2 、;。空格是否会被当作词法单元,取决于空格在 这门语言中是否具有意义。

  • 解析/语法分析(Parsing)

  这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法 结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。
  var a = 2; 的抽象语法树中可能会有一个叫作 VariableDeclaration 的顶级节点,接下 来是一个叫作 Identifier(它的值是 a)的子节点,以及一个叫作AssignmentExpression的子节点。AssignmentExpression 节点有一个叫作 NumericLiteral(它的值是2)的子节点。

  • 代码生成

  将 AST 转换为可执行代码的过程称被称为代码生成。这个过程与语言、目标平台等息 息相关。抛开具体细节,简单来说就是有某种方法可以将 var a = 2; 的 AST 转化为一组机器指 令,用来创建一个叫作a的变量(包括分配内存等),并将一个值储存在 a 中。
  简单来讲,预编译阶段主要执行了两个任务:
1)声明所有var变量(初始值为undefined);
2)解析定义式函数语句。

  也就是说变量”提升“实际上是在JavaScript预编译阶段完成的。

2、执行阶段:

  由上至下逐行执行解析js代码,根据表达式=+-*++—...修改预编译的值。

小结

  我们习惯将var a = 2;看作一个声明,而实际上JavaScript引擎并不这么认为。它将var a和 a = 2 当作两个单独的声明,第一个是预编译阶段的任务,而第二个则是执行阶段的任务。
  这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。