技术标签: chrome 前端 webkit javascript
上一期(传送门)我们讲了浏览器架构的大致内容。本期以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。
首先浏览器通过URL定位到了一堆由HTML、CSS、JS组成的资源文件,通过加载器把资源文件给WebCore。之后HTML Parser会把HTML解析成DOM树,CSS Parser会把CSS解析成CSSOM树。最后把这两棵树合并,生成最终需要的渲染树,再经过布局,与具体WebKit Ports的渲染接口,把渲染树渲染输出到屏幕上,成为了最终呈现在用户面前的Web页面。
我们再逐个分析:
下面列出了部分浏览器的架构图,也许有些架构已经改变,有兴趣可以简单参考看看,除了IE之外,大体上各浏览器的整体架构都是类似的。
Mosaic架构:
Firefox架构:
Chrome架构:
Safari架构:
IE架构:
V8是依托Chrome发展起来的,不局限于浏览器内核。发展至今V8应用于很多场景,例如流行的nodejs,weex,快应用,早期的RN。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借鉴JavaScriptCore的架构,生成字节码。V8采用生成字节码的方式后,整体流程如下图:
现在的 V8 是一个非常复杂的项目,有超过 100 万行 C++代码。它由许多子模块构成,其中有 4 个模块很重要。
采用新的Ignition+TurboFan架构后,比Full-codegen+Crankshaft架构内存降低一半多,且70%左右的网页速度得到了提升。
在运行 C、C++以及 Java 等程序之前,需要进行编译,不能直接执行源码;但对于 JavaScript 来说,我们可以直接执行源码(比如:node test.js),它是在运行的时候先编译再执行,这种方式被称为即时编译(Just-in-time compilation),简称为 JIT。因此,V8 也属于 JIT 编译器。
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引擎动态解释执行,而并不是语言本身的属性。
还是以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 和作用域生成字节码;解释执行字节码;监听热点代码;优化热点代码为二进制的机器代码;反优化生成的二进制机器代码。
关于异步编程和消息队列,UI 线程提供一个消息队列,并将待执行的事件添加到消息队列中,然后 UI 线程会不断循环地从消息队列中取出事件、执行事件,通用 UI 线程宏观架构如下图所示:
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 中看到的内容与您在浏览器中看到的内容相同,尤其是使用同一渲染引擎时:
WebView 最常见的用途之一是显示链接的内容;广告仍然是原生应用最流行的赚钱方式之一,大多数广告是通过 WebView 提供的 Web 内容进行投放的;
应用场景二:Hybrid Apps,混合应用程序很受欢迎有几个原因,最大的一个是提高开发人员的生产力。如果你有一个可以在浏览器中运行的响应式 Web 应用程序,那么让相同的应用程序在各种设备上与混合应用程序一起运行是相当简单的;当你对 Web 应用进行更新时,所有使用它的设备都可以立即使用该更改,因为内容来自一个集中的服务器,而如果是纯原生应用,部署和更新时,你将不得不经历针对每个平台的构建、审核;
应用场景三:原生应用扩展,如 Microsoft Office 中类似维基百科这样的基于网络的扩展就是通过一个 WebView 实现的。
无头浏览器是一种未配置图形用户界面 (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(原名为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/
文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别
文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具
文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量
文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置
文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖
文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...
文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序
文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码
文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型
文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件
文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令
文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线