简单易懂!!超详细带你了解什么是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,你挑战成功了吗?

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

相关推荐
奋飛3 分钟前
TypeScript系列:第六篇 - 编写高质量的TS类型
javascript·typescript·ts·declare·.d.ts
sunbyte11 分钟前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | ThemeClock(主题时钟)
前端·javascript·css·vue.js·前端框架·tailwindcss
小飞悟20 分钟前
🎯 什么是模块化?CommonJS 和 ES6 Modules 到底有什么区别?小白也能看懂
前端·javascript·设计
浏览器API调用工程师_Taylor20 分钟前
AOP魔法:一招实现登录弹窗的全局拦截与动态处理
前端·javascript·vue.js
FogLetter21 分钟前
初识图片懒加载:让网页像"懒人"一样聪明加载
前端·javascript
呆呆的心28 分钟前
JavaScript 深入理解闭包与柯里化:从原理到实践 🚀
javascript·面试
快起来别睡了28 分钟前
看完这篇文章,你就知道什么是proxy
javascript
请你吃div29 分钟前
JavaScript 实用函数大全(超实用)
前端·javascript·面试
一个水瓶座程序猿.31 分钟前
Vue3 中使用 Vueuse
前端·javascript·vue.js
夏梦春蝉31 分钟前
ES6从入门到精通:Symbol与迭代器
前端·javascript·es6