浏览器原理和v8执行过程

659 阅读6分钟

浏览器工作原理

认识浏览器的内核

  • 我们经常会说:不同的浏览器有不同的内核组成
    • Gecko: 早期被Netscape和Mozilla Firefox浏览器使用;
    • Trident: 微软开发,被ie4 - ie11浏览器使用,但是Edge浏览器已经转向Blink;
    • Webkit: 苹果基于KHTML开发、开源的,用于Safari、Chrome之前也在使用;
    • Blink: 是Webkit的一个分支,Google开发,目前应用于Chrome、Edge、Opera等;
    • 等等
  • 事实上,我们经常说的浏览器内核值得是浏览器的排版引擎:
    • 排版引擎,也称为浏览器引擎、页面渲染引擎或者样板引擎

浏览器渲染过程

  • 浏览器在拿到xxx.html后是如何渲染的呢?
    • 拿到index.html后浏览器会开辟一块栈内存,同时分配一个主线程“自上而下,自左而右”的去解析执行;
    • 在解析执行DOM结构时遇到link、img、video…浏览器会开启一个全新线程去加载资源,但不会执行。主线程继续向下执行;
      • 遇到 style 内嵌样式正常解析执行
      • @import 导入样式 (同步)不会开辟新线程去加载资源,而是主线程去获取资源并加载完毕才会继续向下继续解析执行DOM
    • 遇到script、外链js会去获取资源并执行,会阻塞DOM生成;优化:使用defer、async去异步获取资源,等dom渲染完执行;
      • 现代浏览器都有完善的代码扫描机制,如果遇到script需要同步加载执行时,同时会向下扫描代码,如果发现有一些异步获取资源代码,此时就开始请求资源了
      • 自我等待机制:因为在JS中可能会有操作元素样式的情况出现,所以哪怕异步请求的js资源加载完成了,执行js代码也会等到css加载并渲染完成后才会执行
      • defer 异步获取资源后,按照顺序去执行
      • async 异步获取资源后,执行是无序的,谁先加载回来谁先执行
      • DOMContentLoaded() : 当DOM结构加载完成就会触发
      • (function())(function(){})||(doucment).ready(function(){})
      • load() : 所有资源加载完成才会执行,包含图片等资源加载
    • 自上而下执行完毕后,会生成 DOM Tree
    • 主线程现在可以去执行加载回来的css资源了,执行完css会生成 CSSOM
    • 将 DOM Tree 和 CSSOM 结合生成 Render Tree(渲染树)
    • 浏览器通知GPU(显卡)开始按照Render Tree绘制图形到页面中

image.png

HTML Parser: 解析html生成DOM Tree
CSS Parser: 解析css生成CSSOM
Render Tree: 将DOM树和CSSOM树结合生成渲染树
Layout(回流): 根据生成的渲染树,计算它们在设备视口内的确切位置和大小,这个计算阶段就是回流(元素的大小或者位置变化)
Painting(重绘): 根据渲染树以及回流得到的几何信息,得到节点的绝对像素(元素样式的变化)
Display:将像素发送给GPU,展示在页面上
** 重绘不一定回流,回流一定触发重绘 **
  • 那么,JavaScript代码由谁来执行呢?
    • JavaScript引擎

认识JavaScript引擎

  • 为什么需要JavaScript引擎呢?
    • 高级的编程语言都是需要转成最终的机器指令来执行的;
    • 事实上我们编写的JavaScript无论你交给浏览器或者Node执行,最后都是需要被CPU执行的;
    • 但是CPU只认识自己的指令集,实际上是机器语言,才能被CPU所执行;
    • 所以我们需要JavaScript引擎帮助我们将JavaScript代码翻译成CPU指令来执行;
  • 比较常见的JavaScript引擎有哪些呢?
    • SpiderMonkey:第一款JavaScript引擎,由Brendan Eich开发(也就是JavaScript作者);
    • Chakra:微软开发,用于IT浏览器;
    • JavaScriptCore:WebKit中的JavaScript引擎,Apple公司开发;
    • V8:Google开发的强大JavaScript引擎,也帮助Chrome从众多浏览器中脱颖而出;
    • 等等

浏览器内核和JS引擎的关系

  • 这里我们先以WebKit为例,WebKit事实上由两部分组成的:
    • WebCore:负责HTML解析、布局、渲染等等相关的工作;
    • JavaScriptCore:解析、执行JavaScript代码;
  • 看到这里,学过小程序有没有感觉非常的熟悉呢?
    • 在小程序中编写的JavaScript代码就是被JSCore执行的;

image.png

v8执行过程

V8引擎的原理

  • 我们来看一下官方对v8引擎的定义
    • v8是使用c++编写的Google 开源高性能JavaScript和WebAssembly引擎,它用于Chrome和node.js等

    • 它实现ECMAscript和WebAssembly,并在window7或者更高版本,macOS 10.12+和使用x64、IA-32、ARM或者MIPS处理器的linux系统上运行

    • v8可以独立运行,也可以嵌入到任何c++应用程序中

v8执行过程.png

V8引擎的架构

  • V8引擎本身的源码非常复杂,大概有超过100w行C++代码,通过了解它的架构,我们可以知道它是如何对JavaScript执行的:
  • Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码;
    • 如果函数没有被调用,那么是不会被转换成AST的;
    • Parse的V8官方文档:v8.dev/blog/scanne…
  • Ignition是一个解释器,会将AST转换成ByteCode(字节码)
    • 同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算);
    • 如果函数只调用一次,Ignition会执行解释执行ByteCode;
    • Ignition的V8官方文档:v8.dev/blog/igniti…
  • TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码;
    • 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能;
    • 但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是 number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码;
    • TurboFan的V8官方文档:v8.dev/blog/turbof…

V8引擎的解析图(官方)

image.png

V8执行的细节

  • 那么我们的JavaScript源码是如何被解析(Parse过程)的呢?
  • Blink将源码交给V8引擎,Stream获取到源码并且进行编码转换;
  • Scanner会进行词法分析(lexical analysis),词法分析会将代码转换成tokens;
  • 接下来tokens会被转换成AST树,经过Parser和PreParser:
    • Parser就是直接将tokens转成AST树架构;
    • PreParser称之为预解析,为什么需要预解析呢?
      • 这是因为并不是所有的JavaScript代码,在一开始时就会被执行。那么对所有的JavaScript代码进行解析,必然会 影响网页的运行效率;
      • 所以V8引擎就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂 时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
      • 比如我们在一个函数outer内部定义了另外一个函数inner,那么inner函数就会进行预解析;
  • 生成AST树后,会被Ignition转成字节码(bytecode),之后的过程就是代码的执行过程