Open tiantingrui opened 2 years ago
DOM(Document Object Model,文档对象模型)是 JavaScript 操作 HTML 的接口(这里只讨论属于前端范畴的 HTML DOM),属于前端的入门知识,同样也是核心内容,因为大部分前端功能都需要借助 DOM 来实现,比如:
实现动态展开树组件,表单组件级联等这类复杂的操作。
如果你查看过 DOM V3 标准,会发现包含多个内容,但归纳起来常用的主要由 3 个部分组成:
选择区域的使用场景有限,一般用于富文本编辑类业务,我们不做深入讨论;DOM 事件有一定的关联性,将在下一课时中详细讨论;对于 DOM 节点,需与另外两个概念标签和元素进行区分:
举例说明,在下面的代码中,“p” 是标签, 生成 DOM 树的时候会产生两个节点,一个是元素节点 p,另一个是字符串为“Terry”的文本节点。
<p>Terry</p>
- getElementById // 按照 id 查询
- getElementsByTagName // 按照标签名查询
- getElementsByClassName // 按照类名查询
- querySelectorAll // 按照 css 选择器查询
请你创建一个新节点,并把它添加到指定节点的后面。
<html>
<head>
<title>DEMO</title>
</head>
<body>
<div id="container">
<h1 id="title">我是标题</h1>
</div>
</body>
<script>
// 要求你添加一个有内容的 span 节点到 id 为 title 的节点后面,那么我们的做法就是:
// 首先获取父节点
var container = document.getElementById('container')
// 创建新节点
var targetSpan = document.createElement('span')
// 设置 span 节点的内容
targetSpan.innerHTML = 'hello world'
// 把新创建的元素塞进父节点里去
container.appendChild(targetSpan)
</script>
</html>
删除指定的 DOM 节点
// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)
修改 DOM 元素这个动作可以分很多维度,比如说移动 DOM 元素的位置,修改 DOM 元素的属性等。 将指定的两个 DOM 元素交换位置
<html>
<head>
<title>DEMO</title>
</head>
<body>
<div id="container">
<h1 id="title">我是标题</h1>
<p id="content">我是内容</p>
</div>
</body>
</html>
现在需要你调换 title 和 content 的位置,我们可以考虑 insertBefore 或者 appendChild。这里我们给出 insertBefore 的操作示范:
// 获取父元素
var container = document.getElementById('container')
// 获取两个需要被交换的元素
var title = document.getElementById('title')
var content = document.getElementById('content')
// 交换两个元素,把 content 置于 title 前面
container.insertBefore(content, title)
DOM 元素属性的获取和修改 仍然是上面这个 HTML 结构,现在假如需要你获取并修改 title 元素的 id 名,我们可以让 getAttribute 和 setAttribute 来帮忙:
var title = document.getElementById('title')
// 获取 id 属性
var titleId = title.getAttribute('id')
// 修改 id 属性
title.setAttribute('id', 'anothorTitle')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#divA,
#divB,
#divC {
width: 100px;
height:100px;
}
#divA {
background-color: #333;
}
#divB {
background-color: #dd5990;
}
#divC {
background-color: #ccc990;
}
</style>
</head>
<body>
<div id="divA">我是A</div>
<div id="divB">我是B</div>
<div id="divC">我是C</div>
</body>
</html>
如果我们仅仅想监听 divA 这个元素上的点击行为,我们可以用 addEventListener 来安装监听函数:
<script>
var divA = document.getElementById('divA')
document.addEventListener('click',function(){
console.log('我是小A')
})
</script>
这是大家非常熟悉的操作。但是,如果我现在想实现这样一种效果:
在点击A之后,B 和 C 都能感知到 A 被点击了,并且做出相应的行为——就像这个点击事件是点在 B 和 C 上一样。
是不是觉得有点意思了?我们知道,借助时间捕获和冒泡的特性,我们是可以实现父子元素之间的行为联动的。但是此处,A、B、C三者位于同一层级,他们怎么相互感知对方身上发生了什么事情呢?
看看自定义事件怎么解决 首先大家需要了解的是,自定义事件的创建。比如说咱们要创建一个本来不存在的"clickA"事件,来表示 A 被点击了,咱们可以这么写:
var clickAEvent = new Event('clickA');
OK,现在事件有了,我们来完成事件的监听和派发:
// 获取 divB 元素
var divB = document.getElementById('divB')
// divB 监听 clickA 事件
divB.addEventListener('clickA',function(e){
console.log('我是小B,我感觉到了小A')
console.log(e.target)
})
// 获取 divC 元素
var divC = document.getElementById('divC')
// divC 监听 clickA 事件
divC.addEventListener('clickA',function(e){
console.log('我是小C,我感觉到了小A')
console.log(e.target)
})
// A 元素的监听函数也得改造下
divA.addEventListener('click',function(){
console.log('我是小A')
// 注意这里 dispatch 这个动作,就是我们自己派发事件了
divB.dispatchEvent(clickAEvent)
divC.dispatchEvent(clickAEvent)
})
可以看到被正确响应了。 大家可能也注意到了,这里我们安装和派发事件必须得拿到 divC、divA 这样确切的元素才行。有的时候,为了进一步解耦,我们也会考虑把所有的监听函数都塞到 document 这个元素上去,由它来根据不同类型的自定义事件采取不同的动作。这种思路,就和事件代理非常相似了。
事件代理,又叫事件委托。 在如下的 HTML 里,我希望做到点击每一个 li 元素,都能输出它内在的文本内容。你会怎么做?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul id="poem">
<li>鹅鹅鹅</li>
<li>曲项向天歌</li>
<li>白毛浮绿水</li>
<li>红掌拨清波</li>
<li>锄禾日当午</li>
<li>汗滴禾下土</li>
<li>谁知盘中餐</li>
<li>粒粒皆辛苦</li>
<li>背不动了</li>
<li>我背不动了</li>
</ul>
</body>
</html>
按照事件的代理的思路我们可以把事件绑定到父元素 ul 上
var ul = document.getElementById('poem')
ul.addEventListener('click', function(e){
console.log(e.target.innerHTML)
})
e.target 就是指触发事件的具体目标,它记录着事件的源头。所以说,不管咱们的监听函数在哪一层执行,只要我拿到这个 e.target,就相当于拿到了真正触发事件的那个元素。拿到这个元素后,我们完全可以模拟出它的行为,实现无差别的监听效果。 像这样利用事件的冒泡特性,把多个子元素的同一类型的监听逻辑,合并到父元素上通过一个监听函数来管理的行为,就是事件代理。通过事件代理,我们可以减少内存开销、简化注册步骤,大大提高开发效率。