【灵魂拷问】当面试官问你JavaScript预编译
(一) 前言
在腾讯字节等其他大厂的面试中,JavaScript预编译是经常会被问到的问题,本文将带大家了解JS预编译中的具体过程
(二)编译执行步骤
传统编译语言编译步骤
对传统编译型语言来说,其编译步骤一般为:词法分析->语法分析->代码生成,下面让我们来分别介绍这3个过程。
- 词法分析
这个过程会将代码分隔成一个个语法单元,比如var a = 520;
这段代码通常会被分解为var
,a
,=
,520
这4个词法单元。
- 语法分析
这个过程是将词法单元整合成一个多维数组,即抽象语法树(AST),以下面代码为例
if(typeof a == "undefined" ){
a = 0;
} else {
a = a;
}
alert(a);
当JavaScript解释器在构造语法树的时候,如果发现无法构造,就会报语法错误(syntaxError),并结束整个代码块的解析。
- 代码生成
这个过程是将抽象语法树AST转变为可执行的机器代码,让计算机能读懂执行。
JavaScript编译步骤
比起传统的只有3个步骤的语言的编译器,JavaScript引擎要复杂的多,但总体来看,JavaScript编译过程只有下面三个步骤:
1. 语法分析
2. 预编译
3. 解释执行
(三)预编译详解
预编译概述
JavaScript预编译发生在代码片段执行前的几微秒(甚至更短!),预编译分为两种,一种是函数预编译,另一种是全局预编译,全局预编译发生在页面加载完成时执行,函数预编译发生在函数执行的前一刻。预编译会创建当前环境的执行上下文。
函数的预编译执行四部曲
- 创建Activation Object(以下简写为AO对象);
- 找形参和变量声明,将变量声明和形参作为AO的属性名,值为underfined;
- 将实参和形参值统一;
- 在函数体里找函数声明,将函数名作为AO对象的属性名,值赋予函数体。
案例代码
//请问下面的console.log()输出什么?
function fn(a) {
//console.log(a);
var a = 123//变量赋值
//console.log(a);
function a() { }//函数声明
//console.log(a);
var b = function () { }//变量赋值(函数表达式)
//console.log(b);
function d() { }//函数声明
}
fn(1)//函数调用
根据上面的四部曲,对代码注解后,我们可以很轻松的知道四个console.log()输出什么,让我们来看下AO的变化
- 创建AO对象
AO{
//空对象
}
- 找形参和变量声明,将变量声明和形参作为AO的属性名,值为undefined;
AO{
a: undefined
b: undefined
}
- 将实参和形参值统一;
AO{
a: 1,
b: undefined
}
- 在函数体里找函数声明,将函数名作为AO对象的属性名,值赋予函数体。
AO{
a: function(){}
b: undefined
d: function(){}
}
最后,下面是完整的预编译过程
AO:{
a:undefined -> 1 -> function a(){}
b:undefined
d:function d(){}
}
全局的预编译执行三部曲
- 创建Global Object(以下简写为GO对象);
- 找形参和变量声明,将变量声明和形参作为GO的属性名,值为undefined;
- 在全局里找函数声明,将函数名作为GO对象的属性名,值赋予函数体。
案例代码
global = 100;
function fn() {
//console.log(global);
global = 200;
//console.log(global);
var global = 300;
}
fn();
根据全局预编译三部曲我们可以知道他的GO变化过程
- 创建GO对象
GO{
// 空对象
}
- 找形参和变量声明,将变量声明和形参作为GO的属性名,值为underfined
GO: {
global: undefined
}
- 在全局里找函数声明,将函数名作为GO对象的属性名,值赋予函数体
GO: {
global: undefined
fn: function() { }
}
注意这里函数声明会带来函数自己的AO,预编译过程继续套用四部曲即可
(四)总结
当遇到面试官问你预编译过程时,可以根据上面的内容轻松解答,同时面试时也会遇到很多问你console.log()输出值的问题,也可以用上面的公式获取正确答案。
作者:橙玉米
链接:https://juejin.cn/post/7030370931478364196