Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

预编译 #74

Open
Huauauaa opened this issue Mar 21, 2023 · 1 comment
Open

预编译 #74

Huauauaa opened this issue Mar 21, 2023 · 1 comment

Comments

@Huauauaa
Copy link
Owner

预编译

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

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

预编译

JavaScript是解释性语言,也就是说,编译一行,执行一行,但js并非上来就进入编译环节,它在编译之前存在预编译过程。

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,并找形参和变量声明,值赋予undefined
    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

test中定义了变量a,因此打印的a为自身AO中的值。如果test中没有定义a,就会沿着作用域链,当GO中查找a。

注意事项

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
    }

    JavaScript中变量如果未经声明就赋值,会默认将变量挂载到window对象上,这也就是所谓的imply globalc就是imply global

  2. test执行,生成testAO
    // AO还会存储[[scope]]属性,存储AO的作用域链
    
    AO: {
        b: undefined,
        [[scope]]: [TestAO, GO]
    }

    有同学会问,if(a)为false,if内部不会执行,那test的AO中为什么还会有b啊?预编译并不是执行,它只不过把变量、函数等进行提升,只有在执行时,才会设计代码逻辑的判断。

  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等代码逻辑判断是在执行时候做的,预编译不管这些,预编译只管变量、形参、函数等。

Sent from PPHub

@Huauauaa
Copy link
Owner Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant