小程式的由来
在小程式没有出来之前,最初微信WebView逐渐成为移动web重要入口,微信释出了一整套网页开发工具包,称之为 JS-SDK,给所有的 Web 开发者打开了一扇全新的窗户,让所有开发者都可以使用到微信的原生能力,去完成一些之前做不到或者难以做到的事情。但JS-SDK 的模式并没有解决使用移动网页遇到的体验不良的问题,比如受限于装置效能和网络速度,会出现白屏的可能。因此又设计了一个增强版JS-SDK,也就是“微信 Web 资源离线储存”,但在复杂的页面上依然会出现白屏的问题,原因表现在页面切换的生硬和点选的迟滞感。这个时候需要一个使用者体验更好的一个系统,小程式应运而生。小程式主要具有以下的特点:
快速的载入更强大的能力原生的体验易用且安全的微信资料开放高效和简单的开发
小程式与普通网页开发的区别
小程式的开发同普通的网页开发相比有很大的相似性,小程式的主要开发语言也是 JavaScript,但是二者还是有些差别的。普通网页开发可以使用各种浏览器提供的 DOM API,进行 DOM 操作,小程式的逻辑层和渲染层是分开的,逻辑层执行在 JSCore中,并没有一个完整浏览器物件,因而缺少相关的DOM API和BOMAPI。普通网页开发渲染执行绪和指令码执行绪是互斥的,这也是为什么长时间的指令码执行可能会导致页面失去响应,而在小程式中,二者是分开的,分别执行在不同的执行绪中。网页开发者在开发网页的时候,只需要使用到浏览器,并且搭配上一些辅助工具或者编辑器即可。小程式的开发则有所不同,需要经过申请小程式账号、安装小程式开发者工具、配置专案等等过程方可完成。小程式的执行环境
小程式架构
一、技术选型一般来说,渲染界面的技术有三种:
用纯客户端原生技术来渲染用纯 Web 技术来渲染用客户端原生技术与 Web 技术结合的混合技术(简称 Hybrid 技术)来渲染通过以下几个方面分析,小程式采用哪种技术方案
开发门槛:Web 门槛低,Native 也有像 RN 这样的框架支援体验:Native 体验比 Web 要好太多,Hybrid 在一定程度上比 Web 接近原生体验版本更新:Web 支援线上更新,Native 则需要打包到微信一起稽核释出管控和安全:Web 可跳转或是改变页面内容,存在一些不可控因素和安全风险由于小程式的宿主环境是微信,如果用纯客户端原生技术来编写小程式,那么小程式程式码每次都需要与微信程式码一起发版,这种方式肯定是不行的。
所以需要像web技术那样,有一份随时可更新的资源包放在云端,通过下载到本地,动态执行后即可渲染出界面。如果用纯web技术来渲染小程式,在一些复杂的互动上可能会面临一些效能问题,这是因为在web技术中,UI渲染跟JavaScript的指令码执行都在一个单执行绪中执行,这就容易导致一些逻辑任务抢占UI渲染的资源。
所以最终采用了两者结合起来的Hybrid 技术来渲染小程式,可以用一种近似web的方式来开发,并且可以实现线上更新程式码,同时引入元件也有以下好处:
扩充套件 Web 的能力。比如像输入框元件(input, textarea)有更好地控制键盘的能力体验更好,同时也减轻 WebView 的渲染工作绕过 setData、资料通讯和重渲染流程,使渲染效能更好用客户端原生渲染内建一些复杂元件,可以提供更好的效能二、双执行绪模型
小程式的渲染层和逻辑层分别由 2 个执行绪管理:检视层的界面使用了 WebView 进行渲染,逻辑层采用 JsCore 执行绪执行 JS指令码。
那么为什么要这样设计呢,前面也提到了管控和安全,为了解决这些问题,我们需要阻止开发者使用一些,例如浏览器的window物件,跳转页面、操作DOM、动态执行指令码的开放性界面。我们可以使用客户端系统的 JavaScript 引擎,iOS 下的 JavaScriptCore 框架,安卓下腾讯 x5 核心提供的 JsCore 环境。
这个沙箱环境只提供纯 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 进行转发,来实现逻辑层和渲染层的互动和通讯。
而这样一个完整的框架,离不开小程式的基础库。
四、小程式的基础库
小程式的基础库可以被注入到检视层和逻辑层执行,主要用于以下几个方面:
在检视层,提供各类元件来组建界面的元素在逻辑层,提供各类 API 来处理各种逻辑处理资料系结、元件系统、事件系统、通讯系统等一系列框架逻辑由于小程式的渲染层和逻辑层是两个执行绪管理,两个执行绪各自注入了基础库。
小程式的基础库不会被打包在某个小程式的程式码包里边,它会被提前内建在微信客户端。
这样可以:
降低业务小程式的程式码包大小可以单独修复基础库中的 Bug,无需修改到业务小程式的程式码包五、Exparser 框架
Exparser是微信小程式的元件组织框架,内建在小程式基础库中,为小程式的各种元件提供基础的支援。小程式内的所有元件,包括内建元件和自定义元件,都由Exparser组织管理。
Exparser的主要特点包括以下几点:
基于ShadowDOM模型:模型上与WebComponents的ShadowDOM高度相似,但不依赖浏览器的原生支援,也没有其他依赖库;实现时,还针对性地增加了其他API以支援小程式元件程式设计。可在纯JS环境中执行:这意味着逻辑层也具有一定的元件树组织能力。高效轻量:效能表现好,在元件例项极多的环境下表现尤其优异,同时代码尺寸也较小。小程式中,所有节点树相关的操作都依赖于Exparser,包括WXML到页面最终节点树的构建、createSelectorQuery呼叫和自定义元件特性等。
内建元件
基于Exparser框架,小程式内建了一套元件,提供了检视容器类、表单类、导航类、媒体类、开放类等几十种元件。有了这么丰富的元件,再配合WXSS,可以搭建出任何效果的界面。在功能层面上,也满足绝大部分需求。
六、执行机制
小程式启动会有两种情况,一种是“冷启动”,一种是“热启动”。假如使用者已经开启过某小程式,然后在一定时间内再次开启该小程式,此时无需重新启动,只需将后台状态的小程式切换到前台,这个过程就是热启动;冷启动指的是使用者首次开启或小程式被微信主动销毁后再次开启的情况,此时小程式需要重新载入启动。
小程式没有重启的概念当小程式进入后台,客户端会维持一段时间的执行状态,超过一定时间后(目前是5分钟)会被微信主动销毁当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程式的销毁
七、更新机制
小程式冷启动时如果发现有新版本,将会异步下载新版本的程式码包,并同时用客户端本地的包进行启动,即新版本的小程式需要等下一次冷启动才会应用上。 如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。
八、效能优化
主要的优化策略可以归纳为三点:
精简程式码,降低WXML结构和JS程式码的复杂性;合理使用setData呼叫,减少setData次数和资料量;必要时使用分包优化。1、setData 工作原理
小程式的检视层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为执行环境。在架构上,WebView 和 JavascriptCore 都是独立的模组,并不具备资料直接共享的通道。当前,检视层和逻辑层的资料传输,实际上通过两边提供的 evaluateJavascript 所实现。即使用者传输的资料,需要将其转换为字串形式传递,同时把转换后的资料内容拼接成一份 JS 指令码,再通过执行 JS 指令码的形式传递到两边独立环境。
而 evaluateJavascript 的执行会受很多方面的影响,资料到达检视层并不是实时的。
2、常见的 setData 操作错误
频繁的去 setData在我们分析过的一些案例里,部分小程式会非常频繁(毫秒级)的去setData,其导致了两个后果:Android下使用者在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 执行绪一直在编译执行渲染,未能及时将使用者操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到检视层;渲染有出现延时,由于 WebView 的 JS 执行绪一直处于忙碌状态,逻辑层到页面层的通讯耗时上升,检视层收到的资料讯息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;每次 setData 都传递大量新资料由setData的底层实现可知,我们的资料传输实际是一次 evaluateJavascript指令码过程,当资料量过大时会增加指令码的编译执行时间,占用 WebView JS 执行绪, 后台态页面进行setData当页面进入后台态(使用者不可见),不应该继续去进行setData,后台态页面的渲染使用者是无法感受的,另外后台态页面去setData也会抢占前台页面的执行。