javascript --- > 將DOM結構轉換成虛擬DOM && 虛擬DOM轉換成真實的DOM結構

虛擬DOM的實現

使用虛擬DOM的原因: 減少迴流與重繪

將DOM結構轉換成對象保存到內存中

<img /> => { tag: 'img'}

文本節點 => { tag: undefined, value: '文本節點' }

<img title="1" class="c" /> => { tag: 'img', data: { title = "1", class="c" } }

<div><img /></div> => { tag: 'div', children: [{ tag: 'div' }]}

根據上面可以寫出虛擬DOM的數據結構

class VNode {
    constructor(tag, data, value, type) {
        this.tag = tag && tag.toLowerCase()
        this.data = data
        this.value = value
        this.type = type
        this.children = []
    }
    appendChild(vnode){
        this.children.push(vnode)
    }
}

可能用到的基礎知識

  • 判斷元素的節點類型: node.nodeType
let nodeType = node.nodeType
if(nodeType == 1) {
    // 元素類型
} else if (nodeType == 3) {
	// 節點類型
}
  • 獲取元素類型的標籤名和屬性 && 屬性中具體的鍵值對,保存在一個對象中
let nodeName = node.nodeName	// 標籤名
let attrs  = node.attributes	// 屬性
let _attrObj = {}	// 保存各個具體的屬性的鍵值對,相當於虛擬DOM中的data屬性
for(let i =0, len = attrs.length; i< len; i++){
    _attrObj[attrs[i].nodeName] = attrs[i].nodeValue
}
  • 獲取當前節點的子節點
let childNodes = node.childNodes
for(let i = 0, len = childNodes.length; i < len; i++){
    console.log(childNodes[i])
}

算法思路

  • 使用document.querySelector獲取要轉換成虛擬DOM的模板
  • 使用nodeType方法來獲取是元素類型還是文本類型
  • 若是元素類型
    • 使用nodeName獲取標籤名
    • 使用attributes獲取屬性名,並將具體的屬性保存到一個對象_attrObj
    • 創建虛擬DOM節點
    • 考慮元素類型是否有子節點,使用遞歸,將子節點的虛擬DOM存入其中
  • 若是文本類型
    • 直接創建虛擬DOM,不需要考慮子節點的問題
// 虛擬DOM的數據結構
class VNode{
    constrctor(tag, data, value, type){
        this.tag = tag && tag.toLowerCase()
        this.data = data
        this.value = value
        this.type = type
        this.children = []
    }
    appendChild(vnode) {
        this.children.push(vnode)
    }
}

// 獲取要轉換的DOM結構
let root = document.querySelector('#root')
// 使用getVNode方法將 真實的DOM結構轉換成虛擬DOM
let vroot = getVNode(root)	

以上寫了虛擬DOM的數據結構,以及使用getVNode方法將真實DOM結構轉換成虛擬DOM,下面開始逐步實現getVNode方法

  • 判斷節點類型,並返回虛擬DOM
function getVNode(node){
    // 獲取節點類型
    let nodeType = node.nodeType;
    if(nodeType == 1){
        // 元素類型: 獲取其屬性,判斷子元素,創建虛擬DOM
    } else if(nodeType == 3) {
      // 文本類型: 直接創建虛擬DOM
    }
    let _vnode = null;
    return _vnode
}
  • 下面根據元素類型和文本類型分別創建虛擬DOM
if(nodeType == 1){
    // 標籤名
    let tag = node.nodeName
    // 屬性
    let attrs = node.attributes
    /*
     屬性轉換成對象形式: <div title ="marron" class="1"></div>
     { tag: 'div', data: { title: 'marron', class: '1' }}
    */
    let _data = {};   // 這個_data就是虛擬DOM中的data屬性
    for(let i =0, len = attrs.length; i< attrs.len; i++){
        _data[attrs[i].nodeName] = attrs[i].nodeValue
    }
    // 創建元素類型的虛擬DOM
    _vnode = new VNode(tag, _data, undefined, nodeType)
    
    // 考慮node的子元素
    let childNodes = node.childNodes
    for(let i =0, len = childNodes.length; i < len; i++){
        _vnode.appendChild(getVNode(childNodes[i]))
    }
}
// 接下來考慮文本類型
else if(nodeType == 3){
    _vnode = new VNode(undefined, undefined, node.nodeValue, nodeType)
}

總體代碼

class VNode {
    constructor(tag, data, value, type) {
        this.tag = tag && tag.toLowerCase()
        this.data = data
        this.value = value
        this.type = type
        this.children = []
    }
    appendChild(vnode){
        this.children.push(vnode)
    }
}

function getVNode(node) {
  let nodeType = node.nodeType
  let _vnode = null
  if (nodeType == 1) {
    let tag = node.nodeName
    let attrs = node.attributes
    let _data = {}
    for (let i = 0, len = attrs.length; i < len; i++) {
      _data[attrs[i].nodeName] = attrs[i].nodeValue
    }
    _vnode = new VNode(tag, _data, undefined, nodeType)

    let childNodes = node.childNodes
    for (let i = 0, len = childNodes.length; i < len; i++) {
      _vnode.appendChild(getVNode(childNodes[i]))
    }
  } else if (nodeType == 3) {
    _vnode = new VNode(undefined, undefined, node.nodeValue, nodeType)
  }
  return _vnode
}

let root = document.querySelector('#root')
let vroot = getVNode(root)	
console.log(vroot)

將虛擬DOM轉換成真實的DOM結構

此過程就是上面的反過程

可能用到的知識點

  • 創建文本節點
document.createTextNode(value)
  • 創建元素節點
document.createElement(tag)
  • 給元素節點添加屬性
node.setAttribute(attrName, attrValue)
  • 給元素節點添加子節點
node.appendChild(node)

算法思路

  • 虛擬DOM的結構中,元素的節點類型存儲在type中,根據type可以判斷出是文本節點還是元素節點
  • 若爲文本節點,直接返回一個文本節點return document.createTextNode(value)
  • 若爲元素節點
    • 創建一個node節點:_node = document.createElement(tag)
    • 遍歷虛擬DOM中的data屬性,將其中的值賦給node節點
    • 給當前節點添加子節點

具體實現

function parseVNode(vnode){
    let type = vnode.type
    let _node = null
    if(type == 3){
        return document.createTextNode(vnode.value)
    } else if (type == 1){
        _node = document.createElement(vnode.tag)
        
        let data = vnode.data
        let attrName,attrValue
        Object.keys(data).forEach(key=>{
            attrName = key
            attrValue = data[key]
            _node.setAttribute(attrName, attrValue)
        })
        // 考慮子元素
        let children = vnode.children
        children.forEach( subvnode =>{
            _node.appendChild(parseVNode(subvnode))
        })
    }
    return _node
}

驗證:

let root = querySelector('#root')
let vroot = getVNode(root)
console.log(vroot)
let root1 = parseVNode(vroot)
console.log(root1)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章