likes
comments
collection
share

浅谈虚拟DOM

作者站长头像
站长
· 阅读数 6

一、前言

前段时间面试了几位同学,他们对虚拟DOM的回答常常强调虚拟DOM改进了性能,能提到关于重排和重绘的问题。然而虚拟DOM一定比直接操作真实DOM效率更高?除了解决重排和重绘的问题,虚拟DOM又有哪些优势呢?

二、举例

举个例子:假设有个列表,需要更新10项内容

1、直接操作真实DOM的情况:

遍历这个列表,并对每一项执行DOM操作

每次操作可能触发浏览器的重排(reflow)重绘(repaint)

如果这10次是同步操作的,浏览器可能会做出一些优化,但仍然会有比较大的开销,特别是在列表很大或者DOM结构足够复杂的情况下

2、通过虚拟DOM操作真实DOM的情况:

会计算更新后的虚拟DOM和当前虚拟DOM的差异(diffing)

将这些差异批量应用在真实DOM上,通常在一个渲染周期内完成

这种方式能够减少操作真实DOM次数,并减少重排(reflow)重绘(repaint)

3、性能比较:

如果只是简单的更新10次并没有复杂的界面或频繁的状态变化,直接操作真实DOM在渲染速度上可能会更快,因为避免了虚拟DOM的构建差异计算的开销。但如果这些操作导致频繁的重绘和重排,可能会影响帧率和用户交互的响应速度

在实践中虚拟DOM的性能优势并不总是立竿见影的,所以谈到虚拟DOM时不能仅仅只谈到性能问题

另: vue3相比vue2优化了diffing的算法,在diffing上的开销变小了

三、虚拟DOM的优势

虚拟DOM是提供真实DOM操作最小代价的中间商

1、批量更新和最小化DOM操作:减少重排、重绘

2、声明式渲染:让开发者专注于数据模型和如何映射到DOM的规则上,而不是操作DOM本身

3、组件化:描述了数据和真实DOM的关系,配合组件化开花,可以使用UI复用

4、跨平台:虚拟DOM作为一个中间层,可以不局限于浏览器,还可以渲染到其他平台。

总结起来,Vue使用虚拟DOM的原因主要是为了提高性能、简化开发和实现跨平台支持。通过虚拟DOM的机制,Vue可以减少实际DOM操作次数,提高应用的性能;简化开发者对DOM操作的关注,提高开发效率;实现跨平台支持,保持一致的开发体验。

四、vue生成真实DOM的过程

现在按以下步骤简单来实现下过程

1、 解析模板:将模板字符串解析成一个抽象的语法树(AST)

2、 响应式系统:对组件的data对象进行响应式处理

3、 生成虚拟DOM:在首次渲染或数据更新时,渲染函数被调用,返回新的虚拟DOM树

4、 更新数据:当数据发生变化时,响应式系统会注意到这些变化,并触发重新渲染

5、 虚拟DOM Diffing:将虚拟DOM和上次保存的虚拟DOM进行对比,找出差异

6、 打补丁(patch):计算差异应用到真实DOM上,有发生变化的部分会被更新

注:当前只考虑tagName和插值内容,不考虑属性和嵌套标签。

const regex = /<(.+?)\>{{(.+?)}}<\/\1>/;

// 编译模板生成AST
const compileTemplate = (template, data) => {
  const match = template.match(new RegExp(regex, "gi")) || [];
  return match.map((match) => {
    const [, tag, children] = match.match(new RegExp(regex, "i"));
    return {
      tag,
      children: data[children.trim()],
    };
  });
};

let _app = null;
let _template = "";
let _data = null;
let _vDOM = null;

// 响应式数据
const reactive = (data) => {
  _data = new Proxy(data, {
    get(obj, key) {
      return Reflect.get(obj, key);
    },
    set(obj, key, value) {
      const res = Reflect.set(obj, key, value);
      update(key, value);
      return res;
    },
  });

  return _data;
};

// 渲染
const render = (app, template, data) => {
  _vDOM = compileTemplate(template, data);
  _app = app;
  _template = template;
  _data = data;

  const fragment = document.createDocumentFragment();

  _vDOM.forEach((vnode) => {
    const { tag, children } = vnode;
    const node = document.createElement(tag);
    node.innerText = children;
    fragment.appendChild(node);
  });
   
  app.innerHTML = '' 
  app.appendChild(fragment);
};

// 更新
const update = (value) => {
  const newVDOM = compileTemplate(_template, _data);

  _vDOM.forEach((vnode, index) => {
    if (vnode.children !== newVDOM[index].children) {
      patch(newVDOM[index].children, index);
    }
  });
  
   _vDOM = newVDOM;
};

// 打补丁
const patch = (value, index) => {
  const childNodes = _app.children;
  if(childNodes[index]){
      childNodes[index].innerText = value;
  }
};

const template = `
    <p>{{ title }}</p>
    <p>{{ content }}</p>
`;

const data = reactive({
  title: "虚拟DOM",
  content: "vDOM",
});

render(document.getElementById("app"), template, data);

// 更改数据
data.title = "new VDOM"
data.content = "更新后的VDOM的内容"

转载自:https://juejin.cn/post/7360493040136962086
评论
请登录