• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

WebKit渲染引擎特性,以Chrome V8为例

武飞扬头像
红帽小生
帮助1

引言

上一期(传送门)我们讲了浏览器架构的大致内容。本期以WebKit为列,进行简单介绍,以便让你对渲染引擎有一个更多的理解。WebKit由多个重要模块组成,通过下图我们可以对WebKit有个整体的了解:
学新通
WebKit是一个页面渲染以及逻辑处理引擎,前端工程师把HTML、JavaScript、CSS这“三驾马车”作为输入,经过WebKit的处理,就输出成了我们能看到以及操作的Web页面。

从上图我们可以看出来,WebKit由图中框住的四个部分组成。而其中最主要的就是WebCore和JSCore(或者是其它JS引擎)。除此之外,WebKit Embedding API是负责浏览器UI与WebKit进行交互的部分,而WebKit Ports则是让Webkit更加方便的移植到各个操作系统上,提供的一些调用Native Library的接口,比如在渲染层面,iOS系统中,Safari是交给CoreGraphics处理,而Android系统中,Webkit则是交给Skia。

WebKit的渲染流程

首先浏览器通过URL定位到了一堆由HTML、CSS、JS组成的资源文件,通过加载器把资源文件给WebCore。之后HTML Parser会把HTML解析成DOM树,CSS Parser会把CSS解析成CSSOM树。最后把这两棵树合并,生成最终需要的渲染树,再经过布局,与具体WebKit Ports的渲染接口,把渲染树渲染输出到屏幕上,成为了最终呈现在用户面前的Web页面。
学新通
我们再逐个分析:

  • 网络:用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现,负责网络通信和安全。
  • JavaScript 解释器:用于解析和执行 JavaScript 代码,执行结果将传递给渲染引擎来展示。
  • 用户界面后端:用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
  • 数据存储:这是持久层,浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整而轻便的浏览器内数据库。

求同存异的浏览器架构

下面列出了部分浏览器的架构图,也许有些架构已经改变,有兴趣可以简单参考看看,除了IE之外,大体上各浏览器的整体架构都是类似的。

Mosaic架构:
学新通
Firefox架构:
学新通
Chrome架构:
学新通
Safari架构:
学新通
IE架构:
学新通

Chrome V8

V8是依托Chrome发展起来的,不局限于浏览器内核。发展至今V8应用于很多场景,例如流行的nodejs,weex,快应用,早期的RN。V8曾经历过一次比较大的架构调整,主要变化在于“从字节码的放弃到真香”。

V8 的早期架构

V8引擎目的就是要在速度和内存回收上进行革命。JavaScriptCore的架构是采用生成字节码的方式,然后执行字节码。Google觉得JavaScriptCore这套架构不行,生成字节码会浪费时间,不如直接生成机器码快。所以V8在前期的架构设计上是非常激进的,采用了直接编译成机器码的方式。后期的实践证明Google的这套架构速度是有改善,但是同时也造成了内存消耗问题。
学新通
早期的V8有Full-Codegen和Crankshaft两个编译器。V8 首先用 Full-Codegen把所有的代码都编译一次,生成对应的机器码。JS在执行的过程中,V8内置的Profiler筛选出热点函数并且记录参数的反馈类型,然后交给 Crankshaft 来进行优化。所以Full-Codegen本质上是生成的是未优化的机器码,而Crankshaft生成的是优化过的机器码。

随着网页的复杂化,V8也渐渐的暴露出了自己架构上的缺陷:Full-Codegen 编译直接生成机器码,导致内存占用大、编译时间长、启动速度慢;此外,Crankshaft 无法优化try,catch和finally等关键字划分的代码块;且新加语法支持,需要为此编写适配不同的CPU架构代码。

V8 的现有架构

为了解决上述缺点,V8借鉴JavaScriptCore的架构,生成字节码。V8采用生成字节码的方式后,整体流程如下图:
学新通

现在的 V8 是一个非常复杂的项目,有超过 100 万行 C 代码。它由许多子模块构成,其中有 4 个模块很重要。

  1. Parser:负责将 JavaScript 源码转换为 Abstract Syntax Tree (AST)。确切的说,在“Parser”将 JavaScript 源码转换为 AST前,还有一个叫”Scanner“的过程,具体流程如下:
    学新通
  2. Ignition:interpreter,即解释器,负责将 AST 转换为 Bytecode,解释执行 Bytecode;同时收集 TurboFan 优化编译所需的信息,比如函数参数的类型;解释器执行时主要有四个模块,内存中的字节码、寄存器、栈、堆。Ignition的原始动机是减少移动设备上的内存消耗。在Ignition之前,V8的Full-codegen基线编译器生成的代码通常占据Chrome整体JavaScript堆的近三分之一。这为Web应用程序的实际数据留下了更少的空间。Ignition的字节码可以直接用TurboFan生成优化的机器代码,而不必像Crankshaft那样从源代码重新编译。Ignition的字节码在V8中提供了更清晰且更不容易出错的基线执行模型,简化了去优化机制,这是V8 自适应优化的关键特性。最后,由于生成字节码比生成Full-codegen的基线编译代码更快,因此激活Ignition通常会改善脚本启动时间,从而改善网页加载。
  3. TurboFan:compiler,即优化编译器,利用 Ignition 所收集的类型信息,将 Bytecode 转换为优化的汇编代码;TurboFan项目最初于2013年底启动,旨在解决Crankshaft的缺点。Crankshaft只能优化JavaScript语言的子集。例如,它不是设计用于使用结构化异常处理优化JavaScript代码,即由JavaScript的try,catch和finally关键字划分的代码块。很难在Crankshaft中添加对新语言功能的支持,因为这些功能几乎总是需要为九个支持的平台编写特定于体系结构的代码。
  4. Orinoco:garbage collector,垃圾回收模块,负责将程序不再需要的内存空间回收。

采用新的Ignition TurboFan架构后,比Full-codegen Crankshaft架构内存降低一半多,且70%左右的网页速度得到了提升。

在运行 C、C 以及 Java 等程序之前,需要进行编译,不能直接执行源码;但对于 JavaScript 来说,我们可以直接执行源码(比如:node test.js),它是在运行的时候先编译再执行,这种方式被称为即时编译(Just-in-time compilation),简称为 JIT。因此,V8 也属于 JIT 编译器。

JavaScriptCore

V8未诞生之前,早期主流的JavaScript引擎是JavaScriptCore引擎。JavaScriptCore(以下简称JSCore)主要服务于Webkit浏览器内核,他们都是由苹果公司开发并开源出来。JSCore是WebKit默认内嵌的JS引擎,之所以说是默认内嵌,是因为很多基于WebKit分支开发的浏览器引擎都开发了自家的JS引擎,其中最出名的就是前文提到的Chrome的V8。这些「JS引擎的使命都是解释执行JS脚本」。而在渲染流程上,JS和DOM树之间存在着互相关联,这是因为浏览器中的JS脚本最主要的功能就是操作DOM树,并与之交互。我们可以通过下图看下它的工作流程:
学新通

JavaScriptCore主要模块:Lexer 词法分析器,将脚本源码分解成一系列的Token;Parser 语法分析器,处理Token并生成相应的语法树;LLInt 低级解释器,执行Parser生成的二进制代码;Baseline JIT 基线JIT(just in time 实时编译);DFG 低延迟优化的JIT;FTL 高通量优化的JIT。

可以看到,相比静态编译语言生成语法树之后,还需要进行链接,装载生成可执行文件等操作,解释型语言在流程上要简化很多。这张流程图右边画框的部分就是JSCore的组成部分:Lexer(词法分析)、Parser(语法分析)、LLInt以及JIT(解释执行)的部分(之所以JIT的部分是用橙色标注,是因为并不是所有的JSCore中都有JIT部分)。

  • 「词法分析」很好理解,就是「把一段我们写的源代码分解成Token序列的过程」,这一过程也叫「分词」。在JSCore,词法分析是由Lexer来完成(有的编译器或者解释器把分词叫做Scanner,比如Chrome v8)。

  • 跟人类语言一样,我们讲话的时候其实是按照约定俗成,交流习惯按照一定的语法讲出一个又一个词语。那类比到计算机语言,计算机要理解一门计算机语言,也要理解一个语句的语法。「Parser会把Lexer分析之后生成的token序列进行语法分析,并生成对应的一棵抽象语法树(AST)」。之后,ByteCodeGenerator会根据AST来生成JSCore的字节码,完成整个「语法解析」步骤。

  • JS源代码经过了词法分析和语法分析这两个步骤,转成了字节码,其实就是经过任何一门程序语言必经的步骤–编译。但是不同于我们编译运行OC代码,JS编译结束之后,并不会生成存放在内存或者硬盘之中的目标代码或可执行文件。生成的指令字节码,会被立即被JSCore这台虚拟机进行逐行「解释执行」。运行指令字节码(ByteCode)是JS引擎中很核心的部分,各家JS引擎的优化也主要集中于此。

严格的讲,语言本身并不存在编译型或者是解释型,因为语言只是一些抽象的定义与约束,并不要求具体的实现,执行方式。这里讲JS是一门“解释型语言”只是JS一般是被JS引擎动态解释执行,而并不是语言本身的属性。

浏览器与JavaScript

还是以Chrome V8为例,简单阐述浏览器与JavaScript的关系。在 V8 出现之前,所有的 JavaScript 虚拟机所采用的都是解释执行的方式,这是 JavaScript 执行速度过慢的一个主要原因。而 V8 率先引入了即时编译(JIT)的双轮驱动的设计(混合使用编译器和解释器的技术),这是一种权衡策略,混合编译执行和解释执行这两种手段,给 JavaScript 的执行速度带来了极大的提升。V8 出现之后,各大厂商也都在自己的 JavaScript 虚拟机中引入了 JIT 机制,所以目前市面上 JavaScript 虚拟机都有着类似的架构。另外,V8 也是早于其他虚拟机引入了惰性编译、内联缓存、隐藏类等机制,进一步优化了 JavaScript 代码的编译执行效率。

V8 执行一段 JavaScript 的流程如下图所示:
学新通
结合上文介绍的Chrome V8 架构,聚焦到JavaScript上,浏览器拿到JavaScript源码,Parser,Ignition 以及 TurboFan 可以将 JS 源码编译为汇编代码,其流程图如下:
学新通
简单地说,Parser 将 JS 源码转换为 AST,然后 Ignition 将 AST 转换为 Bytecode,最后 TurboFan 将 Bytecode 转换为经过优化的 Machine Code(实际上是汇编代码)。

  • 如果函数没有被调用,则 V8 不会去编译它。

  • 如果函数只被调用 1 次,则 Ignition 将其编译 Bytecode 就直接解释执行了。TurboFan 不会进行优化编译,因为它需要 Ignition 收集函数执行时的类型信息。这就要求函数至少需要执行 1 次,TurboFan 才有可能进行优化编译。

  • 如果函数被调用多次,则它有可能会被识别为「热点函数」,且 Ignition 收集的类型信息证明可以进行优化编译的话,这时 TurboFan 则会将 Bytecode 编译为 Optimized Machine Code(已优化的机器码),以提高代码的执行性能。

图片中的红色虚线是逆向的,也就是说 Optimized Machine Code 会被还原为 Bytecode,这个过程叫做 「Deoptimization」。这是因为 Ignition 收集的信息可能是错误的,比如 add 函数的参数之前是整数,后来又变成了字符串。生成的 Optimized Machine Code 已经假定 add 函数的参数是整数,那当然是错误的,于是需要进行 Deoptimization。

function add(x, y) {
  return x   y;
}

add(1, 2);
add('1', '2');

解释执行和编译执行都有各自的优缺点,解释执行启动速度快,但是执行时速度慢,而编译执行启动速度慢,但是执行速度快。为了充分地利用解释执行和编译执行的优点,规避其缺点,V8 采用了一种权衡策略,在启动过程中采用了解释执行的策略,但是如果某段代码的执行频率超过一个值,那么 V8 就会采用优化编译器将其编译成执行效率更加高效的机器代码。

V8 执行一段 JavaScript 代码所经历的主要流程包括:初始化基础环境;解析源码生成 AST 和作用域;依据 AST 和作用域生成字节码;解释执行字节码;监听热点代码;优化热点代码为二进制的机器代码;反优化生成的二进制机器代码。

Chrome V8 的事件机制

关于异步编程和消息队列,UI 线程提供一个消息队列,并将待执行的事件添加到消息队列中,然后 UI 线程会不断循环地从消息队列中取出事件、执行事件,通用 UI 线程宏观架构如下图所示:
学新通

WebView

WebView 是一种嵌入式浏览器,原生应用可以用它来展示网络内容。WebView 只是一个可视化的组件/控件/微件等,可以作为原生 app 的视觉部分。当你使用原生应用时,WebView 可能只是被隐藏在普通的原生 UI 元素中,你甚至注意不到它。

如果把浏览器想象成两部分,一部分是 UI(地址栏,导航栏按钮等),另一部分是把代码转换成视图的引擎。WebView 就是浏览器引擎部分,可以像插入 iframe 一样将 Webview 插入到原生应用中。

运行在你的 WebView 中的 JavaScript 有能力调用原生的系统 API。这意味着你不必受到 Web 代码通常必须遵守的传统浏览器安全沙箱的限制。下图解释了使用这种技术后的架构差异:
学新通
默认情况下,在 WebView 或 Web 浏览器中运行的任何 Web 代码都与应用的其余部分保持隔离。这样做是出于安全原因,主要是为降低恶意的 JavaScript 代码对系统造成的伤害。对于任意 Web 内容,这种安全级别很有意义,因为你永远不能完全信任加载的 Web 内容。但 WebView 的情况并非如此,对于 WebView 方案,开发人员通常可以完全控制加载的内容。恶意代码进入并在设备上造成混乱的可能性非常低。

这就是为什么对于 WebView,开发人员可以使用各种受支持的方式来覆盖默认的安全行为,并让 Web 代码和原生应用代码相互通信。这种沟通通常称为 bridge。你可以在上文的图片中看到 bridge 可视化为 Native Bridge 和 JavaScript Bridge 的一部分。

WebView 非常好,虽然它看起来像是完全特殊和独特的,但请记住,它们只不过是一个在应用中设置好位置和大小的、没有任何花哨 UI 的浏览器,这就是它的精髓。大多数情况下,除非您调用原生 API,否则您不必在 WebView 中专门测试您的 Web 应用程序。此外,您在 WebView 中看到的内容与您在浏览器中看到的内容相同,尤其是使用同一渲染引擎时:

  • 在 iOS 上,Web 渲染引擎始终是 WebKit,与 Safari 和 Chrome 相同。是的,你没看错。iOS 上的 Chrome 实际上使用了 WebKit。
  • 在 Android 上的渲染引擎通常是 Blink,与 Chrome 相同。
  • 在 Windows,Linux 和 macOS 上,由于这些是更宽松的桌面平台,因此在选择 WebView 风格和渲染引擎时会有很大的灵活性。你看到的流行渲染引擎将是 Blink(Chrome)和 Trident(Internet Explorer),但是没有一个引擎可以依赖。这完全取决于应用以及它正在使用的 WebView 引擎。

WebView 的应用

WebView 最常见的用途之一是显示链接的内容;广告仍然是原生应用最流行的赚钱方式之一,大多数广告是通过 WebView 提供的 Web 内容进行投放的

应用场景二:Hybrid Apps,混合应用程序很受欢迎有几个原因,最大的一个是提高开发人员的生产力。如果你有一个可以在浏览器中运行的响应式 Web 应用程序,那么让相同的应用程序在各种设备上与混合应用程序一起运行是相当简单的;当你对 Web 应用进行更新时,所有使用它的设备都可以立即使用该更改,因为内容来自一个集中的服务器,而如果是纯原生应用,部署和更新时,你将不得不经历针对每个平台的构建、审核;

应用场景三:原生应用扩展,如 Microsoft Office 中类似维基百科这样的基于网络的扩展就是通过一个 WebView 实现的。

Headless browser

无头浏览器是一种未配置图形用户界面 (GUI) 的 Web 浏览器,通常通过命令行或网络通信来执行。它主要由软件测试工程师使用,没有 GUI 的浏览器执行速度更快,因为它们不必绘制视觉内容。无头浏览器的最大好处之一是它们能够在没有 GUI 支持的服务器上运行。

Headless 浏览器对于测试网页特别有用,因为它们能够像浏览器一样呈现和理解超文本标记语言,包括页面布局、颜色、字体选择以及JavaScript和AJAX的执行等样式元素,这些元素在使用其他测试方法时通常是不可用的。

Headless 浏览器有两个主要可交付成果:无头库,它允许嵌入应用程序控制浏览器并与网页交互;一个无头外壳,它是一个示例应用程序,用于执行无头 API 的各种功能。

学新通
Puppeteer 是一个 Node 库,他提供了一组用来操纵 Chrome 的 API, 通俗来说就是一个 headless chrome 浏览器 (当然你也可以配置成有 UI 的,默认是没有的)。既然是浏览器,那么我们手工可以在浏览器上做的事情 Puppeteer 都能胜任, 另外,Puppeteer 翻译成中文是”木偶”意思,所以听名字就知道,操纵起来很方便,你可以很方便的操纵她去实现:
1) 生成网页截图或者 PDF
2) 高级爬虫,可以爬取大量异步渲染内容的网页
3) 实现 UI 自动化测试,模拟键盘输入、表单自动提交、点击、登录网页等
4) 捕获站点的时间线,以便追踪你的网站,帮助分析网站性能问题
5) 模拟不同的设备

Puppeteer 跟 webdriver 以及 PhantomJS 最大的 的不同就是它是站在用户浏览的角度,而 webdriver 和 PhantomJS 最初设计就是用来做自动化测试的,所以它是站在机器浏览的角度来设计的,所以它们 使用的是不同的设计哲学。

Electron

Electron(原名为Atom Shell)是 GitHub 开发的一个开源框架。它通过使用 Node.js(作为后端)和Chromium 的渲染引擎(作为前端)完成跨平台的桌面 GUI 应用程序的开发。现已被多个开源 Web 应用程序用于前端与后端的开发,著名项目包括 GitHub 的 Atom 和微软的 Visual Studio Code。

Electron Architecture 由多个 Render Process 和一个 Main 进程组成。Main Process 启动Render Process,它们之间的通信是通过IPC [Inter Process Communication],如下图所示。
学新通
IDE VS Code 就是基于 Electron 进行开发的。点击 VSCode 帮助 下的 切换开发人员工具,即可打开浏览器控制台面板。

参考链接:

全球浏览器市场份额统计:
https://gs.statcounter.com

w3counter统计数据:
https://www.w3counter.com/globalstats.php

深入剖析 JavaScriptCore:
https://ming1016.github.io/2018/04/21/deeply-analyse-javascriptcore

深入理解JSCore:
https://tech.meituan.com/2018/08/23/deep-understanding-of-jscore.html

JavaScript 引擎 V8 执行流程概述:
http://blog.itpub.net/69912579/viewspace-2668277/

Headless Chrome architecture:
https://www.cnblogs.com/bigben0123/p/13880254.html

Electron:
https://delftswa.gitbooks.io/desosa2018/content/electron/chapter.html

无头浏览器:
https://www.zhihu.com/question/314668782/answer/625880826

WebView:
https://www.jianshu.com/p/3e0136c9e748/

新式网络浏览器:
https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/

Web 浏览器相关的一些概念:
https://keqingrong.cn/blog/2019-11-24-concepts-related-to-web-browsers/
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfibeg
系列文章
更多 icon
同类精品
更多 icon
继续加载