Virtual DOM 的原理与实现

不才
• 阅读 1302

只贴代码 不解释过程 勿喷

博客 文章地址; 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);

点赞
收藏
评论区
推荐文章
秃头王路飞 秃头王路飞
4个月前
webpack5手撸vue2脚手架
webpack5手撸vue相信工作个12年的小伙伴们在面试的时候多多少少怕被问到关于webpack方面的知识,本菜鸟最近闲来无事,就尝试了手撸了下vue2的脚手架,第一次发帖实在是没有经验,望海涵。languageJavaScript"name":"vuecliversion2","version":"1.0.0","desc
光头强的博客 光头强的博客
4个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
小森森 小森森
4个月前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本欢迎添加左边的微信一起探讨!项目地址:(https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n)\2.Bug修复更新日历2.情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意),\\和注意
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
密钥管理系统-为你的天翼云资产上把“锁
本文关键词:数据安全,密码机,密钥管理一、你的云上资产真的安全么?1.2021年1月,巴西的一个数据库30TB数据被破坏,泄露的数据包含有1.04亿辆汽车和约4000万家公司的详细信息,受影响的人员数量可能有2.2亿;2.2021年2月,广受欢迎的音频聊天室应用Clubhouse的用户数据被恶意黑客或间谍窃取。据悉,一位身份不明的用户能够将Clubho
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue
NVIDIA安培架构下MIG技术分析
关键词:NVIDIA、MIG、安培一什么是MIG2020年5月,NVIDIA发布了最新的GPU架构:安培,以及基于安培架构的最新的GPU:A100。安培提供了许多新的特性,MIG是其中一项非常重要的新特性。MIG的全名是MultiInstanceGPU。NVIDIA安培架构中的MIG模式可以在A100GPU上并行运行七个作业。多实