是时候封装一个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',需要分为两步
document.createElement('div')
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)
}
思路如图:
分为两步走:
先把新增的爸爸节点,放到老节点的前面
再把老节点放入新增的爸爸节点的里面
这样就可以使新的爸爸节点包裹住老节点
使用示例:
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循环:
先找到该节点的第一个儿子赋值为x
当x是存在的,我们就把它移除,并放入数组里面(用于获取删除的节点的引用)
再把x赋值给它的下一个节点(当第一个儿子被删除后,下一个儿子就变成了第一个儿子)
反复操作,直到所有子节点被删完
改
读写属性
attr(node,name,value){
if(arguments.length === 3){
node.setAttribute(name,value)
}else if(arguments.length === 2){
return node.getAttribute(name)
}
}
这里运用重载,实现两种不同的功能:
当输入的参数是3个时,就写属性
当如数的参数是2个时,读属性
使用示例:
//写:
//给
dom.attr(test,'title','Hi,I am Wang')
//添加之后:
//读:
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,所以这里是为了适配所有浏览器
同时与读写属性思路相同:
当输入的参数是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]
}
}
}
}
思路:
首先判断输入的参数,如果为3个如:
dom.style(div,'color','red')
就更改它的style
如果输入参数为2个时,先判断输入name的值的类型
如果是字符串,如
dom.style(div,'color')
,就返回style的属性如果是对象,如
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
}
思路:
获取爸爸节点的所有儿子
设置一个变量i
如果i等于想要查询的node
退出循环,返回i值
作者:PrayWang
来源:https://juejin.cn/post/7038171258617331719