虛擬DOM
虛擬DOM其實就是在真實DOM之前加了一層JS對象生成的DOM
- 用JS對象模擬DOM
- 把這個虛擬DOM對象轉爲真實的DOM插入到頁面中
- 如果有事件修改了虛擬DOM,比較兩個虛擬DOM樹的差異,得到差異對象(補丁)
- 把差異對象應用到正則的DOM樹上
diff算法
- Diff 比較兩個虛擬DOM的區別(比較兩個對象的區別)
- 根據兩個虛擬對象的區別,創建出補丁(patch),描述改變的內容,將這個補丁用來更新頁面
- 差異計算: 先序深度優先遍歷
diff算法的三種優化策略
- 比較同級的節點(同一父節點的子節點)
- 當發現節點已經不存在,則該節點及其子節點會被完全刪除掉,不會用於進一步的比較。這樣只需要對樹進行一次遍歷,便能完成整個DOM樹的比較。對於不同層的節點,只有簡單的創建和刪除。 - 相同類型的節點比較:對屬性進行重設實現節點的轉換
renderA: <div style={{color: 'red'}} />
renderB: <div style={{fontWeight: 'bold'}} />
=> [removeStyle color], [addStyle font-weight 'bold']
3. 列表節點的比較(key存在的意義)
- 添加、刪除、排序
- 順序的調整類似插入/刪除
模擬一個虛擬DOM
// 節點類
class Element{
constructor(type,props,children){
this.type= type;
this.props= props;
this.children= children;
}
}
// 生成節點類實例
function createElement(type,props,children){
return new Element(type,props,children)
}
// 創建一個虛擬DOM對象
let virtualDOM= createElement('ul',{class: 'list'},[
createElement('li',{class: 'item'},['a']),
createElement('li',{class: 'item'},['b']),
createElement('li',{class: 'item'},['c'])
])
console.log(virtualDOM)
// 設置屬性的方法
function setAttr(node,key,value){
switch(key){
case 'value': // node是input或者textarea
if(node.tagName.toUpperCase()=== 'INPUT' || node.tagName.toUpperCase()=== 'TEXTAREA'){
node.value= value
}else{
node.setAttribute(key,value)
}
break;
case 'style':
node.style.cssText= value;
break;
default:
node.setAttribute(key,value)
break;
}
}
// render方法將vnode 轉化成真實的dom
function render(eleObj){
let el= document.createElement(eleObj.type); //生成父節點
// 遍歷父節點的props添加屬性
for(let key in eleObj.props){
// 設置屬性的方法
setAttr(el,key,eleObj.props[key])
}
// 遍歷父節點的children
eleObj.children.forEach(item => {
item= (item instanceof Element)? render(item) : document.createTextNode(item) //創建文本節點
el.appendChild(item)
});
return el
}
let el= render(virtualDOM)
console.log(el)
// 將元素插入到頁面
function renderDOM(el,target){
target.appendChild(el)
}
renderDOM(el,document.body)
模擬Diff算法
未完待續。。。