封装自己的DOM

封装自己的DOM

技术杂谈小彩虹2021-08-16 3:29:24220A+A-

封装思路

因为在使用DOM的原生方法的时候总是感觉有很多代码特别的长不是很方便,例如addEventListener,我就想能不能把这些方法进行进一步的封装,形成一个自己可以使用的代码,这样比较的快捷及简便,在封装的过程中,还通过mdn进行了查询一些DOM的操作,找到了一些小窍门,大家可以看我具体实现中的实现方法,希望你可以在该篇文章中有所收获

具体实现

首先要先在window中封装一个对象.

window.dom = {
  
}

初步封装创建的DOM方法

  // 传入参数为string
  create(string) {
    // 创建一个template包容器,这里面可以写任何的代码,但是div不可以写,所以使用的是template
    const container = document.createElement("template");
    // 将div的HTML设置为传入的string
    container.innerHTML = string;
    // 返回div的第一个子元素
    return container.children[0];
  }

在封装该方法的时候,一开始想使用创建包围代码的容器是div,但是发现div在包含时有些属性不支持显示,比如说td等,然后就在网上进行了资料查询,发现在template内可以包含大部分的元素.
在节点的后面插入节点的方法

  after(node,node2) {
    // 找到node的爸爸的节点调用,将node2插到node下一个节点的前面
    node.parentNode.insertBefore(node2, node.nextSibling);
  },

由于原生的DOM没有提供在节点后面插入节点的方法,只提供了insertBefore,所以我们可以通过该方法来插入到节点的后面
在节点的前面插入节点的方法

  before(node,node2) {
    // 由于自带的insertBefore方法,可以直接将node2插入到node的前面
    node.parentNode.insertBefore(node2, node);
  },

给父节点新增一个子节点

  append (parent, child) {
    parent.appendChild(child)
  }

给子节点增加一个父节点

  wrap(node, parent) {
    // 把parent放在node的前面
    dom.before(node, parent)
    // 把node放在parent的里面
    dom.append(parent, node)
  }

这里用到了我们前面封装过得before和append,通过组合完成给子节点增加父节点的方法
删除单个节点的方式

  // 删除节点
  remove(node) {
    // 找到该节点的父节点,然后删除父节点的子节点
    node.parentNode.removeChild(node);
    // 返回删除的节点以便引用
    return node
  },

在这里我们没有直接使用remove的方法,而是调用了很多浏览器都支持的,找到父节点,再删除子节点的方法
删除所有节点的方式

  // 删除所有的子节点
    empty(node) {
        // 直接删除
    // node.innerHTML = '';
    // 找到所有的子节点
    // const childNodes = node.childNodes;
    // 以上的简写
    const {childNodes} = node;
    // 保存删除的数组
    const array = []
    /* 遍历所有子节点 !!!这里的childNodes的长度是实时变化的所以这种方式有弊端 for(let i=0;i<childNodes.length;i++) { // 将所有找到的节点移除 dom.remove(childNodes[i]); // 将删除了的节点保存到数组中,返回以供使用 array.push(childNodes[i]) } return array*/
    // 设置x为node的第一个子节点
    let x = node.firstChild
    while(x){
      // remove后会有返回值,所以可以将删除的push到数组中
      array.push(dom.remove(node.firstChild))
      // 然后让x等于下一个子节点,直到没有子节点
      x = node.firstChild
    }
    // 返回数组
    return array
  }

在删除节点的方法中我想到了三种方法:
1.直接将节点的所有HTML都设置为空,这样很直接的删除了所有节点
2.找到所有的子节点,然后同时遍历子节点然后一个个remove删除掉,但是当实践的时候发现当remove后再进行遍历,所有子节点的长度会实时发生变化,所以这个方法是不合理的.
3.由于第二种方法的子节点长度会实时变化,就想到了另外一种的遍历方式,设置一个变量,然后调用while循环,只要有子节点,就将这个第一个子节点删除,然后将删除后的节点中的第一个节点设置为这个变量,再进行循环,如以上代码所示
设置节点属性

  attr(node, name, value) { // 重载(根据参数个数写不同的代码)
    // 判断实参的长度,如果实参长度为3就设置 
    if(arguments.length === 3) {
      node.setAttribute(name, value)
    }else if(arguments.length === 2){
    // 判断实参的长度,如果实参长度为2就读取
      return node.getAttribute(name)
    }
  }

这是一个用来设置节点的方法,在考虑的时候我们有考虑到传入实参的个数,同时考虑到用户实际想获取还是想设置的同时调用不同的方法
设置节点的内容 会同时改掉该节点里面含有的子节点

  text(node, string) { // 适配两种浏览器
    if(arguments.length === 2){
      // 判断如果有则用,没有就用另一种
      if('innerText' in node) {
        node.innerText = string //该方法是IE的比较通用
      }else {
        node.textContent = string // 该方法火狐谷歌等用的比较多
      }
    }else if(arguments.length ===1) {
      if('innerText' in node) {
        return node.innerText //该方法是IE的比较通用
      }else {
        return node.textContent// 该方法火狐谷歌等用的比较多
      }
    }
  }

此为设置节点内容,同样用到了重载的方法,判断用户是想读取还是想设置,并且根据个别浏览器的支持采用了不同的DOM操作以便进行适配
改变内容的HTML

  html(node, string) {
  // 判断传入参数的长度
    if(arguments.length === 2){
      node.innerHTML = string
    }else if(arguments.length ===1) {
      return node.innerHTML
    }
  },

修改节点的style属性

  style(node, name, value) {
    // 若长度为3 是直接设置style样式
    if(arguments.length === 3) {
      node.style[name] = value
      // 当长度为2时还需要判断第二个值类型为对象(设置),还是字符串(读取)
    }else if(arguments.length === 2) {
      if(typeof name === 'string') {
        return node.style[name]
      }else if(name instanceof Object) {
        // 读取到所有的key
        const object = name
        for(let key in object) {
          // 将每个的key设置为对象的key值
          node.style[key] = object[key]
        }
      }
    }
  },

当实参为3个时直接设置即可,但是当2个实参是要进行判断,第二个实参是字符串的话就是获取,但是若为对象,则应进行遍历然后设置对应的name值
查看移除设置class

  class: {
    // 添加
    add (node, className) {
      node.classList.add(className)
    },
    // 移除
    remove (node, className) {
      node.classList.remove(className)
    },
    // 查看有无
    has (node, className) {
      return node.classList.contains(className)
    }
  },

在has方法中返回的为true or false
添加和移除监听事件

  // 添加监听事件
  on(node, eventName, fn) {
    node.addEventListener(eventName, fn)
  },
  // 移除监听事件
  off(node, eventName, fn) {
    node.removeEventListener(eventName, fn)
  },

运用添加和删除监听的方法,传入函数
给选择器查对应的节点

  find(selector, scope) {
    // 返回的是一个数组
    // 若存在一个返回就用返回内 如果没有就在文档中找
    return (scope || document).querySelectorAll(selector)
  }

要判断是在全局当中选择,还是有给指定范围,通过||操作符若有范围则直接取范围,若没有则在全局中选择
找节点的父亲

  parent(node) {
    return node.parentNode
  }

找节点的儿子

  children(node) {
    return node.children
  }

用于获取兄弟姐妹,除了自己

  siblings(node) {
    // 将生成的伪数组转换成数组,再filter过滤
    return Array.from(node.parentNode.children).filter(x => {
      return x !== node
    })
  }

在之前的文章中有提到过filter函数,可以用来过滤符合与否我们的需求,但是必须为数组才能调用,我们获取到该节点的父亲的子节点后,然后判断只要不是当前节点就返回到一个数组中.
找到下一个节点

  next(node) {
    let x = node.nextSibling
    // 判断是否下一个是文本
    while(x && x.nodeType === 3) {
      x = x.nextSibling
      // 直到x是节点退出循环
    }
    return x
  }

在这个找节点的方法中,我们想找到的是节点,但有时会返回一些空格的文本节点,不符合我们的要求,通过mdn中我查到node.nextSibling有返回值,若返回值为3时则为文本节点的方法,然后进行循环判断,只要不是文本节点则退出循环
找到上一个节点

  prev(node) {
    let x = node.previousSibling
    // 判断是否下一个是文本
    while(x && x.nodeType === 3) {
      x = x.previousSibling
      // 直到x是节点退出循环
    }
    return x
  }

和找到下一个节点有共同的方法,同样需要进行循环判断 遍历所有节点

  each(nodeList,fn) {
    for(let i=0;i<nodeList.length;i++) {
      fn.call(null, nodeList[i])
    }
  }

对所有节点进行遍历的同时,传入第二个函数,将函数的指向设置为空即可,可以同时设置节点的style
找到自己排第几

  index(node) {
    // 获取所有孩子,判断什么时候是自己 然后停止,返回
    const list = dom.children(node.parentNode);
    let i
    for(i =0;i<list.length;i++) {
      if(list[i] === node) {
        break
      }
    }
    return i
  }

用children方法找到节点的父节点的所有孩子,然后进行遍历,若和当前节点相同则停止,返回i,即可找到自己是第几.

总结

这是最近在学习js中学到了很重要的一个思路,要学会使用循环,大家可以看到上面有很多地方都运用到了循环的方法,同时还要学会对文档的查询,这是一种找到问题,解决问题的关键所在,今天的文章就分享到这里了,谢谢阅读.
记得持续学习,不断跟进!加油!

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示 | 支付宝红包
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们