# V8引擎,javaScript

# 纲要

每天更新

  1. "V8是如何执行一段JavaScript代码的?",
  2. "V8是如何执行一段JavaScript代码的",
  3. "V8如何查找变量,怎样提升对象访问速度?",
  4. "JavaScript代码是按顺序执行的吗?",
  5. "V8是如何实现对象继承的?",
  6. "V8是怎么实现1+"2"的,为什么1.toString会报错?",
  7. "V8是如何实现闭包的 ? 闭包会造成"内存泄露"吗?",
  8. "宏任务和微任务 , setTimeout是如何实现的?",
  9. "编译器的前端技术:纯手工打造词法分析器 (一)",
  10. "编译器的前端技术:纯手工打造词法分析器 (进阶)",
  11. "(undesocre源码)别做无效阅读,如何从源码中挖掘价值?",
  12. "Callbacks - 什么是 '接口隔离' 如何理解?",
  13. "Deferred - 如何提高代码复用性?",
  14. "Access - 接口职责是否设计得越单一越好?",
  15. "数据缓存、队列操作 - "高内聚、松耦合"这样的代码设计你会了吗?",
  16. "(undesocre源码) 函数式编程的本质与入门实践",
  17. "架构设计 - 手把手带你将函数封装 改造 函数式接口",
  18. "iteratee - 函数式接口通用迭代器设计",
  19. "深度克隆 - JavaScript完整实现深度克隆对象",
  20. "模板引擎 - 轻量级、高性能的JavaScript模板引擎",
  21. "实战-针对业务的通用框架开发,如何做需求分析和设计?",
  22. "实战-自定义模块 | 接口规范",
  23. "实战-加载器架构分层设计",
  24. "实战-模块静态分析、管理、异步加载方案", "实战-短名称配置解决方案",
  25. "番外:Vuepress+GitHub 构建 '简而美'的API文档"

# V8是如何执行一段JavaScript代码的

# 1.1 主要核心流程分为两步 – 编译和执行

首先将 JavaScript代码 转换为 低级中间代码 或者 机器能够理解的机器代码 ;

执行转换后的代码并输出执行结果;

# 1.2 高级代码为什么需要先编译再执行?

# 1.2.1 CPU只能识别机器代码

机器代码由 CPU 执行,可以把 CPU 看成是一个非常小的运算机器,我们可以通过 二进制的指令 和 CPU 进行沟通;

比如:给 CPU 发出 ‘1000100111011000’ 的二进制指令,这条指令的意思是将一个寄存器中的数据移到另一个寄存器中,当处理器执行到这条指令的时候,便会按照指令的意思去实现相关的操作。

注意:CPU 只能识别二进制的指令,但是对开发者而言,二进制代码难以阅读和记忆,于是我们又将二进制指令集转换为人类可以识别和记忆的符号,这就是 汇编指令集。

# 1.2.2 CPU能直接识别汇编语言吗

答案是:不能! 如果使用了汇编语言编写了一个程序,那么就还需要一个汇编编译器,作用是将汇编代码编程成机器代码。

并且编写汇编语言 工作量巨大,因为:

  1. 不同的CPU有不同的指令集,需要为每种架构的CPU编写特定的汇编语言;
  2. 编写汇编语言,我们需要了解和处理器架构相关的硬件知识;

# 1.2.3 计算机如何执行高级语言

第一种:解释执行,需要先将输入的源代码通过 解析器 编译成中间代码 ,之后直接使用解释器解释执行中间代码,然后直接输出结果;

第二种:编译执行,采用这种方式也需要先将源代码转换为中间代码 ,然后我们的编译器再将中间代码编译成机器代码。通常编译成的机器代码是以二进制文件形式存储的,需要执行这段程序的时候直接执行二进制文件就可以了。

注意:无论是使用解释器进行解释执行,还是使用编译器进行编译后执行,最终源代码都需要被转换为对应平台的 本地机器指令 。

# 1.2.4 编译器和解释器的优缺点

编译器:

  • 优点:将源代码编译成可被CPU直接执行的机器指令,因为执行效率更高;
  • 缺点:跨平台支持不好,不同CPU有不同指令集,同一套源代码需要被编译成不同机器语言;

解释器:

  • 优点:更容易跨平台,解释器自身会将源代码转换为当前平台所需的机器语言;
  • 缺点:执行效率低一些,因为每句源码都要经过解释器解释为可执行的机器语言;

# 1.2.5 即时编译器

是一种平衡策略,平衡了编译执行和解释执行的优缺点,引入了 热点代码 机制

执行过程:

1、当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。

2、在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地机器指令之后,可以获取更高的执行效率。

3、当程序运行环境中内存资源限制较大,可以使用解释器执行节约内存,反之可以使用编译执行来提高效率。

比如Java程序:最初是通过解释器 解释执行 ,当Java虚拟机发现某个方法或者代码块运行特别频繁的时候,会标记其为 热点代码(Hot Spot Code)。JIT即时编译器会将这些热点代码编译成与本地机器相关的机器指令,进行各个层次的优化。

# 1.3 V8执行JS代码的具体流程

V8执行js代码流程.jpg

# 1.3.1 执行之前,准备所需的基础环境

在 V8 启动执行 JavaScript 之前,它还需要准备执行 JavaScript 时所需要的一些基础环境,这些基础环境包括了 堆空间、栈空间、全局执行上下文、全局作用域、消息循环系统、内置函数 等,这些内容都是在执行 JavaScript 过程中需要使用到的。

比如:

JavaScript 全局执行上下文就包含了执行过程中的全局信息,比如一些内置函数,全局变量等信息; 全局作用域包含了一些全局变量,在执行过程中的数据都需要存放在 内存 中; 由于 V8 采用了经典的堆和栈的管理内存管理模式,所以 V8 还需要初始化了内存中的堆和栈结构; 另外,要我们的 V8 系统活起来,还需要 初始化消息循环系统 ,消息循环系统包含了 消息驱动器 和 消息队列 ,它如同 V8 的心脏,不断接受消息并决策如何处理消息。

# 1.3.2 准备好基础环境,向V8提交要执行的JS代码

首先,V8 会接收到要执行的 JavaScript 源代码,不过这对 V8 来说只是一堆 字符串,V8 并不能直接理解这段字符串的含义,它需要 结构化 这段字符串;

# 1.3.3 结构化字符串(JS源代码)

结构化,是指信息经过分析后可 分解成多个互相关联的组成部分。各组成部分间有明确的层次结构,方便使用和维护,并有一定的操作规范;

# 1.3.4 生成AST、作用域

结构化之后,就生成了 抽象语法树(AST),AST 是便于 V8 理解的结构;在生成 AST 的同时,V8 还会 生成相关的作用域,作用域中存放相关变量;

# 1.3.5 生成字节码

有了 AST 和 作用域 之后,接下来就可以生成 字节码 了,字节码是介于 AST 和 机器代码 的中间代码。但是与特定类型的机器代码无关,解释器可以直接解释执行字节码 ,或者通过编译器将其编译为二进制的机器代码再执行;

# 1.3.6 解释器解释执行字节码

生成字节码之后,解释器就登场了,它会 按照顺序 解释执行字节码,并输出执行结果;

注意:关于热点代码。 在解释执行字节码的过程中,如果发现了某一段代码会被重复多次执行,那么监控器会将这段代码标记为 热点代码。当某段代码被标记为热点代码后,V8 就会将这段字节码丢给 优化编译器,优化编译器会在后台将字节码编译成二进制代码,然后再对编译后的二进制代码执行优化操作,优化后的二进制机器代码的执行效率会得到大幅提升。如果下面再执行到这段代码时,那么 V8 会优先选择优化之后的二进制代码,这样代码的执行速度就会大幅提升。

不过,和静态语言不同的是,JavaScript 是一种非常灵活的动态语言,对象的结构和属性是可以在运行时任意修改的,而经过优化编译器 优化过的代码只能针对某种固定的结构,一旦在执行过程中,对象的结构被动态修改了,那么优化之后的代码势必会变成无效的代码,这时候优化编译器就需要执行 反优化操作,经过反优化的代码,下次执行时就会回退到解释器解释执行。

Last Updated: 2020-6-8 22:39:44