Virtual DOM 的原理与实现

不才 等级 414 0 0

只贴代码 不解释过程 勿喷

博客 文章地址; github地址

环境搭建

1.克隆

$ git clone https://github.com/cvgellhorn/webpack-boilerplate.git
$ npm install 
$ npm install @babel/plugin-transform-react-jsx --save-dev

2.配置babel

"plugins": [
    ...其他配置
    [
        "@babel/plugin-transform-react-jsx",
        {
            "pragma": "dom" // 这里表示生成的jsx 函数
        }
    ]
    ...其他配置
]

代码 index.js


/*
 * @Author: bucai
 * @Date: 2019-10-16 20:54:45
 * @LastEditors: bucai
 * @LastEditTime: 2019-10-17 10:11:38
 * @Description: vdom
 */

// 生成的虚拟dom的节点 
function dom(type, props, ...children) {
  return {
    type,
    props,
    children
  }
}

/**
 * 生成真实的dom树
 * @param {object} domObj node节点
 */
function generateDom(domObj) {
  let $el;
  if (domObj.type) {
    $el = document.createElement(domObj.type);
  } else {
    $el = document.createTextNode(domObj);
  }

  if (domObj.props) {
    Object.keys(domObj.props).forEach(key => {
      $el.setAttribute(key, domObj.props[key]);
    });
  }

  if (domObj.children) {
    domObj.children.forEach(child => {
      $el.appendChild(generateDom(child));
    });
  }

  return $el;
}
/**
 * 对比节点是否改变
 * @param {object} nodea 节点A
 * @param {object} nodeb 节点B
 */
// node 发生改变
function isNodeChange(nodea, nodeb) {
  // 如果nodea.type 都不为空表示是 元素节点
  if (nodea.type !== undefined && nodeb.type !== undefined) {
    return nodea.type !== nodeb.type;
  }
  // 如果是其中又一个是文本节点就判断字符串 
  return nodea !== nodeb;
}
/**
 * 是否参数改变
 * @param {object} propa attrlist
 * @param {*} propb attrlist
 */
// props change
function isPropsChange(propa, propb) {
  // 统一化
  const oldProps = propa || {};
  const newProps = propb || {};
  // 获取参数的key
  const oldKeys = Object.keys(oldProps);
  const newKeys = Object.keys(newProps);
  // 长度不同就说明改变了
  if (oldKeys.length !== newKeys.length) {
    return true;
  }
  // 当长度一致的时候
  if (oldKeys.length === 0) {
    return false;
  }
  // 遍历key 来对比是否改变
  for (let i = 0; i < oldKeys.length; i++) {
    const oldkey = oldKeys[i];
    const newkey = newKeys[i];
    // 对key进行遍历
    // if (oldkey !== newkey) { // // 这里没有意义
    //   return true;
    // }
    if (oldProps[oldkey] !== newProps[newkey]) {
      return true;
    }
  }
  // 如果上面都不符合就说是没有更改的
  return false;
}

/**
 *  虚拟DOM对比函数
 * @param {HTMLElement} $parent 父节点
 * @param {object} oldNode 旧的节点对象
 * @param {object} newNode 新的节点对象
 * @param {number} index 子节点的下标
 */
function vDom($parent, oldNode, newNode, index = 0) {
  const $currlenEl = $parent.childNodes[index];
  // 旧的不存在就直接添加到dom中
  if (!oldNode) {
    // append
    return $parent.appendChild(generateDom(newNode));
  }
  // 新的不存在就直接移除旧的节点
  if (!newNode) {
    // REMOVE
    return $parent.removeChild($currlenEl);
  }
  // 判断节点是否改变
  // node change
  if (isNodeChange(oldNode, newNode)) {
    return $parent.replaceChild(generateDom(newNode), $currlenEl);
  }
  // 节点相同就没问题
  // no change textNode
  if (oldNode === newNode) {
    return;
  }

  // change props
  const oldProps = oldNode.props || {};
  const newProps = newNode.props || {};

  if (isPropsChange(oldProps, newProps)) {
    const oldPropsKey = Object.keys(oldProps);
    const newPropsKey = Object.keys(newProps);

    // 如果新的props 为空就将old全部移除
    if (newPropsKey.length === 0) {
      oldPropsKey.forEach(key => {
        $currlenEl.removeAttribute(key);
      });
    } else {

      const propsKeySet = new Set([...oldPropsKey, ...newPropsKey]);

      propsKeySet.forEach(key => {

        if (oldProps[key] === undefined) {
          $currlenEl.setAttribute(key, newProps[key]);
        } else if (newProps[key] === undefined) {
          $currlenEl.removeAttribute(key);
        } else if (newProps[key] !== oldProps[key]) {
          $currlenEl.setAttribute(key, newProps[key]);
        }

      });
    }
  }
  // 子节点
  // children change
  const oldChildren = oldNode.children || [];
  const newChildren = newNode.children || [];

  if (oldChildren.length || newChildren.length) {
    const maxLen = Math.max(oldChildren.length, newChildren.length);
    for (let i = 0; i < maxLen; i++) {
      vDom($currlenEl, oldChildren[i], newChildren[i], i);
    }
  }

}

// 原来的dom树
const profile = (
  <div>
    <h3 class="aaaa">123</h3>
    <p>123</p>
  </div>
);
// 新的dom树
const profileChange = (
  <div class="b">
    <h3 class="bucai" data-user-id="{a:1}">222</h3>
    <span>xxxxx</span>
  </div>
);
// 先生成一个真实的dom树 并将他添加到html中
const $el = generateDom(profile);
const $app = document.querySelector('#app');
$app.appendChild($el);

// 延时,方便查看
setTimeout(() => {
  // 调用虚拟dom 对比两棵树
  vDom($app, profile, profileChange);
}, 3000);

收藏
评论区

相关推荐

教你用200行代码写一个爱豆拼拼乐H5小游戏(附源码)
前言 本文将带大家一步步实现一个H5拼图小游戏,考虑到H5游戏的轻量级和代码体积,我没有使用react或vue这些框架,而采用我自己写的dom库和原生javascript来实现业务功能,具体库代码可见我的文章如何用不到200行代码写一款属于自己的js类库(https://juejin.im/post/6844903880707293198),构建工具我采
《javascript高级程序设计》核心知识总结
此文是对js高级程序设计一书难点的总结,也是笔者在看了3遍之后的一些梳理和感想,希望能借此巩固js的基础和对一些核心概念有更深入的了解。 摘要 js基本的数据类型和关键点 变量,作用域和内存问题 垃圾回收机制 面向对象的程序设计 实现类与继承的经典方式 BOM和DOM对象 DOM扩展与高级API介绍 高级编程技巧 跨文档消息传递和aja
对 JavaScript 中事件循环的理解​
一、是什么 JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事 为什么要这么设计,跟JavaScript的应用场景有关 JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理? 为了解决单
Vue 项目性能优化—实践指南
Vue 项目性能优化—实践指南 前言 Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 DOM 以及如何最高效地操作 DOM;但 Vue 项目中仍然存在项目首屏优化、Webpack 编译配置优化等问题,所以我们仍然需要去关注 Vue 项目性能方面的优化,使项目具有更高效
Virtual DOM 的原理与实现
只贴代码 不解释过程 勿喷 ; 环境搭建1.克隆$ git clone https://github.com/cvgellhorn/webpackboilerplate.git$ npm install $ npm install @babel/plugintransformreactjsx savedev2.配
js-Answers一
JavaScript的组成 JavaScript 由以下三部分组成: 1. ECMAScript(核心):JavaScript 语言基础 2. DOM(文档对象模型):规定了访问HTML和XML的接口 3. BOM(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法 JS的基本数据类型和引用数据类型
React 之Virtual DOM 及内核
什么是 Virtual DOM?Virtual DOM 是一种编程概念。在这个概念里, UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与“真实的” DOM 同步。这一过程叫做。这种方式赋予了 React 声明式的 API:您告诉 React 希望让 UI 是什么状态,React 就确保 DOM 匹
JavaScript 是什么?
前言 引用《JavaScript 高级程序设计第四版》中说的话 ——“从简单的输入验证脚本到强大的编程语言,JavaScript 的崛起没有任何人预测到。它很简单,学会用只要几分钟;它又很复杂,掌握它要很多年。要真正学好用好 JavaScript,理解其本质、历史及局限性是非常重要的”。 面试官:JavaScript 是什么? 我:
Vue.js——60分钟快速入门
是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的。相比于Angular.js,Vue.js提供了更加简洁、更易于理解的API,使得我们能够快速地上手并使用Vue.js。
Vue进阶(四十七):面试必备:2021 Vue经典面试题总结(含答案)_IT全栈 华强工作室
面试必备:2021 Vue经典面试题总结(含答案)一、什么是MVVM?
Vue 性能优化
前言Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 DOM 以及如何最高效地操作 DOM;但 Vue 项目中仍然存在项目首屏优化、Webpack 编译配置优化等问题,所以我们仍然需要去关注 Vue 项目性能方面的优化,使项目具有更高效的性能、更好的用户体验。本文是作者通过实际
javaScript. Dom 基本操作
DOM节点查找jsdocument.getElementById() //通过id查找document.getElementsByTagName() //通过标签名document.getElementsByName() //通过name名查找 document.getElementsByClassName("类名")//通过类名获取元素对象 documen
从未有人把JVM原理讲的这么详细
JVM原理1.简述JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运
前端培训-Vue专题之Vue基础
简介特点:MVVM框架,双向绑定,数据驱动,单页面,组件化。 区别Vue 和 jQuery 的区别:不直接操作DOM,而是操作数据。案例:Hello World 你好,世界HTML代码:xml<h1msg</h1jQuery实现javascript$("h1").text("你好,世界");Vue 实现javascriptthis.msg '你好,世界'
nextTick原理解析
nextTick 是什么\$nextTick:根据官方文档的解释,它可以在 DOM 更新完毕之后执行一个回调函数,并返回一个 Promise(如果支持的话)js// 修改数据vm.msg "Hello";// DOM 还没有更新Vue.nextTick(function() // DOM 更新了);这块理解 EventLoop 的应该一看就懂,其实就是