vue 的 diff 算法网络上介绍他的文章有很多,介绍的也很详细,总结一句话就是同层级比较,不会跨层级比较。uniapp 的 diff 算法在社区里面几乎不曾有人介绍过,这里我详细的介绍一下。
uniapp 的 diff 算法
在 uniapp 中他把 diff 算法放在了运行时的 patch 函数里面,每次调用数据更新的时候,uniapp 都会调用他的 patch 方法。方法如下
var patch = function(oldVnode, vnode) {
var this$1 = this;
if (this.mpType === 'page' || this.mpType === 'component') {
var mpInstance = this.$scope;
var data = Object.create(null);
// ...我把与本篇章无关的内容给去除掉了
// 这里的 diff(data, mpData) 就是调用了他的 diff 算法
var diffData = this.$shouldDiffData === false ? data : diff(data, mpData);
// ... 还有很多操作
}
};
function _diff(current, pre, path, result) {
if (current === pre) { return }
var rootCurrentType = type(current);
var rootPreType = type(pre);
if (rootCurrentType == OBJECTTYPE) {
if (rootPreType != OBJECTTYPE || Object.keys(current).length < Object.keys(pre).length) {
setResult(result, path, current);
} else {
var loop = function ( key ) {
var currentValue = current[key];
var preValue = pre[key];
var currentType = type(currentValue);
var preType = type(preValue);
if (currentType != ARRAYTYPE && currentType != OBJECTTYPE) {
if (currentValue !== pre[key] && nullOrUndefined(currentType, preType)) {
setResult(result, (path == '' ? '' : path + ".") + key, currentValue);
}
} else if (currentType == ARRAYTYPE) {
if (preType != ARRAYTYPE) {
setResult(result, (path == '' ? '' : path + ".") + key, currentValue);
} else {
if (currentValue.length < preValue.length) {
setResult(result, (path == '' ? '' : path + ".") + key, currentValue);
} else {
currentValue.forEach(function (item, index) {
_diff(item, preValue[index], (path == '' ? '' : path + ".") + key + '[' + index + ']', result);
});
}
}
} else if (currentType == OBJECTTYPE) {
if (preType != OBJECTTYPE || Object.keys(currentValue).length < Object.keys(preValue).length) {
setResult(result, (path == '' ? '' : path + ".") + key, currentValue);
} else {
for (var subKey in currentValue) {
_diff(currentValue[subKey], preValue[subKey], (path == '' ? '' : path + ".") + key + '.' + subKey, result);
}
}
}
};
for (var key in current) loop( key );
}
} else if (rootCurrentType == ARRAYTYPE) {
if (rootPreType != ARRAYTYPE) {
setResult(result, path, current);
} else {
if (current.length < pre.length) {
setResult(result, path, current);
} else {
current.forEach(function (item, index) {
_diff(item, pre[index], path + '[' + index + ']', result);
});
}
}
} else {
setResult(result, path, current);
}
}
可以看出在 patch 方法里面他会进行调用 diff 方法,这个diff方法其实最后就是调用了 _diff 方法。根据上面 _diff 的逻辑可以得出以下几点。
- 改变后的值不为对象和数组时,会判断改变前和改变后的值是否相同,不相同并且他们都不是null和 undefined那么就会调用 setResult 的方法来修改值。
- 修改后的值为对象的话,会判断看下修改前的值是不是对象,不是对象或者Object.keys的长度修改后的值比修改前的值要小,那么就会直接调用 setResult的方法。
- 如果修改前的值是对象并且 Object.keys的长度修改后的值比修改前的值要大,他会重新调用_diff方法,如果值相同直接跳出这一次循环,进入下一次循环,key里面的值不为数组和对象的话那么直接调用 setResult 的方法,其实就是第一点
- 修改后的值为数组的话
- 如果修改前的值不为数组,那么就直接调用 setResult 方法
- 如果修改前的值为数组,那么他会遍历修改后的数组,把里面的每一项进行diff,接下来的步骤跟第一,第二步一样
- 如果修改后的值为null或者undefined的话,那么就直接调用 setResult 方法
setResult 其实就是改变对象的key和value值 方法如下:
function setResult(result, k, v) {
// if (type(v) != FUNCTIONTYPE) {
result[k] = v;
// }
}
对 vue 和 uniapp 的 diff 算法有个大致了解以后,我们来对比下他们的不同点。
相同点
如果非要说相同点的话,那么就只有一个,他们都调用了一个叫 patch 的方法,这个方法里面有他们各个的 diff 算法。
不同点
- vue的diff算法是针对于vnode的,uniapp的diff算法是针对于数据的。
- vue的diff算法是同层级优先,不会跨层级比较,也就是说父亲不会和他的孩子进行比较,uniapp的是只跟他相同字段的值进行比较。
- vue的diff算法他会把能复用的节点进行复用,如果新的节点个数比老的节点个数多的话,并且是一个全新的节点的话,那么他会直接创建一个节点。uniapp是分了上面那4个场景进行比较,而且全都是对数据进行比较。