这里是手写 DOM 库第二篇~
1. 配置环境
在终端用 yarn 安装 parcel: yarn global add parcel
启动服务器: parcel src/index.html
2. 对象风格(命名空间风格)封装 DOM 操作
window.dom 是我们提供的全局对象,在src目录中创建index.html, dom.js 和 main.js
3. 功能:删除节点
(1) 删除节点本人
<!--index.html-->
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>手写 DOM 封装</title>
</head>
<body>
Halo
<div id="test">test</div>
<script src="dom.js"></script>
<script src="main.js"></script>
<!--引入dom和main.js-->
</body>
</html>
// ...在dom.js中添加
remove(node){
node.parentNode.removeChild(node)
return node
}
main.js这里使用node.parentNode.removeChild(node),也可以写成node.remove(node),但是由于直接.remove的这个接口比较新,IE可能不支持,所以使用删除节点爸妈的孩子这个方法删除~
// main.js
const div = dom.create('<div>test的弟弟</div>');
const div2 = dom.create('<div>test的哥哥</div>');
const div3 = dom.create('<div>test的孩子</div>');
const div4 = dom.create('<div>test的妈妈</div>');
dom.after(test, div);
dom.before(test,div2);
dom.append(test,div3);
dom.wrap(test,div4);
dom.remove(div3)
得到结果如图,test的孩子被删除了⬇️
删除之前是这样的⬇️
(2) 删除节点后代
- 在index.html中添加节点
<emptyTest>和其后代
<div id="emptyTest">
<div id="小刘">贝贝</div>
<div id="小关">鱼鱼</div>
<div id="小张">菲菲</div>
</div>
- 在dom.js中,声明空数组
array,取节点中的第一个子节点(老大),用dom.remove(node)删除节点中的这个孩子,并将他添加到array的最后一位;以此类推,直到最后一个子节点/最小的孩子也没了,返回array
empty(node){
const array = []
let x = node.firstChild
while (x){
array.push(dom.remove(node.firstChild))
x = node.firstChild
}
return array
}
- 然后在main.js中运行封装好的
dom.empty得到如下图所示
// main.js
const nodes = dom.empty(window.emptyTest)
console.log(nodes)
⚠️ 注意这里每个子节点之间有回车,所以会返回四个text
4. 功能:修改节点
(1) 读写属性
- 在dom.js中,令
attr()函数接受节点,属性名和属性值三个参数;然后使用API.setAttribute设定属性
attr(node, name, value){
node.setAttribute(name, value)
}
- 在main.js中,运行
dom.attr,更改title属性
dom.attr(test, 'title', '这是一个新名字')
效果如下⬇️
- 这里的
attr还可以只接受两个参数,可将其改写为,如果长度为3就set,如果长度为2就get ⬇️
// dom.js
attr(node, name, value){
if(arguments.length === 3){
node.setAttribute(name, value)
}else if(arguments.length === 2){
return node.getAttribute(name)
}
}
- main.js更改为:
dom.attr(test, 'title', '这是一个新名字')
const title = dom.attr(test, 'title')
console.log(`title: ${title}`)
控制台打印出新的title的值如图 ⬇️
(2) 读写文本内容
- dom.js
text(node, string){
node.innerText = string
}
这里还可以优化成 ⬇️ ,兼容 innerText(展示给人看的元素) 和 textContent (所有元素内容),这个操作叫做适配
关于 Node.textContent 和 HTMLElement.innerText 的区别,请详见 MDN 文档
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
}
}
}
- main.js
dom.text(test, '哈喽,这是新的内容~')
此时test文本内容有了新的内容更改⬇️
(3) 读写HTML内容
- dom.js 中添加有适配功能的 dom.html 函数,根据参数的长度(1/2)实现不同的效果(读/写)
html(node, string){
if(arguments.length === 2){
node.innerHTML = string
}else if(arguments.length === 1){
return node.innerHTML
}
}
(4) 修改style样式
- dom.js
style(node, object){
for(let key in object){
node.style[key] = object[key]
}
}
- main.js
dom.style(test, {border: '2px solid red', color: 'blue', background: 'yellow'})
得到效果如图
- dom.style 的适配
// 改写dom.js的dom.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'})
// 如果 name 是 Object 的实例
for(let key in name){
node.style[key] = name[key]
}
}
}
}
// 改写 main.js
dom.style(test, {border: '2px solid red', color: 'blue', background: 'yellow'})
console.log(dom.style(test, 'border'))
dom.style(test, 'border', '1px solid black')
控制台先打印出第一次设置的 border 样式,浏览器渲染出最终设置的 border 样式如图 ⬇️
(5) 添加、修改和查看 class
添加 class
- 在 index.html的
<head>中添加<style>
<style>
.pink{
background: pink;
}
</style>
- dom.js
class: {
add(node, className){
node.classList.add(className)
},
remove(node, className){
node.classList.remove(className)
},
has(node, className){
return node.classList.contains(className)
}
}
- 在main.js中运行
dom.class.add
dom.style(test, {border: '2px solid red', color: 'grey'})
dom.style(test, 'border', '3px dashed purple')
dom.class.add(test, 'pink')
此时背景色被设置为粉色如图
修改 class
- 在main.js中运行
dom.class.remove
dom.class.remove(test, 'pink')
此时页面和class中的粉色均被删去如图 ⬇️
查找 class
- 在 main.js 中运行打印
dom.class.has会返回一个布尔值
console.log(dom.class.has(test, 'pink'))
得到控制台如下,所以 pink 不存在于 class
(6) 添加 / 删除事件监听
- dom.js 中添加
on和off
on(node, eventName, fn){
node.addEventListener(eventName, fn)
},
off(node, eventName, fn){
node.removeEventListener(eventName, fn)
}
- 在main.js中运行
dom.on
dom.on(test, 'click', ()=>{
console.log('test被点击了!')
})
点击前 ⬇️
点击后~ ⬇️
- 在 main.js 中运行
dom.off
const fn = () => {
console.log('被点击了!')
} // 命名一下这个函数,方便之后调用
dom.on(test, 'click', fn)
dom.off(test, 'click', fn)
现在不管怎么点击都没有效果,因为事件监听在添加之后就被删除了~
~本系列未完待续~
Reference List | 参考资料
MDN, Element.setAttribute: developer.mozilla.org/en-US/docs/…
MDN, textContent VS innerText: wiki.developer.mozilla.org/zh-CN/docs/…