欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

javascript基础修炼(10)——VirtualDOM和基本DFS

程序员文章站 2022-05-03 13:06:32
1. Virtual DOM是什么 Virtual DOM,即虚拟DOM树。浏览器在解析文件时,会将 文档转换为 对象,在浏览器环境中运行的脚本文件都可以获取到它,通过操作 对象暴露的接口可以直接操作页面上的DOM节点。但是DOM读写是非常耗性能的,很容易触发不必要的重绘和重排,为了更好地处理DOM ......

javascript基础修炼(10)——VirtualDOM和基本DFS

1. virtual-dom是什么

virtual-dom,即虚拟dom树。浏览器在解析文件时,会将html文档转换为document对象,在浏览器环境中运行的脚本文件都可以获取到它,通过操作document对象暴露的接口可以直接操作页面上的dom节点。但是dom读写是非常耗性能的,很容易触发不必要的重绘和重排,为了更好地处理dom操作,virtual-dom技术就诞生了。virtual-dom就是在javascript中模拟真实dom的结构,通过数据追踪和状态对比来减少对于真实dom的操作,以此来提高程序的效率的一种技术。

virtual-dom技术是前端高性能的基石,它是真实document对象的抽象,通过对比新旧virtual-dom的区别,找出发生变化的dom节点,再利用算法得到相对更合理的dom节点修改方案,最终再将方案应用在document对象上来改变页面的展示内容。

主流前端spa框架都离不开【virtual-dom模型 + dom-diff算法 + 生命周期钩子】这样的核心模型。

2. virtual-dom的基本结构

在上一篇博文《javascript基础修炼(9)——mvvm中双向数据绑定的基本原理》中,我们通过document.getelementbyid()从真实dom中获得了带有自定义属性的待解析结构,这里是有一些问题的,实际的过程是先解析模板字符串得到虚拟dom树,最后生成真实的dom树。

实际上我们在使用spa框架时所编写的html模板,并没有被直接当做dom片段加载到页面上使用,而是将文件当做字符串读入到程序中,然后通过解析来生成virtual-dom树,接着通过spa框架的渲染函数来生成必要的片段后才生成真实的dom节点。例如我们要生成下文示例的html片段(为了方便演示,示例中只涉及了类名和文本节点):

<body class="main">
   <div class="sidebar">
       <ul class="sidebarcontainer">
           <li class="sidebaritem">sidebar-1</li>
           <li class="sidebaritem">sidebar-2</li>
           <li class="sidebaritem">sidebar-3</li>
       </ul>
   </div>
   <div class="maincontent">
        <div class="header">header-zone</div>
        <div class="corecontent">core-content</div>
        <div class="footer">footer-zone</div>
   </div>
   <div class="rightside">暂未开发</div>
</body>

我们需要构建出一个简易模型来表达上面的结构:

virtualdom = {
    name:"body",
    props:{
        classname:"main"
    },
    children:[{
        name:"div",
        props:{...},
        children:[...]
      },{
        name:"div",
        props:{...},
        children:[...]
      },{
        name:"div",
        props:{...},
        children:[...]
      }]
}

建立一个生成虚拟节点的辅助函数:

//构建dom节点的辅助函数
function h(name, props, children) {
    return {
        name:name,
        props:props,
        children:children
    }
}
//手动生成virtual-dom
var tree = h('body',{classname:'main'},[
       h('div',{classname:'sidebar'},[
          h('ul',{classname:'sidebarcontainer'},[
               h('li',{classname:'sidebaritem'},['sidebar-1']),
               h('li',{classname:'sidebaritem'},['sidebar-2']),
               h('li',{classname:'sidebaritem'},['sidebar-3']),
            ])
        ]),
       h('div',{classname:'maincontent'},[
           h('div',{classname:'header'},['header-zone']),
           h('div',{classname:'corecontent'},['core-content']),
           h('div',{classname:'footer'},['footer-zone']),
        ]),
       h('div',{classname:'rightside'},['暂未开发'])
    ]);

通过上面的方法得到的tree对象就涵盖了模板片段中的结构和关键信息。实际开发中并不需要像上面一样手动来填写dom结构,可以将模板字符串挂载到离线dom节点上,然后在递归解析的同时来构建virtual-dom就可以了。

3. 使用dfs从virtual-dom生成dom

至此我们完成了模板的编译,也得到了virtual-dom对象,但它似乎并没有什么用处,毕竟我们已经完成了对模板的解析,渲染出页面没什么问题,其实virtual-dom对于首屏来说并没有什么特别重要的意义,它的价值在模型和视图发生变化时才会体现。上一篇博文的末尾我们已经提到了更新视图时的效率问题,当数据模型发生变化后,我们需要一个方法来收集所有需要修改的dom,并为之提供高效的修改方式(你总不能一有变化就把整个网页重新渲染,或者让数据模型各自去修改各自绑定的dom吧)。那么为了能够收集所有dom节点的变化,我们就需要遍历所有节点。

对数据结构和算法有一定了解的读者很容易想到,遍历解析一个virtual-dom实际上就是对其进行先序深度优先遍历(pre-order depth-first-search),本节中,我们先预热一下,使用这种方式来复现一下dom结构。

function dfswalking(tree) {
    var _childrenlength;
    //执行动作
    if (typeof tree.children[0] === 'string') {
        console.log(`<${tree.name} class="${tree.props.classname}">${tree.children[0]}</${tree.name}>`);
    } else {
        console.log(`<${tree.name} class="${tree.props.classname}">  -->`);
        for(var i = 0, _childrenlength = tree.children.length; i < _childrenlength; i++){
            dfswalking(tree.children[i]);
        }
    }
}

本例中仅打印出字符串的方式来展示,可以在控制台看到输出结果:

javascript基础修炼(10)——VirtualDOM和基本DFS

下一篇博文中将分析如何通过domdiff(oldtree, newtree)的方法通过同样的遍历方法来收集变化并批量更新视图。

4. 声明

本篇只是部分原理的学习笔记,并不代表框架真实源码的实现逻辑。