前言
DOM提供的api实在是不好用,各种各样的都有,需要记住的又多,特性不一,还要考虑兼容性问题。
在学习DOM原生api的同时,自己封装一套好用的api,即把原生api学会了,又做出一套好用的api;
首先是把声明一个dom全局变量,并挂载在window上。
dom = window.dom
一,增加节点
创建节点dom.create(<div>hi</div>)
create(string) {
const container = document.createElement("template");
container.innerHTML = string.trim();
return container.content.children[0];
}
思路:用一个template标签,把传进来的内容包裹住,再返回需要创建的标签。
应为可能创建的标签是全部,比如li标签,div不能直接包裹住li标签。
所以template是最合适的。
新增弟弟dom.after(node, node2)
after(node, node2) {
node.parentNode.insertBefore(node2, node.nextSibling);
}
先来熟悉原生api
- insertBefore(node1, node2):把node1插入到node2前面
- node.nextSibling:node的下一个兄弟节点
思路:找到父节点,插入到node后面兄弟节点的前面。
新增哥哥 dom.before(node, node2)
before(node, node2) {
node.parentNode.insertBefore(node2, node);
},
思路:找到父节点,插入到node节点前面
新增儿子 dom.append(parent, child)
append(parent, node) {
parent.appendChild(node);
}
这个差不多只是把单词字母变少了,和原生的api没有本质区别
新增爸爸 dom.wrap(<div></div>)
wrap(node, parent) {
dom.before(node, parent);
dom.append(parent, node);
},
代码很精简,但是理解起来不是很好理解。
举个栗子node1->node2
现在来一个node3,要求不顺序变成node1->node3->node2
思路:把node3插入到node2前面,node2和node3变成兄弟节点,再把node2变成node3的子节点。
删除
删除节点 dom.remove(node)
remove(node) {
node.parentNode.removeChild(node);
return node;
}
思路:找到node的爸爸,删除node
最后返回node的引用,如果不返回,node就是在内存中真正的消失了。
删除后代 dom.empty(parent)
empty(node) {
const array = [];
let x = node.firstChild;
while (x) {
array.push(dom.remove(node.firstChild));
x = node.firstChild;
}
return array;
},
思路:遍历删除子节点,返回引用(可能其他地方用得到)
这里就是原生api蛋疼的地方
上面代码仔细看,while循环中x = node.firstChild;
因为firstChild会实时更新子元素的长度,所以循环的时候只能看x存不存在,不能用长度作为判断条件,因为长度是变化的。
三、修改
读写属性 dom.attr(node, 'title', ?)
attr(node, name, value) {//重载
if (arguments.length === 3) {
node.setAttribute(name, value);
} else if (arguments.length === 2) {
return node.getAttribute(name);
}
}
当只有两个参数时,就读属性,返回属性
当有三个参数时,就修改属性
原生api
- setAttribute(name, value):给name属性设置value值
- getAtteibute(name):获取name属性的值
读写文本内容 dom.text(node, ?)
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;
}
}
}
如果只有一个参数就读文本内容,返回node.innerText
如果有两个参数,就写文本内容 node.innerText = string
if ('innerText' in node)
这一步是考虑兼容性问题
读写HTML内容 dom.html(node, ?)
html(node, string) {
if (arguments.length === 2) {
node.innerHTML = string;
} else if (arguments.length === 1) {
return node.innerHTML;
}
}
修改stylestyle dom.style(node, {color:'red'})
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) {
node.style[key] = object[key];
}
}
}
}
dom.style(node, 'color', 'red')
设置一个属性dom.style(node, 'color')
读一个属性dom.style(node, {'color': 'red', 'border': '1px solid black'})
传一个对象,同时设置多个属性
添加,删除和查看classclass dom.class.add(node, 'blue')
,dom.class.remove(node,'blue')
class: {
add(node, className) {
node.classList.add(className);
},
remove(node, className) {
node.classList.remove(className)
},
has(node, className) {
return node.classList.contains(className)
}
}
添加事件监听 dom.on(node, 'click', fn)
on(node, eventName, fn) {
node.addEventListener(eventName, fn);
}
是冒泡还是捕获可以视具体情况而定
删除事件监听 dom.off(node, 'click', fn)
off(node, eventName, fn) {
node.removeEventListener(eventName, fn);
}
四、查
获取标签或标签们 dom.find('选择器', scope)
find(selector, scope) {
return (scope || document).querySelectorAll(selector);
}
在指定区域或者全局document里找HTML标签
scope可以不传,那就是在全局查找
注意:返回的是伪数组,使用的时候把下标加上
const t = dom.find('#travel')[0]
获取父元素 dom.parent(node)
parent(node) {
return node.parentNode;
}
获取子元素 dom.children(node)
children(node) {
return node.children
}
注意:返回的是伪数组,使用的时候把下标加上
获取兄弟姐妹元素 dom.siblings(node)
sibling(node) {
return Array.from(node.parentNode.children).filter(n => n !== node);
}
思路:遍历父节点,再把自己删除掉即可
获取弟弟 dom.next(node)
next(node) {
let x = node.nextSibling;
while (x && x.nodeType === 3) {
x = x.nextSibling;
}
return x;
}
x.nodeType=3,x就是文本节点。
x && x.nodeType === 3
防止是空格回车这些。
节点包括HTML标签、 文本、注释等,所以需要这样处理一下,保证我们找到的是一个HTML标签
获取哥哥 dom.previous(node)
previous(node) {
let x = node.previousSibling;
while (x && x.nodeType === 3) {
x = x.previousSibling
}
return x;
}
用于遍历所有节点 dom.each(nodes, fn)
each(nodeList, fn) {
for (let i = 0; i < nodeList.length; i++) {
fn.call(null, nodeList[i])
}
}
获取排行老几 dom.index(node)
index(node) {
const list = dom.children(node.parentNode)
let i;
for (i = 0; i < list.length; i++) {
if (ist[i] === node) {
break;
}
}
return i;
}
总结
可以看出,原生api是真的难用。自己封装出来的,想怎么封装就怎么封装,名字简单,功能齐全。
附上我的GitHub链接源码地址