浅谈虚拟DOM

一、前言

前段时间面试了几位同学,他们对虚拟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和插值内容,不考虑属性和嵌套标签。

js 复制代码
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的内容"
相关推荐
爱分享的鱼鱼1 小时前
对比理解 Vue 响应式 API:data(), ref、reactive、computed 与 watch 详解
前端·vue.js
JS_GGbond1 小时前
【性能优化】给Vue应用“瘦身”:让你的网页快如闪电的烹饪秘籍
前端·vue.js
刘一说2 小时前
Vue Router:官方路由解决方案解析
前端·javascript·vue.js
计算机学姐2 小时前
基于php的摄影网站系统
开发语言·vue.js·后端·mysql·php·phpstorm
暴富的Tdy2 小时前
vue2/vue3前端创建脚手架并引入RBAC权限模型
vue.js·rbac
OpenTiny社区2 小时前
🎉 TinySearchBox 重磅更新:支持 Vue2,一次满足我的所有需求!
前端·javascript·vue.js
JIngJaneIL2 小时前
基于java+ vue学生选课系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
小周同学:2 小时前
vue2 +driver 实现页面导航提示引导
vue.js
Nick_zcy2 小时前
基于Vue和Python的羽毛球拍智能推荐系统, 从“不会选羽毛球拍”到“选对拍”的一站式小工具
前端·vue.js·python·算法·推荐算法
菠菜盼娣3 小时前
vue3知识点
前端·vue.js