js 只是语言,真正能让页面动起来的是 DOM、BOM,夹杂着交互产生的事件。
一、 事件流
事件流是描述事件从产生到被处理的完整过程。
- 捕获阶段:事件从最外层的根结点(
window)开始,逐层向内传播到目标元素的父节点(不包含目标元素) - 目标阶段:事件到达实际触发的目标元素,执行该元素上的事件监听器
- 冒泡阶段:事件从目标元素开始,逐层向外传播回根结点(
window)
通过 addEventListener(type, listener, useCapture) 注册事件,useCapture 参数控制监视器在那个阶段执行
useCapture = true:监听器在捕获阶段触发useCapture = false:监听器在冒泡阶段触发
注意
- 并非所有的事件都支持冒泡(如
focus、blur,需用focusin、focusout代替已支持冒泡) event.target是实际触发事件的元素,event.currentTarget是当前执行监听器的元素(常用于事件委托)removeEventListener的useCapture参数必须和注册时一致,否则无法移除(匿名函数无法移除,因引用不同)
二、 事件委托
事件委托(事件代理)利用事件冒泡机制,将子元素的事件监听器统一绑定在父元素上,由父元素处理子元素的事件。
- 子元素触发事件后,事件会冒泡到父元素
- 父元素通过
event.target判断具体触发事件的子元素,再执行对应的逻辑
注意
- 目标元素判断不准确:若子元素嵌套,
e.target可能是子元素的子元素而非期望的子元素,需使用e.target.closest('xx')向上查找最近的目标子元素 - 误阻止冒泡:子元素调用了
event.stopPropagation()阻止了事件的进一步冒泡,会导致父元素的委托监听器无法执行
三、 DOM 节点的操作
DOM 节点操作是指对 HTML 元素节点进行 创建、添加、移除、复制、替换、查找等操作,是动态修改页面结构的基础。
| 操作 | 方法 | 说明 |
|---|---|---|
| 创建节点 | document.createElement(tag) | 创建元素节点 |
document.createTextNode(text) | 创建文本节点 | |
| 查找节点 | getElementById | 最快,没有之一。但 ID 值需要唯一 |
getElementByClassName | 返回 HTMLCollection(返回类数组,动态) | |
getElementsByTagName | 返回 HTMLCollection(返回类数组,动态) | |
querySelector/querySelectorAll | 支持 CSS 选择器,灵活但性能略低(返回静态列表) | |
| 添加元素 | parent.appendChild(child) | 将子节点添加到父节点的末尾(若子节点已存在于文档树,会移动位置) |
parent.insertBefore(newChild, ref) | 在 ref 节点前插入 newChild 节点 | |
parent.append(...nodes) | 将一个或多个节点或字符串添加到末尾 | |
parent.prepend(...nodes) | 将一个或多个节点或字符串添加到开头 | |
node.after(...nodes) | 在节点之后插入 | |
node.before(...nodes) | 在节点之前插入 | |
node.replaceWith(...nodes) | 用新节点替换当前节点 | |
| 移除节点 | parent.removeChild(child) | 通过父节点移除子节点 (返回被移除的节点) |
child.remove() | 节点自身调用删除(IE 不支持,需调用 removeChild 兼容) | |
| 复制节点 | node.cloneNode(deep) | deep=true 复制节点及所有子节点;deep=false 仅复制节点本身(无事件) |
| 替换节点 | parent.replaceChild(new, old) | 使用 new 节点替换掉 old 的节点 |
| 修改节点 | element.innerHTML | 获取或设置元素的 HTML 内容。注意 XSS 风险 |
element.textContent | 获取或设置元素的纯文本内容。注意 XSS 风险 | |
element.innerText | 获取或设置元素的渲染后的文本内容 |
childNodes:返回所有的子节点(含文本、注释等,可能包含空白文本节点,如换行符)children:仅返回元素子节点(更常用,避免空白节点的干扰)firstChild🆚firstElementChild:前者返回的是第一个节点(可能是文本),后者返回的是第一个元素子节点nextSibling🆚nextElementSibling:前者返回下一个兄弟节点(可能是文本),后者返回下一个元素兄弟节点
注意
- 空白节点干扰:
childNodes包含换行/空格产生的文本节点,循环时需过滤(如node.nodeType === 1只处理元素节点) - 克隆节点丢失事件:
cloneNode不会复制通过addEventListener绑定的事件(若需要需重新监听及移除) - 删除节点后的引用残留:删除节点后若仍保留引用,可能导致内存泄漏(需手动设置为
null) - 创建不等于添加:动态创建的元素需要通过
appendChild或insertBefore插入 DOM 树后才会显示 - 减少频繁操作 DOM:频繁操作 DOM 时,建议先创建文档片段(
document.createDocumentFragment())批量处理,再一次性插入,减少重绘/回流
四、BOM ! 炸了
BOM 是 Browser Object Model 的缩写,及浏览器对象模型,它提供了独立于网页内容而与浏览器窗口进行交互的 API。
window对象是 JavaScript 中的顶层对象navigator包含客户端浏览器的信息screen包含客户端显示屏的信息history包含浏览器窗口访问过的 url 信息location包含当前网页文档的 url 信息document包含整个 HTML 文档,可被用来访问文档内容,及页面所有的页面元素
注意
replace和assign混淆:replace会清除当前历史记录,用户无法返回上一页,需谨慎使用pushState导致 404:SPA 中使用pushState修改 URL 后,直接刷新页面可能会因服务器没有对应的路由而返回 404。需服务器配置支持(如 Nginx 转发到 index.html)popstate事件误解:pushState/replaceState不会触发popstate,仅在用户点击前进/后退或调用back()/forward()/go()时触发