DOM详解
JavaScript中包含三块,分别是ECMAscript、DOM和BOM
DOM的全称是Document Object Model(文档对象模型),是一种对文档结构化表示的方式,通过它可以方便地访问和操作文档的内容、结构和样式 ;同时DOM也提供了操作页面元素的方法和属性,允许我们从文档中创建、删除或者修改元素,以及向元素添加事件
DOM树
通过DOM形式(文档结构化)把整个html页面以树状结构表示的方式
在 DOM 树中,每个 HTML 元素、文本节点、属性等都被表示为一个节点,这些节点之间通过父子关系相互连接,形成了一个层级结构
DOM树主要由4类主要节点组成:文档节点,元素节点,属性节点,文本节点。
- 文档节点:在树的顶端是文档节点,它呈现整个页面
- 元素节点:需要访问DOM树时,需要从查找元素开始。一旦找到所需的元素,然后就可以根据需要来访问它的文本和属性节点。如
<Html>,<a>,<body>
等都是元素节点,即标签 - 属性节点:属性节点不是所在元素的子节点,它们是这个元素的一部分。当访问一个元素时,有特定的方法和属性用来读取或修改这个元素的属性。元素的属性,如
<a>
中的href属性 - 文本节点:当访问元素节点,可以访问元素内部的文本。文本节点没有子节点。向用户展示的内容,如
<title>...</title>
中的"文档标题",<p>hello world</p>
中的内容
通过document.getElementById
和 document.body
获取的元素就是获取元素的节点
在源代码中我们也可以看到这种层级的树状关系
DOM常用操作方法
scss
document.title // 设置页面title
document.getElementById(id) // 根据id获取元素
document.getElementsByTagName(name) // 根据tag获取元素
document.createElement(name) // 创建元素
parentNode.appendChild(node) // 向子节点列表末尾添加一个节点
parentNode.insertBefore() // 把要插入的节点放到某个特定的位置
parentNode.removeChild() // 移除节点
element.innerHTML // 设置/获取元素内容
element.styles // 设置/获取元素样式
element.setAttribute() // 设置元素属性值
element.getAttribute() // 获取元素属性值
element.addEventListener() // 添加事件绑定
DOM事件流
DOM事件(event flow)流指的是是从页面接受事件的顺序
其中存在三个阶段:事件捕获阶段、目标阶段、事件冒泡阶段
1.捕获阶段
事件捕获阶段是指事件从文档根节点向下传播,直到达到实际触发事件的元素。在事件捕获阶段,事件会依次经过每个祖先元素。这意味着最外层的元素首先接收到事件,然后是它的父元素,以此类推,直到达到目标元素
2.目标阶段
事件到达目标元素后,进入当前目标阶段,执行函数代码
3.事件冒泡阶段
事件在执行后会跟随节点继承自的各个父节点冒泡穿过整个DOM树,直到遇到根节点,在传递的过程中,会触发外部元素绑定的事件
javascript
<body>
<div id="outer">
<p id="inner">点击我看事件冒泡效果!</p>
</div>
<script>
document.getElementById('inner').addEventListener('click', function() {
console.log('内部元素被点击');
});
document.getElementById('outer').addEventListener('click', function() {
console.log('外部元素被点击');
});
</script>
//内部元素被点击
//外部元素被点击
在这个例子中外部元素和内部元素都绑定了点击事件,当内部元素被点击时,由于冒泡机制的影响,最终使内外部元素点击事件都被触发
具体的三个阶段图解如上
StopPropagation() 方法
stopPropagation()
方法用于阻止事件在DOM树中的传播,包括冒泡和捕获阶段。它可以防止事件继续向父元素冒泡或向子元素捕获。
假设一个子元素和一个父元素都绑定了相同类型的事件。当子元素上的事件被触发时,如果调用了 event.stopPropagation()
,那么该事件将不会再继续向上传播到父元素;反之,如果父元素调用了 event.stopPropagation()
,事件也不会再向下捕获到其他子元素。
实例如下
xml
<div id="outer" style="padding: 20px; background-color: #f2f2f2;">
<div id="inner" style="padding: 20px; background-color: #e5e5e5;">
点击内部元素停止事件传播
</div>
</div>
<script>
document.getElementById('inner').addEventListener('click', function(event) {
console.log('内部元素被点击');
event.stopPropagation(); // 阻止事件继续传播
});
document.getElementById('outer').addEventListener('click', function() {
console.log('外部元素被点击');
});
</script>
//内部元素被点击
StopImmediatePropagation()方法
stopImmediatePropagation()
方法用于阻止事件的进一步传播,并且它还能够阻止其他相同事件类型的事件处理程序被调用。 这意味着在调用 stopImmediatePropagation()
后,即使该元素上绑定了多个相同类型的事件处理程序,其后续的事件处理程序也将不再执行。
举例来说,假设一个按钮上绑定了两个相同类型的点击事件处理程序。当按钮被点击并且第一个事件处理程序中调用了 event.stopImmediatePropagation()
后,第二个事件处理程序将不会被触发,即使它们按照代码的顺序依次执行。
xml
<body>
<div id="outer" style="padding: 20px; background-color: #f2f2f2;">
<div id="inner" style="padding: 20px; background-color: #e5e5e5;">
点击内部元素同类型事件触发和停止事件传播
</div>
</div>
<script>
document.getElementById('inner').addEventListener('click', function(event) {
console.log('第一个内部点击事件');
event.stopImmediatePropagation(); // 阻止后续事件处理程序执行
});
document.getElementById('inner').addEventListener('mouseenter', function() {
console.log('第一个内部悬停事件');
});
document.getElementById('inner').addEventListener('click', function() {
console.log('第二个内部点击事件');
});
document.getElementById('outer').addEventListener('click', function() {
console.log('第一个外部点击事件');
});
</script>
在上述代码中,当点击了内部元素inner时,第一个内部点击事件被触发,第二个相同类型点击事件被stopImmediatePropagation()
阻止,第一个内部不同类型的事件处理程序正常被触发,而第一个外部点击事件被停止传播。
addEventListener() 方法
在讲事件委托前先回顾一下addEventListener() 方法
addEventListener() 方法用于向指定元素添加事件
语法
element .addEventListener(event , function , useCapture)
参数 | 描述 |
---|---|
event | 字符串,指定事件名称,总称为HTML DOM对象 |
function | 执行时触发的函数,事件对象会作为第一个参数传入函数。 事件对象的类型取决于特定的事件。例如, "click" 事件属于 MouseEvent(鼠标事件) 对象。 |
useCapture | 可选。布尔值,指定事件是否在捕获或冒泡阶段执行,true - 事件句柄在捕获阶段执行 ,false- 默认。事件句柄在冒泡阶段执行 |
在 addEventListener
方法中,事件处理函数中可以不显式地定义参数。即使没有显式定义参数,事件处理函数仍然可以访问事件对象(在这种情况下是点击事件的对象)。JavaScript 在调用事件处理函数时会自动传递事件对象作为参数。
javascript
document.getElementById("myBtn").addEventListener("click", function()
{
document.getElementById("demo").innerHTML = "Hello World";
});
所以即使在示例代码中的匿名函数 function() { ... }
中没有明确指定参数,当用户点击具有 id 为 "myBtn" 的元素时,浏览器还是会在内部创建一个事件对象,并将其传递给该函数。因此,事件处理函数内部依然可以通过默认的参数来访问事件对象,从而进行相应的处理操作。
因此,尽管在 addEventListener
中的函数没有显式定义参数,但是在实际触发事件时,事件对象仍会被传递给该函数供其使用。
事件委托
在 JavaScript 中,事件委托通常用于处理大量相似子元素的事件。通过将事件处理程序添加到父元素,从而利用事件冒泡机制,在实际触发事件时委托给父元素处理。这样可以减少内存占用和提高性能。
csharp
// 获取父元素
var parentElement = document.getElementById('parent-element');
// 添加事件监听器到父元素
parentElement.addEventListener('click', function(event) {
// 检查被点击的元素是否是目标子元素
if (event.target && event.target.classList.contains('child-element')) {
// 在控制台中打印被点击的子元素的文本内容
console.log('Clicked on: ' + event.target.textContent);
}
});
//event.target检查触发事件的元素是否存在
//event.target.classList.contains('child-element')检查触发事件的元素中是否包含'child-element'
在这个示例中,当用户点击 parentElement
的子元素时,由于事件冒泡,父元素上的事件监听器会捕获到事件。然后我们在事件处理函数中检查被点击的元素是否是我们想要处理的子元素,如果是,则执行相应的操作。
那是具体是如何捕获的呢?
我们在上面的addEventListener()
方法中可知,function()在没有明确参数时,是以隐式方式传递点击时触发的对象,当用户点击 parentElement
或其子元素时,会触发 click
事件,并将相应的事件对象(event)传递给该函数。