摘要:的實現(xiàn)了一套高效的算法來快速的比對更新樹。關(guān)于的實現(xiàn)原理將在后面的文章中提到。對于子節(jié)點的操作將被記錄在父節(jié)點的上。方法主要對具體的節(jié)點進行修改。
之前在看vue的源碼時了解了vue關(guān)于Virtual DOM的一些想法,Virtual DOM可以幫助我們更高效的操作DOM。它通過實現(xiàn)一個vnode的js對象,vnode的對象與dom的node對象是一一對應(yīng)的,通過我們對vnode的操作可以實現(xiàn)對dom的操作,這樣就可以避免頻繁的dom操作帶來的效率問題。vue的Virtual DOM實現(xiàn)了一套高效的diff算法來快速的比對更新dom樹。
關(guān)于vue的Virtual DOM實現(xiàn)原理將在后面的文章中提到。為了方便理解和學(xué)習(xí),我寫了一個簡單的Virtual DOM操作DOM樹的demo。這里是完整代碼以及DOM
VNode首先,創(chuàng)建vnode的對象,vnode記錄相應(yīng)的DOM對象的一些屬性。
export default class VNode { constructor (tag, nodeType,key, props, text, children){ this.tag = tag //element類型 this.nodeType = nodeType //node類型,1為普通節(jié)點,3為文本節(jié)點,8為注釋 this.key = key this.props = props //node的屬性 this.text = text //文本節(jié)點的內(nèi)容 this.children = children//子節(jié)點 } //將vnode渲染成DOM節(jié)點的方法 render(){ var el if(this.nodeType===1){ el = document.createElement(this.tag) for(let prop in this.props){ setAttr(el,prop,this.props[prop]) } if(this.children){ this.children.forEach(function(ch,i){ el.appendChild(ch.render()) }) } } else if(this.nodeType===3){ el = document.createTextNode(this.text) } else if(this.nodeType===8){ el = document.createComment(this.text) } el.key = this.key return el } } function setAttr(node,key,value){ if(key==="style"){ for(let val in value){ node.style[val] = value[val] } } else { node.setAttribute(key,value) } }Diff
diff主要是用來對比新舊vnode的區(qū)別,找出區(qū)別的元素并記錄在directives對象上,便于接下來可以通過directives的內(nèi)容對舊的vnode進行替換,繪制新的DOM.
這是diff的入口方法,參數(shù)是舊的vnode和新的vnode,directives是用來記錄每個節(jié)點的改變情況的對象。
export default function diff(oldVNode, newVNode){ directives = {} diffVNode(oldVNode,newVNode,directives) return directives }
我們在diff方法中調(diào)用diffVNode來對節(jié)點進行逐一比較。首先,它會比較oldVNode和newVNode是否是相同的節(jié)點。如果相同,就對節(jié)點類型進行判斷,來選擇比較的方法,對于文本和注釋節(jié)點,只需要比較文本內(nèi)容是否相同即可,對于元素則要比較元素標簽,元素的屬性以及子元素是否相同。
function diffVNode(oldVNode,newVNode){ if(newVNode && isSameTypeNode(oldVNode,newVNode)){ if(newVNode.nodeType===3 || newVNode.nodeType===8){ if(oldVNode.text !== newVNode.text){ addDirectives(newVNode.key,{type:TEXT, content: newVNode.text}) } } else if(newVNode.nodeType===1){ if(oldVNode.tag === newVNode.tag && oldVNode.key == newVNode.key){ var propPatches = diffProps(oldVNode.props, newVNode.props) if(Object.keys(propPatches).length>0){ addDirectives(newVNode.key,{type:PROP, content: propPatches}) } if(oldVNode.children || newVNode.children) diffChildren(oldVNode.children,newVNode.children,newVNode.key) } } } return directives }
這是比較節(jié)點屬性的方法,對于有變化的屬性我們將變化的部分記在patches這個數(shù)組里。
function diffProps(oldProps,newProps){ let patches={} if(oldProps){ Object.keys(oldProps).forEach((prop)=>{ if(prop === "style" && newProps[prop]){ let newStyle = newProps[prop] let isSame = true Object.keys(oldProps[prop]).forEach((item)=>{ if(prop[item] !== newStyle[item]){ isSame = false } }) if(isSame){ Object.keys(newStyle).forEach((item)=>{ if(!prop.hasOwnProperty(item)){ isSame = false } }) } if(!isSame) patches[prop] = newProps[prop] } if(newProps[prop] !== oldProps[prop]){ patches[prop] = newProps[prop] } }) } if(newProps){ Object.keys(newProps).forEach((prop)=>{ if(!oldProps.hasOwnProperty(prop)){ patches[prop] = newProps[prop] } }) } return patches }
下面是比較子節(jié)點的方法,子節(jié)點的更新分為增加子節(jié)點,刪除子節(jié)點和移動子節(jié)點三種操作。對于子節(jié)點的操作將被記錄在父節(jié)點的directives上。
function diffChildren(oldChildren,newChildren,parentKey){ oldChildren = oldChildren || [] newChildren = newChildren || [] let movedItem = [] let oldKeyIndexObject = parseNodeList(oldChildren) let newKeyIndexObject = parseNodeList(newChildren) for(let key in newKeyIndexObject){ if(!oldKeyIndexObject.hasOwnProperty(key)){ addDirectives(parentKey,{type:INSERT,index:newKeyIndexObject[key],node:newChildren[newKeyIndexObject[key]]}) } } for(let key in oldKeyIndexObject){ if(newKeyIndexObject.hasOwnProperty(key)){ if(oldKeyIndexObject[key] !== newKeyIndexObject[key]){ let moveObj = {"oldIndex":oldKeyIndexObject[key],"newIndex":newKeyIndexObject[key]} movedItem[newKeyIndexObject[key]] = oldKeyIndexObject[key] } diffVNode(oldChildren[oldKeyIndexObject[key]],newChildren[newKeyIndexObject[key]]) } else { addDirectives(key,{type:REMOVE,index:oldKeyIndexObject[key]}) } } if(movedItem.length>0){ addDirectives(parentKey,{type:MOVE, moved:movedItem}) } }
在經(jīng)過Diff方法后,我們將得到我們傳入的oldNode與newNode的比較結(jié)果,并記錄在Directives對象中。
PatchPatch主要做的是通過我們之前的比較得到的Directives對象來修改Dom樹。在Patch方法中如果該節(jié)點涉及到更新,將會調(diào)用applyPatch方法。
export default function patch(node,directives){ if(node){ var orderList = [] for(let child of node.childNodes){ patch(child,directives) } if(directives[node.key]){ applyPatch(node,directives[node.key]) } } }
applyPatch方法主要對具體的Dom節(jié)點進行修改。根據(jù)directives的不同類型,調(diào)用不同的方法進行更新。
function applyPatch(node, directives){ for(let directive of directives){ switch (directive.type){ case TEXT: setContent(node,directive.content) break case PROP: setProps(node,directive.content) break case REMOVE: removeNode(node) break case INSERT: insertNode(node,directive.node,directive.index) default: break } } }
具體的更新方法是通過js來操作DOM節(jié)點進行操作。
完整代碼
推薦一個找vue,angular組件的輪子工廠
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/92517.html
摘要:具體而言,就是每次數(shù)據(jù)發(fā)生變化,就重新執(zhí)行一次整體渲染。而給出了解決方案,就是。由于只關(guān)注,通過閱讀兩個庫的源碼,對于的定位有了更深一步的理解。第二個而且,技術(shù)本身不是目的,能夠更好地解決問題才是王道嘛。 前言 React 好像已經(jīng)火了很久很久,以致于我們對于 Virtual DOM 這個詞都已經(jīng)很熟悉了,網(wǎng)上也有非常多的介紹 React、Virtual DOM 的文章。但是直到前不久...
摘要:本文為筆者通過實際操作,實現(xiàn)了一個非常簡單的,加深對現(xiàn)今主流前端框架中的理解。用對象表示樹是用對象表示,并存儲在內(nèi)存中的。如果類型不一致,那么屬性一定是被更新的。如果有不相等的屬性,則認為發(fā)生改變,需要處理的變化。 眾所周知,對前端而言,直接操作 DOM 是一件及其耗費性能的事情,以 React 和 Vue 為代表的眾多框架普遍采用 Virtual DOM 來解決如今愈發(fā)復(fù)雜 Web ...
摘要:變化的只有種更新和刪除。頁面的元素的數(shù)量隨著而變。四總結(jié)本文詳細介紹如何實現(xiàn)一個簡單的算法,再根據(jù)計算出的差異去更新真實的。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React 和 Vue,都不約而同的借助 Virtual DOM 技術(shù)提高頁面的渲染...
摘要:不同的框架對這三個屬性的命名會有點差別,但表達的意思是一致的。它們分別是標簽名屬性和子元素對象。我們先來看下頁面的更新一般會經(jīng)過幾個階段。元素有可能是數(shù)組的形式,需要將數(shù)組解構(gòu)一層。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約...
摘要:目錄前言問題的提出模板引擎和結(jié)合的實現(xiàn)編譯原理相關(guān)模版引擎的詞法分析語法分析與抽象語法樹代碼生成完整的結(jié)語前言本文嘗試構(gòu)建一個前端模板引擎,并且把這個引擎和進行結(jié)合。于是就構(gòu)思了一個方案,在前端模板引擎上做手腳。 作者:戴嘉華 轉(zhuǎn)載請注明出處并保留原文鏈接( https://github.com/livoras/blog/issues/14 )和作者信息。 目錄 前言 問題的提出...
摘要:最后里面沒有第四個元素了,才會把蘋果從移除。四總結(jié)本文基于上一個版本的代碼,加入了對唯一標識的支持,很好的提高了更新數(shù)組元素的效率。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術(shù)提高頁面的渲染...
閱讀 2785·2021-11-04 16:15
閱讀 3458·2021-09-29 09:35
閱讀 4032·2021-09-22 15:45
閱讀 1417·2019-08-30 15:55
閱讀 1689·2019-08-30 15:44
閱讀 2711·2019-08-29 12:56
閱讀 2698·2019-08-26 13:30
閱讀 2169·2019-08-23 17:00