【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)
相关推荐
yours_Gabriel2 分钟前
【java面试】MySQL篇
java·mysql·面试
前端小巷子7 分钟前
JS的 DOM 尺寸与位置属性
前端·javascript·面试
百锦再31 分钟前
# Vue + OpenLayers 完整项目开发指南
开发语言·前端·javascript·vue.js·python·ecmascript·tkinter
Jinxiansen021134 分钟前
Vue + Element Plus 实战:大文件切片上传 + 断点续传
前端·javascript·vue.js
MX_935935 分钟前
JSON基础知识
开发语言·javascript·json
独立开阀者_FwtCoder39 分钟前
11 个 JavaScript 杀手脚本,用于自动执行日常任务
前端·javascript·github
汪子熙1 小时前
深入理解 // eslint-disable-next-line no-eval 注释的作用与应用
前端·javascript·面试
一路向北North1 小时前
PDF.js无法显示数字签名
开发语言·javascript·pdf
Dignity_呱1 小时前
Vue性能优化:从加载提速到运行时优化
前端·vue.js·面试
whltaoin2 小时前
Java面试专项一-准备篇
java·面试