为什么要做小程序框架?
- 组件机制
- 多端复用
小程序实现原理
数据驱动视图更新;视图交互触发事件,事件响应函数修改数据再次触发视图更新
mpvue原理
jsBridge
vue.js和小程序的工作原理一致,可以把小程序的功能托管给vue.js,在正确的时机将数据变更同步到小程序,从而达到开发小程序的目的。这样,我们可以将精力聚焦在 Vue.js 上,参照 Vue.js 编写与之对应的小程序代码,小程序负责视图层展示,所有业务逻辑收敛到 Vue.js 中,Vue.js 数据变更后同步到小程序,如图2所示。如此一来,我们就获得了以 Vue.js 的方式开发小程序的能力
生命周期关联:
vue和小程序的数据彼此隔离,各有不同的更新机制。mpvue从生命周期和事件回调函数切入,在vue触发数据更新时实现数据同步。小程序通过视图层呈现给用户、通过事件响应用户交互,vue在后台维护这数据的变更和逻辑。为了实现数据的同步,mpvue修改了vue的runtime实现,在vue的生命周期中增加了更新小程序数据的逻辑。
事件代理机制:
用户交互触发的数据更新通过事件的代理机制完成。vue中,事件响应函数对应组件的method,vue自动维护了上下文环境。然而在小程序中没有这种机制,vue执行环境中维护了一份实时虚拟DOM,这与小程序视图层完全对应,小程序组件上触发事件后,只要找到虚拟DOM上对应的节点,触发对应的事件就可以了;另一方面,vue事件响应如果触发了数据更新,其生命周期函数更新将自动触发,在此函数上同步更新数据。
事件代理机制源码分析:
对比小程序事件系统,mpvue事件系统和Dom事件系统
// 小程序 event 对象属性(8 个)
["type", "timeStamp", "target", "currentTarget", "detail", "touches", "changedTouches", "_requireActive"]
// DOM event 对象属性 / 方法(54 个)
["isTrusted", "screenX", "screenY", "clientX", "clientY", "ctrlKey", "shiftKey", "altKey", "metaKey", "button", "buttons", "relatedTarget", "pageX", "pageY", "x", "y", "offsetX", "offsetY", ..., "cancelable", "timeStamp", "srcElement", "returnValue", "cancelBubble", "path", "composedPath", "stopPropagation", "stopImmediatePropagation", "preventDefault", "initEvent"]
// mpvue event 对象属性 / 方法(9 个)
["mp", "type", "timeStamp", "x", "y", "target", "currentTarget", "stopPropagation", "preventDefault"]
- 在mpvue生成的wxml中,所有事件都被hanleProxy的函数接管,在handleProxy进行处理后再去调用我们写的真正的事件处理函数。这个方法在initMp时,作为小程序page的构造函数的一个选项,从而可以在wxml中被正确调用
- handleProxy将小程序的event对象传给handleProxyWithVue函数进行进一步处理
- handleProxyWithVue的作用
(1) 从跟实例开始,根据comkey找出事件处理函数所在的mpvue示例(getVm)
(2) 通过遍历找到的vm的vnode,结合eventid找到小程序事件对应的真是的事件处理函数(getHandle)
(3) 将小程序的event对象包装成mpvue的event对象(getWebEventByMP),并添加了preventDafault和stopPropagation方法,但是preventDafault和stopPropagation方法是两个空函数(noop),所以组织冒泡还是得使用.stop修饰符通过compile编译成catchEvent
- 将包装后的event对象(getWebEventByMP), 传给真实的处理函数进行调用
(生成的wxml中绑定事件的节点都有data-comkey和data-eventid属性,在一个事件触发时,它们是被用来寻找事件对应的vm实例和对应的事件处理函数的)
原有的mpvue更新机制:
mpvue实现原理是基于Vue2.js,重写platform部分代码来实现在小程序环境下用Vue组件系统进行运行。在Vue的H5实现中,当我们组件树上的一个组件属性更改后,会触发整个树的检查更新,然后在新老dom树对比算出最小改动后,同步到浏览器的真实dom,这个过程熟悉Vue的开发者很熟悉了,就是diff更新。
但是H5版的diff最后的浏览器runtime代码是基于增删dom节点API进行的,小程序又不提供增删节点的功能,所以为了能在小程序环境实现更新,原版mpvue对整个触发更新检查的V-dom树都取值转换成了小程序对应的JSON,通过setData()接口同步到视图。这个过程在实际运行会造成setData的数据冗余过大。通过代理updateDataToMP打日志的方式我们发现,v-dom树上任意节点的任意属性更改,会引发整棵树的更新,一次形如this.a = 1的操作会引起O(N^2)量级的更新。真实在项目中监控到,一个10个组件左右构成的页面,每次进行一次this.a=1数据更新,在$nextTick渲染时真实传到小程序的数据大概在10k-20k。安卓真机上会造成大概200-300ms的渲染演示,肉眼能较明显觉察。
新mpvue数据更新机制:
新版Mpvue在每次数据更新的时候,会在Vue监听set方法,每次触发属性更新的时候把当前更新的属性key放在当前V-dom的__keyPath属性中。一次更新全部触发后,在实际render过程中遍历keyPath属性,只选择更新的属性放到json里,调用Page.setData来进行更新操作。同时在Vue.$nextTick函数里,触发完所有的render更新后会清理掉全部keyPath,防止下次再更新又冗余。通过这种操作,this.a=1可以O(N^2)量级降低到O(1).上面例子中每次更新10k-20k的页面,在业务中有比如用户操作引起某个组件数字+1,关闭弹窗等操作时,更新量降低到几个字节。安卓卡顿的问题也从框架角度完美解决