Skip to content

Commit bfb5fdb

Browse files
committed
在渲染时使用新的diff算法来减少Vue组件更新引起的Page.setData的实际更新量,达到提升页面性能的目的
1、core/observer的set方法中,监听对象更新值时,用__keyPath:{key:true}方式把更新的key值绑定在vm数据对象上 2、每次真实调用setData前,用mp/runtime/diffData.js进行检查优化大小,规则有 A.第一次vm渲染到小程序上,全量更新,并记下标志 B.第二次及以后再更新,检查_data对象,如果有__keyPath,跟据__keyPath更新,没有 __keyPath表示没有更新,不放到Page.setData B1._data遇到Object类型,检查Object类型__keyPath,存在的话深度遍历所有属性 递归检查更新,不存在__keyPath可能是this.obj = {}重新写对象值造成set没捕捉到, 使用脏检查和页面数据对比更新 B2._data遇到Array类型,直接脏检查,避免数组不能记录__keyPath造成丢失 B3._props属性由于父组件传入不走set,所以遍历_props,值类型直接更新,对象类型 深度遍历进入步骤B1,数组进入B2 B4.遍历上述后最终更新数据再加入_computedWatchers,_mpProps B5.组件树上的节点都完成更新后调用Vue.nextTick钩子,清理所有对象上的__keyPath 为下次检查更新做准备 C.diff优化好data后,如果在Vue.config.devtool == true时打印500ms内的更新量 方便使用者检查页面的更新情况 D.diff好的JSON中,删掉原来的$root.0={},只把必要的更新给Page.setData使用 3、使用效果:减少了只改动一个根节点属性却造成整个组件树的数据都传递给 Page.setData的情况,从而减少因为setData量大引起的真机尤其安卓卡顿的情况
1 parent 427fedd commit bfb5fdb

File tree

4 files changed

+205
-5
lines changed

4 files changed

+205
-5
lines changed

src/core/observer/index.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ export class Observer {
3636
dep: Dep;
3737
vmCount: number; // number of vms that has this object as root $data
3838

39-
constructor (value: any) {
39+
constructor (value: any, key: any) {
4040
this.value = value
4141
this.dep = new Dep()
4242
this.vmCount = 0
43+
if (key) {
44+
this.key = key
45+
}
4346
def(value, '__ob__', this)
4447
if (Array.isArray(value)) {
4548
const augment = hasProto
@@ -103,7 +106,7 @@ function copyAugment (target: Object, src: Object, keys: Array<string>) {
103106
* returns the new observer if successfully observed,
104107
* or the existing observer if the value already has one.
105108
*/
106-
export function observe (value: any, asRootData: ?boolean): Observer | void {
109+
export function observe (value: any, asRootData: ?boolean, key: any): Observer | void {
107110
if (!isObject(value)) {
108111
return
109112
}
@@ -117,7 +120,9 @@ export function observe (value: any, asRootData: ?boolean): Observer | void {
117120
Object.isExtensible(value) &&
118121
!value._isVue
119122
) {
120-
ob = new Observer(value)
123+
ob = new Observer(value, key)
124+
ob.__keyPath = ob.__keyPath ? ob.__keyPath : {}
125+
ob.__keyPath[key] = true
121126
}
122127
if (asRootData && ob) {
123128
ob.vmCount++
@@ -142,11 +147,13 @@ export function defineReactive (
142147
return
143148
}
144149

150+
// TODO: 先试验标记一下 keyPath
151+
145152
// cater for pre-defined getter/setters
146153
const getter = property && property.get
147154
const setter = property && property.set
148155

149-
let childOb = !shallow && observe(val)
156+
let childOb = !shallow && observe(val, undefined, key)
150157
Object.defineProperty(obj, key, {
151158
enumerable: true,
152159
configurable: true,
@@ -169,6 +176,7 @@ export function defineReactive (
169176
if (newVal === value || (newVal !== newVal && value !== value)) {
170177
return
171178
}
179+
172180
/* eslint-enable no-self-compare */
173181
if (process.env.NODE_ENV !== 'production' && customSetter) {
174182
customSetter()
@@ -178,8 +186,10 @@ export function defineReactive (
178186
} else {
179187
val = newVal
180188
}
181-
childOb = !shallow && observe(newVal)
189+
childOb = !shallow && observe(newVal, undefined, key)
182190
dep.notify()
191+
obj.__keyPath = obj.__keyPath ? obj.__keyPath : {}
192+
obj.__keyPath[key] = true
183193
}
184194
})
185195
}
@@ -212,6 +222,9 @@ export function set (target: Array<any> | Object, key: any, val: any): any {
212222
return val
213223
}
214224
defineReactive(ob.value, key, val)
225+
// Vue.set 添加对象属性,渲染时候把val传给小程序渲染
226+
target.__keyPath = target.__keyPath ? target.__keyPath : {}
227+
target.__keyPath[key] = true
215228
ob.dep.notify()
216229
return val
217230
}
@@ -239,6 +252,9 @@ export function del (target: Array<any> | Object, key: any) {
239252
if (!ob) {
240253
return
241254
}
255+
target.__keyPath = target.__keyPath ? target.__keyPath : {}
256+
// Vue.del 删除对象属性,渲染时候把这个属性设置为undefined
257+
target.__keyPath[key] = 'del'
242258
ob.dep.notify()
243259
}
244260

src/platforms/mp/runtime/diff-data.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import Vue from 'core/index'
2+
import { diffLog } from './runtime-trace'
3+
4+
function getDeepData (keyList, viewData) {
5+
if (keyList.length > 1) {
6+
const _key = keyList.splice(0, 1)
7+
const _viewData = viewData[_key]
8+
if (_viewData) {
9+
return getDeepData(keyList, _viewData)
10+
} else {
11+
return null
12+
}
13+
} else {
14+
if (viewData[keyList[0]]) {
15+
return viewData[keyList[0]]
16+
} else {
17+
return null
18+
}
19+
}
20+
}
21+
function compareAndSetDeepData (key, newData, vm, data) {
22+
// 比较引用类型数据
23+
try {
24+
const keyList = key.split('.')
25+
const oldData = getDeepData(keyList, vm.$root.$mp.page.__viewData__)
26+
if (oldData === null || JSON.stringify(oldData) !== JSON.stringify(newData)) {
27+
data[key] = newData
28+
}
29+
} catch (e) {
30+
console.log(e, key, newData, vm)
31+
}
32+
}
33+
34+
function cleanKeyPath (vm) {
35+
if (vm.__mpKeyPath) {
36+
Object.keys(vm.__mpKeyPath).forEach((_key) => {
37+
delete vm.__mpKeyPath[_key]['__keyPath']
38+
})
39+
}
40+
}
41+
42+
function minifyDeepData (rootKey, originKey, vmData, data, _mpValueSet, vm) {
43+
try {
44+
if (vmData instanceof Array) {
45+
// 数组
46+
compareAndSetDeepData(rootKey + '.' + originKey, vmData, vm, data)
47+
} else {
48+
// Object
49+
let __keyPathOnThis = {} // 存储这层对象的keyPath
50+
if (vmData.__keyPath) {
51+
// 有更新列表 ,按照更新列表更新
52+
__keyPathOnThis = vmData.__keyPath
53+
Object.keys(vmData).forEach((_key) => {
54+
if (vmData[_key] instanceof Object) {
55+
// 引用类型 递归
56+
if (_key === '__keyPath') {
57+
return
58+
}
59+
minifyDeepData(rootKey + '.' + originKey, _key, vmData[_key], data, null, vm)
60+
} else {
61+
// 更新列表中的 加入data
62+
if (__keyPathOnThis[_key] === true) {
63+
if (originKey) {
64+
data[rootKey + '.' + originKey + '.' + _key] = vmData[_key]
65+
} else {
66+
data[rootKey + '.' + _key] = vmData[_key]
67+
}
68+
}
69+
}
70+
})
71+
// 根节点可能有父子引用同一个引用类型数据,依赖树都遍历完后清理
72+
vm['__mpKeyPath'] = vm['__mpKeyPath'] || {}
73+
vm['__mpKeyPath'][vmData.__ob__.dep.id] = vmData
74+
} else {
75+
// 没有更新列表
76+
compareAndSetDeepData(rootKey + '.' + originKey, vmData, vm, data)
77+
}
78+
}
79+
} catch (e) {
80+
console.log(e, rootKey, originKey, vmData, data)
81+
}
82+
}
83+
84+
function getRootKey (vm, rootKey) {
85+
if (!vm.$parent.$attrs) {
86+
rootKey = '$root.0' + ',' + rootKey
87+
return rootKey
88+
} else {
89+
rootKey = vm.$parent.$attrs.mpcomid + ',' + rootKey
90+
return getRootKey(vm.$parent, rootKey)
91+
}
92+
}
93+
94+
export function diffData (vm, data) {
95+
const vmData = vm._data || {}
96+
const vmProps = vm._props || {}
97+
let rootKey = ''
98+
if (!vm.$attrs) {
99+
rootKey = '$root.0'
100+
} else {
101+
rootKey = getRootKey(vm, vm.$attrs.mpcomid)
102+
}
103+
Vue.nextTick(() => {
104+
cleanKeyPath(vm)
105+
})
106+
// console.log(rootKey)
107+
108+
// 值类型变量不考虑优化,还是直接更新
109+
const __keyPathOnThis = vmData.__keyPath || vm.__keyPath || {}
110+
delete vm.__keyPath
111+
delete vmData.__keyPath
112+
delete vmProps.__keyPath
113+
if (vm._mpValueSet === 'done') {
114+
// 第二次赋值才进行缩减操作
115+
Object.keys(vmData).forEach((vmDataItemKey) => {
116+
if (vmData[vmDataItemKey] instanceof Object) {
117+
// 引用类型
118+
if (vmDataItemKey === '__keyPath') { return }
119+
minifyDeepData(rootKey, vmDataItemKey, vmData[vmDataItemKey], data, vm._mpValueSet, vm)
120+
} else {
121+
// _data上的值属性只有要更新的时候才赋值
122+
if (__keyPathOnThis[vmDataItemKey] === true) {
123+
data[rootKey + '.' + vmDataItemKey] = vmData[vmDataItemKey]
124+
}
125+
}
126+
})
127+
128+
Object.keys(vmProps).forEach((vmPropsItemKey) => {
129+
if (vmProps[vmPropsItemKey] instanceof Object) {
130+
// 引用类型
131+
if (vmPropsItemKey === '__keyPath') { return }
132+
minifyDeepData(rootKey, vmPropsItemKey, vmProps[vmPropsItemKey], data, vm._mpValueSet, vm)
133+
} else {
134+
data[rootKey + '.' + vmPropsItemKey] = vmProps[vmPropsItemKey]
135+
}
136+
// _props上的值属性只有要更新的时候才赋值
137+
})
138+
139+
// 检查完data和props,最后补上_mpProps & _computedWatchers
140+
const vmMpProps = vm._mpProps || {}
141+
const vmComputedWatchers = vm._computedWatchers || {}
142+
Object.keys(vmMpProps).forEach((mpItemKey) => {
143+
data[rootKey + '.' + mpItemKey] = vmMpProps[mpItemKey]
144+
})
145+
Object.keys(vmComputedWatchers).forEach((computedItemKey) => {
146+
data[rootKey + '.' + computedItemKey] = vmComputedWatchers[computedItemKey]
147+
})
148+
// 更新的时候要删除$root.0:{},否则会覆盖原正确数据
149+
delete data[rootKey]
150+
}
151+
if (vm._mpValueSet === undefined) {
152+
// 第一次设置数据成功后,标记位置true,再更新到这个节点如果没有keyPath数组认为不需要更新
153+
vm._mpValueSet = 'done'
154+
}
155+
if (Vue.config.devtools) {
156+
// console.log('更新VM节点', vm)
157+
// console.log('实际传到Page.setData数据', data)
158+
diffLog(data)
159+
}
160+
}

src/platforms/mp/runtime/render.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// 节流方法,性能优化
22
import { getComKey } from '../util/index'
33

4+
import { diffData } from './diff-data'
5+
46
// 全局的命名约定,为了节省编译的包大小一律采取形象的缩写,说明如下。
57
// $c === $child
68
// $k === $comKey
@@ -128,6 +130,8 @@ function getPage (vm) {
128130
return page
129131
}
130132

133+
// 优化js变量动态变化时候引起全量更新
134+
131135
// 优化每次 setData 都传递大量新数据
132136
export function updateDataToMP () {
133137
const page = getPage(this)
@@ -136,6 +140,9 @@ export function updateDataToMP () {
136140
}
137141

138142
const data = formatVmData(this)
143+
144+
diffData(this, data)
145+
139146
throttleSetData(page.setData.bind(page), data)
140147
}
141148

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Vue from 'core/index'
2+
var updateDataTotal = 0 // 总共更新的数据量
3+
export function diffLog (updateData) {
4+
updateData = JSON.stringify(updateData)
5+
if (!Vue._mpvueTraceTimer) {
6+
Vue._mpvueTraceTimer = setTimeout(function () {
7+
clearTimeout(Vue._mpvueTraceTimer)
8+
updateDataTotal = (updateDataTotal / 1024).toFixed(1)
9+
console.log('这次操作引发500ms内数据更新量:' + updateDataTotal + 'kb')
10+
Vue._mpvueTraceTimer = 0
11+
updateDataTotal = 0
12+
}, 500)
13+
} else if (Vue._mpvueTraceTimer) {
14+
updateData = updateData.replace(/[^\u0000-\u00ff]/g, 'aa') // 中文占2字节,中文替换成两个字母计算占用空间
15+
updateDataTotal += updateData.length
16+
}
17+
}

0 commit comments

Comments
 (0)