Skip to content

DOM

Document Object Model

1.节点

1. 节点类型

nodeType

nodeType 属性返回节点类型的常数值。不同的类型对应不同的常数值,12 种类型分别对应 1 到 12 的常数值

nodeName

nodeName 属性返回节点的名称

nodeValue

nodeValue 属性返回或设置当前节点的值,格式为字符串

Attrvalue of attribute
CDATASectioncontent of the CDATA Section
Comment注释内容,不包含注释符号
Documentnull
DocumentFragmentnull
DocumentTypenull
Elementnull
EntityReferencenull
Notationnull
ProcessingInstructionentire content excluding the target
Text节点内容

1.元素节点

Node.ELEMENT_NODE (1)

元素节点 element 对应网页的 HTML 标签元素元素节点的节点类型 nodeType 值是 1,节点名称 nodeName 值是大写的标签名,nodeValue 值是 null。

2.特性节点

Node.ATTRIBUTE_NODE (2)

特性节点 attribute 对应网页种 HTML 标签的属性,它只存在在元素的 attribute 属性中,并不是 DOM 文档树的一部分,特性节点的节点类型 nodeType 值是 2,节点名称 nodeName 值是属性名,nodeValue 值是属性值。

3.文本节点Node.TEXT_NODE (3)

文本节点 text 代表网页中的 HTML 标签内容。文本节点的节点类型 nodeType 值是 3,节点名称 nodeName 值是'#text' ,nodeValue 值是标签内容值。

4.CDATA 节点

Node.CDATA_SECTION_NODE (4)

CDATASection 类型只针对 XML 的文档,只出现在 XML 文档中。该类型节点的节点类型 nodeType 的值为 4,节点名称 nodeName 的值为'#cdata-section' ,nodevalue 的值是 CDATA 区域中的内容。

5.实体引用名称节点

Node.ENTITY_REFERENCE_NODE (5)

实体是一个声明,指定了在 XML 中取代内容或标记而使用的名称,在实体声明中定义的名称将在 XML 中使用。 在 XML 中使用时,该名称称为实体引用。实体引用名称节点 entry_reference 的节点类型 nodeType 的值为 5,节点名称 nodeName 的值为实体引用的名称,nodeValue 的值为 null。

6.实体名称节点

Node.ENTITY_NODE (6)

使用实体声明将名称绑定到替换内容。 实体声明是使用 语法在文档类型定义(DTD) 或 XML 架构中创建的。实体名称节点类型 nodeType 的值为 6,节点名称 nodeName 的值为实体名称,nodeValue 的值为 null。

7.处理指令节点

Node.PROCESSING_INSTRUCTION_NODE (7)

处理指令节点 ProcessingInstruction 的节点类型 nodeType 的值为 7,节点名称 nodeName 的值为 target,nodeValue 的值为 entire content excluding the target。

8.注释节点

Node.COMMENT_NODE (8)

注释节点 comment 表示网页中的 HTML 注释。注释节点的节点类型 nodeType 的值为 8,节点名称 nodeName 的值为'#comment' ,nodeValue 的值为注释的内容。

9.文档节点

Node.DOCUMENT_NODE (9)

文档节点 document 表示 HTML 文档,也称为根节点,指向 document 对象。文档节点的节点类型 nodeType 的值为 9,节点名称 nodeName 的值为'#document' ,nodeValue 的值为 null。

10.文档类型节点

Node.DOCUMENT_TYPE_NODE (10) 文档类型节点 DocumentType 包含着与文档的 doctype 有关的所有信息。文档类型节点的节点类型 nodeType 的值为 10,节点名称 nodeName 的值为 doctype 的名称,nodeValue 的值为 null。

11.文档片段节点

Node.DOCUMENT_FRAGMENT_NODE (11)

文档片段节点 DocumentFragment 在文档中没有对应的标记,是一种轻量级的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。该节点的节点类型 nodeType 的值为 11,节点名称 nodeName 的值为'#document-fragment' ,nodeValue 的值为 null。

12.DTD 声明节点

Node.NOTATION_NODE (12)

DTD 声明节点 notation 代表 DTD 中声明的符号。该节点的节点类型 nodeType 的值为 12,节点名称 nodeName 的值为符号名称,nodeValue 的值为 null

2.获取节点

返回值类型为 nodelist,是个类数组。

  1. 使用forEach 和 迭代器 进行遍历
  2. Array.from(els) 转为数组,方便使用数组的方法进行操作。
  1. getElementById

  2. getElementsByClassName()

  3. getElementsByTagName()

  4. getElementsByName()

  5. querySelector()

  6. querySelectorAll)()

1. 父节点相关

属性作用补充说明
parentNode获取直接父节点最常用,返回元素/文档等节点
parentElement获取直接父元素节点只返回 nodeType === 1 的元素节点(比 parentNode 更精准)

2. 兄弟节点相关

属性作用注意点
previousSibling获取上一个兄弟节点包含文本节点、注释节点等所有节点
nextSibling获取下一个兄弟节点同上
previousElementSibling获取上一个元素兄弟节点只返回元素节点(过滤文本/注释)
nextElementSibling获取下一个元素兄弟节点同上(开发中更常用)

3. 子节点相关

属性作用注意点
firstChild获取第一个子节点包含文本/注释节点(比如换行、空格)
lastChild获取最后一个子节点同上
childNodes获取所有类型的子节点返回 NodeList 集合(类数组),包含文本、注释、元素等
children获取所有元素子节点返回 HTMLCollection 集合,只包含 nodeType === 1 的元素节点(开发中最常用)
firstElementChild获取第一个元素子节点过滤文本/注释,直接拿元素
lastElementChild获取最后一个元素子节点同上
childElementCount获取元素子节点的数量等价于 children.length

关键示例

html
<div id="parent">
  <!-- 注释 -->
  <p>第一个元素</p>
  <span>第二个元素</span>
</div>

<script>
  const parent = document.getElementById('parent');

  // 1. 错误示范(含文本/注释节点)
  console.log(parent.firstChild); // #comment(注释节点,因为换行/空格也算节点)
  console.log(parent.nextSibling); // 文本节点(换行)

  // 2. 正确用法(只拿元素节点)
  console.log(parent.firstElementChild); // <p>第一个元素</p>
  console.log(parent.children); // HTMLCollection(2) [p, span]
  console.log(parent.nextElementSibling); // null(无下一个元素兄弟)
</script>
  1. 开发中优先用 Element 后缀的属性(如 previousElementSibling/children),过滤无用的文本/注释节点;
  2. childNodes 包含所有节点类型,children 只包含元素节点,这是最常用的区分点。

3.节点操作

1. 创建节点

js
document.createElement('p'); // <p></p> nodeType 1
// 创建指定命名空间的元素节点 NameSpace 命名空间的名称 素节点名称
createElementNS('http://www.w3.org/2000/svg', 'svg'); // <svg></svg>  nodeType 1
document.createTextNode('文本节点 nodeType 3');
document.createComment('我是注释 nodeType 8'); // <!--我是注释 nodeType 8-->

let p = document.createElement('p');
p.append('hello world');
p.id = 'mydiv';
p.className = 'boxw';

2. 虚拟节点

虚拟节点:通过document.createDocumentFragment()创建的节点,它是一个存在于内存中的 DOM 节点,并没有添加到文档树中,所以不会影响页面的布局,因此可以用来临时存放节点,在需要的时候一次性添加到文档中,从而提高性能。

js
const fragment = document.createDocumentFragment();
fragment.appendChild(child1);
fragment.appendChild(child2);
document.body.appendChild(fragment);

3. 插入节点

js
let parent = document.createElement('div');
let child1 = document.createElement('p');
let child2 = document.createElement('span');
let child3 = document.createTextNode('文本节点');

// 1. appendChild() - 在父节点的子节点列表末尾添加一个子节点
parent.appendChild(child1);
parent.appendChild(child2);
parent.appendChild(child3);

// 2. insertBefore() - 在参考节点之前插入一个节点
let newChild = document.createElement('strong');
parent.insertBefore(newChild, child2);

// 3. insertAdjacentElement() - 在指定位置插入元素
// beforebegin: 元素自身的前面
// afterbegin: 插入元素内部的第一个子节点之前
// beforeend: 插入元素内部的最后一个子节点之后
// afterend: 元素自身的后面
let target = document.createElement('div');
let adjacent = document.createElement('p');
target.insertAdjacentElement('beforebegin', adjacent);
target.insertAdjacentElement('afterbegin', adjacent);
target.insertAdjacentElement('beforeend', adjacent);
target.insertAdjacentElement('afterend', adjacent);

// 4. insertAdjacentHTML() - 在指定位置插入 HTML 字符串
target.insertAdjacentHTML('beforebegin', '<p>beforebegin</p>');
target.insertAdjacentHTML('afterbegin', '<p>afterbegin</p>');
target.insertAdjacentHTML('beforeend', '<p>beforeend</p>');
target.insertAdjacentHTML('afterend', '<p>afterend</p>');

// 5. insertAdjacentText() - 在指定位置插入文本
target.insertAdjacentText('beforebegin', '文本内容');

// 6. append() - 在父节点的子节点列表末尾添加一个或多个节点
parent.append(child1, child2, '文本节点');

// 7. prepend() - 在父节点的子节点列表开头添加一个或多个节点
parent.prepend(child1, child2, '文本节点');

// 8. after() - 在元素之后插入节点
child1.after(child2, '文本节点');

// 9. before() - 在元素之前插入节点
child1.before(child2, '文本节点');

// 10. replaceWith() - 用一组节点替换自身
child1.replaceWith(child2, '新节点');

// 11. replaceChild() - 用新节点替换旧节点
parent.replaceChild(newChild, child1);

4. 删除节点

js
let parent = document.createElement('div');
let child1 = document.createElement('p');
let child2 = document.createElement('span');
let child3 = document.createElement('div');

parent.appendChild(child1);
parent.appendChild(child2);
parent.appendChild(child3);

// 1. removeChild() - 从父节点中删除指定的子节点
let removed = parent.removeChild(child1);
console.log(removed); // 返回被删除的节点

// 2. remove() - 从 DOM 中删除自身
child2.remove();

// 3. 删除所有子节点
while (parent.firstChild) {
  parent.removeChild(parent.firstChild);
}

// 4. 使用 innerHTML 清空子节点(不推荐,有安全风险)
parent.innerHTML = '';

// 5. 使用 textContent 清空子节点(推荐)
parent.textContent = '';

// 6. 删除特定类型的子节点
function removeElementsByTagName(parent, tagName) {
  let elements = parent.getElementsByTagName(tagName);
  for (let i = elements.length - 1; i >= 0; i--) {
    elements[i].remove();
  }
}
removeElementsByTagName(parent, 'div');

// 7. 删除带有特定类名的子节点
function removeElementsByClassName(parent, className) {
  let elements = parent.getElementsByClassName(className);
  for (let i = elements.length - 1; i >= 0; i--) {
    elements[i].remove();
  }
}

// 8. 使用 querySelectorAll 删除匹配的节点
parent.querySelectorAll('.to-remove').forEach(el => el.remove());

// 9. 删除节点的所有属性
function removeAllAttributes(element) {
  while (element.attributes.length > 0) {
    element.removeAttribute(element.attributes[0].name);
  }
}

// 10. 替换节点(删除并添加)
let newNode = document.createElement('article');
parent.replaceChild(newNode, child3);

5. 克隆节点

js
let original = document.createElement('div');
original.className = 'box';
original.textContent = '原始节点';

// 1. cloneNode() - 克隆节点
// 参数为 true 时,克隆节点及其所有后代节点
// 参数为 false 时,只克隆节点本身
let shallowClone = original.cloneNode(false);
let deepClone = original.cloneNode(true);

console.log(shallowClone.textContent); // 空字符串
console.log(deepClone.textContent); // '原始节点'

// 2. 克隆并添加到文档
document.body.appendChild(deepClone);

// 3. 克隆多个节点
let list = document.createElement('ul');
for (let i = 0; i < 5; i++) {
  let item = document.createElement('li');
  item.textContent = `项目 ${i + 1}`;
  list.appendChild(item);
}

let clonedList = list.cloneNode(true);
document.body.appendChild(clonedList);

6. 节点属性操作

js
let element = document.createElement('div');

// 1. 设置属性
element.setAttribute('id', 'myId');
element.setAttribute('class', 'myClass');
element.setAttribute('data-value', '123');

// 2. 获取属性
let id = element.getAttribute('id');
let className = element.getAttribute('class');
let dataValue = element.getAttribute('data-value');

// 3. 删除属性
element.removeAttribute('class');

// 4. 检查属性是否存在
let hasId = element.hasAttribute('id');
let hasClass = element.hasAttribute('class');

// 5. 直接属性访问(常用)
element.id = 'newId';
element.className = 'newClass';
element.style.color = 'red';
element.style.fontSize = '16px';

// 6. data-* 属性操作
element.dataset.userId = '123';
element.dataset.userName = '张三';
console.log(element.dataset.userId);
console.log(element.dataset.userName);

// 7. classList 操作
element.classList.add('active');
element.classList.remove('inactive');
element.classList.toggle('highlight');
element.classList.contains('active');

// 8. 多个类名操作
element.classList.add('class1', 'class2', 'class3');
element.classList.remove('class1', 'class2');

// 9. 替换类名
element.classList.replace('oldClass', 'newClass');
style 操作
setProperty

设置 CSS 样式属性

css
:root {
  --primary-color: red;
  --font-size: 16px;
}
js
// 1. setProperty() - 设置 CSS 样式属性
element.style.setProperty('color', 'red');
element.style.setProperty('font-size', '16px');
element.style.setProperty('--primary-color', 'blue');
element.style.setProperty('--font-size', '20px');
getPropertyValue

获取 CSS 样式属性值

js
// 1. getPropertyValue() - 获取 CSS 样式属性值
let color = element.style.getPropertyValue('color');
let fontSize = element.style.getPropertyValue('font-size');
removeProperty

移除 CSS 样式属性

js
// 1. removeProperty() - 移除 CSS 样式属性
element.style.removeProperty('font-size');
getPropertyValue

返回的是属性的优先级字符串 important! 或空字符串 ''

js
let hasColor = element.style.getPropertyPriority('color');
let hasFontSize = element.style.getPropertyPriority('font-size');

2. getComputedStyle

获取元素的计算样式,包含继承的样式。

js
// 1. getComputedStyle() - 获取元素的计算样式
let computedStyle = getComputedStyle(element);

console.log(computedStyle.color); // 'red'
console.log(computedStyle.fontSize); // '16px'
console.log(computedStyle.getPropertyValue('--primary-color')); // 'blue'
console.log(computedStyle.getPropertyValue('--font-size')); // '20px'
// 2. getComputedStyle() - 获取伪元素的计算样式
let beforeContent = getComputedStyle(ele, '::before').attributeStyleMap.get(
  'content'
).value;
console.log(beforeContent); // 'before content'

3. getBoundingClientRect

获取元素的位置和大小,包含边框(border)和滚动条(scrollbar)。 相对于窗口的坐标 相对于文档的坐标 getBoundingClientRect() 加上当前页面滚动。

js
// 1. getBoundingClientRect() - 获取元素的位置和大小
let rect = element.getBoundingClientRect();

console.log(rect.left); // 元素左边界距离视口左边界的距离
console.log(rect.top); // 元素上边界距离视口上边界的距离
console.log(rect.width); // 元素宽度
console.log(rect.height); // 元素高度
console.log(rect.x); // 元素左边界距离视口左边界的距离
console.log(rect.y); // 元素上边界距离视口上边界的距离
  • left = x
  • top = y
  • right = x + width
  • bottom = y + height

1. client

元素的可见部分大小,不包含滚动条。

  1. clientWidth:元素的宽度,包含内边距(padding),不包含边框(border)和滚动条(scrollbar)。
  2. clientHeight:元素的高度,包含内边距(padding),不包含边框(border)和滚动条(scrollbar)。

2. offset

元素的位置和大小,包含边框(border)和滚动条(scrollbar)。

  1. offsetWidth:元素的宽度,包含内边距(padding)、边框(border)和滚动条(scrollbar)。
  2. offsetHeight:元素的高度,包含内边距(padding)、边框(border)和滚动条(scrollbar)。
  3. offsetLeft:元素的左偏移量,相对于最近的定位祖先元素(position 为 relative、absolute 或 fixed)。
  4. offsetTop:元素的上偏移量,相对于最近的定位祖先元素(position 为 relative、absolute 或 fixed)。

3. scroll

元素的滚动大小,包含滚动条(scrollbar)。

  1. scrollWidth:元素的宽度,包含内容区域(content)、内边距(padding)、因溢出而隐藏的部分
  2. scrollHeight:元素的高度,包含内容区域(content)、内边距(padding)因溢出而隐藏的部分
  3. scrollLeft:元素的水平滚动位置,即元素内容区域左侧隐藏部分的宽度。
  4. scrollTop:元素的垂直滚动位置,即元素内容区域顶部隐藏部分的高度。

4. 事件

1. addEventListener

监听的事件类型

Event 接口表示在 DOM 中出现的事件

js
div.addEventListener('事件名称',(e)=>{},Boolean)
//Boolean: true 捕获阶段调用函数  false 冒泡阶段调用函数
function a (Event){ console.log(123)}
button.addEventListener('click',a,false)
button.removeEventListener('click',a,false)

2. 事件冒泡

事件冒泡是指当一个元素上的事件被触发时,该事件会从该元素开始,逐级向上冒泡到文档根元素。 事件冒泡的过程中,每个元素上绑定的事件处理函数都会被调用。 事件冒泡可以用于事件委托,即通过将事件处理函数绑定在父元素上,来处理子元素上的事件。 e.target 指向触发事件的元素,而 e.currentTarget 指向绑定事件处理函数的元素。 事件冒泡可以通过 e.stopPropagation() 方法来阻止。 事件冒泡可以通过 e.preventDefault() 方法来阻止默认行为。

js
// 事件冒泡示例
document.body.addEventListener('click', function (e) {
  console.log('body 被点击了');
});

document.getElementById('myDiv').addEventListener('click', function (e) {
  console.log('div 被点击了');
});

document.getElementById('myButton').addEventListener('click', function (e) {
  console.log('button 被点击了');
});

3. 鼠标事件

事件名中文释义触发时机核心特点
click单击鼠标按下并松开在同一元素上触发mousedown + mouseup 组合
dblclick双击短时间内连续两次单击同一元素依赖两次 click 触发
contextmenu右键菜单鼠标右键按下时触发(可阻止默认菜单)右键专属
mousedown鼠标按下鼠标任意按键按下瞬间触发只按不松就触发
mouseup鼠标松开鼠标按下的按键松开瞬间触发松键时触发
mousemove鼠标移动鼠标指针在元素范围内每移动一个像素就触发一次高频触发(移动即触发)
mouseover鼠标移入鼠标指针进入被选元素 或其子元素 时触发会冒泡(子元素触发父元素)
mouseenter鼠标进入鼠标指针仅进入被选元素本身时触发(子元素无影响)不冒泡(性能更好)
mouseout鼠标移出鼠标指针离开被选元素 或其子元素 时触发会冒泡
mouseleave鼠标离开鼠标指针仅离开被选元素本身时触发(子元素无影响)不冒泡
属性说明
clientX以浏览器窗口左上顶角为原点,定位 x 轴坐标
clientY以浏览器窗口左上顶角为原点,定位 y 轴坐标
offsetX以当前事件的目标对象左上顶角为原点,定位 x 轴坐标
offsetY以当前事件的目标对象左上顶角为原点,定位 y 轴坐标
pageX以 document 对象(即文档窗口)左上顶角为原点,定位 x 轴坐标
pageY以 document 对象(即文档窗口)左上顶角为原点,定位 y 轴坐标
screenX计算机屏幕左上顶角为原点,定位 x 轴坐标
screenY计算机屏幕左上顶角为原点,定位 y 轴坐标
layerX最近的绝对定位的父元素(如果没有,则为 document 对象)左上顶角为元素,定位 x 轴坐标
layerY最近的绝对定位的父元素(如果没有,则为 document 对象)左上顶角为元素,定位 y 轴坐标

4. touch 事件

触摸事件是指在移动设备上触发的事件,例如触摸屏幕、滑动、缩放等。 触摸事件可以通过 touchstarttouchmovetouchend 等事件类型来监听。 触摸事件对象(TouchEvent)包含了触摸相关的信息,例如触摸点的坐标、触摸时间等。

5. 键盘事件

事件名中文释义触发时机核心特点
keydown按键按下任意按键按下瞬间触发只按不松就触发
keypress按键按下字母、数字、符号键按下瞬间触发(不包括功能键)只按不松就触发
keyup按键松开任意按键松开瞬间触发松键时触发
属性名含义示例(按下回车键)核心特点
event.key按键的实际含义 / 字符(与键盘布局、大小写有关)Enter/'a'/'A'最直观,优先使用(比如判断 "按的是回车")
event.code按键的物理位置(与键盘布局无关,固定)Enter/KeyA适合判断 "按的是哪个物理键"(比如 WASD 方向)

关键区别举例:

  • 按下小写 a:key = 'a',code = 'KeyA',keyCode = 65
  • 按下大写 A(Shift+A):key = 'A',code = 'KeyA',keyCode = 65
  • 按下数字键 5(主键盘):key = '5',code = 'Digit5'
  • 按下数字键 5(小键盘):key = '5',code = 'Numpad5'
按键event.keyevent.codeevent.keyCode(参考)
回车键EnterEnter13
ESC 键EscapeEscape27
空格键" "Space32
Backspace(退格键)BackspaceBackspace8
Tab(制表键)TabTab9
左方向键ArrowLeftArrowLeft37
上方向键ArrowUpArrowUp38
右方向键ArrowRightArrowRight39
下方向键ArrowDownArrowDown40
数字 0-9(主键盘)0-9Digit0-Digit948-57
A-Z 键a/A-z/ZKeyA-KeyZ65-90

compositionstart/compositionupdate/compositionend

当用户开始输入复合字符(例如中文、日文等)时触发。 复合字符是指由多个字符组成的字符,例如中文字符、日文假名等。 当用户开始输入复合字符时,浏览器会先触发 compositionstart 事件,然后触发 compositionupdate 事件,最后触发 compositionend 事件。

transitionend

当 CSS 过渡(transition)或动画(animation)结束时触发。 过渡是指元素从一种状态平滑过渡到另一种状态的过程,例如改变元素的位置、大小、颜色等。 动画是指元素通过一系列状态变化而产生的效果,例如旋转、闪烁等。 当过渡或动画结束时,浏览器会触发 transitionend 事件。

animationend

当 CSS 动画(animation)结束时触发。 动画是指元素通过一系列状态变化而产生的效果,例如旋转、闪烁等。 当动画结束时,浏览器会触发 animationend 事件。

visibilitychange

当文档的可见性状态发生变化时触发(例如用户切换到其他标签页)元素的可见性发生变化时,浏览器会触发 visibilitychange 事件。

5. 表单操作

1. value / valueAs*

1. 普通 input(text/password/textarea)

  • value字符串
  • valueAsDatenull
  • valueAsNumberNaN

2. type="number"

  • value → 字符串
  • valueAsNumber数字(推荐)
  • valueAsDate → null

3. 日期时间 input(date/time/datetime-local)

  • value字符串yyyy-mm-dd
  • valueAsDateDate 对象
  • valueAsNumber毫秒时间戳

2.常用工具方法

1. 清空表单

js
form.reset();

2. 判断输入框是否为空

js
function isEmpty(val) {
  return val === undefined || val === null || val.trim() === '';
}

3. 获取数字(自动转数字)

js
function getNum(el) {
  return el.valueAsNumber || Number(el.value) || 0;
}

6. Observer

1. IntersectionObserver

当元素进入视口(或其他指定元素)时触发回调函数。 可以用于实现懒加载、无限滚动等功能。

1.1 构造函数

javascript
const observer = new IntersectionObserver(callback, options);
  • callback:当观察到交叉状态变化时调用的函数,接收两个参数:

  • entries:一个 IntersectionObserverEntry 对象数组,每个对象描述一个目标元素的变化。

  • observer:调用该回调的观察器实例。

  • options(可选):配置对象,包含以下属性:

  • root:指定目标元素的祖先容器(必须是目标元素的父级或更高层),默认为 null,即使用视口作为容器。

  • rootMargin:一个字符串,用于扩展或缩小 root 的判定区域,格式类似 CSS 的 margin(例如 "10px 20px 30px 40px")。默认 "0px"。

  • threshold:一个数值或数值数组,表示目标元素可见比例达到多少时触发回调。取值范围 0~1,例如 0.5 表示元素有一半进入视口时触发。默认 0(只要有一个像素进入就触发)。

1.2 常用方法

  • observe(target):开始观察指定的 target 元素。

  • unobserve(target):停止观察指定的 target 元素。

  • disconnect():停止观察所有目标元素。

1.3 IntersectionObserverEntry 对象

在回调函数的 entries 数组中,每个条目包含以下常用属性:

  • target:被观察的目标元素。

  • isIntersecting:布尔值,表示目标元素是否与 root 相交(即当前是否可见)。

  • intersectionRatio:目标元素的可见比例(0~1)。

  • intersectionRect:目标元素与 root 相交区域的矩形信息(DOMRectReadOnly)。

  • boundingClientRect:目标元素的边界矩形。

  • rootBounds:root 元素的边界矩形。

  • time:发生交叉时的时间戳。

1.4 示例:图片懒加载

html
<img data-src="real-image.jpg" class="lazy" />
javascript
const lazyImages = document.querySelectorAll('img.lazy');

const observer = new IntersectionObserver(
  (entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src; // 替换真实地址
        img.classList.remove('lazy');
        observer.unobserve(img); // 加载后停止观察
      }
    });
  },
  {
    rootMargin: '50px', // 提前 50px 进入视口时加载
    threshold: 0.01, // 只要有 1% 进入就触发
  }
);

lazyImages.forEach(img => observer.observe(img));

1.5 注意事项

回调默认在微任务中执行(类似于 Promise),但在浏览器内部实现中可能是异步的。

rootMargin 会影响交叉区域的判定,可用于提前加载。

兼容性:所有现代浏览器均支持,IE 不支持,可引入 polyfill。

2. MutationObserver

用途:监听 DOM 树的变化,包括子节点的添加/删除、属性的修改、文本内容的变更等。

常见应用:监听 DOM 动态插入的内容、实现撤销/重做功能、监控第三方脚本对页面的修改。

2.1 构造函数

javascript
const observer = new MutationObserver(callback);
  • callback:当 DOM 变化发生时调用的函数,接收两个参数:

  • mutations:一个 MutationRecord 对象数组,每个对象描述一个具体的变化。

  • observer:调用该回调的观察器实例。

2.2 常用方法

javascript
observe(target, options);
  • observe(target, options):开始观察指定的 target 元素,根据 options 配置需要监听的变化类型。

  • disconnect():停止观察所有目标元素,并清除记录队列。

  • takeRecords():清空记录队列并返回未处理的 MutationRecord 列表。

2.3 配置选项(options)

  • observe 方法的第二个参数是一个对象,必须至少指定以下子项之一:

  • childList:布尔值,监听目标元素的子节点(包括文本节点)的添加或移除。

  • attributes:布尔值,监听目标元素的属性变化。

  • characterData:布尔值,监听目标元素的文本内容变化(通过 data 属性)。

此外,还可以设置:

  • subtree:布尔值,如果为 true,则监听目标元素的整个子树(后代节点)的变化。

  • attributeOldValue:布尔值,记录变化前的属性值(需要 attributes: true)。

  • characterDataOldValue:布尔值,记录变化前的文本内容(需要 characterData: true)。

  • attributeFilter:数组,指定要监听的属性名列表(例如 ['class', 'style']),不设置则监听所有属性。

2.4 MutationRecord 对象

在回调函数的 mutations 数组中,每个记录包含以下常用属性:

  • type:变化类型,值为 'attributes'、'childList' 或 'characterData'。

  • target:发生变化的节点。

  • addedNodes:添加的节点列表(NodeList)。

  • removedNodes:移除的节点列表(NodeList)。

  • previousSibling / nextSibling:被添加/移除节点的前后兄弟节点。

  • attributeName:被修改的属性名。

  • oldValue:变化前的旧值(如果启用了 attributeOldValue 或 characterDataOldValue)。

2.5 示例:监听属性变化

javascript
const target = document.getElementById('myDiv');

const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes') {
console.log(`属性 ${mutation.attributeName} 发生变化`);
console.log(`旧值:${mutation.oldValue}`);
console.log(`新值:${mutation.target.getAttribute(mutation.attributeName)}`);
}
});
});

observer.observe(target, {
attributes: true,
attributeOldValue: true,
attributeFilter: ['class', 'style'] // 只监听 class 和 style 属性
});

// 之后修改属性会触发回调
target.classList.add('active');
2.6 示例:监听子节点变化
javascript
const list = document.getElementById('list');

const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => console.log('添加了节点:', node));
mutation.removedNodes.forEach(node => console.log('移除了节点:', node));
}
});
});

observer.observe(list, {
childList: true,
subtree: true // 同时监听后代节点的子节点变化
});

// 添加新元素
const newItem = document.createElement('li');
newItem.textContent = '新项目';
list.appendChild(newItem);

2.7 性能与注意事项

MutationObserver 的回调是异步触发的,会在所有 DOM 变化完成后批量执行,避免同步回调带来的性能问题。

尽量精确配置 options,避免监听不必要的变化,以减少性能开销。

在回调中修改 DOM 可能会导致无限循环,除非使用 disconnect() 暂时断开或条件判断。

兼容性:所有现代浏览器均支持,IE 11 部分支持(需注意 subtree 等选项表现一致)。