我来了……

用JavaScript自己写Virtual DOM

用JavaScript自己写virtual DOM

课程地址: http://t.cn/REeKJp0
Github代码: https://github.com/chalecao/virtualdom

前端进阶系列课程

《用JavaScript自己写MVVM前端框架》:http://t.cn/REeKJp0

《前端函数式编程FP易学易用》:http://t.cn/REeKVSk

《前端自己用NodeJS编写区块链BlockChain》:http://t.cn/REeoF7v

《程序语言进阶之DSL与AST实战解析》:http://t.cn/R3XoQJA

适用人群

帮助前端感兴趣的同学理解Virtual DOM相关知识,需要有HTML基础和JavaScript基础知识(含ES5和ES6)即可。

课程概述

关于MVVM前端框架大家都有了解,或多或少的使用过,比如Angular,React,VUE等等。那么你是否也想自己手写一个MVVM的前端框架呢,我们从Virtual DOM入手,手把手教你写基于Virtual DOM的前端框架,在整个编写的过程中,希望大家学习更多,理解更多。

学完本课程,推荐本系列下个专题《函数式编程FP易学易用》,让你理解更深刻,编程水平更上一层楼。

喜爱编程的小伙伴们,我希望您和我一样都是行动派。我们做事踏踏实实、风风火火,要做就做最好,不吹牛x。习大大教导我们,只要技术扎实,到哪都能吃麻辣香锅!!!

课程大纲

章节1: 认识DOM与VirtualDOM

真实的DOM是网页上的文档对象模型,由一个个HTML元素节点构成的树形结构。
如图中所示,我们用JS创建出来的节点就是虚拟节点,Virtual node,当然由这些虚拟节点vd构成的树形结构就称为虚拟DOM,Virtual DOM。我们本节课介绍的就是要如何创建这样的虚拟DOM。

章节2: 如何构建VirtualDOM

    课时2  构建Virtual Node

首先我们需要分析一个node节点的构成,比如他的节点类型type,节点属性的集合props,子元素的集合。这样我们就可以抽象一个数据模型来表示这个节点。虚拟DOM是由许多虚拟节点按照层级结构组合起来的,那么我们实现虚拟节点的数据模型抽象之后,就可以构建虚拟DOM的数据模型抽象。

    课时3  构建VirtualDOM

手工实现DOM模型构建不太合理,我们可以借助JSX的工具来完成这个转换。本节我们以rollup打包工具结合babel转换插件实现数据的抽象。具体代码配置参考:github中package.json配置和rollup.config.js

const vdom = (
    <div id="_Q5" style="border: 1px solid red;">
        <div style="text-align: center; margin: 36px auto 18px; width: 160px; line-height: 0;">
            <img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png" height="56" style="border: none; margin: 8px 0px;"></img>
            hello
        </div>
    </div>)

上面我们定义的vdom片段采用JSX处理器处理后如下面代码:

/* fed123.com */
'use strict';

var vdom = vnode(
    "div",
    { id: "_Q5", style: "border: 1px solid red;" },
    vnode(
        "div",
        { style: "text-align: center; margin: 36px auto 18px; width: 160px; line-height: 0;" },
        vnode("img", { src: "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png", height: "56", style: "border: none; margin: 8px 0px;" }),
        "google"
    )
);

是不是很好理解,JSX编译后会自动根据定义好的语法格式提取出元素的类型和属性和子元素,并填入vnode方法中,我们只需要实现vnode方法就可以。我们可以编写vnode方法用于构建虚拟节点的模型,编写createElement方法用于根据vnode模型创建元素。并且把vnode的子元素追加到父元素上,形成树形层级结构。

function vnode(type, props, ...children) {
    return { type, props, children };
  }

function createElement(node) {
    if (typeof node === 'string') {
        return document.createTextNode(node);
    }
    const $el = document.createElement(node.type);
    node.children
        .map(createElement)
        .forEach($el.appendChild.bind($el));
    return $el;
}
document.body.appendChild(createElement(vdom));

这样我们就完成了虚拟节点vnode和虚拟vDOM的构建。

章节3: Diff VirtualDOM 与Update DOM

如图展示了最简单的一层DOM的结构变化,无非也就这么几种:增加元素节点、修改节点,删除节点。我们可以基于DOM API来实现这些基本的操作,代码如下:

function updateElement($parent, newnode, oldnode) {
    var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;

    if (!newnode) {
        $parent.removeChild($parent.childNodes[index]);
    } else if (!oldnode) {
        $parent.appendChild(createElement(newnode));
    } else if (isChange(newnode, oldnode)) {
        $parent.replaceChild(createElement(newnode), $parent.childNodes[index]);
    } else if (newnode.type) {
        var newL = newnode.children.length;
        var oldL = oldnode.children.length;
        for (var i = 0; i < newL || i < oldL; i++) {
            updateElement($parent.childNodes[index], newnode.children[i], oldnode.children[i], i);
        }
    }
}

上面的代码中我们实际上是把diff VirtualDOM 和update vdom放在一起处理了,采用了深度优先遍历的算法,从根节点优先查到子节点,判断子节点是否变化,有变化就进行变更处理,然后再回到上级节点。

章节4: 处理DOM属性和事件绑定

{
  type: “div”,
  props: {“style”: ”…”}, 
  children: [
      {type: “img”, props: {“src”: ”…”}
]}

上面我们抽取的vnode的模型中已经把props拿出来了,我们这里需要把这些样式设置到对应元素上就好了。我们先看下元素的属性变化有哪几种情况:

如上,元素属性可以增加可以减少,我们通过DOM API实现属性的更新操作,代码如下:

//handle props

function setProp($el, name, value) {
    if (typeof value == "boolean") {
        handleBoolProp($el, name, value);
    } else {
        $el.setAttribute(name, value);
    }
}
function handleBoolProp($el, name, value) {
    if (!!value) {
        $el.setAttribute(name, value);
        $el[name] = !!value;
    } else {
        $el[name] = !!value;
    }
}
function removeProp($el, name, value) {
    if (typeof value == "boolean") {
        $el[name] = false;
    }
    $el.removeAttribute(name, value);
}
function updateProp($el, name, newvalue, oldValue) {
    if (!newvalue) {
        removeProp($el, name, oldValue);
    } else if (!oldValue || newvalue != oldValue) {
        setProp($el, name, newvalue);
    }
}
function updateProps($el) {
    var newprops = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    var oldProps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

    var _props = Object.assign({}, newprops, oldProps);
    Object.keys(_props).forEach(function (key) {
        updateProp($el, key, newprops[key], oldProps[key]);
    });
}

代码比较长,但是思路很清晰的,就是更新元素的属性,中间对于一些特殊bool类型属性做了特殊处理,bool类型的属性用元素可以直接访问的,所以我们把这些布尔属性的值也挂到了元素上。然后我们在更新元素的时候就可以先更新下属性。

function updateElement($parent, newnode, oldnode, index = 0) {
    if (!newnode) {
        $parent.removeChild($parent.childNodes[index])
    } else if (!oldnode) {
        $parent.appendChild(createElement(newnode))
    } else if (isChange(newnode, oldnode)) {
        $parent.replaceChild(createElement(newnode),
            $parent.childNodes[index])
    } else if (newnode.type) {
        updateProps($el, newnode.props, oldnode.props)
        let newL = newnode.children.length;
        let oldL = oldnode.children.length;
        for (var i = 0; i < newL || i < oldL; i++) {
            updateElement($parent.childNodes[index],
                newnode.children[i],
                oldnode.children[i],
                i);
        }
    }
}

 

课时6  处理Virtual DOM属性方法与思路

 

章节5: 封装组件与工程化应用

未经允许不得转载:前端学堂fed123 » 用JavaScript自己写Virtual DOM

分享到:更多 ()