JavaScript之预编译学习(内含多个面试题)

简介: JavaScript之预编译学习(内含多个面试题)

前言


在学习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
}
复制代码
  1. 形参实参相统一


AO :{
    a: 1,
    b: undefined
}
复制代码
  1. 找函数声明,值赋予函数体


AO :{
    a: function a() {},
    b: undefined,
    d: function d() {}
}
复制代码
  1. 预编译过程结束,挨着分析一下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(){}
复制代码


全局预编译


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


全局预编译三部曲


生成GO(Global Object)

  1. 找变量声明,由于全局变量默认挂载在window之上,若window当前已存在当前属性,忽略当前操作,若没有,变量作为属性名,值赋予undefined
  2. 找函数声明,函数与变量类似,先去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() {}
}
复制代码


  1. 因此第一个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


  1. test执行,生成testAO


// AO还会存储[[scope]]属性,存储AO的作用域链
AO: {
    b: undefined,
    [[scope]]: [TestAO, GO]
}
复制代码


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


  1. 分析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销毁
复制代码


  1. 分析剩余代码:  


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



相关文章
|
27天前
|
前端开发 JavaScript
个人征信电子版无痕修改, 个人信用报告pdf修改,js+html+css即可实现【仅供学习用途】
本代码展示了一个信用知识学习系统的前端实现,包含评分计算、因素分析和建议生成功能。所有数据均为模拟生成
|
1月前
|
算法 Java 关系型数据库
校招 Java 面试基础题目解析及学习指南含新技术实操要点
本指南聚焦校招Java面试,涵盖Java 8+新特性、多线程与并发、集合与泛型改进及实操项目。内容包括Lambda表达式、Stream API、Optional类、CompletableFuture异步编程、ReentrantLock与Condition、局部变量类型推断(var)、文本块、模块化系统等。通过在线书店系统项目,实践Java核心技术,如书籍管理、用户管理和订单管理,结合Lambda、Stream、CompletableFuture等特性。附带资源链接,助你掌握最新技术,应对面试挑战。
54 2
|
27天前
|
前端开发
个人征信PDF无痕修改软件,个人征信模板可编辑,个人征信报告p图神器【js+html+css仅供学习用途】
这是一款信用知识学习系统,旨在帮助用户了解征信基本概念、信用评分计算原理及信用行为影响。系统通过模拟数据生成信用报告,涵盖还款记录
|
2月前
|
JavaScript 数据可视化 前端开发
three.js简单实现一个3D三角函数学习理解
1.Three.js简介 Three.js是一个基于JavaScript编写的开源3D图形库,利用WebGL技术在网页上渲染3D图形。它提供了许多高级功能,如几何体、纹理、光照、阴影等,以便开发者能够快速地创建复杂且逼真的3D场景。同时,Three.js还具有很好的跨平台和跨浏览器兼容性,让用户无需安装任何插件就可以在现代浏览器上观看3D内容。
88 0
|
5月前
|
自然语言处理 JavaScript 前端开发
当面试官再问我JS闭包时,我能答出来的都在这里了。
闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。
201 16
当面试官再问我JS闭包时,我能答出来的都在这里了。
|
8月前
|
Web App开发 JavaScript 前端开发
如何学习JavaScript?
如何学习JavaScript?
162 5
|
8月前
|
JavaScript 前端开发 索引
JavaScript学习第二章--字符串
本文介绍了JavaScript中的字符串处理,包括普通字符串和模板字符串的使用方法及常见字符串操作方法如`charAt`、`concat`、`endsWith`等,适合前端学习者参考。作者是一位热爱前端技术的大一学生,专注于分享实用的编程技巧。
74 2
|
9月前
|
JavaScript
js学习--制作猜数字
js学习--制作猜数字
79 4
js学习--制作猜数字
|
8月前
|
存储 JavaScript 前端开发
JavaScript学习第一章
本文档介绍了JavaScript的基础知识,包括其在网页中的作用、如何通过JavaScript动态设置HTML元素的CSS属性,以及JavaScript中的变量类型(`var`、`let`、`const`)和数据类型(基本数据类型与引用数据类型)。通过实例代码详细解释了JavaScript的核心概念,适合初学者入门学习。
112 1

热门文章

最新文章