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

1. 前言

上一篇文章,我们一起聊了 JS 的作用域和作用域链,知道了作用域有全局作用域,函数作用域和块级作用域,也知道了作用域链中变量的查找规则是由内向外的。但是大家有没有想过为什么呢,今天我们就来聊一下 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 引擎会做三件事

  1. 创建变量对象(变量声明,普通函数声明会在普通变量声明之后)
  2. 确定 this 指向
  3. 确定作用域
执行阶段

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

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

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

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

3. 执行栈

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

如图

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

4. 预编译

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

全局编译过程

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

函数体编译过程

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

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

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();
  1. 程序开始运行,创建全局执行上下文,并由 JS 引擎压入栈底

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

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

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

  1. 函数 foo 执行完毕,它的执行上下文被销毁,并从栈顶推出,控制权交还给全局上下文
  2. 程序结束运行,全局执行上下文出栈,整个执行栈被销毁 以上就是 JS 引擎在上述代码运行时的活动,故我们不难看出,上述代码的执行结果为:10;20;undefined;30

注意!!!

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

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

5. 总结

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

js 复制代码
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)
相关推荐
独立开阀者_FwtCoder40 分钟前
【Augment】 Augment技巧之 Rewrite Prompt(重写提示) 有神奇的魔法
前端·javascript·github
我想说一句1 小时前
事件机制与委托:从冒泡捕获到高效编程的奇妙之旅
前端·javascript
汤姆Tom1 小时前
JavaScript reduce()函数详解
javascript
小飞悟1 小时前
你以为 React 的事件很简单?错了,它暗藏玄机!
前端·javascript·面试
中微子1 小时前
JavaScript 事件机制:捕获、冒泡与事件委托详解
前端·javascript
蓝翔认证10级掘手2 小时前
🤯 家人们谁懂啊!我的摸鱼脚本它...它成精了!🚀
javascript
前端康师傅2 小时前
JavaScript 中你不知道的按位运算
前端·javascript
tianchang2 小时前
策略模式(Strategy Pattern)深入解析与实战应用
前端·javascript·代码规范
best6662 小时前
JavaScript的Math内置对象,到底是何方神圣?
javascript
掘金安东尼2 小时前
技术解析:高级 Excel 财务报表解析器的架构与实现
前端·javascript·面试