【JavaScript】一篇文章,带你拿捏JS的预编译

【JavaScript】一篇文章,带你拿捏JS的预编译

1. 前言

上一篇文章,我们一起聊了 JS 的作用域和作用域链,知道了作用域有全局作用域,函数作用域和块级作用域,也知道了作用域链中变量的查找规则是由内向外的。但是大家有没有想过为什么呢,今天我们就来聊一下 JS 引擎在你运行代码时都做了些什么

首先,考一下你哦,告诉我以下代码输出是什么

a=10

console.log(a);

var b=20;

function foo() {

console.log(b);

console.log(a);

var a=30;

console.log(a);

}

var a;

foo();

10;20;30;20 ,你的答案是这个吗

呜呜,错了哟,正确答案是:10;20;undefined;30

你可能会想为什么呢,在 foo 第一次输出 a 时,不是应该遵循由内向外的原则,和输出 b 一样的去全局作用域寻找 a 并输出为30吗,这个问题就与 JS 的预编译有关了,接下来就让我们来探讨其中的奥秘吧

2. 执行上下文

在了解预编译之前,我们先来了解一下执行上下文

执行上下文是 JS 代码执行时的运行环境,包含了代码运行所需的所用信息。

每当 JS 引擎执行一段代码的时候,就会创建一个执行上下文。

2.1 执行上下文的生命周期

执行上下文的创建分为两个阶段:创建阶段和执行阶段

创建阶段

在创建阶段,JS 引擎会做三件事

创建变量对象(变量声明,普通函数声明会在普通变量声明之后)确定 this 指向确定作用域

执行阶段

在执行阶段,JS 引擎也会做三件事

为所创建变量对象赋值(包括变量赋值和函数表达式赋值)调用函数顺序调用其他代码

2.2 执行上下文和作用域的区别

执行上下文是在运行时确定的,随时可能改变;作用域在定义的时候就确定,并且不会改变作用域是静态的,作用域中的值一旦被确定,永远都不会变。函数可以不被调用,但是作用域中的值,在函数创建的时候就已经被写入了,并且存储在函数作用域链对象里面

3. 执行栈

执行栈是一个存储函数调用的栈结构,遵循先进后出的原则。

如图

当程序开始执行时,全局执行上下文会被创建,并被 JS 引擎压入执行栈的底部,作为整个程序运行的基础环境(入栈)任何因当前所在的执行上下文而产生的新的执行上下文会被进一步添加到调用栈中,并运行到他们被上个程序调用到的位置程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文

4. 预编译

介绍完了执行上下文和执行栈,接下来我们正式来学习 JS 的预编译。JS 的预编译是 JS 引擎执行代码前的准备阶段,主要处理变量和函数的声明提升。下面我们简单介绍一下其在全局以及函数体内的编译过程

全局编译过程

创建全局执行上下文对象找到变量声明,将变量和形参名作为上下文的属性名,值为undefined找到函数声明,函数名作为上下文对象的属性名,值为函数体执行函数体

函数体编译过程

创建函数的执行上下文对象找到形参和变量声明,将变量和形参名作为上下文的属性名,值为 undefined将实参值和形参统一在函数体里面找函数声明,函数名作为上下文对象的属性名,值为函数体执行函数体

ok,现在让我们一起来分析一下最开始时的那段代码

a=10

console.log(a);

var b=20;

function foo() {

console.log(b);

console.log(a);

var a=30;

console.log(a);

}

var a;

foo();

程序开始运行,创建全局执行上下文,并由 JS 引擎压入栈底

编译阶段,全局执行上下文为:{ a:undefined ,b:undefined , foo:[function:fn] }

执行阶段,全局执行上下文变为:{ a:10 , b:20 , foo:[function:fn]}

函数(foo)执行上下文被创建,并被 JS 引擎压入栈中

编译阶段,函数执行上下文为:{ a:undefined }

执行阶段,函数执行上下文变为:{ a:30 }

函数 foo 执行完毕,它的执行上下文被销毁,并从栈顶推出,控制权交还给全局上下文程序结束运行,全局执行上下文出栈,整个执行栈被销毁

以上就是 JS 引擎在上述代码运行时的活动,故我们不难看出,上述代码的执行结果为:10;20;undefined;30

预编译的核心机制:变量提升

变量提升是预编译的直接结果,它允许在变量声明前访问变量,但此时变量的值为 undefined。怎么样,看到这句话有没有想到什么,对了,就是 let和const.在上一篇文章中我们有讲过 let和const 是不存在变量提升的,若你在它声明前使用变量,会形成 暂时性死区,所以下面这句话要记清楚啦:

预编译仅处理 var 声明的变量和函数声明(function fn() {}),不处理 let/const 和箭头函数。

5. 总结

最后的最后,来道题目检查一下自己有没有学懂吧,把你的答案打在评论区吧

function foo(a,b) {

console.log(a);

c=0

var c;

a=3

b=2

console.log(b);

function b() {}

console.log(b);

}

foo(1)

你可能也喜欢

小米14 Pro龙晶玻璃最新极限测试,真的太硬了!
beat365中文官网

小米14 Pro龙晶玻璃最新极限测试,真的太硬了!

📅 10-08 👀 2122
滴滴快车司机审核时效揭秘 多久能通过
365bet官网地址

滴滴快车司机审核时效揭秘 多久能通过

📅 07-24 👀 6212
血色大厅副本入口(血色副本在哪个地图)
beat365中文官网

血色大厅副本入口(血色副本在哪个地图)

📅 07-01 👀 2611
27寸有多大 27寸是几厘米×几厘米
36500365体育在线投注

27寸有多大 27寸是几厘米×几厘米

📅 07-12 👀 4264
CCD传感器的工作原理、优缺点及其广泛应用前景分析
丹麦国家队历届世界杯战绩,丹麦世界杯历史排名