JavaScript 是怎么运行起来的?,复习指南

  • 内存堆 - 这是内存分配发生的地方,是一个非结构化的内存池,它存储我们应用程序需要的所有对象。

  • 调用堆栈 - 是我们的代码实际执行的地方

EcmaScript 和 JavaScript 引擎的关系


ECMAScript 指的是 JavaScript 的语言标准及语言版本,比如 ES6 表示语言(标准)的第 6 版。它由一个推动 JavaScript 发展的委员会制定,这个委员会指的是技术委员会( Technical Committee )第 39 号,我们一般简称 TC39,由各个主流浏览器厂商的代表以及一些互联网大厂构成。

JavaScript 引擎的核心就是实现 ECMAScript 标准,此外还提供一些额外的机制(例如 V8 提供的垃圾回收器)。

一些最新的 ECMAScript 提案,到达 stage3stage4 后,就会被 JavaScript 引擎实现,例如 v8 会把它的一些对语言标准的实现更新在它的博客上:https://v8.dev/

运行时环境


JavaScript 引擎并不能孤立运行,它需要一个好的运行时环境才能发挥更大的作用,例如 Node.js 就是一个 JavaScript 运行时环境,各种浏览器也是 JavaScript 的运行时环境。

这些运行时环境往往会提供诸如:事件处理网络请求 API回调队列或消息队列事件循环 这样的附加能力。

9e02df6207b33d2dac7b1c9f2e7a6e56.png

那么 JavaScript 引擎怎么配合这些能力在运行时环境中发挥作用呢?我们拿 Chrome 来举个例子。

0862bd6e8ef7cf19b502f594142ee038.png

Chrome 是一个多进程的架构,我们打开一个浏览器时会启动多个不同的进程协助浏览器将页面为我们呈现出来:

  • 浏览器进程:浏览器最核心的进程,负责管理各个标签页的创建和销毁、页面显示和功能(前进,后退,收藏等)、网络资源的管理,下载等。

  • 插件进程:负责每个第三方插件的使用,每个第三方插件使用时候都会创建一个对应的进程、这可以避免第三方插件crash影响整个浏览器、也方便使用沙盒模型隔离插件进程,提高浏览器稳定性。

  • GPU进程:负责3D绘制和硬件加速

  • 渲染进程:浏览器会为每个窗口分配一个渲染进程、也就是我们常说的浏览器内核,这可以避免单个 page crash 影响整个浏览器。

我们常说的浏览器内核,比如 webkit 内核,就是浏览器的渲染进程,从接收下载文件后再到呈现整个页面的过程,由浏览器渲染进程负责。浏览器内核是多线程的,在内核控制下各线程相互配合以保持同步,一个浏览器内核通常由以下常驻线程组成:

  • GUI 渲染线程:负责渲染浏览器界面 HTML 元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。

  • 定时触发器线程:浏览器定时计数器并不是由 JavaScript 引擎计数的, 因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。

  • 事件触发线程:当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

  • 异步http请求线程:XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。

  • JavaScript 引擎线程:解释和执行 JavaScript 代码。

GUI 渲染线程与 JavaScript 引擎为互斥的关系,当 JavaScript 引擎执行时 GUI 线程会被挂起, GUI 更新会被保存在一个队列中等到引擎线程空闲时立即被执行。

JavaScript 是一种单线程编程语言,所以在浏览器内核中只有一个 JavaScript 引擎线程。

但是,在 JavaScript 的一个运行环境中,因为可能有多个渲染进程,所以可能有多个 JavaScript 引擎线程。

为啥是单线程


那么,为什么 JavaScript 不设计成多个线程呢?这样不是效率更高?

作为浏览器脚本语言, JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生, JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

那么既然 JavaScript 本身被设计为单线程,为何还会有像 WebWorker 这样的多线程 API 呢?我们来看一下 WebWorker 的核心特点就明白了:

  • 创建 Worker 时, JS 引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作 DOM

  • JS 引擎线程与 Worker 线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)

所以 WebWorker 并不违背 JS引擎是单线程的 这一初衷,其主要用途是用来减轻 cpu 密集型计算类逻辑的负担。

在单线程上运行代码非常容易,你不必处理多线程环境中出现的复杂场景 — 例如死锁。

调用堆栈的执行过程


JavaScript 是一种单线程编程语言,这意味着它有一个调用堆栈,一次只能做一件事。

调用堆栈是一种数据结构,它基本上记录了我们在程序中的位置。如果我们执行一个函数,它放会放在栈顶。如果我们从一个函数返回,其会从栈顶弹出,这就是调用堆栈的执行过程。下面这个动图很好的解释了整个运行过程:

20d5a46ada1fcff21ee908086b362f22.gif

调用堆栈中的每个条目被称为 堆栈帧。当调用堆栈中的一个 堆栈帧 需要大量时间才能被处理时,就会产生卡顿,因为浏览器没法做其他事情了。

JavaScript 代码的执行过程


我们从宏观上看到了 JavaScript 调用堆栈是怎么执行的,那么具体到每段代码上是怎么解析执行的呢?

下面我们就以 V8 为例,来看看一段 JavaScript 代码的解析执行过程。

616ef25b538eef6a53362ff188ea041f.png

上面的图展示了 V8 大体的工作流程,画的很复杂,我们简化一下,其实核心模块是下面三个:

  • 解析器(Parser):负责将 JavaScript 代码转换成 AST 抽象语法树。

  • 解释器(Ignition):负责将 AST 转换为字节码,并收集编译器需要的优化编译信息。

  • 编译器(TurboFan):利用解释器收集到的信息,将字节码转换为优化的机器码。

在执行 JavaScript 代码时,首先解析器会将源码解析为 AST 抽象语法树,解释器会将 AST 转换为字节码,一边解释一边执行。然后编译器根据解释器的反馈信息,优化并编译字节码,最后生成优化的机器码,这就是 V8 大体的工作流程。

词法分析和语法分析

我们常常提到的词法分析和语法分析的过程就是发生在解析器(Parser)执行阶段。

词法分析就是将字符序列转换为标记(token)序列的过程。

所谓 token ,就是源文件中不可再进一步分割的一串字符,类似于英语中单词,或汉语中的词。

一般来说程序语言中的 token 有:常数(整数、小数、字符、字符串等),操作符(算术操作符、比较操作符、逻辑操作符),分隔符(逗号、分号、括号等),保留字,标识符(变量名、函数名、类名等)等。

比如下面这段代码:

const 公众号 = ‘code秘密花园’;

经过词法分析后,会被转换为下面这些 token

  • const(保留字)

  • 公众号(变量名)

  • =(赋值操操作算符)

  • 'code秘密花园'(字符串常数)

语法分析 将这些 token 根据语法规则转换为 AST

{

“type”: “Program”,

“start”: 0,

“end”: 23,

“body”: [

{

“type”: “VariableDeclaration”,

“start”: 0,

“end”: 23,

“declarations”: [

{

“type”: “VariableDeclarator”,

“start”: 6,

“end”: 22,

“id”: {

“type”: “Identifier”,

“start”: 6,

“end”: 9,

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

总结一下

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

能**

linux

前端资料汇总

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值