面试题:JavaScript预编译、作用域链

前言

在专栏《你不知道的JavaScript》中,也就是上一篇《"深入探索JavaScript:从词法分析到词法作用域的欺骗"》,深入探讨了编译过程:从词法分析->语法分析->代码生成,那么在JavaScript这门语言中这个编译过程是怎么样的呢?

比起传统的只有3个步骤的语言的编译器,JavaScript引擎要复杂的多,但总体来看,JavaScript编译过程只有下面三个步骤: 1. 语法分析 2. 预编译 3. 解释执行

引入面试题

执行结果是什么

javascript 复制代码
function test(a, b) {
    console.log(a);  // 1
    c = 0
    var c;
    a = 3
    b = 2
    console.log(b); // 2
    function b() { }
    console.log(b); //2
}
test(1)

分析过程

众所周知,如果你不了解编译原理,一定会以就近原则定论,那么接下来就让我们探讨一下原理,剖析完原理之后,再返回来看这道题目,届时你一定会豁然开朗。

在 JavaScript 中,预编译是在代码执行前进行的一项操作,它会把变量声明提前,函数声明也提前,把这些按照一定的规则,放在创建的对象里面去。这个过程主要包括以下几个步骤:

  • 发生在全局
    1. 创建GO对象
    2. 找变量声明,将变量名作为GO的属性名,值为undefined
    3. 在全局找函数声明,将函数名作为GO的属性名,值为该函数体
  • 发生在函数体内
    1. 创建 AO(Activation Object)对象:在函数执行前,会创建一个 AO 对象,用于存储函数内部的局部变量。
    2. 找形参和变量声明:在函数体内,找到形参和变量声明,并将变量和形参名作为 AO 属性名,值为 undefined
    3. 将实参和形参统一:将实参和形参统一到函数体中。
    4. 找函数声明:在函数体内,找到函数声明,并将函数值赋予函数体。

通过预编译,JavaScript 可以在函数执行前准备好所需的变量和函数,提高代码的执行效率。

我们一起来看这一份代码,理解了上述分析过程,那么在JavaScript引擎的眼里这个分析过程应该是这样的:

  • 首先看见了一个函数,发生在全局,那么创建一个Global Object GO{ 找变量,没找到,找函数,找到了,因此在这个对象中就有属性test,值为:function(){}}
  • 接下来就读到了调用,但是在调用之前,引擎需要知道要执行什么东西,因此要对这个函数进行预编译
  • 发生在函数体内的预编译,建立一个AO对象{找形参、变量声明,值为undefined,因此就有:a:undefined,b:undefined,c:undefinded,然后将形参与实参统一,即a:1,接下来找函数声明,即b:function(){}},预编译完成之后就开始执行代码,此时AO{

a:1,

b:function(){},

c:undefined

} 引擎从上往下开始执行,1.执行打印a,a为1, 2.将0赋值给c,即c=0,a=3,b=2,此时AO{ c=0, a=3, b=2 },接下来就到了打印b的代码,即b=2,接下来又是一个打印b的代码,即b=2.

因此正确答案就是a=1,b=2,b=2

思考

如果JavaScript引擎在AO里面没找到要找的属性怎么办?他会去哪里找?

举个例子

ini 复制代码
function a() {
    function b() {
        b = 22;
    }
    var a = 111;
    b();
    console.log(a);
}
var glob = 100;
a();

分析

在 JavaScript 中,作用域可以分为以下几种类型:

  1. 全局作用域:变量在整个脚本中都可访问。
  2. 函数作用域:变量仅在函数内部可访问。

作用域链的形成与函数的调用和嵌套有关。当函数被调用时,会创建一个新的作用域,并将其添加到作用域链中。函数内部的变量和函数可以访问外部作用域中的变量和函数,从而形成了作用域链的结构。

首先创建GO对象,里面有属性a,值为这个a的函数体,然后有属性glob,值为100。然后就碰到了调用a函数,此时JavaScript引擎就会去预编译a函数,创建AO对象,里面有属性a,值为111,属性b,值为这个function函数体,紧接着调用b函数,又创建一个AO对象,里面有b属性,值为22。

可以看见,首先a的作用域会先指向这个GO对象,他先创建GO对象。那么作用域0号位置,指向这个GO对象,紧接着,他又创建了AO对象,那么此时,a的作用域的0号位置就会优先指向这个AO对象,然后1号位置指向GO,后来引出了b,那么同理,b的作用域0号位置首先指向他自己创建的AO对象然后再指向引出他的a(1号位置)。

作用域链由一系列的作用域组成,每个作用域都包含了可访问的变量和函数。当代码中访问一个变量时,JavaScript 会沿着作用域链依次查找该变量。

那么在上述的这个分析过程,这么一个东西就是我们的作用域链了,大家可以通过画图理解。

由此我们就知道了,为什么内层函数可以访问外层,而外层函数无法访问到我们的内层。当JavaScript引擎没有在我们的AO对象中找到我们需要的属性时,他就会去下一个作用域找,也就是我们的GO对象中继续找。

小结

通过这篇文章,相信你对JavaScript预编译以及作用域又有了更加深刻的理解,当面试官给你写了一串代码,问你输出结果时,你能够冷静应对了吗?

相关推荐
夜斗(dou)3 分钟前
node.js文件压缩包解析,反馈解析进度,解析后的文件字节正常
开发语言·javascript·node.js
恩爸编程35 分钟前
纯 HTML+CSS+JS 实现一个炫酷的圣诞树动画特效
javascript·css·html·圣诞树·圣诞树特效·圣诞树js实现·纯js实现圣诞树
神雕杨38 分钟前
node js 过滤空白行
开发语言·前端·javascript
网络安全-杰克1 小时前
《网络对抗》—— Web基础
前端·网络
m0_748250741 小时前
2020数字中国创新大赛-虎符网络安全赛道丨Web Writeup
前端·安全·web安全
周伯通*1 小时前
策略模式以及优化
java·前端·策略模式
艾斯特_1 小时前
前端代码装饰器的介绍及应用
前端·javascript
Sokachlh1 小时前
【elementplus】中文模式
前端·javascript
轻口味1 小时前
【每日学点鸿蒙知识】hap安装报错、APP转移账号、import本地文件、远程包构建问题、访问前端页面方法
前端·华为·harmonyos
m0_748245341 小时前
BY组态-低代码web可视化组件
前端·低代码