【硬核写DOM库】--简单封装DOM

297 阅读2分钟

封装DOM -- 硬核写DOM库

目录

  • 前言
  • 什么是封装
  • 术语
  • 常用功能自己手写DOM封装
  • 封装DOM的总结
  • 附上自己封装DOM库的github仓库

一、前言

【正文】

记住一个事实:原生DOM]很难用!,如果你觉得DOM很傻,不要怀疑自己,相信自己的直觉!API的函数名和属性真的是长的不得了,本来我自己想简单写个原生DOM的博客,没想到就花了我好几个小时的时间去码字,真的是吐了,而且容易被这些函数名给绕晕!好了,废话就到这里了,接下来让我们进入正题吧!


二、什么是封装

  • 举例

    一个电脑就是CPU、内存、硬盘、主板、显卡等的封装,用户只需要接触显示器、键盘、鼠标、触控板等设备,即可操作复杂的计算机。

  • 接口

    被封装的东西需要暴露出一些功能给外部,这些功能就是接口,如USB接口,HDMI接口等。设备只要支持这些接口,即可与被封装的东西进行信息交换。比如键盘、鼠标支持USB接口,显示器支持HDMI接口。

  • 两张图的对比就能说明一切!

三、术语

  • 我们把提供给其他人用的工具代码叫做库,如jQuery,Underscore等。

  • API:Application Programming Interface

    库暴露出来的函数或属性叫做API(应用编程接口)

  • 框架

    当你的库变的很大,并且需要学习才能看懂,那么这个库就加框架,比如Vue/React等。


四、硬核写DOM库

  1. 首先准备一个全局变量dom,直接将它挂载到window上

     windom.dom = {
       /* 待写的封装函数 */
     }
    
  2. 程序员的宿命:增删改查

    dom.create('<div> hi </div>') | dom.create('div'); --> 创建带有内容的节点或者单个元素
    
    dom.after(node,node2); --> 用于将node2添加在node之后
    
    dom.before(node,node2); --> 用于将node2插入到node前面
    
    dom.append(parent,child); --> 用于新增儿子
    
    dom.wrap(newParent); --> 用于新增爸爸
    

    实现后的代码:template能容纳所有的标签,即里面可以加入任何标签

    windom.dom = {
    
      /* 增 */
      create(string) {
        if (!(string.trim()[0] === "<")) {
          return document.createElement(string);
        } 
        else {
          const contaier = document.createElement("template");
          contaier.innerHTML = string.trim();
          return contaier.content.firstChild;
        }
      },
    
      after(node1, node2) {
        node1.parentNode.insertBefore(node2, node1.nextSibling);
      },
    
      before(node1, node2) {
        node1.parentNode.insertBefore(node2, node1);
      },
    }
    

  • dom.remove() | 用于删除节点
    dom.empty() | 用于删除所有后代
    
    实现后的代码:
    remove(node) {
    node.parentNode.removeChild(node);
    return node;
    },
    
    empty(node) {
    const arr = [];
    let x = node.firstChild;
    while (x) {
      arr.push(dom.remove(node.firstChild));
      x = node.firstChild;
    }
    return arr;
    }
    

  • dom.attr(node,'title',?) | 用于读写属性
    
    dom.text(node,?) | 用于读写内容
    
    dom.html(node,?) | 用于读写HTML内容
    
    dom.style(node,{color:"red"}) | 用于修改style
    
    dom.class.add(node,'blue') | 用于添加类名
    
    dom.class.remove(node,'blue') | 用于删除class
    
    dom.on(node,'click',fn) |用于添加事件监听
    
    dom.off(node,'click',fn) | 用于删除事件监听
    

    实现后的代码

attr(node, name, value) { if (arguments.length === 3) { node.setAttribute("data-" + name, value); } else if (arguments.length === 2) { return node.getAttribute(name); } },

text(node, string) {
  /* 兼容写法 */
  if (arguments.length === 2) {
    if ("innerText" in node) {
      node.innerText = strng;
    } else {
      node.textContent = string;
    }
  } else if (arguments.length === 1) {
    if ("innerText" in node) {
      return node.innerText;
    } else {
      return node.textContent;
    }
  }

},

html(node, string) {
  if (arguments.length === 2) {
    node.innerHTML = string;
  } else if (arguments.length === 1) {
    return node.innerHTML;
  }
},

style(node, name) {
  if (name instanceof Object) {
    const obj = name;
    for (let k in obj) {
      node.style[k] = obj[k];
    }
  } else if (typeof name === String) {
    return node.style[name];
  }
},

class: {
  add(node, className) {
    node.classList.add(className);
  },
  remoce(node, className) {
    node.classList.remove(className);
  },
  has(node, className) {
    return node.classList.contains(className);
  },
},

on(node, eventName, fn) {
  node.addEventListener(eventName, fn);
},

off(node, eventName, fn) {
  node.removeEventListener(eventName, fn);
},
```
  • dom.find('选择器') | 用于获取标签或标签们
    
    dom.parent(node) | 用于获取父元素
    
    dom.children(node) | 用于获取子元素
    
    dom.siblings(node) | 用于获取兄弟姐妹元素
    
    dom.next(node) | 用于获取下一个弟弟元素
    
    dom.previous(node) | 用于获取上一个哥哥元素
    
    dom.each(nodes,fn) | 用于遍历所有节点
    
    dom.index(node) | 用于获取排行老几
    
    实现后的代码
    find(string, scope) {
      return (scope || document).querySelectorAll(string);
    },
    
    parent(node) {
      return node.parentNode;
    },
    
    children(node) {
      return node.children;
    },
    
    siblings(node) {
      return Array.from(node.parentNode.children.filter((n) => n !== node));
    },
    
    next(node) {
      let x = node.nextSibling;
      while (x && x.nodeType === 3) {
        // 除开文本节点
        x = x.nextSibling;
      }
      return x;
    },
    
    
    previous() {
      let x = node.previousSibling;
      while (x && x.nodeType === 3) {
        x = x.previousSibling;
      }
      return x;
    },
    
    each(nodeList, fn) {
      for (let i = 0; i < nodeList.length; i++) {
        fn.call(null, nodeList[i]);
      }
    },
    
    index(node) {
      console.log(node.parentNode);
      const list = dom.children(node.parentNode);
      for (let i = 0; i < list.length; i++) {
        if (list[i] === node) {
          return i + 1;
        }
      }
    },
    

五、总结

这是我第一次尝试去封装DOM,尝试将一些难用或者函数名很长的原生DOM封装成简单的、函数名短的简单API。从以上的函数封装来说。无疑是受益匪浅的,不仅能从中理清思维逻辑,还能进一步了解框架的一些封装技巧。下一次我将会总结一些关于jQuery的封装技巧,简直让我重新认识到编程。

这是我的github链接--手写的DOM的源代码,欢迎讨论交流😁