js原理:变量提升与函数提升

542 浏览发布于 作者 zouyang (欢迎转载-请注明出处链接)留下评论分享按钮

1、首先看3个例子:


console.log( a );  //会打印出什么呢?
var a = 1;

b = 2;
var b;
console.log( b );  //会打印出什么呢?

foo();  //会打印出什么呢?
var foo;
function foo() {
  console.log( 1 );
}
foo = function() {
  console.log( 2 );
};

如果你对以上例子的执行原理还不太熟悉,不要紧,跟着本文的介绍,你会对此有所掌握的。(对结论感兴趣的可以直接跳到本文最后总结)

 

2、变量的提升

众所周知 js 的代码执行顺序一般是从上而下的一条条执行。但是在进行变量申明和函数申明时,会有提升的情况。接下来我们看看 js 是如何提升变量和函数声明的。
js 代码在被编译器编译时,会经过3个阶段:词法分析、语法分析、代码生成;变量的提升发生在语法分析阶段。
当我们在申明一个变量时,比如 var a = 1;
我们习惯将 var a = 1; 看作一个声明, 而实际上 JavaScript 引擎并不这么认为。 它将 var a 和 a = 1 当作两个单独的声明, 第一个是编译阶段的任务, 而第二个则是执行阶段的任务,具体如下:
1)首先,会进行词法分析,找出哪些词是代码语句,上例申明会被拆分为:vara=1; 这5个词法单元(可能我们会觉得这不是废话吗,其实这个过程是找出那些正确的代码词法单元,以便后续的分析,毕竟有时开发者在书写 js 代码时会有不严格的情况,比如有的开发者忘记分号、多余或少空格等等之类不规范)
2)其次,会进行语法分析,组装词法单元并分析,会将词法单元流(数组)转换成一个由元素逐级嵌套而成的“抽象语法树”,上例 var a = 1,js 引擎会将其分解成2个申明:
var a;
a = 1;
编译器在遇到 var a 时,编译器在词法分析时会询问作用域内是否已经存在一名名字叫做 a 的变量,如果是,则编译器会忽略该声明继续进行编译,否则它会在当前作用域的集合内申明一个新的变量,并命名为 a,又因为每个作用域集合是在该作用域代码的运行时最开始执行的,所以就会出现 变量声明 被提前的情形。
3)最后,会进行代码生成,编译器会为 js 引擎生成运行时所需的代码,这些代码被用来处理 a = 1 这个赋值操作。
 
综上,所以第一个例子:


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

实际执行流程是:


var a;
console.log( a );  //会打印出 undefined
a = 1;

 
第二个例子:


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

实际执行流程是:


var b;
b = 2;
console.log( b );  //会打印出 2

 

3、关于函数的提升

函数声明跟变量声明一样会被提升。

 

总结:

1、无论作用域中的声明出现在什么地方, 都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数) 都会被“移动” 到各自作用域的最顶端, 这个过程被称为提升。
2、变量在申明时,会出现提升,只要是 var 一个变量,该变量申明会被提升到当前作用域的最开始部分。
3、函数在申明时,会出现提升,该函数申明会被提升到当前作用域的最开始部分。
4、另外值得注意的是,每个作用域都会进行提升操作,比如:


foo();
function foo() {
  console.log( a ); // undefined
  var a = 1;
}

实际执行流程是:


function foo() {
  var a;
  console.log( a ); // undefined
  a = 1;
}
foo();

 

PS:本文不讨论 ES6 的 let 申请变量的方式带来的改变(比如,没有变量提升、不可重复申明变量、作用域只在它申明时所在的代码块内有效)

(完)

想要打赏,请点击这里

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注