虚拟Dom和diff算法

vue2.0加入了virtual dom,有点向react靠拢的意思。vue的diff位于patch.js文件中,复杂度为O(n)。 了解diff过程,我们先从虚拟dom开始。
在这里插入图片描述
到这里人可能会问,模拟DOM是干嘛为什么要这样做?虚拟dom对应的是真实dom, 使用document.CreateElement 和 document.CreateTextNode创建的就是真实节点。
我们可以做个试验。打印出一个空元素的第一层属性,可以看到标准让元素实现的东西太多了。如果每次都重新生成新的元素,对性能是巨大的浪费。

var odiv = document.createElement('div');
for(var k in odiv ){
   console.log(k)
}

复制代码
看看你的打印台,有你想要的结果。
实现步骤
• 用JavaScript对象模拟DOM
• 把此虚拟DOM转成真实DOM并插入页面中
• 如果有事件发生修改了虚拟DOM
• 比较两棵虚拟DOM树的差异,得到差异对象
• 把差异对象应用到真正的DOM树上
代码实现

class crtateElement {
   constructor (el, attr, child) {
       this.el = el
       this.attrs = attr
       this.child = child || []
   }
   render () {
       let virtualDOM =  document.createElement(this.el)
       // attr是个对象所以要遍历渲染
       for (var attr in this.attrs) {
           virtualDOM.setAttribute(attr, this.attrs[attr])
       }
       // 深度遍历child
       this.child.forEach(el => {
           console.log(el instanceof crtateElement)
           //如果子节点是一个元素的话,就调用它的render方法创建子节点的真实DOM,如果是一个字符串的话,创建一个文件节点就可以了
           // 判断一个对象是否是某个对象的实力
           let childElement = (el instanceof crtateElement) ? el.render() : document.createTextNode(el);
           virtualDOM.appendChild(childElement);
       });
       return virtualDOM
   }
}
function element (el, attr, child) {
   return new crtateElement(el, attr, child)
}
module.exports = element

复制代码
用JavaScript对象结构表示DOM树的结构;然后用这个树构建一个真正的DOM树,插到文档当中

let element = require('./element')
let myobj = {
   "class": 'big_div'
}
let ul = element('div',myobj,[
   '我是文字',
   element('div',{'id': 'xiao'},['1']),
   element('div',{'id': 'xiao1'},['2']),
   element('div',{'id': 'xiao2'},['3']),
])
console.log(ul)
ul = ul.render()
document.body.appendChild(ul)

复制代码
DOM DIFF
比较两棵DOM树的差异是Virtual DOM算法最核心的部分.简单的说就是新旧虚拟dom 的比较,如果有差异就以新的为准,然后再插入的真实的dom中,重新渲染。、 借网络一张图片说明:
在这里插入图片描述
比较只会在同层级进行, 不会跨层级比较。

比较后会出现四种情况:

1、此节点是否被移除 -> 添加新的节点
2、属性是否被改变 -> 旧属性改为新属性
3、文本内容被改变-> 旧内容改为新内容
4、节点要被整个替换 -> 结构完全不相同 移除整个替换

看diff.js 的简单代码实现,下面都有相应的解释说明

let utils = require('./utils');
let keyIndex = 0;
function diff(oldTree, newTree) {
   //记录差异的空对象。key就是老节点在原来虚拟DOM树中的序号,值就是一个差异对象数组
   let patches = {};
   keyIndex = 0;  // 儿子要起另外一个标识
   let index = 0; // 父亲的表示 1 儿子的标识就是1.1 1.2
   walk(oldTree, newTree, index, patches);
   return patches;
}
//遍历
function walk(oldNode, newNode, index, patches) {
   let currentPatches = [];//这个数组里记录了所有的oldNode的变化
   if (!newNode) {//如果新节点没有了,则认为此节点被删除了
       currentPatches.push({ type: utils.REMOVE, index });
       //如果说老节点的新的节点都是文本节点的话
   } else if (utils.isString(oldNode) && utils.isString(newNode)) {
       //如果新的字符符值和旧的不一样
       if (oldNode != newNode) {
           ///文本改变
           currentPatches.push({ type: utils.TEXT, content: newNode });
       }
   } else if (oldNode.tagName == newNode.tagName) {
       //比较新旧元素的属性对象
       let attrsPatch = diffAttr(oldNode.attrs, newNode.attrs);
       //如果新旧元素有差异 的属性的话
       if (Object.keys(attrsPatch).length > 0) {
           //添加到差异数组中去
           currentPatches.push({ type: utils.ATTRS, attrs: attrsPatch });
       }
       //自己比完后再比自己的儿子们
       diffChildren(oldNode.children, newNode.children, index, patches, currentPatches);
   } else {
       currentPatches.push({ type: utils.REPLACE, node: newNode });
   }
   if (currentPatches.length > 0) {
     patches[index] = currentPatches;
   }
}
//老的节点的儿子们 新节点的儿子们 父节点的序号 完整补丁对象 当前旧节点的补丁对象
function diffChildren(oldChildren, newChildren, index, patches, currentPatches) {
   oldChildren.forEach((child, idx) => {
       walk(child, newChildren[idx], ++keyIndex, patches);
   });
}
function diffAttr(oldAttrs, newAttrs) {
   let attrsPatch = {};
   for (let attr in oldAttrs) {
       //如果说老的属性和新属性不一样。一种是值改变 ,一种是属性被删除 了
       if (oldAttrs[attr] != newAttrs[attr]) {
           attrsPatch[attr] = newAttrs[attr];
       }
   }
   for (let attr in newAttrs) {
       if (!oldAttrs.hasOwnProperty(attr)) {
           attrsPatch[attr] = newAttrs[attr];
       }
   }
   return attrsPatch;
}
module.exports = diff;

复制代码
其中有个需要注意的是新旧虚拟dom比较的时候,是先同层比较,同层比较完看看时候有儿子,有则需要继续比较下去,直到没有儿子。搞个简单的图来说明一下吧:

同层比较,比较顺序是上面的数字来,把不同的打上标记,放到数组里面去,统一交给patch处理。
patch.js的简单实现

let keyIndex = 0;
let utils = require('./utils');
let allPatches;//这里就是完整的补丁包
function patch(root, patches) {
   allPatches = patches;
   walk(root);
}
function walk(node) {
   let currentPatches = allPatches[keyIndex++];
   (node.childNodes || []).forEach(child => walk(child));
   if (currentPatches) {
       doPatch(node, currentPatches);
   }
}
function doPatch(node, currentPatches) {
   currentPatches.forEach(patch => {
       switch (patch.type) {
           case utils.ATTRS:
               for (let attr in patch.attrs) {
                   let value = patch.attrs[attr];
                   if (value) {
                       utils.setAttr(node, attr, value);
                   } else {
                       node.removeAttribute(attr);
                   }
               }
               break;
           case utils.TEXT:
               node.textContent = patch.content;
               break;
           case utils.REPLACE:
               let newNode = (patch.node instanceof Element) ? path.node.render() : document.createTextNode(path.node);
               node.parentNode.replaceChild(newNode, node);
               break;
           case utils.REMOVE:
               node.parentNode.removeChild(node);
               break;
       }
   });
}
module.exports = patch;

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值