前言:
当涉及到JavaScript(JS)预编译时,这可能会是初学者挥发如雨的一个阶段。然而,理解它对于建立坚实的JavaScript基础至关重要。在编写JavaScript代码时,你可能已经听说过"预编译"。但你知道它到底是什么吗?本文将揭示预编译的秘密,帮助你更好地理解JavaScript代码的执行过程。
正文:
什么是预编译?
各位萌新小白应该已经知道了,JavaScript这门语言的代码是从上到下依次执行的
但是倘若真是如此,为何又能出现以下情形呢?
从正常碳基生物的思维来看,x无论是声明还是赋值都在log输出之后,按道理来说log是压根不知道x的存在的,应该会直接甩给你一个通红的“x is not defined”报错,而不是委婉地给你一个undefined,难不成这log这么好欺负?显然不是,人家log可不是娇滴滴的女王。实际上这里是预编译的作用,预编译是JavaScript代码在实际执行之前的一个重要阶段。这个阶段确保了变量和函数的声明在代码执行时能够被正确理解,从而影响了代码的行为。为了更详细地解释预编译,我这个大白将为各位小白分解这个过程并深入了解它的工作原理。
预编译涉及两种情况:全局预编译和函数预编译。限于篇幅限制,这里暂时只对全局编译进行详细讨论。
全局预编译的四部曲
有一说一,对于初出茅庐的小白来说,各种各样的概念和专有名词实在让人头大,本文秉着让各位小白有手就行的观念,这里将如同庖丁解牛一般,利用代码和图解将预编译完全摊开为您讲解
示例代码:
f1()
console.log(a);
console.log(d);
function f1() {
a=2
console.log("函数f1已经执行");
}
var d = 5
这段代码中,我们在最开始就直接调用了f1函数,随后通过console.log()分别输出了a和d两个变量,之后再声明并编写f1函数的具体内容,最后声明d,并赋值为5
- 创建 GO 对象 (Global Object)
在全局上下文执行前,JavaScript引擎会在调用栈内创建全局执行上下文对象,我们这里将其称之为GO对象(Global Object)。这个对象在全局作用域内用于存储变量和函数的声明。不清楚调用栈概念的读者可以移步鄙人的上一篇文章:
(解析JavaScript调用栈:轻松理解代码执行 - 掘金 (juejin.cn))
- 找形参和变量声明
JavaScript引擎会查找函数内的形参和变量声明,并将它们作为AO对象的属性名,值为undefined。这样,你可以在声明变量之前使用它们,但它们的值将为undefined。在这段代码中,共有a,b,c和d四个变量,唯独d是在全局中声明,因此这里有且仅有d作为AO对象的属性名且值为undefined
- 找函数声明
在全局内查找函数声明,并将函数名作为AO对象的属性名,将函数体作为属性值赋予函数名。
此时,预编译结束,开始执行代码。第一行代码直接调用函数f1,由于a未声明直接复制(直接写的a=2),所以这里的a会成为全局变量,随后输出"函数f1已经执行"。此时,f1函数执行完毕,第2行代码是输出变量a,之前提到过a已经成为了全局变量,因此这里可以访问到,并输出a的值:2。最后执行第3行代码输出变量d,从图中我们可以看出d在全局执行上下文中已经存在,但未被赋值,所以输出undefined,此时,代码执行完毕。最后结果为:
预编译的重要性
预编译在 JavaScript 代码执行之前起着重要的作用,它确保了代码在执行阶段能够按照预期的方式运行。理解 JavaScript 的预编译过程有助于开发者更好地理解代码执行的机制,避免一些常见的问题并写出更加可靠的代码。但不得不提到的是,在es6中,新添加了let与const声明方式不存在预编译,但也有更多其他特殊属性(如:与{}形成块级作用域等),这些东西会在之后提到。
总结
JavaScript预编译是一项关键的概念,它有助于你理解代码在执行前的处理过程。尽管对各位萌新小白的头发和脑细胞来说可能有些残忍,但通过不断学习和实践,你将更深入地理解JavaScript的运作方式,并能够编写更健壮的代码。继续前进,加油!小萌新!