前言
在JavaScript中,预编译是一个重要且常常被忽视的概念。在代码执行前,JavaScript引擎会进行一系列的预处理操作,这些操作包括变量和函数声明的提升、作用域的创建以及this关键字的绑定等。本文将深入探讨JavaScript预编译的过程,揭示其背后的秘密,并帮助读者更好地理解JavaScript代码在执行前的准备工作。通过对预编译过程的深入了解,读者将能够更加熟练地运用JavaScript语言,写出更加高效、可靠的代码。
函数是否存在声明提升?
我们来看这段代码:
console.log(a);//undefined
var a = 123//声明提升
foo()//可以访问 函数声明整体提升
function foo(){
var a = 132
console.log(a);
}
在我们之前的文章中
("跃迁ES6:探索JavaScript语言的最新进展(一)")
提到var
声明的变量是存在声明提升的,在访问a时会显示undefined,那我们在定义一个foo()方法前访问foo()是否能访问到呢?答案是:Yes!为什么我们并没有定义函数却能直接访问呢?这就是这篇文章的主要内容:“js的预编译”
JavaScript预编译
1. 预编译发生在函数执行之前(四部曲)
-
创建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 b(){}
function d(){}
var d = a
console.log(d);//123
}
//AO:{
// a:undefined 1 function a(){},
// b:undefined,
// d:undefined function d() {},
//}
fn(1)
我们来看看到底是怎样运行的:
①创建AO对象(Action Object)记录有效标识符
此时,我们会在fn全局上下文创建一个AO对象用来记录有效标识符
② 找形参和变量声明,将变量声明和形参作为AO属性名,值为undefined
先看形参a,在AO对象创建了一个键值对,key为a,value 为undefined,没有其他形参了
此时再找变量声明:
var a = 123 这里又定义了a,将AO中的key: a覆盖,值依旧为undefined
var b = function(){} 又在AO中添加了一个键值对 b:function(){}
var d = a 添加键值对 d:undefined
此时形参和变量声明就找完了
③将形参和实参值统一
我们向foo()中传入了一个实参a=1,将AO中的a对应的value改为1
④在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体
function a(){}//函数声明 这里将a又覆盖为function a(){}
function d(){} 这里创建键值对 d: function d(){}
到这里我们的预编译工作就全部结束了,现在开始运行
console.log(a);//function a(){}
a = 123 赋值将a改为123
console.log(a);//123
var b = function(){}//函数表达式 b赋值为 b = function(){}
console.log(b);//function b(){}
d =a 赋值
console.log(d);//123
此时,程序运行完毕!
2.预编译发生在全局
- 创建GO对象(Global Object)
- 找变量声明,将变量声明和形参作为GO属G性名,值为undefined
- 在全局内找函数声明,将函数名作为GO对象的属性名,值赋予函数体
预编译发生在全局时,有三个步骤,与预编译发生在函数执行之前大致相同,只是全局编译没有形参而已
同样的,我们看代码理解:
global = 100
function fn(){
console.log(global);//undefined
global = 200
console.log(global);//200
var global = 300
}
fn()
var global
① 创建GO对象(Global Object)
在全局上下文创建一个GO对象
② 找变量声明,将变量声明作为GO属性名,值为undefined
这里我们开始找声明变量,不过多阐述 找到 global:undefined
③在全局内找函数声明,将函数名作为GO对象的属性名,值赋予函数体
找到 fn:function fn(){}
到这时我们的全局预编译就完成了,我们可以看到在运行时调用fn()方法,在调用之前,会展开对于fn()方法的预编译,开始执行四部曲。
最后我们再来看一段代码:
// Go :{
// global:undefined 100
// fn : function fn(){}
// }
var global = 100
function fn(){
console.log(global);
}
// Ao:{
//
// }
fn()//100
//有一个调用栈
//全局执行上下文GO先入栈
// 全局执行上下文中有
//变量环境 :var声明的变量 global = 100 fn = func(){}
//词法环境:let,const声明的变量
//fn执行上下文再入栈
//此时fn中未声明变量和方法
//调用console.log(global);
//先找fn的词法环境,再找变量环境,没找到,但是有一个指针指向全局执行上下文
//于是又在全局执行上下文的词法环境中找,没找到在变量环境找
当我们在调用发fn()方法时,方法中没有global属性,但是仍然可以输出global = 100,这是为什么呢?这是因为有一个调用栈,全局执行上下文GO先入栈,全局执行上下文中有
变量环境 :var声明的变量 global = 100 ,fn = func(){},词法环境:let,const声明的变量,fn执行上下文再入栈,此时fn中未声明变量和方法,调用console.log(global),先找fn的词法环境,再找变量环境,没找到,但是有一个指针指向全局执行上下文,于是又在全局执行上下文的词法环境中找,没找到在变量环境找,找到global且值为100,输出。
结尾
总而言之,预编译是JavaScript中一个重要的概念,它通过提前处理变量和函数的声明,为代码执行阶段做准备。通过预编译,我们可以在代码执行之前创建变量和函数,并将它们存储在内存中供后续使用。了解和掌握预编译的原理和过程对于理解JavaScript的执行机制至关重要。通过充分利用预编译的特性,我们可以更好地组织和管理我们的代码,提高代码的可用性和可读性。
因此,在编写JavaScript代码时,我们应该充分利用预编译的特性,合理地声明变量和函数,以便代码能够更加清晰、易读和易于维护。