Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

小程序原理 #38

Open
Yuanfang-fe opened this issue May 12, 2022 · 0 comments
Open

小程序原理 #38

Yuanfang-fe opened this issue May 12, 2022 · 0 comments

Comments

@Yuanfang-fe
Copy link
Owner

Yuanfang-fe commented May 12, 2022

小程序与普通网页的区别

  • 渲染线程和脚本线程。普通网页开发渲染线程和脚本线程是互斥的,脚本的运行会阻塞渲染线程,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。
  • Dom操作不同。普通网页开发可以使用浏览器提供的DOM API,进行DOM操作,小程序的逻辑层和渲染层是分开的,逻辑层运行在JSCore中,没有完整的浏览器对象,因而缺少相关的DOM API 和BOM API。
  • 执行环境不同。普通网页开发是对面的各种各样的浏览器,而小程序面对的是iOS、安卓和开发工具。
    image

小程序的架构

为了能够兼顾体验好和在线版本更新,小程序采用了客户端原生技术与web技术结合的混合技术(简称Hybrid技术)来渲染,这么做有以下好处:

  • 扩展 web 的能力,比如像输入框组件(input,textarea)有更好地控制键盘的能力
  • 体验更好,同时也减轻了WebView的渲染工作
  • 绕过setData、数据通信和重渲染流程,使渲染性能更好
  • 用客户端原生组件,可以提供更好的性能
  • 对前端开发者友好,学习成本低
  • 版本更新方便,只需要微信审核通过即可发布生产

架构图解

小程序的渲染层和逻辑层分别由2个线程管理:视图层的界面使用了WebView进行渲染,逻辑层采用JsCore线程运行JS脚本。

小程序架构图解 (1)

那为什么要这样设计呢,前面也提到了管控和安全,为了解决这些问题,我们需要阻止开发者使用一些API,例如浏览器的window对象,跳转页面、操作DOM、动态执行脚本的开放性接口。

逻辑层只提供纯JavaScript的解释执行环境,没有任何浏览器相关接口。

小程序双线程模型各模块功能:

  • 逻辑层:创建一个单独的线程去执行JavaScript,在这里执行的都是有小程序业务逻辑的代码,负责逻辑处理、数据请求、接口调用等

  • 视图层:界面渲染相关的任务都在WebView线程里执行,通过逻辑层代码去控制渲染哪些界面。一个小程序存在多个界面,所以视图层存在多个WebView线程

  • JSBridge起到架起上层开发与Native(系统层)的桥梁,使得小程序可通过API使用原生的功能,且部分组件为原生组件实现,从而有良好体验

双线程通信

把开发者的JS逻辑代码放到单独的线程去运行,但在WebView线程里,开发者没法直接操作DOM。

那如何实现动态更新页面呢?

如上图所示,逻辑层和视图层的通信会由微信客户端(下文中会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发。

所以 DOM 更新也是可以通过数据通信来实现的,Virtual DOM流程大致是:用JS对象模拟DOM树 => 比较两颗虚拟DOM树的差异 => 把差异应用到真正的DOM树上。

如图:

1. 在渲染层把WXML转化为对应的JS对象。
2. 在逻辑层发生数据变更的时候,通过宿主环境提供的setData方法把数据从逻辑层传递到Native,再转发到渲染层。
3. 经过对比前后差异,把差异应用在原来的DOM树上,更新界面。

我们通过把WXML转化为数据,通过Native进行转发,来实现逻辑层和渲染层的交互和通信。

小程序的能力需要Native来支撑,而这离不开小程序基础库

小程序基础库

小程序的基础库可以被注入到视图层和逻辑层运行,主要用于以下几个方面:

- 在视图层,提供各类组建页面的元素
- 在逻辑层,提供各类API来处理各种逻辑
- 处理数据绑定、组件系统、事件系统、通信系统等一系列框架逻辑
- 自定义组件和插件

所以我们可以看到,小程序的基础库主要包括:

  • 提供 VD 渲染机制相关基础代码。(Exparser 框架)
  • 提供封装后的内置组件。
  • 提供逻辑层的 API。
  • 提供其他补充能力(自定义组件和插件等)的基础代码。

小程序的基础库不会被打包在某个小程序的代码包里边,他会被提前内置在微信客户端。

这样做的好处:

  • 降低业务小程序的代码包体积
  • 可以单独修复基础库中的bug,无需修改到业务小程序的代码包

Exparser 框架

Exparser 是微信小程序的组件组织框架,内置在小程序基础库中,为小程序的各种组件提供基础的支持。小程序内的所有组件,包括内置组件和自定义组件,都由Exparser组织管理。

Exparser的主要特点包括以下几点:

  • 基于Shadow DOM模型:模型上与WebComponents的ShadowDOM高度相似,但不依赖浏览器的原生支持,也没有其他依赖库;实现时,还针对性地增加了其他API以支持小程序组件编程。
  • 可在纯 JS 环境中运行:这意味着逻辑层也具有一定的组件树组织能力。
  • 高效轻量:性能表现好,在组件实例极多的环境下表现尤其优异,同时代码尺寸也较小。

基于这个框架,内置了一套组件,以涵盖小程序的基础功能,便于开发者快速搭建出任何界面。同时也提供了自定义组件的能力,开发者可以自行扩展更多的组件,以实现代码复用。

内置组件

小程序基于Exparser框架,内置了一套组件,提供了视图容器类、表单类、导航类、媒体类、开放类等几十种组件

内置组件在小程序框架里的定义是:在小程序架构里无法实现或者实现不好某类功能,使用组件内置到小程序框架里。
常见组件有:

  • 开放类组件:如open-data组件提供展示群名称、户信息等微信体系下的隐私信息,有 button 组件里 open-type 属性所提供分享、跳转 App 等敏感操作的能力
  • 视图容器类组件:如 movable-view 这种因双线程模型导致手势识别不好实现的组件(在双线程模型中,触摸事件从渲染层发出,派发到逻辑层,这中间是有一定的延时而导致视图跟随手指运动这类交互变得有些卡顿)

###API
宿主环境提供了丰富的API,可以很方便的调起微信提供的能力。
小程序提供的API按照功能主要分为几大类:网络、媒体、文件、数据缓存、位置、设备、界面、界面节点信息还有一些特殊的开放接口。

需要注意API调用大多数都是异步的。

###自定义组件
自定义组件是开发者可以自行扩充的组件。开发者可以将常用的节点树结构提取成自定义组件,实现代码复用。

在使用自定义组件的小程序页面中,Exparser 将接管所有的自定义组件注册与实例化。以 Component 为例:

  • 在小程序启动时,构造器会将开发者设置的 properties、data、methods 等定义段,写入 Exparser 的组件注册表中。
  • 这个组件在被其它组件引用时,就可以根据这些注册信息来创建自定义组件的实例。

Page 构造器的大体运行流程与之相仿,只是参数形式不一样。这样每个页面就有一个与之对应的组件,称为“页面根组件”。

在初始化页面时,Exparser 会创建出页面根组件的一个实例,用到的其他组件也会响应创建组件实例(这是一个递归的过程)。

插件

插件是对一组 js 接口、自定义组件或页面的封装,用于嵌入到小程序中使用。

插件不能独立运行,必须嵌入在其他小程序中才能被用户使用;而第三方小程序在使用插件时,也无法看到插件的代码。因此,插件适合用来封装自己的功能或服务,提供给第三方小程序进行展示和使用。

插件开发者可以像开发小程序一样编写一个插件并上传代码,在插件发布之后,其他小程序方可调用。小程序平台会托管插件代码,其他小程序调用时,上传的插件代码会随小程序一起下载运行。

运行机制

小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台状态的小程序切换到前台,这个过程就是热启动;冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。

image

挂起

小程序进入「后台」状态一段时间后(目前是 5 秒),微信会停止小程序 JS 线程的执行,小程序进入「挂起」状态。此时小程序的内存状态会被保留,但开发者代码执行会停止,事件和接口回调会在小程序再次进入「前台」时触发。

当开发者使用了后台音乐播放后台地理位置等能力时,小程序可以在「后台」持续运行,不会进入到「挂起」状态

小程序销毁

如果用户很久没有使用小程序,或者系统资源紧张,小程序会被「销毁」,即完全终止运行。具体而言包括以下几种情形:

  • 当小程序进入后台并被「挂起」后,如果很长时间(目前是 30 分钟)都未再次进入前台,小程序会被销毁。
  • 当小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收。

更新机制

小程序冷启动时如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。 如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。

性能优化

主要的优化策略可以归纳为三点:

  • 精简代码,降低WXML结构和JS代码的复杂性;
  • 合理使用setData调用,减少setData次数和数据量;
  • 必要时使用分包优化。

setData过程

  • 逻辑层虚拟 DOM 树的遍历和更新,触发组件生命周期和 observer 等;
  • 将 data 从逻辑层传输到视图层;
  • 视图层虚拟 DOM 树的更新、真实 DOM 元素的更新并触发页面渲染更新。

setData 数据通信

由于小程序的逻辑层和视图层是两个独立的运行环境、分属不同的线程或进程,不能直接进行数据共享,需要进行数据的序列化、跨线程/进程的数据传输、数据的反序列化,因此数据传输过程是异步的、非实时的。

iOS/iPadOS/MacOS 上,数据传输是通过 evaluateJavascript 实现的,还会有额外 JS 脚本解析和执行的耗时。
数据传输的耗时与数据量的大小正相关,如果对端线程处于繁忙状态,数据会在消息队列中等待。

常见的 setData 操作错误

  1. 频繁的去 setData在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去setData,其导致了两个后果:Android下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;
  2. 每次 setData 都传递大量新数据由setData的底层实现可知,我们的数据传输实际是一次 evaluateJavascript
  3. 由于小程序逻辑层是单线程运行的,后台态页面去 setData 也会抢占前台页面的运行资源,且后台态页面的的渲染用户是无法感知的,会产生浪费。在某些平台上,小程序渲染层各 WebView 也是共享同一个线程,后台页面的渲染和逻辑执行也会导致前台页面的卡顿。

参考链接:https://developers.weixin.qq.com/community/develop/article/doc/00008efe5780d8582ef73dcc15b813
https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips/runtime_setData.html
https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/operating-mechanism.html
https://developers.weixin.qq.com/community/develop/article/doc/0008a4c4f28f30fe3eb863b2750813

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant