微信小程序原理从双线程架构到快速渲染

802 阅读4分钟

小程序现在已然成为了一个主流应用,各大公司也基本都有自己的小程序,微信小程序是其中重要的组成部分,其背后原理值得开发者关注。本文主要分析的是微信小程序渲染层部分原理,通过层层递进的方式讲述从双线程架构到快速渲染。

双线程架构

双线程的优势

微信小程序中渲染层和逻辑层是分开的,分别使用两个线程来处理。渲染层使用的是webview,逻辑层在不同的端会运行在不同环境中,ios中javascript代码运行在JSCode中,android中通过X5内核来解析,开发者工具中运行在nwjs(chrome内核)。

对比一般的公众号h5单线程,微信小程序的双线程有如下主要优点:
1、javascript脚本执行不会抢占ui渲染资源,使整体页面渲染更快;
2、由于渲染层使用的是webview,体验上更接近原生,比公众号h5网页体验要好;
vue3 (5).png 3、安全管控,独立的沙箱环境运行javascript逻辑代码,避免了浏览器的开放api操作dom、跳转页面等,更加安全

小程序里的双线程

小程序视图层和逻辑层是分开的,视图层会有多个页面,会存在多个webview渲染,Native层连接视图和逻辑层并对网络请求、微信能力等做统一处理。视图层发出事件后,Native决定谁来处理,一般事件会传递到逻辑层这边,逻辑层处理完后把Data通过JSBridge给到View层,视图层渲染更新页面。

image.png

渲染线程

渲染层webview

打开微信开发者工具,点击微信开发者工具->调试->调试微信开发者工具

截屏2023-01-14 下午11.31.18.png

然后会看到一个调试页面,因开发者工具是一个模拟微信的环境,这个调试页面包含了左侧微信区和开发者工具自身的调试功能。在Console下输入getElementsByTagName方法会看到有几个webview,这里面就包含当前渲染层webview,逻辑层webview(开发者工具模拟双线程)以及开发者工具的webview。

document.getElementsByTagName('webview')

image.png

使用开发者工具左上角的捕获按钮,以及Elements查找当前渲染层webview

image.png

文件分析

在控制台Console下执行showDevTools方法打开当前渲染层webview,查看其内容

// 具体是第几个webview,根据具体项目确定
document.getElementsByTagName('webview')[0].showDevTools(true, null)

image.png

可以看到html文档结构和通常的一样包含style、script标签以及页面内容body。
style标签里包含的是整个页面用到的样式代码
script标签中引入了相关js文件,有小程序的配置文件wxconfig.js,设备相关信息deviceinfo.js,渲染底层基础库WAWebview.js,视频处理相关工具hls.js等。以wxconfig.js为例,控制台打开__wxConfig查看配置

image.png

body中是一个类似于webComponent的组件系统Exparser,使用它来维护整个页面的dom结构、属性以及事件绑定等。

快速渲染

渲染模板

小程序渲染层是通过渲染模板pageframe渲染出来的,利用这种快速技术,通过渲染模板能像工厂一样快速生成业务渲染层所需要的基础版webview。接着来看下这个模板的结构,打开对应的webview的instanceframe.html

image.png

分析这个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)
}

渲染过程

从渲染模板到实际渲染,渲染主流程如图所示:

image.png

以渲染home页面为例:

<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层的原因,为了更好的性能。

image.png

写在最后

本篇写到这里就结束了,欢迎点赞+关注+评论支持一下,如果文中有错误或你有更好的见解,可以讨论交流~