Skip to content
简单的虚拟DOM实现
JavaScript HTML CSS
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
app
assets
.babelrc
.editorconfig
.gitignore
LICENSE
README.md
index.ejs
package-lock.json
package.json
renovate.json
webpack.config.build.js
webpack.config.dev.js
webpack.config.js

README.md

只贴代码 不解释过程 勿喷

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

You can’t perform that action at this time.