注册

是时候封装一个DOM库了

由于原始的DOM提供的API过长,不方便记忆,
于是我采用对象风格的形式封装了一个DOM库->源代码链接
这里对新封装的API进行总结
同样,用增删改查进行划分,我们先提供一个全局的window.dom对象

创建节点

create (string){
       const container = document.createElement("template")//template可以容纳任意元素
       container.innerHTML = string.trim();//去除字符串两边空格
       return container.content.firstChild;//用template,里面的元素必须这样获取  
}

首先,如果用原始的DOM API,我们想要创建一个div,div里面含有一个文本'hi',需要分为两步

  1. document.createElement('div')

  2. div.innerText = 'hi'

而这里,只需要一步就能完成dom.create('

hi
')
它可以直接创建多标签的嵌套,如create('
你好
')
为什么能这样写?
因为我们用innerHTML直接把字符串写进了HTML里,字符串直接变成了HTML里面的内容
为什么使用template?
因为template可以容纳任意元素,如果使用div,div不能直接容纳标签,但template就可以

新增哥哥

before(node,node2){
   node.parentNode.insertBefore(node2,node);
}

这个比较简单,找到爸爸节点,然后使用```JavaScript,新增一个node2即可

新增弟弟

after(node,node2){
   node.parentNode.insertBefore(node2,node.nextSibling); //把node2插到node下一个节点的前面,即使node的下一个节点为空,也能插入  
}

由于原始的DOM只有insertBefore,并没有insertAfter,所以要实现这个功能我们需要一个曲线救国的方法:
node.nextSibling 表示node节点的下一个节点,
而想在node的后面插入一个节点,就等于说在node的下一个节点前插入一个新节点node2即可
如上代码就是实现了这个操作,而即使node的下一个节点为空,也能成功插入

新增儿子

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

找到爸爸节点,用appendChild即可

新增爸爸

 wrap(node,parent){
       dom.before(node,parent)
       dom.append(parent,node)
}

思路如图:

image.png

分为两步走:

  1. 先把新增的爸爸节点,放到老节点的前面

  2. 再把老节点放入新增的爸爸节点的里面

这样就可以使新的爸爸节点包裹住老节点

使用示例:

const newDiv = dom.create('
')
dom.wrap(test, newDiv)

删节点

remove(node){
      node.parentNode.removeChild(node)
      return node
}

找到爸爸节点,removeChild即可

删除所有子节点

empty(node){
   const array = []
   let x = node.firstChild
   while (x) {
       array.push(dom.remove(node.firstChild))
       x = node.firstChild//x指向下一个节点
  }
   return array
}

其实一开始的思路,是用for循环

for(let i = 0;i<childNodes.length;i++){
   dom.remove(childNodes[i])
}

但这样的思路有一个问题:childNodes.length是会随着删除而变化的
所以我们需要改变思路,用while循环:

  1. 先找到该节点的第一个儿子赋值为x

  2. 当x是存在的,我们就把它移除,并放入数组里面(用于获取删除的节点的引用)

  3. 再把x赋值给它的下一个节点(当第一个儿子被删除后,下一个儿子就变成了第一个儿子)

  4. 反复操作,直到所有子节点被删完

读写属性

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

这里运用重载,实现两种不同的功能:

  1. 当输入的参数是3个时,就写属性

  2. 当如数的参数是2个时,读属性

使用示例:

//写:
//给
test
添加属性
dom.attr(test,'title','Hi,I am Wang')
//添加之后:
test

//读:
const title = dom.attr(test,'title')
console.log(`title:${title}`)
//打印出:title:Hi,Hi,I am Wang

读写文本内容

text(node,string){
   if(arguments.length === 2){
       if('innerText' in node){
           node.innerText = string
      }else{
           node.textContent = string
      }
  }else if(arguments.length === 1){
       if('innerText' in node){
           return node.innerText
      }else{
           return node.textContent
      }
  }
}

为什么这里需要适配,innerText与textContent?
因为虽然现在绝大多数浏览器都支持两种,但还是有非常旧的IE只支持innerText,所以这里是为了适配所有浏览器

同时与读写属性思路相同:

  1. 当输入的参数是2个时,就在节点里写文本

  2. 当输入的参数是1个时,就读文本内容

读写HTML的内容

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

同样,2参数写内容,1参数读内容

修改Style

style(node,name,value){
       if(arguments.length === 3){
           //dom.style(div,'color','red')
           node.style[name] = value
      }else if(arguments.length === 2){
           if(typeof name === 'string'){
            //dom.style(div,'color')
           return node.style[name]    
          }else if(name instanceof Object){
               //dom.style(div,{color:'red'})
               const Object = name
               for(let key in Object){
                   //key:border/color
                   //node.style.border = ...
                   //node.style.color = ...
                   node.style[key] = Object[key]
              }
          }
      }
}

思路:

  1. 首先判断输入的参数,如果为3个如:dom.style(div,'color','red')

  2. 就更改它的style

  3. 如果输入参数为2个时,先判断输入name的值的类型

  4. 如果是字符串,如dom.style(div,'color'),就返回style的属性

  5. 如果是对象,如dom.style(div,{border:'1px solid red',color:'blue'}),就更改它的style

增删查class

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

注:查找一个元素的classList里是否有某一个class, 用的是contains

添加事件监听

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

使用示例:

const fn = ()=>{
   console.log('点击了')
}
dom.on(test,'click',fn)

这样当点击id为test的div时,就会打印出'点击了'

删除事件监听

off(node,eventName,fn){
   node.removeEventListener(eventName,fn)
}

获取单个或多个标签

find(selector,scope){
   return (scope || document).querySelectorAll(selector)
}

可以在指定区域或者全局的document里找

使用示例:
在document中查询:

const testDiv = dom.find('#test')[0]
console.log(testDiv)

在指定范围内查询:

 <div>
       <div id="test"><span>test1span>
       <p class="red">段落标签p>
       div>
       <div id="test2">
           <p class="red">段落标签p>
       div>
div>

我只想找test2里面的red,应该怎么做

const test2 = dom.find('#test2')[0]
console.log(dom.find('.red',test2)[0])

注意:末尾的[0]别忘记写

获取父元素

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
}

为什么这里需要while(x && x.nodeType === 3)
因为我们不想获取文本节点(空格回车等)
所以当读到文本节点时,自动再去读取下一个节点,直到读到的内容不是文本节点为止

获取哥哥

previous(node){
       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])
      }
}

注:null用于填充this的位置
使用示例:
利用fn可以更改所有节点的style

const t = dom.find('#travel')[0]
dom.each(dom.children(t),(n)=>dom.style(n,'color','red'))

遍历每个节点,把每个节点的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
}

思路:

  1. 获取爸爸节点的所有儿子

  2. 设置一个变量i

  3. 如果i等于想要查询的node

  4. 退出循环,返回i值

作者:PrayWang
来源:https://juejin.cn/post/7038171258617331719

0 个评论

要回复文章请先登录注册