diff
我们都知道, 通过虚拟 DOM, 可以减少 DOM 操作, 提高页面渲染性能
要实现虚拟 DOM, 主要三部曲:
compile view to vnode
diff vnode 的变化
patch vnode 变化到实际的 DOM
假想的黑粉:"所以这篇文章是要深入虚拟 DOM 的实现原理和实现细节吗?"
非也非也, 我们开始愉快地进入正题吧
三部曲中, diff 的性能很关键, 所以一般对 vnode 的 type 和 key 作比较, 如果不一致, 则该 vnode 及以下孩子们全部干掉(好残忍, 无法直视(>﹏<)), 用新的直接替换, 不再往下对比.
假想的黑粉:"这个大家都懂, 所以文章至此可以结束了?"
还没... 还没开始(#~▽~#)
要 diff, 那就得有 diff 的两个 vnode, 一个 old vnode, 一个 new vnode, 那 new vnode 如何产生的呢?
假想的黑粉:"简单啊, 把 old vnode 赋值给 new vnode"
额~, 像以下这样吗?
- let oldVnode = {
- a: {
- a1: 1
- }, b: {
- b1: 1
- }
- }
- let newVnode = oldVnode
- newVnode === oldVnode // true
可以看到新旧一致, 无论如何赋值都是同个对象, 无从对比啦
假想的黑粉:"我是想说 clone 一个啦, shadow 就行了" <(~︶~)>
额~, 像以下这样吗?
- let oldVnode = {
- a: {
- a1: 1
- }, b: {
- b1: 1
- }
- }
- let newVnode = Object.assign({
- }, oldVnode)
- oldVnode === newVnode // false
- newVnode.a.a1 = 2
- oldVnode.a.a1 // 2
可以看到更改了 new vnode 的 a1 值, old vnode 的 a1 值也被改了, 也就无法得知变化了
假想的黑粉:"刚为了性能考虑说了 shadow copy, 那实在不行就 deep copy 吧"
额~, 像以下这样吗?
- const _ = require('lodash');
- let oldVnode = {
- a: {
- a1: 1
- }, b: {
- b1: 1
- }
- }
- let newVnode = _.cloneDeep(oldVnode)
- newVnode.a.a1 = 2
- oldVnode === newVnode // false
- oldVnode.a.a1 === newVnode.a.a1 // false
看上去没什么问题, 功能是可以实现了, 但这篇文章是要讲 更 高效 diff, 上面方案有两个较不好的性能问题:
deep copy 造成资源浪费, 没更新的结点也被复制了一份
每次要遍历所有 vnode 进行对比, 无论该 vnode 有没产生变化
假想的黑粉:"看样子你是有更好的方案, 有什么花招赶紧使出来吧~"
那就让我慢慢道来, 先来个中横线分割一下先(~︶~)↗
只要避免上面提到的两点对性能的影响, 即可更高效 DIFF, 对应的措施如下:
按需 copy: 没出现变化的 vnode 不作 copy
按需 diff: 没出现变化的 vnode 不作 diff
假设 vnode 的数据结构以及图形表示如下:
- let oldVnode = {
- a: {
- a1: { a1_1: 1 },
- a2: { a2_1: 1 }
- },
- b: { b1: { b1_1: 1 } }
- }
image.PNG
当把 a1_1 的值更改为 2 时, 我们希望只对以下高亮节点进行 shadow copy 或赋值, 以下即为 new vnode
image.PNG
所以在对比 old vnode 和 new vnode 时, 只有下图高亮的节点需要进行比对
image.PNG
当 a2 和 b 节点下面的子节点越多时, copy 和 diff 所带来的性能收益就越明显
最后献上这种方案的极简单极粗糙的实现 (update 方法, 只考虑对象, 没考虑数组) 以更好的从代码层面去理解这种思路
- const assert = require('assert');
- let oldVal = {a: {a1: 1}, b: {b1: 2}}
- function update(obj, path, val) {
- let fileds = path.split('.');
- let shadowCopy = targetObj => Object.assign({}, targetObj);
- let result = shadowCopy(obj);
- if (fileds.length> 0) {
- if (fileds.length === 1) {
- result[fileds[0]] = val;
- } else {
- result[fileds[0]] = update(obj[fileds[0]], fileds.length> 1 ? fileds.splice(1).join('.') : '', val)
- }
- }
- return result;
- }
- const newVal = update(oldVal, 'a.a1', 2);
- assert.notStrictEqual(oldVal, newVal);
- assert.notStrictEqual(oldVal.a, newVal.a);
- assert.notStrictEqual(oldVal.a.a1, newVal.a.a1);
- assert.strictEqual(oldVal.b, newVal.b);
来源: http://www.jianshu.com/p/f5623e642d5b