浏览器原理-v8引擎-JS执行原理

142 阅读6分钟

前置知识点

  1. 编程语言发展史,分三个阶段
    • 机器语言:1000100111011000,机器指令
    • 汇编语言:mov ax bx等
    • 高级语言:c、c++、java、js、python
  2. 计算机转换执行流程:高级语言 -> 汇编语言 -> 机器语言
  3. 浏览器内核介绍
    • 浏览器内核是浏览器的排版引擎,也称浏览器引擎、页面渲染引擎、样板引擎
  4. 浏览器内核目前使用状况
    • Gecko: FireFox使用
    • Trident: 由微软开发,被IE4~IE11在使用,但后续Edge转向了Blink
    • Webkit: 苹果基于KHTML(是HTML网页排版引擎之一,由C++开发)开发,开源的。Safari在使用。Google Chrome之前使用
    • Blink: 是Webkit的一个分支,由Google开发,Google Chrome、Edge、Opera等在使用
  5. 浏览器内核组成,主要包括两个部分(拿Webkit举例)
    • WebCore: 负责HTML解析、布局、渲染等工作
    • JavaScriptCore: 解析、执行JS代码(又称JS引擎)

浏览器工作原理(渲染过程)

graph TD
静态资源 --> 输入服务器 --> index.html

  1. 遇到link标签(如CSS文件)进行解析
  2. 遇到script标签,下载js文件

渲染流程图:

image.png 首先浏览器先对HTML和CSS分别进行解析。

  • HTML通过HTML Parser构建DOM Tree,此时如果JS操作了DOM,则再次更新DOM Tree。
  • CSS通过CSS Parser进行解析,解析后形成Style Rules,也是树形结构,类似DOM Tree。
  • 完成上述解析后,DOM Tree和Style Rules进行关联操作后,生成Render Tree(呈现树)。Render Tree 是一个计算好样式并且与 HTML 对应的(包括哪些显示,哪些不显示)的 Tree。
  • 呈现器在创建完成并添加到呈现树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。布局就是从上到下从左到右;重排是根据当前浏览器窗口大小变化而重新计算,进行重排。
  • 呈现好以后,进行Painting绘制页面操作。
  • 最后进行显示。

v8引擎

JS引擎主要负责对JS代码进行解析。v8引擎是一款由Google开发的开源由C++编写的JS和WebAssembly引擎(浏览器下一代语言),用于Chrome和Node.js,可以独立运行,嵌入到任何C++应用程序。

原理流程图:

stateDiagram-v2
JavaScript源代码 --> Parse
Parse --> AST语法树

AST语法树 --> Ignition
Ignition --> bytecode字节码
Ignition --> TurboFan
bytecode字节码 --> TurboFan
bytecode字节码 --> 运行结果
TurboFan --> MachineCode优化机器码
MachineCode优化机器码 --> bytecode字节码
MachineCode优化机器码 --> 运行结果

流程描述:

  1. 首先JS源代码会被v8引擎的Parse模块进行解析,生成AST抽象语法树。

Parse: 模块会将JS代码转换成AST,因为解释器并不直接认识JS代码;

  • 如果函数没有被调用,那么是不会被转换成AST的

Parse的过程分为两步:如对const name = "why"进行解析

  • 词法分析:会解析为tokens数组,内部含有多个对象,解析内容如下
···tokens: [{ type: 'keyword', value: 'const' },{ type: 'idntifier', value: 'name'}]
  • 语法分析:生成AST抽象语法数(ts和vue都先解析为AST)

image.png

  1. Ignition是一个解释器,会将AST转化字节码。
  • 同时会收集TurboFan优化所需的信息(比如函数参数的类型信息,有了类型才能进行真实的运算)
  • 如果函数只调用一次,Ignition会解释执行ByteCode 为什么没有直接转成机器码?因为当前环境可能是不同的操作系统环境,字节码是跨平台的,所以可以在多个不同操作系统运行。
  1. 经过Ignition生成字节码以后,再生成机器码供计算机运行。
  2. TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码。
  • 如果一个函数被多次调用,就会被标记为热点函数,就会经过TurboFan转换成优化的机器码,提高代码执行性能。
  • 但是,机器码实际上也会被还原为字节码,因为如果后续执行函数过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型),之前优化的机器码并不能正确处理运算,就会逆向的转换成字节码。

JS源码的Parse过程是怎样的呢?

官方流程图:

image.png

v8引擎官网地址

  1. 首先浏览器内核Blink通过解析html,找到js代码,交给v8引擎。
  2. v8中的Stream获取到源码并且进行编码转换
  3. 转换后给到Scanner,Scanner会进行词法分析,转换成tokens。
  4. Scanner词法分析后会开始进行PreParser和Parser操作。
  • PreParser为预解析:
    • 因为并不是所有的js代码,一开始就会被执行。所以如果对所有的js代码进行解析,必然会影响网页的运行效率。
    • v8引擎实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,即只解析暂时需要的内容,而对函数的全量解析是在函数被调用时进行
    • 比如一个函数outer内部定义了另外一个函数inner,那么inner函数就会被预解析。即一个函数嵌套在另一个函数中,当进行解析过程中,只对被嵌套的函数名进行解析,不对函数内部进行解析
  • Parser为解析,直接将tokens转成AST抽象语法树。
  1. AST生成后传到Ignation解释器,为生成字节码。
  2. 字节码生成后,再生成机器码后,被执行

JS代码如何被执行?

  1. 代码首先被解析后,v8引擎内部会帮助我们创建一个对象(GlobalObject,简称GO)。也就是会在代码执行之前,在堆内存中创建一个全局对象,即GO。
    • 该对象所有的作用域(scope)都可以访问
    • 里面会包含Data、Array、String、Number、setTimeout、setInterval等等
    • 其中还有一个window属性指向自己
      伪代码:
      const GlobalObject = { 如Math,String,Date,Number,setTimeOut,setInterval,window:this... }
      同时,GO会把当前解析到的JS文件里面的变量也存在GO中,如伪代码:
      var orange; console.log(orange);// 在v8引擎解析js代码时,会把orange变量存放在GO中,{ orange: undefined } orange = 'ss' 当一个变量还没有执行赋值,就进行打印,返回的是undefined,因为当前变量在GO里面了,这个过程叫作用域提升(后续文章会再做剖析)
  2. js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),用于执行代码的调用栈。
    • ECS会部内容(全局代码块)
    • 全局代码块为了方便自己被执行,会构建一个Global Execution Context(GEC)
    • GEC会被放入ECS中执行
    • 即ECS中含有了:GEC,GO。随后开始自上而下执行代码。

结语

以上内容对浏览器大致原理,v8引擎原理以及JS执行原理(过程)做了描述。希望本文可以让你对以上三个内容的理解有所帮助。
如果有不对的地方,同时也欢迎大家指正。