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。這個也很重要。時間不早了,改天寫。你的點贊是我不斷的動力。感謝,歡迎拋磚~~