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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章