![](https://img.haomeiwen.com/i14626058/1355e058601a4891.jpg)
最近项目重心转移到了小程序与web一体化上。由于太久没接触过小程序,回去看了一下文档,发现大有不同,功能更加强大。
随着文档一直看到了《小程序宿主环境》这一章节,读到“渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本”这句话,开始疯狂搜索自己的知识库,意识到自己对于js跨平台运行这一块基础非常薄弱,深挖到浏览器渲染原理、js引擎等等概念都很模糊。
在此复习一下并记录这块的知识。以下大部分来源于网络
浏览器引擎Webkit
![](https://img.haomeiwen.com/i14626058/c5cd9659090cac2b.jpg)
WebKit 所包含的 WebCore绘制引擎和 JSCore 引擎,均是从KDE的KHTML及KJS引擎衍生而来。它们都是自由软件,在GPL条约下授权,同时支持BSD系统的开发。所以Webkit也是自由软件,同时开放源代码。
WebCore 与平台无关,各个浏览器移植所共享。包含HTML解析,CSS解析,渲染,调试等部分。
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。
WebCore
![](https://img.haomeiwen.com/i14626058/c8743f7609b27bb3.jpg)
首先浏览器通过URL加载一堆由HTML、CSS、JS组成的资源文件,通过加载器(这个加载器的实现也很复杂,在此不多赘述)把资源文件给WebCore。之后HTML Parser 会把HTML解析成DOM树,CSS Parser会把CSS解析成CSSOM树。最后把这两棵Tree 合并,生成最终需要的渲染树,再经过布局,与具体WebKit Ports的渲染接口,把渲染树渲染输出到屏幕上,成为了最终呈现在用户面前的网页。
简单总结:
主要从网页的 URL 到构建完 DOM 树,接着 从 DOM 树到构建完 WebKit 的绘图上下文,从绘图上下文到生成最终的UI图像。
JavascriptCore
业界流行的动态化方案,如Facebook的RN,阿里的Weex 都采用了前端系的DSL方案,而它们在iOS系统上能够顺利的运行,都离不开一个背后的功臣:JavaScriptCore(以下简称JSCore),它建立起了OC和JS 两门语言之间沟通的桥梁。无论是这些流行的动态化方案,还是WebView Hybrid方案,亦或是之前广泛流行的JSPatch,JSCore都在其中发挥了举足轻重的作用。作为一名Android 开发工程师,如果想从事跨平台开发和类似RN框架实现,了解 JSCore已经逐渐成为了必备技能之一。
JavascriptCore是使用在ReactNative和iOS平台上的Javascript引擎。目前 JavaScript 引擎还有 Google 的 V8 ,Mozilla 的 SpiderMonkey。
当然Android是同个类的是JavascriptInterace.
JavaScriptCore是一个优化的VM。 JavaScriptCore由以下构建块组成:词法分析器,解析器,启动解释器(LLInt),基线JIT,低延迟优化JIT(DFG)和高并发优化JIT(FTL)。
Lexer:词法分析器,生成 tokens,大部分代码都在 parser/Lexer.cpp 里。
Parser:语法分析,基于 Lexer 的 tokens 生成语法树。手写了个 recusive descent parser 递归下降解析器,代码主要在 parser/Parser.cpp 里。
LLInt:Low Level Interpreter 执行 Parser 生成的 Byte code。代码在 llint/ 里,使用汇编,在 offlineasm/ 里,可以编译为 x86 和 ARMv7 的汇编和 C 代码。LLInt 希望达成除了词法和语法分析外零启动消耗,同时遵守用 JIT 在调用,堆栈和起存器的约定。
Baseline JIT:实时编译,性能不好用这个。在函数调用了 6 次,或者某段代码循环了大于100次会被触发。BaseLine JIT 的代码在 jit/ 里。BaseLine JIT 还对几乎所有堆的访问执行了复杂的多态内联高速缓存(Polymorphic inline caches)。多态内联缓存是 Smalltalk 社区优化动态分发的一个经典技术。
DFG JIT:低延迟优化 JIT,更差性能就用这个生成更优化的机器码来执行。在函数被调用了60次或者代码循环了1000次会触发。在 LLInt 和 Baseline JIT 中会收集一些包括最近参数,堆以及返回值中的数据等轻量级的性能信息,方便 DFG 进行类型判断。先获取类型信息可以减少大量的类型检查,推测失败 DFG 会取消优化,也叫 OSR exit。取消可以是同步的也可以是异步的。取消后会回到 Baseline JIT,退回一定次数会进行重新优化,收集更多统计信息,看情况再次调用 DFG。重新优化使用的是指数式回退策略应对一些怪异的代码。DFG 代码在 dfg/ 里。
FTL:高吞吐量优化 JIT,全称 Faster Than Light,DFG 高层优化配合 B3 底层优化。以前全称是 Fourth Tier LLVM 底层优化使用的是 LLVM。B3 对 LLVM 做了裁剪,对 JavaScriptCore 做了特性处理,B3 IR 的接口和 LLVM IR 很类似。B3 对 LLVM 的替换主要是考虑减少内存开销,LLVM 主要是针对编译器,编译器在这方面优化动力必然没有 JIT 需求高。B3 IR 将指针改成了更紧凑的整数来表示引用关系。不可变的常用的信息使用固定大小的结构来表示,其它的都放到另外的地方。紧凑的数据放到数组中,更多的数组更少的链表。这样形成的 IR 更省内存。Filip Pizlo 主导的这个改动,DFG JIT 也是他弄的,为了能够更多的减少内存上的开销,他利用在 DFG 里已经做的 InsertionSet 将 LLVM IR 里的 def-use 干掉了,大概思路是把单向链表里批量插入新 IR 节点先放到 InsertionSet 里,在下次遍历 IR 时再批量插入。Filip Pizlo 还把 DFG 里的 UpsilonValue 替代 LLVM SSA 组成部分。B3 后面会把 LLVM 的寄存器分配算法 Greedy 一直到 B3 中。
再来看看JsCore的核心类
JSContext 代表JS的执行环境,通过-evaluateScript:方法就可以执行JS代码
JSValue 封装了JS与OC中的对应的类型,以及调用JS的API等
JSExport 是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,才能调用。
![](https://img.haomeiwen.com/i14626058/a3bc873425b401b5.jpg)
在 Native 中开启多个线程来异步执行不同API ,也就意味着开发者可创建多个 JSVirtualMachine VM,同时相互隔离不影响,这样保证了并行地执行不同 JS 任务。
在一个 JSVirtualMachine 中还可以关联多个 JSContext,并通过 JSValue( 来和 Native 进行数据传递通信,同时可以通过 JSExport,将 Native 中遵守此解析的类的方法和属性转换为 JS 的接口供其调用。
总结
跨平台技术实现大体分两个部分,主要是页面渲染和js脚本的运行。
1.渲染一般走是各平台原有的渲染流程,比如小程序中就会使用webview来实现页面的渲染,而react native则会全部提交到native compnent然后走平台原来的渲染流程。
2.js脚本则运行依赖jscore作为ios端、V8作为安卓端的js引擎。
网友评论