0
点赞
收藏
分享

微信扫一扫

JavaScript之预编译学习(附带多个面试题)

夏木之下 2022-03-12 阅读 50

前言

在学习JavaScript预编译之前,先了解一下JavaScript从编译到执行的过程,大致分为四步:

  1. 词法分析
  2. 语法分析:检查代码是否存在错误,若有错误,引擎会抛出语法错误。同时会构建一颗抽象语法树(AST)。
  3. 预编译
  4. 解释执行

预编译

js中预编译一般有两种:全局的预编译和函数的预编译,分别发生在script内代码执行前和函数的执行前。

函数预编译

首先来看一个例子:

function test(a) {
    console.log(a);       
    var a = 123;
    console.log(a);       
    function a() {}
    console.log(a);       
    var b = function() {}
    console.log(b);       
    function d() {}
}
test(1)

就以上述例子中的a为例,有形参a,变量a,函数a,那test函数执行时,此时的a到底是什么呢?

输出结果:

? a() {}
123
123
? () {}

要想弄明白最终的输出结果,就不得不好好学习一下预编译的详细过程。

在预编译学习中,经常听到一句话:函数声明整体提升,变量声明提升

这句话可以解决大多数场景下的预编译问题,但光凭这句话无法理解透预编译,接下来我们来一起捋一下函数预编译的大致流程。

函数预编译四部曲

  1. 预编译开始,会建立AO(Activation Object)对象
  2. 找形参和变量声明,使其作为AO的属性名,值赋予undefined
  3. 实参和形参相统一(将实参值赋值给形参)
  4. 找函数声明,函数名作为AO属性名,值赋予函数体

案例分析

学习了函数的预编译过程,就可以回头细细的品味一下上面的案例:

  1. 先建立AO,并找形参和变量声明

    AO :{
        a: undefined,
        b: undefined
    }
    
  2. 形参实参相统一

    AO :{
        a: 1,
        b: undefined
    }
    
  3. 找函数声明,值赋予函数体

    AO :{
        a: function a() {},
        b: undefined,
        d: function d() {}
    }
    
  4. 预编译过程结束,挨着分析一下console的打印结果:

    第一个console.log(a); // 此时AO中a的值为function a() {} 
    执行赋值操作:
        a = 123 // AO中的a值修改为123
        第二个console.log(a) // 123
        第三个console.log(a) // 123
        b = function() {} // AO中的b值修改为function b(){}
        console.log(b) // function b(){}
    

全局预编译

全局中不存在形参和实参,所以只需处理变量声明和函数声明

全局预编译三部曲

  1. 生成GO(Global Object)
  2. 找变量声明,由于全局变量默认挂载在window之上,若window当前已存在当前属性,忽略当前操作,若没有,变量作为属性名,值赋予undefined
  3. 找函数声明,函数类似与变量,先去window上查看,不存在,函数作为函数名,值为函数体

案例分析

将函数预编译案例稍微修改,如下:

// test部分的结果与函数部分相同,再次只分析全局部分
console.log(a);
var a = 1;
console.log(a);
function test(a) {
    console.log(a);
    var a = 123;
    console.log(a);
    function a() {}
    console.log(a);
    console.log(b);
    var b = function() {}
    console.log(b);
}
test(2);
  1. 生成GO,变量提升,函数提升,得到GO如下:

    GO/window: {
        a: undefined,
        test: function() {}
    }
    
  2. 因此第一个a的值为undefined,随后a赋值为1,所以第二个a的值为1

注意事项

1. 当函数中出现同样名称的函数名和变量名,编译器真的会先做变量提升再去函数提升吗?查看了一些大佬的博客,当出现变量名和函数名相同时,变量提升应该会被忽略掉,不会做重复的无用之功

2. let/const声明的变量应当同样进行了变量提升,只不过它与var声明的变量做了一定的区分

常见面试题分析

题目一

function test() {
console.log(b);
if (a) {
    var b = 100;
}
console.log(b);
c = 234;
console.log(c);
}
var a;
test();
a = 10;
console.log(c);
  1. 生成GO

    GO: {
        a: undefined,
        test: function test() {},
        c: undefined
    }
    
  2. test执行,生成test的AO

    // AO还会存储[[scope]]属性,存储AO的作用域链
    
    AO: {
        b: undefined,
        [[scope]]: [TestAO, GO]
    }
    
  3. 分析test函数执行

    console.log(b) // AO中b为undefined
    if (a) // AO中无a,沿[[scope]]找到GO中的a,值为undefined
    b = 100; // 不执行
    console.log(b) // undefined
    c = 234; // AO中没有c属性,沿[[scope]]找到GO中的c修改为234
    console.log(c) // 打印的是GO中的c,234
    // test执行完毕,AO销毁
    
  4. 分析剩余代码:

    a = 10; // GO中的a修改为10
    console.log(c) // GO中c值为234,234
    

题目二

var foo = 1;
function bar() {
    console.log(foo);  
    if (!foo) {
        var foo = 10;
    }
    console.log(foo); 
}

bar();

答案

undefined
10

题目三

var a = 1;
function b() {
    console.log(a);  
    a = 10;
    return;
    function a() { }
}
b();
console.log(a); 

return; 与上面案例的if一样,预编译环节不会处理

答案:

function a() { }
1

题目四

console.log(foo); 
var foo = "A";
console.log(foo)  
var foo = function () {
    console.log("B");
}
console.log(foo); 
foo(); 
function foo(){
    console.log("C");
}
console.log(foo)  
foo(); 

答案:

? foo(){
    console.log("C");
}
A
? () {
    console.log("B");
}
B
? () {
    console.log("B");
}
B

题目五

var foo = 1;


function bar(a) {
    var a1 = a;
    var a = foo;
    function a() {
        console.log(a); 
    }
    a1();
}

bar(3);

答案:

1

总结

预编译的题目多数情况下就可以采用以下原则:

  • 函数声明,整体提升
  • 变量声明,声明提升

如果遇到复杂的情况,就要按照最原始的方式一步一步的解决问题。

最后,在预编译时一定要注意:return、if等代码逻辑判断是在执行时候做的,预编译不管这些,预编译只管变量、形参、函数等。

举报

相关推荐

0 条评论