什么是DOM和BOM
JavaScript的三大组成部分
上面这张图,我们可以看到有四个元素:JavaScript,ECMAScript,DOM和BOM,那么它们四个之间有什么联系呢?
1 | JavaScript = ECMAscript + BOM + DOM |
ECMAScript 是一种由 ECMA国际(前身为欧洲计算机制造商协会)通过 ECMA-262 标准化的脚本程序设计语言,它是JavaScript(简称JS)的标准,浏览器就是去执行这个标准。
ECMAscript更像一个规定,规定了各个浏览器怎么样去执行JavaScript的语法,因为我们知道JavaScript是运行在浏览器上的脚本语言!有了规定,但是我们还缺少与页面中各个元素交互的方式,此时下面的DOM诞生了!
DOM
Document Object Model,文档对象模型
简单说,DOM 是浏览器将 HTML 文档解析后生成的树状结构对象,它将 HTML 中的每个标签、文本、属性等都转换成可被 JS 操作的 “对象”,让 JS 能动态修改页面内容、结构和样式。
它也可以理解成一种独立于语言,用于操作xml,html文档的应用编程接口,对于JavaScript,为了能够使JavaScript操作Html,JavaScript就有了一套自己的DOM编程接口。
树状结构:HTML 文档的每个部分(如
<html>、<div>、文本、属性)都是树中的一个
“节点(Node)”,根节点是 document(代表整个文档)。
例:一个简单的 HTML 结构对应的 DOM 树:
1 | <html> |
对应的 DOM 树结构:
document → html 节点 → body
节点 → 包含 h1 节点(文本 “Hello”)和 p
节点(文本 “World”)。、
很明显,BOM树中,每个节点可以有两个身份:可以是父节点的子节点,也可以是其他子节点的父节点
BOM
Browser Object Model,浏览器对象模型
BOM 是浏览器提供的与浏览器窗口交互的对象模型,它不针对页面内容,而是控制浏览器本身的行为(如窗口大小、地址栏、历史记录等)。
BOM 是为了控制浏览器的行为而出现的接口。对于JavaScript,为了能够让JavaScript能控制浏览器的行为,JavaScript就有了一套自己的BOM接口。
核心特点:
- 以
window为核心:window是 BOM 的顶层对象,代表浏览器窗口,所有 BOM 对象都是window的属性。 - 主要子对象:
window.document:指向 DOM 的根节点(因此 DOM 可以看作是 BOM 的一部分)。window.location:控制浏览器地址栏(如location.href = 'https://www.baidu.com'可跳转页面)。window.history:操作浏览器历史记录(如history.back()后退一页)。window.navigator:获取浏览器信息(如浏览器版本、设备类型)。window.screen:获取屏幕信息(如屏幕分辨率)。- 弹窗相关:
alert()、confirm()、prompt()等。
DOM 与 BOM 的关系
- 包含关系:DOM 是 BOM
的一部分(
window.document属于 BOM)。 - 作用范围:
- DOM 专注于页面内容(HTML 结构、样式、文本)。
- BOM 专注于浏览器本身(窗口、地址、历史等)。
JavaScript操作DOM
DOM 树中的每个元素都是 “节点”,常见节点类型包括:
- 元素节点(Element):HTML 标签(如
<div>、<p>),是最常用的节点类型。 - 文本节点(Text):标签内的文本内容(如
<p>文本</p>中的 “文本”)。 - 属性节点(Attribute):标签的属性(如
<img src="pic.jpg">中的src)。 - 文档节点(Document):整个 HTML
文档的根节点(
document对象)。
可以通过节点的 nodeType 属性判断类型(元素节点为
1,文本节点为 3,文档节点为
9)。
获取节点的DOM方法
要操作节点,首先需要 “找到” 它。DOM 提供了多种查找方法:
按照标签名查找
1 | //1.通过元素的id属性值来获取元素,返回的是一个元素对象,唯一 |
按照 name 属性查找
1 | //2.通过元素的name属性值来获取元素,返回的是一个元素对象的数组 |
按照类名查找
1 | // 通过元素的class属性值来获取元素,返回的是一个元素对象的数组 |
按照标签名查找
1 | // 通过标签名获取元素,返回的是一个元素对象数组 |
按 CSS 选择器查找
1 | // 返回匹配选择器的第一个元素 |
注意:
getElementsByXXX返回的是动态集合(页面元素变化时会自动更新)。querySelectorAll返回的是静态集合(不会随页面变化)
可交互示例:尝试不同的获取节点方法
下面是一个可交互的示例,你可以尝试不同的获取节点方法:
<!DOCTYPE html><h3>获取节点方法演示</h3>
<div id="test-area">
<p id="para1" class="item active">段落1 (id: para1, class: item active)</p>
<p id="para2" class="item">段落2 (id: para2, class: item)</p>
<span name="test" class="item">Span元素 (name: test)</span>
<p class="item active">段落3 (class: item active)</p>
</div>
<div style="margin: 15px 0;">
<button onclick="testGetById()">getElementById('para1')</button>
<button onclick="testGetByClass()">getElementsByClassName('active')</button>
<button onclick="testGetByTag()">getElementsByTagName('p')</button>
<button onclick="testGetByName()">getElementsByName('test')</button>
<button onclick="testQuerySelector()">querySelector('.active')</button>
<button onclick="testQuerySelectorAll()">querySelectorAll('.item')</button>
<button onclick="clearHighlight()">清除高亮</button>
</div>
<div class="result-box" id="result">点击上方按钮查看结果...</div>
操作 DOM 节点(增删改查)
创建节点
1 | // 创建元素节点(<p>) |
每个属性节点都有一个
name(属性名,如href、class)和value(属性值,如https://example.com)。属性节点不能独立存在,必须通过
setAttributeNode绑定到某个元素节点上才有效。所以创建属性节点这个方法,要搭配具体的元素,也就是你要先获取某个具体元素,然后对这个元素创建一个属性节点,最后对这个元素添加这个属性节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<!-- 初始元素 -->
<div id="box"></div>
<script>
// 1. 获取元素节点
const box = document.getElementById('box');
// 2. 创建属性节点(指定属性名:"class")
const classAttr = document.createAttribute('class');
// 3. 给属性节点设置属性值("active")
classAttr.value = 'active';
// 4. 将属性节点绑定到元素上
box.setAttributeNode(classAttr);
// 此时元素变为:<div id="box" class="active"></div>
</script>但是几乎没有像上面这么写的,上面的操作可以用
setAttribute直接完成(无需显式创建属性节点)1
box.setAttribute('class', 'active'); // 直接设置属性名和值
添加节点
1 | // 向父节点末尾添加子节点 |
注意,添加节点之前,你要先创建好节点,同时要选好父节点element,在指定子节点前插入新节点,用这个方法你还要找好插入位置后面的兄弟节点。
可交互示例:动态创建和添加节点
下面是一个可交互的示例,你可以动态创建节点并添加到页面中:
<!DOCTYPE html><h3>创建和添加节点演示</h3>
<div>
<label>节点内容:</label>
<input type="text" id="nodeContent" value="新节点" placeholder="输入节点内容">
</div>
<div>
<label>插入位置:</label>
<select id="insertPosition">
<option value="append">末尾添加 (appendChild)</option>
<option value="before">在第一个节点前 (insertBefore)</option>
</select>
</div>
<div>
<button onclick="createAndAdd()">创建并添加节点</button>
<button onclick="clearAll()">清空所有节点</button>
</div>
<div id="parent-container">
<p class="new-item">这是初始节点1</p>
<p class="new-item">这是初始节点2</p>
</div>
<div class="info" id="info">节点数量:2</div>
删除节点
删除节点的核心是将某个节点从 DOM 树中移除,使其不再参与页面渲染。DOM 提供了两种常用方法:
1 | // 父节点删除子节点 |
parent.removeChild(child):通过父节点删除子节点
父节点.removeChild(要删除的子节点)
从父节点的子节点列表中移除指定的子节点,并返回被删除的节点(可后续复用)。
1 | <div id="parent"> |
必须明确指定 “父节点” 和 “要删除的子节点”,且子节点必须是父节点的直接子节点
注意,被删除的节点并未被销毁,只是从 DOM 树中移除,可通过变量保存后重新添加到其他位置。
child.remove():节点自身删除(更简洁)
要删除的节点.remove(),直接删除当前节点,无需显式指定父节点(内部会自动找到其父节点并执行删除)。
1 | const child2 = document.getElementById('child2'); |
IE 不支持
可交互示例:删除节点演示
下面是一个可交互的示例,你可以尝试删除节点:
<!DOCTYPE html><h3>删除节点演示</h3>
<div id="list-container">
<div class="list-item" id="item1">
<span>项目 1</span>
<button class="delete-btn" onclick="removeByParent(this)">使用 parent.removeChild() 删除</button>
</div>
<div class="list-item" id="item2">
<span>项目 2</span>
<button class="delete-btn" onclick="removeBySelf(this)">使用 element.remove() 删除</button>
</div>
<div class="list-item" id="item3">
<span>项目 3</span>
<button class="delete-btn" onclick="removeByParent(this)">使用 parent.removeChild() 删除</button>
</div>
<div class="list-item" id="item4">
<span>项目 4</span>
<button class="delete-btn" onclick="removeBySelf(this)">使用 element.remove() 删除</button>
</div>
</div>
<div class="info" id="info">当前节点数量:4</div>
替换节点(parent.replaceChild)
替换节点是用一个新节点替换父节点中的某个旧节点,本质是 “先删除旧节点,再添加新节点” 的合并操作。
父节点.replaceChild(新节点, 旧节点)
将父节点中的 “旧节点” 替换为 “新节点”,并返回被替换的旧节点,旧节点并非被销毁,只是移出DOM树,可后续复用。
1 | <div id="parent"> |
新节点可以是:
- 新创建的节点(如
createElement创建的元素); - 从其他位置 “移动” 过来的节点(此时会从原位置移除,添加到新位置)。
1
2
3// 从 otherParent 中移动 node 到 parent 中,替换 oldNode
const node = document.getElementById('node');
parent.replaceChild(node, oldNode);- 新创建的节点(如
旧节点必须是父节点的直接子节点,否则会报错(同
removeChild)。
复制节点(cloneNode)
复制节点用于创建一个节点的副本(克隆),便于快速生成结构相同的节点。
被复制的节点.cloneNode(deep)
- 参数
deep:布尔值,true表示 “深复制”(复制节点本身及所有子节点),false表示 “浅复制”(只复制节点本身,不包含子节点)。 - 返回值:复制出的新节点(未添加到 DOM 树中,需手动添加)。
浅复制(deep: false)
只复制节点本身,不包含子节点(文本、子元素等都不会复制)。
1 | <p id="original"> |
深复制(deep: true)
复制节点本身及所有子节点(包括文本、嵌套元素等,递归复制整个子树)。
1 | // 深复制:复制 <p> 及所有子节点(文本和 <span>) |
注意事项:
复制节点不会复制事件监听器:
无论
addEventListener绑定的事件,还是onclick等属性绑定的事件,都不会被复制到新节点。复制节点的
id属性会重复:若原节点有
id(唯一标识),复制后新节点会保留相同id,导致页面中id重复(不符合 HTML 规范)。需手动修改新节点的id1
2const clone = original.cloneNode(true);
clone.id = 'clone-' + original.id; // 避免 id 重复表单元素的状态不会被复制:
如输入框的
value、复选框的checked等用户操作后的状态,深复制时会保留默认值(而非当前值)。1
2
3
4
5
6
7<input type="text" id="input" value="默认值">
<script>
const input = document.getElementById('input');
input.value = '用户输入'; // 手动修改值
const clone = input.cloneNode(true);
console.log(clone.value); // 输出 "默认值"(而非 "用户输入")
</script>
修改节点内容与样式
内容修改
textContent:纯文本内容(推荐,安全,不会解析 HTML)。innerHTML:HTML 内容(会解析 HTML,有 XSS 风险,谨慎使用)。
1 | const p = document.querySelector('p'); |
样式修改
- 通过
style属性直接修改行内样式(CSS 属性名用驼峰式,如fontSize)。 - 通过
classList操作 CSS 类(推荐,更符合分离思想)。
1 | const div = document.querySelector('div'); |
可交互示例:修改节点内容和样式
下面是一个可交互的示例,你可以尝试修改节点的内容和样式: <!DOCTYPE html><h3>修改节点内容和样式演示</h3>
<div id="target-element">这是目标元素,尝试修改它的内容和样式!</div>
<div class="control-group">
<h4>修改内容:</h4>
<input type="text" id="textContent" placeholder="使用 textContent" value="纯文本内容">
<button onclick="setTextContent()">设置 textContent</button>
<br>
<input type="text" id="innerHTML" placeholder="使用 innerHTML" value="<strong>HTML</strong>内容">
<button onclick="setInnerHTML()">设置 innerHTML</button>
</div>
<div class="control-group">
<h4>修改样式(style 属性):</h4>
<input type="color" id="colorPicker" value="#000000">
<button onclick="setColor()">设置文字颜色</button>
<br>
<input type="text" id="fontSize" placeholder="字体大小,如: 20px" value="16px">
<button onclick="setFontSize()">设置字体大小</button>
</div>
<div class="control-group">
<h4>操作 CSS 类(classList):</h4>
<button onclick="addClass('style-red')">添加红色样式</button>
<button onclick="addClass('style-blue')">添加蓝色样式</button>
<button onclick="addClass('style-green')">添加绿色样式</button>
<button onclick="addClass('style-large')">添加大字体</button>
<button onclick="addClass('style-border')">添加边框</button>
<br>
<button onclick="removeClass('style-red')">移除红色</button>
<button onclick="removeClass('style-blue')">移除蓝色</button>
<button onclick="removeClass('style-green')">移除绿色</button>
<button onclick="removeClass('style-large')">移除大字体</button>
<button onclick="removeClass('style-border')">移除边框</button>
<br>
<button onclick="toggleClass('style-large')">切换大字体</button>
<button onclick="checkClass('style-red')">检查红色类</button>
<button onclick="clearAllClasses()">清除所有类</button>
</div>
操作节点属性
HTML 标签的属性(如
src、href、id)可以通过 DOM
方法操作:
获取/设置元素的属性值的DOM方法
他们是通用方法(setAttribute/getAttribute),适用于所有属性(包括自定义属性)
1 | //1.获取元素的属性值,传参是属性名,例如class、id、href |
属性节点是一个
“容器”,里面存储着属性名和属性值(attrNode.name
是属性名,attrNode.value 是属性值)。
属性值是属性节点中存储的具体内容,即属性名对应的 “值”。例如:
<img src="pic.jpg" alt="图片">中,src的属性值是pic.jpg,alt的属性值是图片。- 属性值可以是字符串、布尔值(如
disabled的值可以是true或false)等。
原生属性(如
id、src)
1 | const img = document.querySelector('img'); |
自定义属性(data-*)
HTML5 允许通过 data-* 定义自定义属性,通过
dataset 访问:
1 | <div data-user-id="123" data-user-name="张三"></div> |
1 | const div = document.querySelector('div'); |
DOM 遍历(节点关系)
每个节点都有属性表示与其他节点的关系,可用于遍历 DOM 树:
DOM 树中,节点之间的关系类似 “家族关系”,可分为三大类:父节点与子节点、兄弟节点、祖先与后代节点。
每个节点都有一系列的属性用于描述这些关系,那么遍历就可以通过这些属性来进行
父节点相关属性(向上遍历)
| 属性 | 含义 |
|---|---|
node.parentNode |
返回当前节点的直接父节点(如果没有父节点,返回
null)。 |
node.parentElement |
返回当前节点的直接父元素节点(仅返回元素节点,非元素节点返回
null)。 |
1 | <div id="parent"> |
parentNode可以返回任何类型的父节点(元素、文档、文档片段等)。- 那我这里为什么要提这么个注释节点呢?因为非元素节点(这里是注释节点)”
的父节点属性用法,会在
parentNode和parentElement有细微差异- HTML 中
<!-- 注释内容 -->会被浏览器解析为 注释节点(DOM 节点类型nodeType = 8),它不属于元素节点(元素节点是<div>、<p>这类标签),但也是 DOM 树的一部分,有自己的父节点。 - 首先,
document.body是<body>元素节点,childNodes是<body>下的所有子节点(包括元素、文本、注释等)。document.body.childNodes[1];是<div id="parent">下的<!-- 注释节点 --> 而parentNode会返回它的 “直接父节点”,不管父节点是什么类型。而parentElement只返回 “直接父元素节点”,也就是父节点必须是元素节点,比如<div>、<body>这类标签,不能是文本、文档等节点。
- HTML 中
子节点相关属性(向下遍历)
| 属性 | 含义 |
|---|---|
node.childNodes |
返回当前节点的所有子节点集合(NodeList 类型,包括元素、文本、注释等)。 |
node.children |
返回当前节点的所有子元素节点集合(HTMLCollection 类型,仅包含元素节点)。 |
node.firstChild |
返回当前节点的第一个子节点(任意类型)。 |
node.lastChild |
返回当前节点的最后一个子节点(任意类型)。 |
node.firstElementChild |
返回当前节点的第一个子元素节点(仅元素节点)。 |
node.lastElementChild |
返回当前节点的最后一个子元素节点(仅元素节点)。 |
1 | <div id="parent"> |
childNodes会包含空白文本节点(由 HTML 中的换行、空格产生),遍历时常需过滤(如node.nodeType === 1只保留元素节点)。children只包含元素节点,无需处理文本 / 注释节点,更适合大多数场景,一般都用这个
兄弟节点相关属性(水平遍历)
| 属性 | 含义 |
|---|---|
node.nextSibling |
返回当前节点的下一个兄弟节点(任意类型,包括文本、注释等)。 |
node.previousSibling |
返回当前节点的上一个兄弟节点(任意类型)。 |
node.nextElementSibling |
返回当前节点的下一个兄弟元素节点(仅元素节点)。 |
node.previousElementSibling |
返回当前节点的上一个兄弟元素节点(仅元素节点)。 |
1 | <div id="parent"> |
- 带
Element的属性(如nextElementSibling)会自动跳过文本、注释等非元素节点,直接定位到元素节点,更实用。 - 不带
Element的属性(如nextSibling)需手动处理空白文本节点,否则可能拿到意料之外的节点。
一些注意
空白文本节点的干扰:
HTML 中的换行、空格会被解析为文本节点(
nodeType = 3),使用childNodes、nextSibling等属性时需注意过滤(推荐优先用带Element的属性)。动态集合的特性:
children(HTMLCollection)和childNodes(NodeList)是动态集合,当 DOM 结构变化时会自动更新。遍历过程中若修改 DOM(如删除节点),可能导致遍历异常(如跳过或重复处理节点)。解决方法:将动态集合转为静态数组(如
Array.from(children))再遍历。
JS 的 DOM 事件处理
事件相关内容
DOM 事件处理是 JavaScript 与用户交互的核心机制,它让页面能够响应各种操作
- 事件(Event):指用户在页面上的操作(如点击按钮、输入文本)或浏览器自身的状态变化(如页面加载完成、窗口大小改变)。
- 事件源(Event Target):触发事件的 DOM 节点(如被点击的按钮、输入的文本框)。
- 事件处理程序(Event Handler):当事件触发时执行的函数(也叫 “事件监听器”)。
例如,点击按钮弹出提示框,其中 “点击” 是事件,“按钮” 是事件源,“弹出提示框的函数” 是事件处理程序
事件绑定的三种方式
将事件处理程序与事件源关联的过程,称为 “事件绑定”。
HTML 内联绑定
不推荐
直接在 HTML 标签中通过 onXXX
属性绑定事件处理函数(XXX 为事件类型,如
onclick)。
1 | <!-- 直接写函数调用 --> |
不推荐,为什么?
- HTML 与 JavaScript 代码混合,而且极易因为函数名重复导致冲突(会覆盖之前的定义)
- 所以只能绑定一个处理函数(重复绑定会覆盖)。
DOM 属性绑定(onXXX
属性)
通过 JS 获取 DOM 节点后,直接给节点的 onXXX
属性赋值事件处理函数。
1 | <button id="btn">点击我</button> |
- 逻辑清晰(JS 代码集中管理),比内联绑定更推荐。
- 同样只能绑定一个处理函数(重复绑定会覆盖)。
- 兼容性好(支持所有浏览器,包括 IE)。
可交互示例:三种事件绑定方式对比
下面是一个可交互的示例,对比三种事件绑定方式:
<!DOCTYPE html><h3>三种事件绑定方式对比</h3>
<div class="button-group">
<h4>1. HTML 内联绑定(不推荐)</h4>
<button class="inline-btn" onclick="handleInlineClick()">内联绑定按钮</button>
<button class="inline-btn" onclick="handleInlineClick()">重复绑定(会覆盖)</button>
<p style="color: #666; font-size: 12px;">注意:两个按钮都调用同一个函数,但只能绑定一个处理函数</p>
</div>
<div class="button-group">
<h4>2. DOM 属性绑定(onXXX)</h4>
<button class="property-btn" id="propertyBtn1">属性绑定按钮1</button>
<button class="property-btn" id="propertyBtn2">属性绑定按钮2(重复绑定会覆盖)</button>
</div>
<div class="button-group">
<h4>3. addEventListener(推荐)</h4>
<button class="listener-btn" id="listenerBtn">addEventListener 按钮</button>
<button class="listener-btn" id="removeListenerBtn">移除事件监听</button>
<p style="color: #666; font-size: 12px;">可以绑定多个处理函数,且可以移除</p>
</div>
<div class="log-area" id="logArea">
<div class="log-item">事件日志将显示在这里...</div>
</div>
<button onclick="clearLog()" style="background: #757575; color: white;">清空日志</button>
addEventListener
方法(推荐)
通过 DOM 节点的 addEventListener
方法绑定事件,支持绑定多个处理函数,是大伙开发的首选方式。
语法:
1 | element.addEventListener(eventType, handler, useCapture); |
eventType:事件类型字符串(如'click'、'input',不带on前缀)。handler:事件触发时执行的函数。useCapture:布尔值(可选,默认false),表示是否在 “捕获阶段” 触发事件(详见 “事件流” 部分)。
1 | <button id="btn">点击我</button> |
点击按钮后,控制台会依次输出 点击事件1 和
点击事件2。
Java 中的 swing 也是这样为按钮绑定事件的
他也能移除事件,通过 removeEventListener
移除已绑定的事件(需传入与绑定相同的事件类型和函数):
1 | function handleClick() { |
事件对象
当事件触发时,浏览器会自动创建一个事件对象(Event Object),包含事件的详细信息(如触发位置、按键信息等),它会作为参数传递给事件处理函数。
常用属性和方法:
| 属性 / 方法 | 说明 |
|---|---|
target |
事件的实际触发节点(事件源)。 |
currentTarget |
绑定事件处理函数的节点(当前处理事件的节点,通常与 this
一致)。 |
type |
事件类型(如 'click'、'input')。 |
clientX/clientY |
鼠标触发事件时,相对于浏览器可视区域的坐标。 |
key |
键盘事件中,按下的按键值(如
'Enter'、'a')。 |
preventDefault() |
阻止事件的默认行为(如阻止链接跳转、表单提交)。 |
stopPropagation() |
阻止事件冒泡(详见 “事件流”)。 |
1 | <a href="https://example.com" id="link">链接</a> |
事件流
事件流就是事件传播机制
当一个事件触发时,会在 DOM 树中按照特定顺序传播,这个过程称为事件流。DOM 事件流分为 3 个阶段:
捕获阶段(Capture Phase):
事件从最顶层的节点(
document)向下传播到事件源的父节点(不包括事件源本身)。目标阶段(Target Phase):
事件到达实际触发事件的节点(事件源)。
冒泡阶段(Bubbling Phase):
事件从事件源向上传播回最顶层的节点(
document)。
1 | <div id="grandparent" style="padding: 50px; background: red;"> |
当我们点击蓝色的 child 节点,输出顺序为:
1 | 爷爷捕获 → 爸爸捕获 → 儿子捕获 → 儿子冒泡 → 爸爸冒泡 → 爷爷冒泡 |
可通过 event.stopPropagation()
阻止事件冒泡(避免父节点处理事件)。
可交互示例:事件流(捕获和冒泡)可视化
下面是一个可交互的示例,直观展示事件流的三个阶段:
<!DOCTYPE html><h3>事件流(捕获和冒泡)可视化演示</h3>
<div class="info-box">
<strong>说明:</strong>点击最内层的蓝色方块,观察事件如何在 DOM 树中传播。
事件会经历三个阶段:<strong>捕获阶段</strong>(向下)→ <strong>目标阶段</strong>(到达目标)→ <strong>冒泡阶段</strong>(向上)
</div>
<div id="grandparent">
<div id="parent">
<div id="child"></div>
</div>
</div>
<div class="controls">
<button onclick="clearLog()">清空日志</button>
<button onclick="toggleStopPropagation()" id="stopBtn">启用 stopPropagation()</button>
<span id="stopStatus" style="margin-left: 10px; color: #666;"></span>
</div>
<div class="log-area" id="logArea">
<div class="log-item log-target">点击上面的蓝色方块开始...</div>
</div>
常见事件类型
按场景分类,常用事件类型如下:
鼠标事件
| 事件名 | 触发时机 |
|---|---|
click |
鼠标左键单击 |
dblclick |
鼠标左键双击 |
mouseover |
鼠标移入节点 |
mouseout |
鼠标移出节点 |
mousemove |
鼠标在节点上移动 |
mousedown |
鼠标按下(任意键) |
mouseup |
鼠标松开(任意键) |
键盘事件
| 事件名 | 触发时机 |
|---|---|
keydown |
按键按下(持续按住会重复触发) |
keyup |
按键松开 |
keypress |
按下字符键(已逐步被弃用) |
表单事件
| 事件名 | 触发时机 |
|---|---|
input |
表单元素值变化(实时触发) |
change |
表单元素值变化且失去焦点(如下拉框选择后) |
submit |
表单提交(点击提交按钮或按 Enter) |
focus |
元素获得焦点(如输入框被点击) |
blur |
元素失去焦点 |
页面事件
| 事件名 | 触发时机 |
|---|---|
load |
页面或资源(如图片)加载完成 |
resize |
浏览器窗口大小改变 |
scroll |
页面或元素滚动 |
DOMContentLoaded |
DOM 加载完成(无需等待图片等资源) |
事件委托(Event Delegation)
利用事件冒泡,让父节点帮子节点干活
把多个子节点的事件处理逻辑集中到父节点
当需要给多个子节点绑定相同事件时(如列表项点击),直接遍历绑定会导致性能问题(尤其是动态生成的节点)。
事件委托利用事件冒泡机制,将事件处理函数绑定到父节点,通过
event.target 判断实际触发事件的子节点,从而统一处理。
1 | <ul id="list"> |
我这个太保守了,假设我们上面的这一个列表,里面有 100
个列表项(<li>),一个个绑有点费人
既然所有<li>都有同一个父节点(比如<ul>),而且事件会冒泡(点击子节点时,事件会向上传播到父节点),那我们可以只给父节点绑定一次事件,让父节点
“代理” 所有子节点的事件。
给父节点绑定事件
1 | const list = document.getElementById('list'); |
点击任何子节点(比如<li>)时,事件会通过
“冒泡”
机制传到父节点<ul>,所以父节点的事件处理函数会被触发。
判断实际点击的子节点
当父节点的事件处理函数被触发时,我们需要知道 “到底是哪个子节点被点击了”,你不可能都让所有子节点全绑上
这时可以通过事件对象(event)的target属性获取实际触发事件的节点(即被点击的子节点)。
1 | list.addEventListener('click', function(event) { |
tagName返回节点的标签名(大写,如LI、SPAN),通过它可以筛选出我们需要的节点。
当点击 “添加项”
按钮时,新的<li>会被添加到<ul>中:
1 | document.getElementById('add').addEventListener('click', function() { |
由于新的<li>也是<ul>的子节点,点击它时,事件同样会冒泡到<ul>,父节点的事件处理函数会自动处理这个新节点的点击
——不需要重新绑定事件!
可交互示例:事件委托实战演示
下面是一个完整的事件委托示例,展示如何高效处理动态列表:
<!DOCTYPE html><h3>事件委托实战演示</h3>
<div class="info-box">
<strong>优势:</strong>只给父节点 `<ul>` 绑定一次事件,所有子节点(包括动态添加的)都能自动响应点击事件,无需为每个子节点单独绑定。
</div>
<div class="controls">
<input type="text" id="newItemText" placeholder="输入新项目内容" value="新项目">
<button onclick="addItem()">添加项目</button>
<button onclick="addMultipleItems()">批量添加 5 个项目</button>
<button onclick="clearList()">清空列表</button>
</div>
<ul id="list">
<li>项目 1 - 点击我试试</li>
<li>项目 2 - 点击我试试</li>
<li>项目 3 - 点击我试试</li>
</ul>
<div class="log-area" id="logArea">
<div class="log-item">点击列表项查看事件委托效果...</div>
</div>







