最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 从0到1: 实现一个虚拟 DOM(上)

    正文概述 掘金(零和幺)   2021-02-03   485

    写在前面

    本文分上下两篇,实现一个基础版本的虚拟 DOM。

    上篇首先介绍什么是虚拟 DOM、为什么要使用虚拟 DOM,其次完成项目创建、实现 h 函数、render 函数以及 mount 函数,完成创建虚拟 DOM 到挂载到页面成为真实 DOM的过程;下篇将通篇介绍虚拟 DOM 的核心,diff 算法的实现。

    本文上篇代码已上传GitHub。

    目录

    • 1 什么是虚拟 DOM
    • 2 为什么要使用虚拟 DOM
    • 3 虚拟 DOM 实现
      • 3.1 项目创建
      • 3.2 h 函数:用 JS 对象模拟 DOM 树
      • 3.2 render 函数:实现渲染
      • 3.3 mount 函数:实现挂载
      • 3.4 diff 算法

    1. 什么是虚拟 DOM

    用 JavaScript 对象描述真实的 DOM 结构,就是虚拟 DOM。

    从0到1: 实现一个虚拟 DOM(上)

    <div id="app">
      	<h1>hello world!</h1>
    </div>
    

    因为真实 DOM 本身天然具有树状结构,因此用 JavaScript 对象描述非常容易。在这个对象中,我们只需要使用 tagNamepropschildren 三个属性就可以完整的描述上面的 HTML 结构:

    {
        tagName: 'div',
        props: {
          	id: 'app'
        },
        children: [
          	{
              	tagName: 'h1',
          		props: {},
               	children: ['hello world!']
            }
        ]
    }
    

    上面的代码就是一个虚拟 DOM 了,简单吧!

    从0到1: 实现一个虚拟 DOM(上)

    2. 为什么要使用虚拟 DOM?

    第一个原因:DOM 操作太慢了。这里的慢有两方面,一是性能低,页面慢。二是手动操作 DOM,效率低,从而导致开发节奏慢。

    我们可以在浏览器打开一个空白页面:about:blank,打开控制台,输入以下代码:

    const div = document.createElement('div')
    let str = ''
    
    for (let key in div) {
        str = str + key + ','
    }
    
    console.log(str)
    

    此时你会看到:

    从0到1: 实现一个虚拟 DOM(上)

    仅仅一个 div 元素的属性就这么庞大,可想而知为什么操作 DOM 的花销会及其巨大了。

    从0到1: 实现一个虚拟 DOM(上)

    而相比于 DOM 对象,JavaScript 对象处理起来就非常快了,再加上 diff 算法,找出最小差异,然后进行批量 patch。这样我们可以极大的减少真实的 DOM 操作,减少重排,提升性能。

    第二个原因:真实 DOM 与浏览器强相关,而虚拟 DOM 本质上是 JavaScript 对象,JavaScript 对象能够更方便地进行跨平台操作。如服务端渲染,移动端开发等等。

    3. 虚拟 DOM 实现

    说了这么多理论,我们就开始 step by step,一步步实现一个虚拟 DOM 吧。

    从0到1: 实现一个虚拟 DOM(上)

    3.1 项目创建

    首先,我们创建项目目录:

    $ mkdir virtual-dom-lite
    $ cd virtual-dom-lite
    

    然后,我们进行 npm 初始化

    $ npm init -y
    

    最后,我们安装一下 Parcel Bundler,一个无需配置的小型项目打包器。安装后可以启动一个 dev-server,并具有热更新功能。

    $ npm install parcel-bundler
    

    安装好依赖后,创建一个 src 目录,在 src 目录下,创建两个文件:

    src/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Virtual DOM</title>
    </head>
    <body>
        hello world
    </body>
    <script src="./main.js"></script>
    </html>
    

    src/main.js

    const vApp = {
        tagName: 'div',
        props: {
            id: 'app'
        }
    }
    
    console.log(vApp)
    

    最后修改 package.json。

    package.json

    {
      	"scripts": {
          	"dev": "parcel src/index.html"
        }
    }
    

    在终端输入 npm run dev 就可以启动我们的项目啦。

    3.2 h 函数:用 JS 对象模拟 DOM 树

    一般情况下,我们都使用 createElement 方法创建虚拟 DOM。在绝大多数虚拟 DOM 库以及流行框架中,通常用 h 函数指代,因此我们也来创建一个 h 函数。

    还记得 JavaScript 对象中用哪三个属性就可以描述 DOM 树么?对,就是 tagNameprops 以及 children。其中 propschildren 是可选项,因为想要表示一个 DOM 结构,标签是必选项,但可以没有属性,也可以没有子元素。

    src/vdom/creatElement.js

    export default (tagName, opts) => {
        const vElement = Object.create(null)
    
        Object.assign(vElement, {
            tagName,
            props: opts.props || {},
            children: opts.children || []
        });
        
        return vElement;
    }
    
    

    使用对象解构后,可以优化上面的代码

    src/vdom/createElement.js

    export default (tagName, { props = {}, children = [] } = {}) => {
        const vElement = Object.create(null)
    
        Object.assign(vElement, {
            tagName,
            props,
            children
        });
        
        return vElement;
    }
    

    src/main.js

    import h from './vdom/createElement'
    
    const vApp = h('div', {
        props: {
            id: 'app'
        }
    })
    
    console.log(vApp)
    

    这样,在浏览器的控制台中,我们就可以看到虚拟 DOM 对象啦。

    从0到1: 实现一个虚拟 DOM(上)

    3.2 render 函数:实现渲染

    有了虚拟 DOM 对象,那么如何将它转化为真正的 DOM 呢?这就是渲染函数要干的事情。

    在这里我们仅针对元素节点文本节点进行渲染。

    src/vdom/render.js

    const render = (vNode) => {
        // 如果字符串,认为是文本节点,我们创建一个一个文本节点
        if (typeof vNode === 'string') return document.createTextNode(vNode)
        
        // 其他情况默认为元素节点
    
        // 创建一个元素
        const $el = document.createElement(vNode.tagName)
    
        // 添加虚拟 DOM 对象上所有的属性
        for (const [key, value] of Object.entries(vNode.props)) {
            $el.setAttribute(key, value)
        }
    
        // 如果虚拟 DOM 上有子元素,则追加(这里其实有一个递归思想)
        for (const child of vNode.children) {
            $el.appendChild(render(child))
        }
    
        return $el
    }
    
    export default render
    

    回到 main.js 文件,我们尝试用 render 函数渲染一下我们的虚拟 DOM,并在控制台打印一下:

    src/main.js

    import h from './vdom/createElement'
    import render from './vdom/render'
    
    const vApp = h('div', {
        props: {
            id: 'app'
        },
        children: [
            h('h1', {
                props: {
                    id: 'title'
                },
                children: ['hello world!']
            })
        ]
    })
    
    const $app = render(vApp)
    
    console.log($app)
    

    此时,在控制台打印,可以看到以下结果:

    从0到1: 实现一个虚拟 DOM(上)

    3.3 mount 函数:实现挂载

    现在,我们可以创建虚拟 DOM 并渲染其为真实 DOM 了。下一步就是激动人心的时刻 —— 将渲染出来的真实 DOM 挂载到页面上!!!

    我们首先修改 index.html 页面

    src/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Virtual DOM</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
    <script src="./main.js"></script>
    </html>
    

    然后编写我们的挂载函数:

    src/vdom/mount.js

    export default ($node, $target) => {
        return $target.appendChild($node)
    }
    

    src/main.js

    import h from './vdom/createElement'
    import render from './vdom/render'
    import mount from './vdom/mount'
    
    const vApp = h('div', {
        props: {
            id: 'app'
        },
        children: [
            h('h1', {
                props: {
                    id: 'title'
                },
                children: ['hello world!']
            })
        ]
    })
    
    const $app = render(vApp)
    
    mount($app, document.getElementById('root'))
    

    从0到1: 实现一个虚拟 DOM(上)

    终于,我们的页面可以出现自己渲染的虚拟 DOM 啦!里程碑!扭起来!

    从0到1: 实现一个虚拟 DOM(上)

    高兴的同时,其实现在的页面还是有一些的问题的:当我们需要修改页面时,无论是增删改元素、属性或者子元素,我们只能全量渲染和挂载,这又会造成不必要的性能损耗,因此,整个实现虚拟 DOM 过程中最重要的 diff 算法要出场了。

    因为 diff 算法是整个虚拟 DOM 实现的核心,篇幅略长,我们在下一篇重点实现。

    写在最后

    你的点赞会给我一天好心情,如果能顺手 来个 star,再顺便关注下公众号 零幺小馆 就更完美了。


    起源地下载网 » 从0到1: 实现一个虚拟 DOM(上)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元