
2025-02-22 日报 Day105

今日的鸡汤
落日沉溺于橘色的海,
晚风沦陷于赤诚的爱。
今日学习内容
1、JS 红皮书 P443-444 第十四章:DOM编程
今日笔记
1、异步回调与记录队列: MutationObserver 接口是出于性能考虑而设计的,其核心是异步回调与记录队列模型。为了在大量变化事件发生时不影响性能,每次变化的信息(由观察者实例决定)会保存在 MutationRecord实例中,然后添加到记录队列。这个队列对每个 MutationObserver 实例都是唯一的,是所有 DOM变化事件的有序列表。
- 记录队列: 每次 MutationRecord 被添加到 MutationObserver 的记录队列时,仅当之前没有已排期的微任务回调时(队列中微任务长度为 0),才会将观察者注册的回调(在初始化 MutationObserver 时传入)作为微任务调度到任务队列上。这样可以保证记录队列的内容不会被回调处理两次。
不过在回调的微任务异步执行期间,有可能又会发生更多变化事件。因此被调用的回调会接收到一个 MutationRecord 实例的数组,顺序为它们进入记录队列的顺序。回调要负责处理这个数组的每一个实例,因为函数退出之后这些实现就不存在了。回调执行后,这些 MutationRecord 就用不着了,因此记录队列会被清空,其内容会被丢弃。 - takeRecords()方法: 调用 MutationObserver 实例的 takeRecords()方法可以清空记录队列,取出并返回其中的所有 MutationRecord 实例。看这个例子:这在希望断开与观察目标的联系,但又希望处理由于调用 disconnect()而被抛弃的记录队列中的MutationRecord 实例时比较有用。
1
2
3
4
5
6
7
8
9
10let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords));
observer.observe(document.body, { attributes: true });
document.body.className = 'foo';
document.body.className = 'bar';
document.body.className = 'baz';
console.log(observer.takeRecords());
console.log(observer.takeRecords());
// [MutationRecord, MutationRecord, MutationRecord]
// []
2、性能、内存与垃圾回收: - MutationObserver的引用: MutationObserver 实例与目标节点之间的引用关系是非对称的。MutationObserver 拥有对要观察的目标节点的弱引用。因为是弱引用,所以不会妨碍垃圾回收程序回收目标节点。
然而,目标节点却拥有对 MutationObserver 的强引用。如果目标节点从 DOM 中被移除,随后被垃圾回收,则关联的 MutationObserver 也会被垃圾回收。 - MutationRecord的引用: 记录队列中的每个 MutationRecord 实例至少包含对已有 DOM 节点的一个引用。如果变化是childList 类型,则会包含多个节点的引用。记录队列和回调处理的默认行为是耗尽这个队列,处理每个 MutationRecord,然后让它们超出作用域并被垃圾回收。
有时候可能需要保存某个观察者的完整变化记录。保存这些 MutationRecord 实例,也就会保存 它们引用的节点,因而会妨碍这些节点被回收。如果需要尽快地释放内存,建议从每个 MutationRecord中抽取出最有用的信息,然后保存到一个新对象中,最后抛弃 MutationRecord。
上述概念比较抽象举例如下:
⸻
📌 假设场景:
你有一个页面上某个
⸻
✅ 示例代码:
1 | const targetNode = document.getElementById('container'); |
⸻
📎 问题分析:
1. MutationObserver 回调中触发的 mutationsList 是一组 MutationRecord。
2. 对于 childList 类型的变化,每个 MutationRecord 都会引用新插入或删除的子节点(比如上面的 newNode)。
3. 如果你 将整个 mutationsList 保存下来(如 window.savedMutations = mutationsList),这些 MutationRecord 就还在内存中。
4. 那么:
• MutationRecord → 引用 newNode
• newNode → 本应随着 DOM 清理被回收
• 但因为你保留了 MutationRecord,newNode 的引用还在,于是它就 不会被垃圾回收!
⸻
💡 正确做法(释放引用):
如果你只需要变化的一些信息,比如变化类型和添加了多少个节点,可以这样处理:
1 | const observer = new MutationObserver((mutationsList) => { |
这样做之后,MutationRecord 实例就会失去引用,垃圾回收机制就能正常回收其中引用的 DOM 节点。
3、小结: 文档对象模型(DOM,Document Object Model)是语言中立的 HTML 和 XML 文档的 API。DOM Level 1 将 HTML 和 XML 文档定义为一个节点的多层级结构,并暴露出 JavaScript 接口以操作文档的底层结构和外观。
DOM 由一系列节点类型构成,主要包括以下几种。
Node 是基准节点类型,是文档一个部分的抽象表示,所有其他类型都继承 Node。
Document 类型表示整个文档,对应树形结构的根节点。在 JavaScript 中,document 对象是Document 的实例,拥有查询和获取节点的很多方法。
Element 节点表示文档中所有 HTML 或 XML 元素,可以用来操作它们的内容和属性。
其他节点类型分别表示文本内容、注释、文档类型、CDATA 区块和文档片段。
Node
├── Document
├── Element
│ └── HTMLElement(例如
)
├── Text
├── Comment
└── DocumentFragment
DOM 编程在多数情况下没什么问题,在涉及