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]);
        }
    }
}

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

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

4. 声明

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

原文地址:http://blog.51cto.com/13869008/2321387

时间: 2024-08-29 22:05:35

javascript基础修炼(10)——VirtualDOM和基本DFS的相关文章

javascript基础修炼(8)——指向FP世界的箭头函数

一. 箭头函数 箭头函数是ES6语法中加入的新特性,而它也是许多开发者对ES6仅有的了解,每当面试里被问到关于"ES6里添加了哪些新特性?"这种问题的时候,几乎总是会拿箭头函数来应付.箭头函数,=>,没有自己的this , arguments , super , new.target ,"书写简便,没有this"在很长一段时间内涵盖了大多数开发者对于箭头函数的全部认知(当然也包括我自己),如果只是为了简化书写,把=>按照function关键字来解析就好了

javascript基础修炼——一道十面埋伏的原型链面试题

javascript基础修炼--一道十面埋伏的原型链面试题 在基础面前,一切技巧都是浮云. 题目是这样的 要求写出控制台的输出. function Parent() { this.a = 1; this.b = [1, 2, this.a]; this.c = { demo: 5 }; this.show = function () { console.log(this.a , this.b , this.c.demo ); } } function Child() { this.a = 2;

javascript基础修炼(2)——What‘s this(上)

javascript基础修炼(2)--What's this(上) 开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. [TOC] 一.this是什么 this是javascript关键字之一,是javascript能够实现面向对象编程的核心概念.用得好能让代码优雅高端,风骚飘逸,用不好也绝对是坑人坑己利器.我们常常会在一些资料中看到对this的描述是: this是一个特殊的与Execution Contexts相关的对象,用于指明当前代码执行时的Execution

javascript基础修炼(7)——Promise,异步,可靠性

开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 别人是开发者,你也是 Promise技术是[javascript异步编程]这个话题中非常重要的,它一度让我感到熟悉又陌生,我熟悉其所有的API并能够在编程中相对熟练地运用,却对其中原理和软件设计思想感到陌生,即便我读了很多源码分析和教程也一度很难理解为什么Promise这样一个普通的类能够实现异步,也曾尝试着去按照Promise/A+规范来编写Promise,但很快便陷入了一种更大的混乱之中.直到我接触到一些软

javascript基础修炼(1)——一道十面埋伏的原型链面试题

在基础面前,一切技巧都是浮云. 题目是这样的 要求写出控制台的输出. function Parent() { this.a = 1; this.b = [1, 2, this.a]; this.c = { demo: 5 }; this.show = function () { console.log(this.a , this.b , this.c.demo ); } } function Child() { this.a = 2; this.change = function () { th

javascript基础修炼(5)—Event Loop

开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 一道考察异步知识的面试题 题目是这样的,要求写出下面代码的输出: setTimeout(() => { console.log(1) }, 0) new Promise((resolve, reject) => { console.log(2) for (let i = 0; i < 10000; i++) { i === 9999 && resolve() } console.log(

javascript基础修炼(12)——手把手教你造一个简易的require.js

目录 一. 概述 二. require.js 2.1 基本用法 2.2 细说API设计 三. 造轮子 3.1 模块加载执行的步骤 3.2 代码框架 3.3 关键函数的代码实现 示例代码托管在我的代码仓:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文目录 华为云社区地址:[你要的前端打怪升级指南] 一. 概述 许多前端工程师沉浸在使用脚手架工具的快感中,认为require.js这种前端模块化的库已经过气了,的确如果只从使用场

javascript基础修炼(6)——前端路由的基本原理

[造轮子]是笔者学习和理解一些较复杂的代码结构时的常用方法,它很慢,但是效果却胜过你读十几篇相关的文章.为已知的API方法自行编写实现,遇到自己无法复现的部分再有针对性地去查资料,最后当你再去学习官方代码的时候,就会明白这样做的价值,总有一天,你也将有能力写出大师级的代码. 一. 前端路由 现代前端开发中最流行的页面模型,莫过于SPA单页应用架构.单页面应用指的是应用只有一个主页面,通过动态替换DOM内容并同步修改url地址,来模拟多页应用的效果,切换页面的功能直接由前台脚本来完成,而不是由后端

JavaScript基础系列目录(2014.06.01~2014.06.08)

下列文章,转载请亲注明链接出处,谢谢! 链接地址: http://www.cnblogs.com/ttcc/tag/JavaScript%20%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%20%E6%80%BB%E7%BB%93/ 1. Javascript基础---语法(待完成) 2. JavaScript基础---数据类型(待完成) 3. JavaScript基础---Array数组(待完成) 4. JavaScript基础---正则表达式(待完成) 5. Jav