vue dom diff算法

电玩女神 2023-02-14 10:38 144阅读 0赞

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>dom diff算法</title>
  6. </head>
  7. <body>
  8. <div id="root"></div>
  9. <script src="js/element.js"></script>
  10. <script src="js/diff.js"></script>
  11. <script src="js/patch.js"></script>
  12. <script> /* let virtualDom = createElement('ul', {class: 'list'}, [ createElement('li', {class: 'item'}, ['a']), createElement('li', {class: 'item'}, ['b']), createElement('li', {class: 'item'}, ['c']), ] ) renderDom(render(virtualDom), document.getElementById('root')) */ let virtualDom1 = createElement('ul', { class: 'list'}, [ createElement('li', { class: 'item'}, ['a1']), createElement('li', { class: 'item'}, ['b']), createElement('li', { class: 'item'}, ['c1']), ] ) let virtualDom2 = createElement('ul', { class: 'list1'}, [ createElement('li', { class: 'item'}, ['1']), createElement('li', { class: 'item'}, ['b']), createElement('div', { class: 'item'}, ['3']), ] ) let el = render(virtualDom1); renderDom(el, document.getElementById('root')) let patchs = diff(virtualDom1, virtualDom2); // 将差异element放到页面中 patchRender(el, patchs) </script>
  13. </body>
  14. </html>

element.js

  1. /* * @desc 创建标签 * @params * {String} type 属性类型 * {Object} props 属性值 * {Array} children 子集 * */
  2. class Element{
  3. constructor(type, props, children) {
  4. this.type = type;
  5. this.props = props;
  6. this.children = children;
  7. }
  8. }
  9. // eslint-disable-next-line no-unused-vars
  10. /* * @desc 创建标签 * @params * {String} type 属性类型 * {Object} props 属性值 * {Array} children 子集 * */
  11. function createElement(type, props, children) {
  12. return new Element(type, props, children)
  13. }
  14. /* * @desc 设置属性 * @params * {Element} node 当前标签 * {String} key 属性 * {String, ...} value 属性值 * */
  15. function setAttr(node, key, value) {
  16. switch(key){
  17. case 'value':
  18. if(node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() === 'textarea'){
  19. node.value = value
  20. }
  21. else{
  22. node.setAttribute(key, value)
  23. }
  24. break;
  25. case 'style':
  26. node.style.cssText = value;
  27. break;
  28. default:
  29. node.setAttribute(key, value);
  30. break;
  31. }
  32. }
  33. // eslint-disable-next-line no-unused-vars
  34. /* * @desc 创建标签,并且设置属性 * @params * {Object} eleobj 虚拟树 * */
  35. function render(eleobj) {
  36. let el = document.createElement(eleobj.type);
  37. for(let key in eleobj.props){
  38. setAttr(el, key, eleobj.props[key])
  39. }
  40. eleobj.children.forEach(child => {
  41. child = (child instanceof Element) ? render(child) : document.createTextNode(child);
  42. el.appendChild(child);
  43. })
  44. return el;
  45. }
  46. // eslint-disable-next-line no-unused-vars
  47. /* * @desc 将渲染好的标签添加到页面中 * @params * {Element} el 渲染好的树 * {Element} target 目标位置 * */
  48. function renderDom(el, target) {
  49. target.appendChild(el)
  50. }

diff.js

  1. /* * @desc diff算法 * @params * {Object} oldTree 旧树 * {Object} newTree 新树 * @return * {Object} 补丁包 * @example * {1: [], 2: []} * */
  2. function diff(oldTree, newTree) {
  3. let patches = { };
  4. let index = 0;
  5. // 对比
  6. walk(oldTree, newTree, index, patches)
  7. return patches;
  8. }
  9. /* * @desc 对比属性 * @params * {Object} oldAttrs 旧属性 * {Object} newAttrs 新属性 * @return * {Object} 返回带有差异的属性 * */
  10. function diffAttr(oldAttrs, newAttrs) {
  11. let obj = { };
  12. for(let key in oldAttrs){
  13. let newAttr = newAttrs[key];
  14. let oldAttr = oldAttrs[key];
  15. if(newAttr !== oldAttr){
  16. obj[key] = newAttr; // 如果新属性上某一个属性被删除,则为undefined
  17. }
  18. }
  19. // 以下为旧属性没有,新属性有值
  20. for(let key in newAttrs){
  21. let newAttr = newAttrs[key];
  22. if(!oldAttrs.hasOwnProperty(key)){
  23. obj[key] = newAttr;
  24. }
  25. }
  26. return obj;
  27. }
  28. /* * @desc 对比新旧树子节点 * @params * {Array} oldChildren 旧子节点 * {Array} newChildren 新子节点 * {Object} patches 两棵树子元素的差异 * */
  29. let Index = 0;
  30. function diffChildren(oldChildren, newChildren, patches) {
  31. oldChildren.forEach((child, idx) => {
  32. walk(child, newChildren[idx], ++Index, patches)
  33. })
  34. }
  35. /* * @desc 判断是否为为文本节点 * @params * {Element} node 要判断的元素 * */
  36. function isString(node) {
  37. return Object.prototype.toString.call(node) === '[object String]'
  38. }
  39. /* * @desc 将两棵树差异返出来 * @params * {Object} oldTree 旧树 * {Object} newTree 新树 * {Number} index 当前children的下标 * {Object} patches 两棵树的差异 * */
  40. function walk(oldTree, newTree, index, patches) {
  41. let currentPath = [];
  42. // 节点被删除
  43. if(!newTree){
  44. currentPath.push({ type: 'REMOVE', index})
  45. }
  46. // 判断是否是文本节点
  47. else if(isString (oldTree) && isString(newTree)){
  48. if(oldTree !== newTree){
  49. currentPath.push({ type: 'TEXT', text: newTree})
  50. }
  51. }
  52. // 节点标签完全一样
  53. else if(oldTree.type === newTree.type){
  54. // 比较属性是否有更改
  55. let attrs = diffAttr(oldTree.props, newTree.props);
  56. if(Object.keys(attrs).length > 0){
  57. currentPath.push({ type: 'ATTRS', attrs})
  58. }
  59. // 如果有子节点,遍历子节点
  60. diffChildren(oldTree.children, newTree.children, patches)
  61. }
  62. // 节点被替换
  63. else{
  64. currentPath.push({ type: 'REPLACE', newTree})
  65. }
  66. // 当前元素有差异
  67. if(currentPath.length > 0){
  68. patches[index] = currentPath
  69. }
  70. }

patch.js

  1. /* * @desc 将两棵树差异性放到页面中 * @params * {Element} el 将旧virtualDom转化为element * {Object} patches 新旧两棵树差异 * @example * patches: {0: [attrs: {class: "list1"},type: "ATTRS"], 2: Array(1), 6: [text: "3",type: "TEXT"]} * */
  2. let allPatches;
  3. let index = 0; // 默认哪个属性需要打补丁
  4. function patchRender(el, patches) {
  5. console.log(patches)
  6. allPatches = patches;
  7. walkRender(el)
  8. }
  9. /* * @desc 渲染子节点 * @params * {Element} node 旧有的dom * */
  10. function walkRender(node) {
  11. // 将patches里面的0,1,2...当做key值,取出对应数组
  12. let currentPath = allPatches[index++];
  13. let childNodes = node.childNodes;
  14. childNodes.forEach(child => {
  15. walkRender(child)
  16. })
  17. if(currentPath){
  18. doPath(node, currentPath)
  19. }
  20. }
  21. /* * @desc 设置属性 * @params * {Element} node 当前标签 * {String} key 属性 * {String, ...} value 属性值 * */
  22. function setAttr(node, key, value) {
  23. switch(key){
  24. case 'value':
  25. if(node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() === 'textarea'){
  26. node.value = value
  27. }
  28. else{
  29. node.setAttribute(key, value)
  30. }
  31. break;
  32. case 'style':
  33. node.style.cssText = value;
  34. break;
  35. default:
  36. node.setAttribute(key, value);
  37. break;
  38. }
  39. }
  40. /* * @desc 将不定渲染到页面中 * @params * {Element} node 旧有的dom * {Array} patches 每一级对应不定数组 * */
  41. function doPath(node, patches) {
  42. console.log(patches)
  43. patches.forEach(patch => {
  44. switch (patch.type){
  45. case 'ATTRS':
  46. for(let key in patch.attrs){
  47. let value = patch.attrs[key];
  48. if(value){
  49. setAttr(node, key, value)
  50. }
  51. else{
  52. node.removeAttribute(key)
  53. }
  54. }
  55. break;
  56. case 'TEXT':
  57. node.textContent = patch.text;
  58. break;
  59. case 'REPLACE':
  60. let newNode = (patch.newTree instanceof Element) ? render(patch.newTree) : document.createTextNode(patch.newTree);
  61. node.parentNode.replaceChild(newNode, node)
  62. break;
  63. case 'REMOVE':
  64. node.parentNode.removeChild(node);
  65. break;
  66. default:
  67. break;
  68. }
  69. })
  70. }

发表评论

表情:
评论列表 (有 0 条评论,144人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Virtual DOM diff算法

    首先浏览器加载一个HTML页面时会经过创建DOM 树、创建样式规则(style rules)、构建渲染树(render tree)、布局layout 和 绘制页面(painti