vue2.0加入了virtual dom,有点向react靠拢的意思。vue的diff位于patch.js文件中,复杂度为O(n)。 听大神说了解diff过程可让咱们更高效的使用框架,工做和女友都更加好找了,咱们赶快了解哈~。 了解diff过程,咱们先从虚拟dom开始。vue
所谓的virtual dom,也就是虚拟节点。它经过JS的Object对象模拟DOM中的节点,而后再经过特定的render方法将其渲染成真实的DOM节点 dom diff 则是经过JS层面的计算,返回一个patch对象,即补丁对象,在经过特定的操做解析patch对象,完成页面的从新渲染, 上一张图让你们更加清晰点:node
咱们能够作个试验。打印出一个空元素的第一层属性,能够看到标准让元素实现的东西太多了。若是每次都从新生成新的元素,对性能是巨大的浪费。react
var odiv = document.createElement('div'); for(var k in odiv ){ console.log(k) } 复制代码
看看你的打印台,有你想要的结果。git
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树,插到文档当中github
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树的差别是Virtual DOM算法最核心的部分.简单的说就是新旧虚拟dom 的比较,若是有差别就以新的为准,而后再插入的真实的dom中,从新渲染。、 借网络一张图片说明:算法
比较只会在同层级进行, 不会跨层级比较。
比较后会出现四种状况:
一、此节点是否被移除 -> 添加新的节点
二、属性是否被改变 -> 旧属性改成新属性
三、文本内容被改变-> 旧内容改成新内容
四、节点要被整个替换 -> 结构彻底不相同 移除整个替换
数组
看diff.js 的简单代码实现,下面都有相应的解释说明bash
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比较的时候,是先同层比较,同层比较完看看时候有儿子,有则须要继续比较下去,直到没有儿子。搞个简单的图来讲明一下吧:markdown
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; 复制代码
说明改天有空补上,欢迎留言。diff部分还有一个key的设置,它能够更高效的利用dom。这个也很重要。时间不早了,改天写。你的点赞是我不断的动力。感谢,欢迎抛砖~~