JS 语言的特点
在深入了解 JavaScript 的预编译之前,不得不先来回忆一下 JS 语言的特点:
- 解释型语言: 区别于编译型语言,逐行编译,就是编译一行,执行一行,而且对速度要求并不是太高;
- JS 引擎是单线程的;
- JS 符合 ECMA 标准;
- JS 执行队列类似于轮转时间片。
JS 运行三部曲
在这里,最重要的就是第一点:解释型语言的运行过程。 JS 运行有三部曲:
- 语法分析:很简单,就是通篇扫描一下有没有低级语法(语义)错误;
- 预编译: 简单地说就是在内存中开辟了一些空间,存放一些变量与函数;
- 解释执行:解释一行,执行一行。
JS 预编译实例
下面正式进入预编译的介绍,看例子为什么控制台可以打印出 ‘a’
<script type="text/javascript">
test();
function test() {
console.log('a');
}
</script>
如上边示例,因为有预编译的存在,test() 的调用虽然在声明之前,函数也可以执行。这是为什么呢?引出 预编译前奏 这个概念:
预编译前奏:
- imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有;
eg: a = 123;
eg: var a = b = 123;
- 一切声明的全局变量,全是 window 的属性。
eg: var a = 123;
下图为一个实例,变量 a 虽然声明了,但不是全局变量,所以不能在函数体外访问到,但是变量 b 满足预编译前奏的第一点,未经声明就被赋值,此变量为全局对象所有,所以可以访问到。
知道了预编译前奏的概念,那么我们的理解就已经深入了一半了,顺着脉络继续。预编译前奏生成 GO(Global Object)对象,之后如果有函数就进行预编译,预编译四部曲如下:
预编译四部曲:
- 创建 AO 对象(Activation Object 俗称执行期上下文);
- 找形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined;
- 将实参值和形参统一;
- 在函数体里面找函数声明,值赋予函数体。
通过简单的实例来认识一下 GO 与 AO 的结合:预编译前奏时,生成 window.b = 10;①预编译时创建 AO 对象;②将变量声明 a 放入 AO,值为 undefined;③传入参数,使得实参形参相统一,本例中没有形参;④如果函数体里还有函数声明,值赋予函数体,但是本例中没有。
最终,函数会打印出 undefined 。
再来一个例子,跟着我们前边的节奏来分析,先写下 GO 对象,再找 AO 对象。
<script type="text/javascript">
global = 100;
function fn() {
console.log(global);
global = 200;
console.log(global);
var global = 300;
}
fn();
var global;
</script>
大家先来分析一下分别会输出什么呢?
我们来分析一下,global 未经声明就赋值,生成 GO 对象 global = 100;在执行到 fn() 的前一刻进行预编译,生成 AO 对象,其中只有 global,值为undefined,之后既没有实参的传入,也没有函数声明,那么第15行就打印 undefined了,经过16行之后,undefined 被修改为200 。所以两个答案分别是 undefined 和 200,你答对了吗?
百度2013面试题:
题目一:
因为在函数 bar 最顶端return foo,那我们直接看到 AO 的第四部:返回函数声明,那么程序会打印 function foo()。题目二:
这题和上一个类似,因为函数的最后返回 foo,而 foo 在上边被赋值过,那么 foo 一定为该值咯。
看完之后我们发现,其实预编译也并不难,再梳理一下:
总结
预编译前奏:生成 GO 对象,有两个规则;
预编译四部曲:
1.创建 AO 对象
2. 找形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined;
3. 将实参值和形参统一;
4. 在函数体里面找函数声明,值赋予函数体。