今天看啥  ›  专栏  ›  啦啦啦啦啦同志

关于虚拟dom的一些浅见

啦啦啦啦啦同志  · 掘金  ·  · 2021-03-18 14:54
阅读 16

关于虚拟dom的一些浅见

老文章了,从简书迁移过来的。

什么是虚拟DOM

已经9102年了,前端er或多或少都接触过一些mvvm库,了解有虚拟DOM这么个东西,可能你还没有深入了解过这个东西,其实很简单,来往下看。

虚拟DOM简而言之就是,用JS去按照DOM结构来实现的树形结构对象,也可以看做是一个JS对象而已。

结构思考

什么样的对象才能比较匹配的上dom结构呢?

来看一下如下图所示的dom结构

image.png

上图是比较简单的一个结构,比较理想的是使用如下对象来模拟:

{
    // 节点类型
    type: 'ul',
    // 节点的属性,包括dom原生属性和自定义属性
    props: {
        class: 'list',
        style: 'color:red;'
    },
    // 子节点数组
    // 子对象结构也是一样,包含了type,props,children,没有子节点的话就是普通文本
    // 子对象拥有子节点时,继续往下扩展就行
    children: [
        {type: 'li',props: {class: 'list'},children: ['利群']},
        {type: 'li',props: {class: 'list'},children: ['玉溪']},
        {type: 'li',props: {class: 'list'},children: ['黄鹤楼']}
    ]
}
复制代码

OK,对象结构已经解析完成

批量创建

我们需要一个类来帮助我们创建对象

// 定义一个构造函数来
class Element {
	constructor(type, props, children) {
		this.type = type;
		this.props = props;
		this.children = children;
	}
}

//批量调用构造函数
function createElement(type,props,children){
	return new Element(type,props,children);
}

let virtualDom = createElement('ul',{class:'list',style:'color:red;'},[
        createElement('li',{class:'item'},['利群']),
        createElement('li',{class:'item'},['玉溪']),
        createElement('li',{class:'item'},['黄鹤楼']),
    ]);

// 对象形势的结构展示
console.log(virtualDom);
复制代码

OK,查看打印出来的对象是这样的 image.png

将对象变现

对象有了,那么就该生成dom了

// 把虚拟对象转成dom对象
function render(domObj) {
    // 根据元素类型来创建dom
    let el = document.createElement(domObj.type);

    // 遍历props对象,然后给创建的元素el设置属性
    for (let key in domObj.props) {
        // 设置属性的方法
        setAttr(el, key, domObj.props[key]);
    }

    // 遍历子节点数组
    domObj.children.forEach(child = >{
        // 需要注意的是:如果child是虚拟dom,就继续递归渲染
        if (child instanceof Element) {
            child = render(child);
        } else {
            // 只是普通的文本内容
            child = document.createTextNode(child);
        }
        // 把子节点添加到父节点中
        el.appendChild(child);
    });

    return el;
}

// 上面用到的设置属性
function setAttr(node, key, value) {
    // 需要判断key是什么
    switch (key) {
    case 'value':
        // 属性是value就要看标签类型了,input和textarea的value就需要做区别
        if (node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() == 'textarea') {
            node.value = value;
        } else {
            node.setAttribute(key, value);
        }
        break;
    case 'style':
        node.style.cssText = value;
        break;
    default:
        node.setAttribute(key, value);
        break;
    }
}

// 将元素插入页面
function renderDom(el, target) {
    target.appendChild(el);
}

// 定义一个构造函数来
class Element {
    constructor(type, props, children) {
        this.type = type;
        this.props = props;
        this.children = children;
    }
}

//批量调用构造函数
function createElement(type,props,children){
    return new Element(type,props,children);
}

// 调用之前的createElement方法
let virtualDom = createElement('ul',{class:'list',style:'color:red;'},[
        createElement('li',{class:'item'},['利群']),
        createElement('li',{class:'item'},['玉溪']),
        createElement('li',{class:'item'},['黄鹤楼']),
    ]);

// 对象形势的结构展示
console.log(virtualDom);
			
// 渲染dom
let el = render(virtualDom); // 得到dom元素
console.log(el);
renderDom(el,document.querySelector('body'));
复制代码

OK,结果如下:

image.png

我们为什么要模拟dom

这里不讲实现逻辑,只讲下这个虚拟dom的优点(我目前知道的)。

  1. 虚拟dom对象发生改变时,方便我们做新旧对象的对比分析,比如说只是修改了class属性,那么我们只需要修改对应的点,而不用全部覆盖式的更新视图
  2. 如果频繁的去获取操作dom浏览器也会频繁的去更新视图,那么也会有一定的性能损失,造成性能的浪费,现在我们可以收集到修改操作之后,再一次性的把视图修改了,也免得引起浏览器过多的重绘和重排
  3. 对于多端操作的更方便适配。浏览器中我们把虚拟dom生成html,但是到了app和桌面软件中,就不能够使用了,如果想用js写更多端的代码,有了虚拟dom,我们只需要拿这些dom去生成其他的布局结构就行了,如果不涉及到获取和操作宿主环境的api,内部逻辑甚至都可以基本保持不变,这就很强大了



原文地址:访问原文地址
快照地址: 访问文章快照