前言
在之前的学习中,我们知道了什么是js中的作用域和什么是声明提升 (什么是作用域 - 掘金 (juejin.cn)),今天带大家深入了解声明提升的底层原理。
声明提升
首先简单的说一下什么是声明提升,这里主要讲两个,一个是var声明的函数变量存在声明提升
console.log(a)//此时输出undefined
var a=1
其次是函数声明整体提升(函数名和函数体都提升)
foo()
function foo(){
console.log('hello');
}
很奇怪,为什么一个变量没有声明就输出却不会报错而是undefined?函数没有先声明就调用却能正常执行。今天就来深入的说一说声明提升的底层逻辑————预编译。
预编译
预编译发生在函数体内时
当预编译发生在函数体内时简单的就可以概括为以下4点。
- 创建函数的AO对象(Action object)//函数的作用域
- 找形参和变量声明,将形参和变量声明作为AO的属性名,值赋为undefined。
- 将形参和实参统一。
- 在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体。
接下来通过这段代码来对这四条基本逻辑进行讲解。
function fn(a){
console.log(a);//function a(){}
var a=123;
console.log(a);//123
function a(){}//函数声明
console.log(a)//123
var b=function(){}//函数表达式 赋值语句
console.log(b)//function(){}
var d=a
function d(){}
console.log(d)//123
}
fn(1)
a作为形参,b,d作为变量声明被传入AO作为AO属性名并且被赋值为undefined。
a作为形参先传入的值为1所以a由undefined变为1,接着是a,d都作为函数声明被传入AO,a的值再次变为function a(){},b变为function。
至此,预编译全部完成,接下来就是代码的运行了。
图中标出了a的值的传递过程,首先我们需要明确知道,代码的运行过程是不会再运行变量和函数的声明,因为这一部分是编译的内容,图中红线部分就是全部运行过程需要处理的代码了。代码由上至下运行,首先输出a的值此时a为function a(){},接着a被赋值为123,再次输出a的值,此时a的值则是123。第5行的函数声明在预编译阶段已经完成所以代码运行阶段不运行。所以第6行的a输出仍然为123。剩余代码不多赘述。
预编译发生在全局时
首先仍然是先介绍预编译发生在全局时的基本规则
- 创建一个GO对象(全局作用域)
- 找变量声明,将变量名作为GO的属性名,值赋予undefined
- 在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体
相较于在函数内的预编译更简单一点。通过以下的代码来理解发生在全局的预编译。
global =100
function fn(){
console.log(global);//undefined
global=200;
console.log(global)//200
var global=300
}
fn()
var global;
在全局预编译时,找变量声明只有一个global,但是注意第一行仍然只是一个赋值语句,预编译阶段不执行,是最后一行才是一个变量声明传入GO中并将值赋为undefined,然后在全局找函数声明则是第二行的fn传入GO。此时预编译阶段就结束了。接着是代码的执行阶段,global被赋为100,然后执行fn()函数。此时,又是函数内的预编译了。生成一个AO作用域,fn()函数最后一行声明global传入AO中赋值为undefined。然后预编译阶段就结束直接进入运行阶段。
尾声
对预编译这种JavaScript的底层的原理的学习,能让我们在一个新的维度去审视JavaScript的代码,对于JavaScript有了更深的理解,对于面试时一些绕来绕去的代码题想必能信手拈来,希望能对大家的学习和工作有些帮助。