资讯专栏INFORMATION COLUMN

自己实现一个简单的虚拟 DOM

luffyZh / 1293人阅读

摘要:直到内部的全部循环结束为止,才进入下一个元素,当循环结束时,内部的节点都已经生成好了。

自己实现虚拟 DOM 从 HTML 中提炼数据结构

先来看下我们的 HTML

傅雷家书

读家书,想付雷

从 HTML 中我们可以抽离出它的数据结构:

首先页面中只需要一个根节点root,定义为:nodesDate数组

root内有两个子元素h1span,数组有两项,每项为内容为tagchildren

接下来内部所有元素都是如此定义,直到遇到文本元素,将他定义为text

nodesDate = {
    tag:"div",
    children:[{
        tag:"h1",
        children:[{
            tag:"span",
            children:[{
                tag:"#text",
                text:"傅雷家书"
            }]
        }]
    },{
        tag:"span",
        children:[{
            tag:"#text",
            text:"读家书,想傅雷"
        }]
    }]
}

用这种视野在看 HTML 的话,就不是单纯的 HTML 了,而是一堆hash

从上面数据结构中我们可以提炼出3个有用的属性,分别是tagchildrentext,那我们是不是可以定义一个方法,传递这三个参数,就能满足我们的需求呢?

构造 HTML 方法

想一下我们拿到这三个参数后,要干什么呢?

当然是在页面中生成 DOM 元素啊!

对,这三个参数是我们各自私有属性,通过这三个属性能生成各自的 DOM,生成 DOM 的方法是不是公用的呢?

所以可以用用构造函数模式创建我们要的方法,(PS:之前讲过new操作符的背后的逻辑,不理解的可移步:使用 new 操作符内部到底在做什么)

function vNode(tag,children,text){
    this.tag = tag
    this.children = children
    this.text = text
}

vNode.prototype.render = function(){
    //如 tag 为文本的话,创建一个文本节点
    if(this.tag === "#text"){
        return document.createTextNode(this.text)    // 返回文本
    }
    
    //tag 不是文本的话,创建一个 DOM 节点,并且遍历 children,每次遍历都是调用自身的 render 方法
    let element = document.createElement(this.tag)
    this.children.forEach((vChild)=> {
        element.appendChild(vChild.render())    //在遍历 h1 时,没有直接跳出,而是在其内部不断循环。直到 h1 内部的 children 全部循环结束为止,才进入下一个元素 span,当 h1 循环结束时,h1 内部的节点都已经生成好了。
    })
    return element        //返回节点
}

function v(tag,children,text){
    //如果 chilren 为字符串,那么就把 children 赋值给 text,并把 children 初始化为 [],不然后面会报错
    if(typeof children === "string"){
        text = children
        children = []
    }
    return new vNode(tag,children,text)
}

//格式参见 nodesData,vNode 的实例化
let vNodes = v("div",[
    v("h1",[
        v("span",[
            v("#text","傅雷家书")])
    ]),
    v("span",[
        v("#text","——傅敏")])
])

const root = document.querySelector(".root")    //获取 root 节点
root.appendChild(vNodes.render())    //这里只运行一次,把最终的 DOM 添加进页面中
实现增删改

如果此时一个数据变动比如,按照以前的逻辑

root.innerText = ""
root.appendChild(vNodes.render())

如果数据非常大,用这种方法根本没啥意义,每一次改动 DOM 树都要重新渲染一遍,造成性能低下,有什么好的方法可以实现呢?

function patchElement(parent, newVNodes, oldVNodes, index = 0) {
    //如果没有传递老的 VNodes,默认就是新的
    if(!oldVNodes) {
        parent.appendChild(newVNodes.render())
      } else if(!newVNodes) {
        parent.removeChild(parent.childNodes[index])
      } else if(newVNodes.tag !== oldVNodes.tag || newVNodes.text !== oldVNodse.text) {    
          //如果元素不一样或者文本不一样,走这边
          //当有走这边时,newVNodes 是和 oldVNodes 不同的那个值,这里的 parent 是当前元素或文本的 parent
          //replaceChild(sp1,sp2),是将 sp2 换成 sp1
        parent.replaceChild(newVNodes.render(), parent.childNodes[index])
      } else {
        for(let i = 0; i < newVNodes.children.length || i < oldVNodes.children.length; i++) {
        //取值永远是 newVNodes.length,除非不传 newVNodes
        //这里 index 只有当 i 变化时,下一次才是 index 才等于 i 的值
        //当 i = 0 时,这次的 parent.childNode[index],是下一次的 parent,所以这里要用 index
        //当循环走完,发现元素或者文本不一样时,才走第三个逻辑
              patchElement(parent.childNodes[index], newVNodes.children[i], oldVNodes.children[i], i)
        }
      }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96998.html

相关文章

  • 虚拟DOM

    摘要:什么是虚拟举例说明如果网页中有一个表格,表头是姓名,年级,分数。即我们用虚拟的结构替换需要处理的结构,对虚拟的进行操作之后再进行渲染,就成为了真实的数据。当状态变更的时候用修改后的新渲染的的对象和旧的虚拟对象作对比,记录着两棵树的差异。 虚拟DOM 可以看看这个文章如何理解虚拟DOM? - 戴嘉华的回答 - 知乎 https://www.zhihu.com/questio... 深度剖...

    yanwei 评论0 收藏0
  • 虚拟DOM

    摘要:什么是虚拟举例说明如果网页中有一个表格,表头是姓名,年级,分数。即我们用虚拟的结构替换需要处理的结构,对虚拟的进行操作之后再进行渲染,就成为了真实的数据。当状态变更的时候用修改后的新渲染的的对象和旧的虚拟对象作对比,记录着两棵树的差异。 虚拟DOM 可以看看这个文章如何理解虚拟DOM? - 戴嘉华的回答 - 知乎 https://www.zhihu.com/questio... 深度剖...

    alin 评论0 收藏0
  • 从零开始实现一个React(一):JSX和虚拟DOM

    摘要:前言是前端最受欢迎的框架之一,解读其源码的文章非常多,但是我想从另一个角度去解读从零开始实现一个,从层面实现的大部分功能,在这个过程中去探索为什么有虚拟为什么这样设计等问题。 前言 React是前端最受欢迎的框架之一,解读其源码的文章非常多,但是我想从另一个角度去解读React:从零开始实现一个React,从API层面实现React的大部分功能,在这个过程中去探索为什么有虚拟DOM、d...

    曹金海 评论0 收藏0
  • 如何编写自己虚拟DOM

    摘要:要构建自己的虚拟,需要知道两件事。现在来看看如何处理上面描述的所有情况。代码如下节点的替换首先,需要编写一个函数来比较两个节点旧节点和新节点,并告诉节点是否真的发生了变化。总结现在我们已经编写了虚拟实现及了解它的工作原理。 showImg(https://segmentfault.com/img/bVbmPue?w=2000&h=684); 要构建自己的虚拟DOM,需要知道两件事。你甚...

    mushang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<