元素的样式为什么频频被改?前端
DOM 的属性为什么离奇失踪??数组
消失的 DOM 到底是何人所为???浏览器
出现的陌生 DOM 到底是人是鬼????微信
这一切的背后是人性的扭曲仍是道德的沦丧?????异步
是无心写的 Bug 仍是有人故意而为之??????函数
让咱们跟随镜头,探寻变幻无穷的 DOM。工具
最近在作项目的时候,遇到一个问题:组件化
就是一个好久之前写的页面,里面的代码很乱。而个人任务是将页面的高度与屏幕适配。在部分页面存在着几个 <iframe>
,我须要调整样式使其高度与屏幕适配,可是不管我怎么调整,总会有一个 JavaScript 在不停地修改 <iframe>
的高度,使得它的高度超出屏幕而出现两个滚动条。性能
因为代码很乱,因此很难找到到底是哪一个代码在捣乱。spa
在 Chrome 中开发前端是很是开心的一件事情,由于浏览器提供了很是多的调试工具。而我常常用的一个就是:断点调试。
Chrome 提供了一个 DOM 监视的功能,当 DOM 发生变化的时候,自动暂停,这样就能很快定位是谁修改了 <iframe>
的高度了!
在 Elements 页面,选中指定的 DOM 节点,点击最前面的“...”符号(或者使用右键点击),在弹出的菜单中选择“Break On”便可,能够选择多个。
只要启用其中一个,在具体事件发生的时候,Chrome 就会自动中断到当前执行的脚本代码处。
因为 JavaScript 修改样式无非就是增长/删除 class,或是修改 style 属性,因此,使用 Attribute Modifications
便可找到具体是哪一个脚本在修改 DOM 了。
在作前端开发的时候,常常会想要监听 DOM 节点的变化。当 DOM 变化的时候,触发一系列事件。
通常来讲,使用轮询的方式能够很是简单地解决这个问题,就是使用 setInterval
来不断地检查 DOM 是否发生了变化。这种方式简单粗暴,可是会遇到两个问题:时间间隔设置过长,DOM 变化响应不够及时;时间间隔设置太短,不只浪费 CPU,并且可能出现卡顿。
固然,也可使用 requestAnimationFrame
来作,原理其实和 setInterval
同样,只不过在必定程度上能够获得 DOM 变化实时响应,可是依旧是致使 CPU 运行时间片的浪费。
有没有更好的办法呢?
在旧版 DOM Events 标准中,有一个 Mutation events
,能够用来监听 DOM 的变化,在 DOM 变化的时候触发事件。
在 DOM3 中定义了 9 种 Mutation 事件:DOMAttrModified
、DOMAttributeNameChanged
、DOMCharacterDataModified
、DOMElementNameChanged
、DOMNodeInserted
、DOMNodeInsertedIntoDocument
、DOMNodeRemoved
、DOMNodeRemovedFromDocument
、DOMSubtreeModified
。
这 9 种事件能够直接经过 element.addEventListener
添加到 DOM 元素上。
可是,Mutation 事件已经被反对使用!而且从 Web 标准事件中删除了!
因为性能问题,Mutation 事件会致使 DOM 修改的性能下降 1.5~7 倍,而且不能经过移除事件来恢复性能。
而且这个事件在各个浏览器上的实现也存在差别。
因此,DOM4 开始,推荐使用 Mutation Observers
来代替 Mutation events
。
Mutation Observer API 能够用来监视 DOM 的变化,包括属性的变化、节点的增减、内容的变化等。
为何 Mutation Observers
要比 Mutation Events
好?
因为 Mutation Events
是监视到 DOM 发生变化时产生的事件,它会在任何一个 DOM 发生变化的时候马上被触发。而且,因为事件是同步进行的,因此若是 DOM 的变化较多,就会产生大量的事件回调,致使严重的性能问题。
而 Mutation Observers
虽然和 Mutation Events
很像,可是 Mutation Observers
不是事件,它是异步触发的,而且不是每次 DOM 变更都会触发,而是会等待屡次 DOM 变更完成后一次性触发,使用一个数组来记录 DOM 变更的步骤。这样一来,即便是频繁的 DOM 操做,对性能的影响也不会有多明显。
举个例子,我如今须要将一篇包含 1000 个段落的文章显示到页面上,也就是要往页面中插入 1000 个 <p></p>
。
若是使用 Mutation Events
的话,这时就会产生 1000 个 DOMNodeInserted
事件;而若是使用 Mutation Observers
就不同了,它只会触发一次,获得一个数组,包含了 1000 个插入节点的信息。
MutationObserver
是一个构造函数,可使用 new
来建立一个 MutationObserver
的实例。这个构造函数接受一个回调函数做为参数,也就是每次 Mutation Observers
触发时调用的函数,函数接受两个参数,第一个参数是 MutationRecord
数组,用于存储 DOM 的变化记录,第二个参数是 MutationObserver
实例自己。
MutationObserver
的实例有 3 个成员方法:observe
、disconnect
、takeRecords
。
observe
用于注册监听器,接受两个参数,第一个参数是要监听的节点,第二个参数是监听的配置。
监听配置是一个对象,能够有 childList
、attributes
、characterData
、subtree
、attributeOldValue
、characterDataOldValue
、attributeFilter
,要监听哪一种变化,只须要将对应的属性设置为 true
便可,其中 childList
、attributes
、characterData
三者必须至少出现一个。
属性 | 数据类型 | 描述 |
---|---|---|
childList | boolean | 观察目标增长或移除了子节点 |
attributes | boolean | 观察目标增长、删除或修改了某个属性 |
characterData | boolean | (目标为 characterData 节点时有效,包括文本节点、注释节点、处理指令节点等)文本内容发生了变化 |
subtree | boolean | 不只监视 ovserve 第一个参数指定的观察目标,同时监视全部的下级节点 |
attributeOldValue | boolean | 在监视 attributes 的时候,属性发生变化后是否要记录变化前的内容 |
characterDataOldValue | boolean | 在监视 characterData 的时候,文本内容发生变化后是否要记录变化前的内容 |
attributeFilter | Array<string> | 一个属性名数组,能够用于过滤 attributes 的变化 |
注册成功后,构造函数里提供的回调函数将会被调用,第一个参数就获得了变化数组。变化对象的结构包含如下属性:
属性 | 数据类型 | 描述 |
---|---|---|
type | String | 变化类型,对应监听配置对象中的 childList 、attributes 、characterData |
target | Node | 变化的目标节点,若是 type 是 attributes 或 characterData ,则 target 为变化节点,不然为变化节点的父节点 |
addedNodes | NodeList | 被添加的节点列表(可能为 null ) |
removedNodes | NodeList | 被删除的节点列表(可能为 null ) |
previousSibling | Node | 被添加或被删除的节点的前一个兄弟节点(可能为 null ) |
nextSibling | Node | 被添加或被删除的节点的后一个兄弟节点(可能为 null ) |
attributeName | String | 变化的属性名称(可能为 null ) |
attributeNamespace | String | 变化的属性所在的 XML 命名空间(可能为 null ) |
oldValue | String | 若是 type 是 attributes 或 characterData ,则 oldValue 为变化前的值,不然为 null |
disconnect
用于中止监听。
takeRecords
用于清空并返回当前 MutationObserver
记录的 DOM 变化步骤。
能够看到,兼容性仍是很是好的,能够放心使用。
MutationObserver
提供了比 Mutation Events
更高效、更灵活的 DOM 监视方案,能够根据本身的须要自定义监视对象,在组件化项目中能够发挥更大的价值——不须要组件内部提供接口,就能够收到组件内容变化的通知。
可是,MutationObserver
虽好,可不要滥用哦!
Chrome 提供的 Break On
功能看起来就像是 MutationObserver
的精简版,很是实用。
关注微信公众号:创宇前端(KnownsecFED),码上获取更多优质干货!