预编译
前言
Javascript核心概念之一就是预编译,这是Javascript独有的语言特性,Javascript属于解释性语言,何为解释性语言?那就是解释一行执行一行,不像Java等其它语言在执行前需要先进行编译工作,虽然Javascript语言不需要进行提前编译,但是Javascript执行引擎在执行Javascript代码时,会先进行一次通篇扫描进行语法分析,这个过程可以排除一些基本的错误,然后它会执行一个被称之为预编译的过程,因此我们需要了解什么是预编译
预编译的触发时机
在Javascript中预编译触发时机有两种情况,一种是Javascript程序运行前会进行一次预编译,这次预编译会生成一个GO对象(Golbal Object)称之为全局上下文,另一种情况是函数执行前也会进行预编译的过程,函数的预编译会产生一个AO对象(Activation Object)执行期上下文
预编译的作用
不管是全局上下文(Golbal Object)还是执行期上下文(Activation Object),它们都属于一个概念:作用域,对于全局上下文,它储存的信息可以被整个Javascript程序使用,而对于执行期上下文,它保存着函数内部的信息,供函数执行时使用,在Javascript中作用域呈现链式结构,形成作用域链,全局上下文被作为顶层的作用域,函数的执行期上下文在函数执行完毕后会被销毁
预编译的四步曲
我们先来看看函数的预编译过程:
- 第一步,创建执行期上下文
生成一个 AO 对象(Activation Object),我们可以把执行期上下文看作是一个对象:
AO {
}
- 第二步,形参和变量声明提升
查找形参和变量声明,把形参和声明的变量作为AO对象的属性名,属性值为undefined:
例如有个fn函数
function fn(a) {
var b = 0;
function c () {};
};
fn(1);
生成的AO对象如下
AO {
a: undefined
b:undefined
}
- 第三步,形参和实参相映射
统一函数的形参和实参值,将实参值作为AO对象上的形参名对应的属性值:
AO {
a: 1
b:undefined
}
- 第四步,函数声明提升
查找函数声明,把函数名作为AO对象的属性名,属性值为函数体:
AO {
a: undefined
b:1
c: function c() {}
}
经过以上四个步骤后,函数的预编译过程就完成了,注意如果在预编译过程中存在同样的标识符,也就是形参、变量名以及函数名存在同名的情况下,由于预编译的步骤顺序,后执行的操作将会覆盖先前的操作。
而接下去函数执行的时候则会忽略那些已经进行过预编译的工作,直接执行有效的代码语句:
例如
var a = 0;
// 可以理解为:
var a; // 对于执行引擎而言,经过预编译后这行代码就是无意义的,因此会选择忽略它
a = 0;
我们再来看看全局的预编译过程:
全局预编译基本上和函数预编译没什么区别,首先:
- 第一步,创建全局上下文
生成GO对象(Golbal Object)
GO {
}
- 第二步,变量声明提升
查找变量声明,把声明的变量当作GO对象的属性名,属性值为undefined
GO {
a: undefined
}
- 第三步,函数声明提升
查找函数声明,把函数名作为AO对象的属性名,属性值为函数体
GO {
a: undefined
fn: function fn(){}
}
相比于函数预编译而言,全局预编译由于不存在形参和实参的概念,因此少了相关的操作,但其余步骤与函数预编译并无差别,因此整个Javacript程序执行其实也可以看作是一个函数的执行