微信小程序是介于Native和WebApp之间的产物,它依托浏览器WebView展示同时可以调用原生能力,比如获取通讯录、拍照等,同一份代码可运行在Android、iOS和微信调试开发工具内。
与React Native的跨平台不同的是,小程序大部分UI组件并不是原生渲染,还是类似WebApp使用浏览器渲染。Native
组件层在WebView
层之上,只有少量组件使用Native
实现,比如<canvas>
、<video>
、<map>
、<textarea>
。
浏览器运行环境
浏览器主要组件包括
- 用户界面(User Interface)
地址栏、前进后退按钮、书签菜单等 - 浏览器引擎(Browser Engine)
在用户界面和呈现引擎之间传递指令 - 呈现引擎(Renderering Engine)
负责显示请求的内容 - JavaScript解释器
用于解释和执行JavaScript代码 - 网络(Networking)
用于网络调用,比如HTTP请求,其接口与平台无关并为所有平台提供实现。 - 数据存储(Data Persistence)
数据持久层,浏览器在本地磁盘上保存的数据。比如Cookie、网络数据库等。 - 用户界面后端(UI Backend)
用于绘制基本的窗口组件,比如组合框和窗口。
通常而言浏览器运行在一个进程中,Google Chrome比较特殊每个标签都是一个独立的进程。浏览器是多线程的,重要的线程包括
- 呈现引擎(渲染引擎):运行在UI线程中
- JS解释器(JS解释引擎):运行在JS引擎线程中
浏览器中UI渲染线程和JS引擎线程是互斥的,当JS引擎线程执行时UI渲染引擎线程会被挂起,UI更新时会被保存在一个队列中等到JS引擎线程空闲时会立即被执行。
小程序与网页开发
- 网页开发
网页开发渲染线程和脚本线程是互斥的,网页开发使用浏览器暴露的DOM API进行DOM选中操作,网页开发者面对的环境是各种浏览器。 - 小程序
小程序的渲染层和逻辑层是分开运行在不同的线程中,逻辑层运行在JSCore中因此没有一个完整的浏览器对象,因而缺少DOM API和BOM API。JSCore和NodeJS环境不同,NPM包在小程序中无法运行。小程序开发需面对iOS和Android两大操作系统。
小程序与H5页面区别
- 运行环境
小程序是基于浏览器内核重构的内置解析器,H5的宿主环境是浏览器。所以小程序中没有DOM和BOM相关的API,jQuery和NPM包无法在小程序中使用。 - 系统权限
小程序能够获得更多的系统权限,比如网络通信状态、数据缓存能力等。 - 渲染机制
小程序的逻辑层和渲染层是分开的,H5页面UI渲染跟JS脚本执行都在一个单线程中而互斥,因此H5页面中长时间的脚本执行会导致页面失去响应。
小程序和Web页面对比
- 静态UI界面差别不大
- Web页面交互存在卡顿不如原生顺畅
- Web页面切换生硬不如原生流畅
- Web页面加载存在白屏不如原生体验好
导致这些问题的主要原因是因为Web页面是基于DOM,在生成和操作DOM耗时且耗性能,另外浏览器对于网页渲染都是单线程处理的方式,包括布局、渲染、JS执行(阻塞)、图像解码等。浏览器重绘时页面刷新频率为60FPS,即16ms/frame,如果16毫秒内不能完成DOM操作就会出现跳帧。Web页面没有GPU图形加速页导致UI界面不流畅。因此,现在的Vue、React等前端框架都推崇使用虚拟DOM,以间接地减少DOM操作频率。
小程序运行环境
小程序开发面对的是iOS和Android微信客户端和辅助开发小程序的开发工具,这三种运行环境的区别在于:
运行环境 | 渲染层 | 逻辑层 |
---|---|---|
iOS | WKWebView | JavaScriptCore |
Android | X5浏览器 | X5 JSCore |
小程序开发工具 | Chrome WebView | NWJS |
小程序框架分层
小程序的框架包括两部分分别是View渲染层和AppService逻辑层
- 渲染层
View层用来渲染页面结构,渲染层界面使用WebView进行渲染,一个小程序存在多个界面所以渲染层存在多个WebView线程。 - 逻辑层
逻辑层采用JSCore线程运行JS脚本,进行逻辑处理、数据请求、接口调用等。
逻辑层会将数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。
视图层和逻辑层分别在两个WebView进程中运行,视图层和逻辑层通过系统层的JSBridge进行通信,逻辑层将数据变化通知到视图层,触发视图层页面更新,视图层将触发的事件通知到逻辑层进行业务处理。
小程序的这两个线程的通信经由客户端进行中转,逻辑层把数据变化通知到渲染层,触发渲染层面更新,渲染层把触发的事件通知到逻辑层进行业务处理。
小程序的UI视图和逻辑处理使用多个WebView实现,逻辑处理的JS代码会全部加载到一个WebView中称之为AppService,整个小程序只有一个且整个生命周期常驻内存,所有的视图(WXML和WXSS)都是单独的WebView来承载即AppView。所以一个小程序打开时至少存在2个WebView进程,正是因为每个视图都是一个而独立的WebView进程,考虑到性能消耗,小程序不允许打开超过5层级的页面,同时也是为了更好的体验。
对于AppService可理解为一个页面主要功能是负责逻辑处理部分的执行,底层提供了一个WAService.js文件来提供API接口,接口主要是消息通信封装的WeixinJSBridge。
小程序线程模型
双线程模型
小程序的渲染层和逻辑层分别由两个线程进行管理,小程序运行时有两个线程分别是View视图线程和AppService逻辑线程,这两个线程是相互隔离的,通过桥接协议WeixinJsBridge进行通信。
- 逻辑层
界面渲染相关任务在WebView线程中执行,一个小程序可以存在多个界面,所以渲染层存在多个WebView线程。 - 逻辑层
逻辑层采用JsCore线程运行JS脚本
线程 | 模块 | 代码 | 原理 | 备注 |
---|---|---|---|---|
View | 视图层,可多个 | WXML/WXSS | WebView渲染 | WXML编译器把WXML文件转化为JS,构建Virtual DOM。 |
AppService | 逻辑层,仅一个 | JS | JsCore运行 | 无法访问window/document对象 |
小程序的视图层线程和逻辑层线程是如何进行数据传递的呢?
通过两边提供的evaluateJavascript
实现,即用户传输的数据需将其转换为字符形式传递,同时把转换后的数据内容拼接成为一份JS脚本,再通过执行JS脚本的形式传递到两边独立的环境中。也就是说,两个线程是通过系统层的WeixinJsBridge来通信的,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层今昔那个业务处理。
页面渲染的具体流程是在渲染层宿主环境会将WXML转化为对应的JS对象,在逻辑层发生数据变更时需通过宿主环境提供的setData
方法将数据从逻辑层传递到渲染层,对比前后差异后将差异应用到原来的DOM树上,以渲染出正确的UI界面。
小程序双线程模型与Web前端框架不同之处在于,小程序双线程模型可以更好地管控和提供更加安全的环境,缺点是带来无处不在的异步问题,因为任何数据传输都是线程间通信,也就存在一定的延迟,不过小程序在框架层面已经封装了异步带来的时序问题。
小程序框架
从下往上看,小程序的解析顺序
- 最底层是微信,当开发版式小程序开发工具会把代码和框架一起进行打包,微信中打开小程序时微信会将打包好的代码下载到微信App中,这样就可以像在开发工具里一样在微信中运行小程序。
- native层是小程序的框架,框架封装了UI层组件和逻辑层组件,这些组件可以通过微信App提供的接口调用手机硬件信息。
- 最上层是需进行操作的视图层和逻辑层,视图层和逻辑层的交互式通过数据经由native层进行交互的,视图层和逻辑层可调用native框架中封装好的组件和方法。
小程序生命周期
小程序生命周期可分为应用生命周期和页面生命周期两部分
- 应用生命周期
- 用户首次打开小程序会触发
onLaunch
事件,全局只触发一次。 - 小程序初始化完成后会触发
onShow
事件,监听小程序显示。 - 小程序从前台进入后台会触发
onHide
事件 - 小程序从后台进入前台显示会触发
onShow
事件 - 小程序后台运行一定时间或系统资源占用过高时会被销毁
- 页面生命周期
- 小程序注册完成后会加载页面并触发
onLoad
事件方法 - 页面载入后会触发
onShow
事件方法而显示页面 - 首次显示页面会触发
onReady
方法,渲染页面元素和样式,一个页面只会调用一次。 - 当小程序后台运行或跳转到其它页面时会触发
onHide
事件方法 - 当小程序由后台进入到前台运行或重新进入页面时会触发
onShow
方法 - 当使用重定向
wx.redirectTo()
方法或关闭当前页面返回上一页wx.navigateBack()
方法时会触发onUnload
事件方法。
小程序组件系统
小程序的基本组件是基于Exparser框架的,Exparser基于WebComponents的ShadowDOM模型,不依赖浏览器的原生支持,可在纯JS环境中运行。小程序所有节点树操作都依赖于Exparser,包括WXML到页面最终节点树的构建,CreateSelectorQuery
调用、自定义组件特性等。