DOM封装可以被叫做对象风格封装或者命名空间封装
(实操学习)
代码示例:coolkechang/-DOM: 封装一个DOM (github.com)
增删改查
window.dom = {} 是我们提供的全局对象
增
1.创建节点: dom.create('<div>hi</div>')
(DOM封装)
create(string){
const container = document.createElement("template");
container.innerHTML = string.trim();
return container.content.firstChild;
},
- container:容器
- template 标签 :
- 1.可以容纳任意元素;
- 2.不能通过container.children[0]拿到 ; 要用container.content.firstChild;
- string.trim() : trim 功能是将字符串两边的空格去掉
(JS实现)
const div = dom.create("<div>newDiv</div>");
2.新增弟弟 dom.after(node, node2)
(DOM封装)
after(node, node2){
node.parentNode.insertBefore(node2, node.nextSibling);
},
- dom只提供了insertBefore操作,没有提供insertAfter操作
- 用 insertBefore 的方法把 node2 插到 node 的下一个节点的前面,那也就是在node后面插个node2。即使node是最后一个也可以。
(JS实现)
dom.after(test, div); //在test后加上div
3.新增哥哥 dom.before(node, node2)
(DOM封装)
before(node,node2){
node.parentNode.insertBefore(node2, node);
},
(JS实现)
dom.before(test, div); //在test前面加上div
4.新增儿子 dom.append(parent, child)
(DOM封装)
append(parent,node){
parent.appendChild(node);
},
5.新增爸爸 dom.wrap('<div></div>')
(DOM封装)
wrap(node,parent){
dom.before(node,parent);
dom.append(parent,node);
},
把 parent 放到 node 前面;再把 node append 到 parent 里
(JS实现)
const div3 = dom.create('<div id="parent"></div>');
dom.wrap(test, div3);
先把 div3 放到 test 的前面,再 div3.appendChild(test)
删
1.删除节点 dom.remove(node)
(DOM封装)
remove(node){
node.parentNode.removeChild(node);
return node;//return一下可以保留对于这个节点的引用。
},
2.删除后代 dom.empty(parent)
(DOM封装)
empty(node){
const {childNodes} = node;//简写,等同于 const childNodes = node.childNodes
const array = [];
let x = node.firstChild;
while(x){
array.push(dom.remove(node.firstChild))
x = node.firstChild
}
return array;//删除并保留对于这个节点的引用。
},
- 很容易想到的一个思路是,用for循环遍历
Parent.childNodes, 然后每次都remove - 这里不用for循环遍历是因为childNodes里面的元素删掉后它的length是会变化的,最终会多出一个undefined。
- 注意这里用while循环会出现文本节点,因为有空格(回车)。
(JS实现)
const nodes = dom.empty(window.empty);
改
1.读写属性 dom.attr(node, 'title', ?)
(DOM封装)
attr(node, name, value){
if(arguments.length === 3){
node.setAttribute(name, value)
}else if(arguments.length === 2){
return node.getAttribute(name)
}//这种方法叫“重载”;也就是根据不同的参数,获取到不同的效果
},//attr : attribute的缩写
- 这里用到了重载,即当函数的参数不一样时,做不一样的处理
- 当只有两个参数时,就读属性,返回属性
- 当有三个参数时,就修改属性
- 这里由于同时可以读写属性,用到了getter和setter设计模式
(JS实现)
dom.attr(test, 'title', 'hi,I am web');//三个参数实现了写title
const title = dom.attr(test, 'title');//两个参数实现了读test的 'title',并返回一个值给变量
console.log(`title: ${title}`);
2.读写文本内容 dom.text(node, ?)
(DOM封装)
text(node, string){
if(arguments.length === 2){
if('innerText in node'){
node.innerText = string//(ie)
}else{
node.textContent = string//(firefox / chrome)
}//以上的写代码方式成为“适配”,可满足所有浏览器;
//除了少数IE浏览器只支持第一种写法外外其他浏览器都支持两种写法;
//会改变对应标签内所有内容,不保留标签内的标签
}else if(arguments.length === 1){
if('innerText in node'){
return node.innerText
}else{
return node.textContent
}
}
},
- 如果只有一个参数就读文本内容,返回node.innerText
- 如果有两个参数,就写文本内容 node.innerText = string
(JS实现)
dom.text(test, '你好,这是新的内容');//改写
dom.text(test);//读
3.读写 HTML 内容 dom.html(node, ?)
(DOM封装)
html(node, string){
if(arguments.length === 2){
node.innerHTML = string
}else if(arguments.length === 1){
return node.innerHTML
}
},
4.修改 style dom.style(node, {color: 'red'})
(DOM封装)
style(node, name, value){
if(arguments.length === 3){// dom.style(div, 'color', 'red')
node.style[name] = value
}else if(arguments.length === 2){// dom.style(div, 'color')
if(typeof name === 'string'){
return node.style[name]
}else if(name instanceof Object){// dom.style(test, {color:'blue'})
const object = name
for(let key in object){
node.style[key] = object[key]
}//key可能是border、color...是一个变量,不能用node.style.key = ...;如果这样写key是字符串,不是变量。
}
}
},
注意,这里会有三种情况使用style函数:
dom.style(node, 'color', 'red')设置一个属性dom.style(node, 'color')读一个属性dom.style(node, {'color': 'red', 'border': '1px solid black'})传一个对象,同时设置多个属性
我们需要对不同的参数进行处理,需要用到适配器模式。
- 首先我们需要判断参数的个数,
- 如果是3,则是第一种情况,直接
node.style[name] = value - 如果是2,在此前提下再次进行判断
- 如果第二个参数是字符串,直接
return node.style[name] - 如果第二个参数是一个对象,遍历这个对象
(JS实现)
dom.style(test, {border: '1px solid red', color:'blue'});
console.log(dom.style(test, 'border'));
dom.style(test, 'border', '1px solid black');
5.添加与删除class
- 添加class:
dom.class.add(node, 'blue') - 删除class:
dom.class.remove(node, 'blue')
(DOM封装)
class:{
add(node, className){
node.classList.add(className)
},
remove(node, className){
node.classList.remove(className)
},
has(node, className){
return node.classList.contains(className)
}
},
(JS实现)
dom.class.add(test, 'red');
dom.class.add(test, 'blue');
dom.class.remove(test, 'blue');
console.log(dom.class.has(test, 'blue'));
7.添加事件监听 dom.on(node, 'click', fn)
(DOM封装)
on(node, eventName, fn){
node.addEventListener(eventName, fn)
},
(JS实现)
const fn = ()=>{
console.log('点击了')
}
dom.on(test, 'click', fn)
8.删除事件监听 dom.off(node, 'click', fn)
(DOM封装)
off(node, eventName, fn){
node.removeEventListener(eventName, fn)
},
(JS实现)
const fn = ()=>{
console.log('点击了')
}
dom.off(test, 'click', fn)
查
1.获取标签(们) dom.find('选择器')
(DOM封装)
find(selector, scope){
return (scope || document).querySelectorAll(selector)
},
- 给一个选择器,返回对应的元素;无论给的选择器有几个元素,通过调用
document.querySelectorAll全部返回一个数组 - 如果有scope,就在scope里面调用document.querySelectorAll;没有就用document调用
document.querySelectorAll - scope : 范围
(JS实现)
const testDiv = dom.find('#test')[0]
console.log(testDiv)
const test2 = dom.find('#test2')[0]
console.log(dom.find('.red', test2)[0])
2.获取父元素 dom.parent(node)
(DOM封装)
parent(node){
return node.parentNode
},
(JS实现)
console.log(dom.parent(test))
3.获取子元素 dom.children(node)
(DOM封装)
children(node){
return node.children
},
(JS实现)
console.log(dom.children(siblings))
4. 获取兄弟姐妹元素dom.siblings(node)
(DOM封装)
siblings(node){
return Array.from(node.parentNode.children)
.filter(n=>n!==node)
},
- 获取父节点的子节点,然后删除自己
- 注意:这里
node.parentNode.children得到的是伪数组,需要将其变成数组后使用 filter 将不等于 node 的放到数组里面
(JS实现)
console.log(dom.siblings(dom.find('#s2')[0]))
5. 获取弟弟dom.next(node)
(DOM封装)
next(node){
let x = node.nextSibling
while(x && x.nodeType === 3){
x = x.nextSibling
}
return x
},
- 代码意思是当 x 存在并且 x 的节点类型常量是文本时,继续找下一个,直到不是文本的节点,最后没有了也要返回
- 这样写的原因是,节点里可能包括HTML标签、 文本、注释等,所以需要这样处理一下,保证我们找到的是一个HTML标签
- nodeType 指节点类型
- 3 表示节点类型常量的值是 3 ,这个常量是
Node.TEXT_NODE( Element 或者 Attr 中实际的文字) - nodeType 可查MDN看到其常量和对应的值
(JS实现)
const s2 = dom.find('#s2')[0]
console.log(dom.next(s2))
注:这里还可以用元素节点:
next(node) {
let x = node.nextSibling
while (x && x.nodeType !== 1) {
x = x.nextSibling
}
return x
},
- 代码意思是如果 x 存在且 x 的节点类型常量不是HTML元素节点,就继续找下一个,直到找到一个节点是HTML元素节点
- 1 表示节点类型常量的值是 1 ,这个常量是
Node.ELEMENT_NODE( 一个 元素节点,例如<p>和<div>)
6.获取哥哥 dom.previous(node)
(DOM封装)
previous(node){
let x = node.previousSibling
while(x && x.nodeType === 3){
x = x.previousSibling
}
return x
},
(JS实现)
const s2 = dom.find('#s2')[0]
console.log(dom.previous(s2))
7.遍历所有节点 dom.each(nodes, fn)
(DOM封装)
each(nodeList, fn){
for(let i=0;i<nodeList.length;i++){
fn.call(null,nodeList[i])
}
},
- 给一个 nodeList (节点列表,即一堆节点),调用一个函数,遍历这个节点列表。
(JS实现)
const t = dom.find('#travel')[0]
dom.each(dom.children(t), (n)=> dom.style(n, 'color', 'red'))
- 找到 t 的所有子节点,对其进行 each 操作,每一个用 n 占位,加一个style(n 的颜色为红)
8. 获取排行老几dom.index(node)
(DOM封装)
index(node){
const list = dom.children(node.parentNode)
let i
for(i=0;i<list.length;i++){
if(list[i] === node){
break
}
}
return i
}
(JS实现)
console.log(dom.index(s2))
参考资料:饥人谷前端学习课程