小程序现在已然成为了一个主流应用,各大公司也基本都有自己的小程序,微信小程序是其中重要的组成部分,其背后原理值得开发者关注。本文主要分析的是微信小程序渲染层部分原理,通过层层递进的方式讲述从双线程架构到快速渲染。
双线程架构
双线程的优势
微信小程序中渲染层和逻辑层是分开的
,分别使用两个线程来处理。渲染层使用的是webview
,逻辑层在不同的端会运行在不同环境中,ios中javascript代码运行在JSCode中,android中通过X5内核来解析,开发者工具中运行在nwjs(chrome内核)。
对比一般的公众号h5单线程,微信小程序的双线程有如下主要优点:
1、javascript脚本执行不会抢占ui渲染资源
,使整体页面渲染更快;
2、由于渲染层使用的是webview,体验上更接近原生
,比公众号h5网页体验要好;
3、安全管控,独立的沙箱环境运行javascript逻辑代码,避免了浏览器的开放api操作dom、跳转页面等,更加安全
。
小程序里的双线程
小程序视图层和逻辑层是分开的,视图层会有多个页面,会存在多个webview渲染,Native层连接视图和逻辑层并对网络请求、微信能力等做统一处理
。视图层发出事件后,Native决定谁来处理,一般事件会传递到逻辑层这边,逻辑层处理完后把Data通过JSBridge给到View层,视图层渲染更新页面。
渲染线程
渲染层webview
打开微信开发者工具,点击微信开发者工具->调试->调试微信开发者工具
然后会看到一个调试页面,因开发者工具是一个模拟微信的环境,这个调试页面包含了左侧微信区和开发者工具自身的调试功能。在Console下输入getElementsByTagName方法
会看到有几个webview,这里面就包含当前渲染层webview,逻辑层webview(开发者工具模拟双线程)以及开发者工具的webview。
document.getElementsByTagName('webview')
使用开发者工具左上角的捕获按钮
,以及Elements
查找当前渲染层webview
文件分析
在控制台Console下执行showDevTools方法
打开当前渲染层webview,查看其内容
// 具体是第几个webview,根据具体项目确定
document.getElementsByTagName('webview')[0].showDevTools(true, null)
可以看到html文档结构和通常的一样包含style、script标签以及页面内容body。
style标签里包含的是整个页面用到的样式代码
;
script标签中引入了相关js文件
,有小程序的配置文件wxconfig.js,设备相关信息deviceinfo.js,渲染底层基础库WAWebview.js,视频处理相关工具hls.js等。以wxconfig.js为例,控制台打开__wxConfig查看配置
body中是一个类似于webComponent的组件系统Exparser
,使用它来维护整个页面的dom结构、属性以及事件绑定等。
快速渲染
渲染模板
小程序渲染层是通过渲染模板
pageframe渲染出来的,利用这种快速技术,通过渲染模板能像工厂一样快速生成业务渲染层所需要的基础版webview。接着来看下这个模板的结构,打开对应的webview的instanceframe.html
分析这个html文件,会发现除了引用wxconfig、WAWebview等js文件外,会有占位符,以及无内容的空body。注意到在script标签内在监听load以及document状态,并且通过alert在通知事件
,这里是利用的nw.js拦截alert,最终知道初始化完成。
// script标签内代码
document.addEventListener("generateFuncReady", function(){
setTimeout(function() {
Object.defineProperty(document.body, 'scrollTop', {
get() {
return document.documentElement.scrollTop
},
set(value) {
document.documentElement.scrollTop = value
},
configurable: true,
})
}, 100)
})
if (document.readyState === 'complete') { // 资源加载完成
alert("DOCUMENT_READY") // alert发送通知
} else {
const fn = () => {
alert("DOCUMENT_READY")
window.removeEventListener('load', fn)
}
window.addEventListener('load', fn)
}
渲染过程
从渲染模板到实际渲染,渲染主流程如图所示:
以渲染home页面为例:
- 首先小程序内
初始化一个模板webview
,设置其src为http://127.0.0:55595/__pageframe__/instanceframe.html ,此模板用于承载新页面; - 然后再使用wx.navigateTo等方法打开home新页面,会使用渲染模板,进入页面,利用nw.js把home编译后的代码注入,通过
history.pushState
修改webview的src为http://127.0.0.1:55595/__pageframe__/pages/home/index
<script>
history.pushState('', '', 'http://127.0.0.1:55595/__pageframe__/pages/home/index')
__wxConfig=Object.assign(__wxConfig || {}, {"window":{"backgroundColor":"#ffffff","backgroundTextStyle":"light","navigationBarBackgroundColor":"#fff","navigationBarTitleText":"Weixin","navigationBarTextStyle":"black","usingComponents":{}}})
eval('...')
...
</script>
- 最后
WAWebview.js把virtual dom生成真实的dom
,将其挂载到页面body上,整个渲染流程也就结束。
此时再次在控制台打开document.getElementsByTagName('webview'),会发现多了一个home页面的webview,增加了内存
,这也是为何小程序限制页面栈最多10层的原因,为了更好的性能。
写在最后
本篇写到这里就结束了,欢迎点赞+关注+评论支持一下,如果文中有错误或你有更好的见解,可以讨论交流~