简单易懂!!超详细带你了解什么是JavaScript预编译!

今天我们来详细说说什么是JavaScript的预编译,深入了解它的底层原理,读完这篇文章之后,就再也不怕面试官在js预编译的本质上对你刨根问底的追问啦!

声明提升

声明提升可是与js的预编译紧密相连的呢,所以我们先来学习一下声明提升又是怎么一回事。

简单来说:

1. var 声明的变量会存在声明提升

以代码为例:

css 复制代码
console.log(a);
var a = 1

在这个例子中,变量a在声明之前被使用,但由于 var 声明 a 得到了声明提升,不会抛出错误,而是输出undefined。上面的代码等同于:

css 复制代码
var a    //声明提升
console.log(a);
a = 1

2. 函数声明会整体提升

scss 复制代码
foo()
function foo(){
    console.log('hello');
}

函数调用在声明之前,但执行后仍然可以输出 hello ,同样,函数声明被提升了,即:

scss 复制代码
function foo(){
    console.log('hello');
}
foo()

对于声明提升的介绍在我之前那篇《写给小白的JavaScript作用域及声明提升详解》有详细解释,如果想深入了解的话欢迎去看━(*`∀´ *)ノ亻!

接下来就是我们今天的重点了(敲黑板!!!),我们将分为两个部分来讲js的预编译,一起来看看吧。

预编译发生在函数体内时

我们需要记住这四个步骤:

  1. 创建函数的AO对象 (Action Object)
  2. 找形参和变量声明,将形参和变量声明作为AO的属性名,值赋予undefined
  3. 将形参和实参统一
  4. 在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体

它们是什么意思呢?我们等下再来解释,我们先思考以下代码:

css 复制代码
var a = 1
function foo(a){
    var a = 2
    function a(){}
    var b = a
    console.log(a);  
}
foo(3)

看完这段代码你能想到最后结果会得到什么吗?函数体内这么多个a到底是输出了哪个呢?我们刚刚讲了声明提升,变量a会声明提升,函数a也会声明提升,那函数a的声明提升是在变量a的声明提升前还是后呢?

这样?

css 复制代码
var a = 1
function foo(a){
    var a 
    function a(){}
    a = 2
    var b = a
    console.log(a);  
}
foo(3)

还是这样?

css 复制代码
var a = 1
function foo(a){
    function a(){}
    var a
    a = 2
    var b = a
    console.log(a);  
}
foo(3)

执行后我们会得到结果为2,看到这里,你是不是会觉得:哦~我懂了是第二种,原因是变量a先声明提升,函数a再声明提升,于是函数a的声明提升跑到最顶上了。那如果我们将代码改成这样,结果又是什么呢?

css 复制代码
  var a = 1
function foo(a){
    function a(){}
    var a = 2        
    var b = a
    console.log(a);  
}
foo(3)

结果还是2,现在你真正懂了,原来是函数名和变量名冲突的时候,函数名会提升到变量名前面。

那我们现在再来看一段代码,先不用想它会输出什么:

javascript 复制代码
 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(){}
    var d = a
    console.log(d);
} 
fn(1)

看到这代码的第一眼,你说:我嘞个豆,还有这么恶心的代码?五个输出!?现在你还要傻傻的继续一个一个看它们的声明提升看到脑袋发晕吗?不!!我们用js的预编译来解决它,让你轻松得出答案!记得我们最开始说的四个步骤吗,我们复习一遍:

  1. 创建函数的AO对象 (Action Object)
  2. 找形参和变量声明,将形参和变量声明作为AO的属性名,值赋予undefined
  3. 将形参和实参统一
  4. 在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体

执行这段代码,在调用函数fn后,会先进行函数体内的编译,再去执行函数体内的命令,而函数体内的编译是怎么进行的呢?

首先我们创建函数的AO对象,也就是整个函数的作用域对象:

再来第二个步骤,找形参和变量声明,将形参和变量声明作为AO的属性名,值赋予undefined

为什么要找形参和变量声明呢,因为它们都是函数体内的有效标识符。我们找的时候,会发现形参也是a,有个变量声明也是a,但是js这门编程语言当中,对象里是不允许有两个重复的key,所以其中一个a会被覆盖,只剩一个a。

第二步完成之后,进行第三步:将形参和实参统一

我们可以看到实参为1,那么我们将1给形参:

然后我们进行最后一步,在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体,我们找到了a的函数声明,d的函数声明:

好啦!预编译的四个步骤都完成了,现在开始执行函数体内的命令:

首先第一条就是输出a,此时a的值是函数体,所以这条命令会输出一个函数体;下一个命令将a赋值123,此时a的值为123:

接下来又是输出a的命令,这条则会输出123;再下一行为函数声明不用执行,之后又是输出a,依然为123;再之后给b赋值函数体:

命令输出b则会输出一个函数体;下一条命令将a赋值给d,a的值为123,则d也为123,最后一条命令输出会123,所以最终的结果是:

学会了请扣1,没学会再看一遍!!!

预编译发生在全局

牢牢记住这三个步骤~~

  1. 创建GO对象 (Global Object)
  2. 找变量声明,将变量名作为GO的属性名,值赋予undefined
  3. 在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体

看以下代码:

php 复制代码
var global = 100
function fn(){
    console.log(global);
}
fn()

和在函数体中的预编译很相似,第一步创建一个GO对象 ,第二步找变量声明,将变量名作为GO的属性名,值赋予undefined ,第三步在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体,完成后GO对象是这样子的:

之后执行代码,第一行命令将100赋值给global:

之后再执行fn的调用,此时执行函数体,在此之前进行函数体的编译,建立一个AO对象,进行我们前文所说的在函数体内的预编译,最后AO里面为空,进行函数体内命令的执行,输出global,即为100。

来看一个难度提升版:

最后输出为undefined和200,你挑战成功了吗?

到这里我们的知识就讲完啦!!今天又进步了一点点,一起加油!!

相关推荐
It'sMyGo16 分钟前
Javascript数组研究09_Array.prototype[Symbol.unscopables]
开发语言·javascript·原型模式
李是啥也不会33 分钟前
数组的概念
javascript
无咎.lsy43 分钟前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec1 小时前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec1 小时前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
JUNAI_Strive_ving2 小时前
番茄小说逆向爬取
javascript·python
看到请催我学习2 小时前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins35203 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky3 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~3 小时前
分析JS Crash(进程崩溃)
java·前端·javascript