JS:MutationObserver和Event构造函数

发布于 2024-07-28  1168 次阅读


1. 概述

  • Mutation Observer是用于代替Mutation events作为观察DOM树结构发生变化时,做出相应处理的API;
  • 为什么要使用Mutation Observer去代替Mutation Events呢?我们先了解一下Mutation Events

Mutation Events

  • Mutation Events是在DOM3中定义的,用于监听DOM树结构变化的事件;
  • Mutation Events列表:
    • DOMAttrModified
    • DOMAttributeNameChanged
    • DOMCharacterDataModified
    • DOMElementNameChanged
    • DOMNodeInserted
    • DOMNodeRemoved
    • DOMNodeInsertedIntoDocument
    • DOMSubtreeModified
    • 其中DOMNodeRemovedDOMNodeInsertedDOMSubtreeModified分别用于监听元素子项的删除、新增、修改(包括删除和新增);
    • DOMAttrModified是监听元素属性的修改,并且能够提供具体的修改动作;
  • 用法如下: js代码解读复制代码document.getElementById('list').addEventListener("DOMSubtreeModified", function(){ console.log('列表中子元素被修改'); }, false);
  • Mutation Events遇到的问题:
    • 浏览器兼容性问题:
      • IE9不支持Mutation Events
      • Webkit内核不支持DOMAttrModified特性;
      • DOMElementNameChangedDOMAttributeNameChangedFirefox上不被支持;
    • 性能问题:
      • Mutation Events是同步执行的:
        • 它的每次调用,都需要从事件队列中取出事件并执行,然后事件队列中移除,期间需要移动队列元素;
        • 如果事件触发的较为频繁的话,每一次都需要执行上面的这些步骤,那么浏览器会被拖慢;
      • Mutation Events本身是事件,所以捕获是采用的是事件冒泡的形式;
        • 如果冒泡捕获期间又触发了其他的Mutation Events的话,很有可能就会导致阻塞Javascript线程,甚至导致浏览器崩溃;

2.1 Event() 构造函数

Event()  构造函数, 创建一个新的事件对象 Event

语法

javascript代码解读复制代码event = new Event(typeArg, eventInit);

参数

  • typeArg: 是DOMString类型,表示所创建事件的名称。
  • eventIni(可选):是 EventInit 类型的字典,接受以下字段:
    • "bubbles",可选,Boolean类型,默认值为 false,表示该事件是否冒泡。
    • "cancelable",可选,Boolean类型,默认值为 false, 表示该事件能否被取消。
    • "composed",可选,Boolean类型,默认值为 false,指示事件是否会在影子DOM根节点之外触发侦听器。

2.2 EventTarget.dispatchEvent

向一个指定的事件目标派发一个事件,  并以合适的顺序同步调用目标元素相关的事件处理函数。标准事件处理规则(包括事件捕获和可选的冒泡过程)同样适用于通过手动的使用dispatchEvent()方法派发的事件。

语法

javascript代码解读复制代码cancelled = !target.dispatchEvent(event)

参数

  • event 是要被派发的事件对象。
  • target 被用来初始化 事件 和 决定将会触发 目标.

返回值

  • 当该事件是可取消的(cancelable为true)并且至少一个该事件的 事件处理方法 调用了Event.preventDefault(),则返回值为false;否则返回true

如果该被派发的事件的事件类型(event's type)在方法调用之前没有被经过初始化被指定,就会抛出一个 UNSPECIFIED_EVENT_TYPE_ERR 异常,或者如果事件类型是null或一个空字符串. event handler 就会抛出未捕获的异常; 这些 event handlers 运行在一个嵌套的调用栈中: 他们会阻塞调用直到他们处理完毕,但是异常不会冒泡。

同步调用

与浏览器原生事件不同,原生事件是由DOM派发的,并通过事件循环(event loop)异步调用事件处理程序,而dispatchEvent()则是同步调用事件处理程序。在调用dispatchEvent()后,所有监听该事件的事件处理程序将在代码继续前执行并返回。

dispatchEvent()是create-init-dispatch过程的最后一步,用于将事件调度到实现的事件模型中。可以使用 Event 构造函数来创建事件。

JavaScript代码解读复制代码var event = new Event('build');

// Listen for the event.
elem.addEventListener('build', function (e) { 
    console.log('build');
 }, false);

// Dispatch the event.
elem.dispatchEvent(event);

自定义事件其实就是通过JavaScript代码来触发事件目标上注册的某个监听器,dispatchEvent(event)其实就类似于我们点击鼠标然后触发click事件处理程序。

MutationObserver

动态监听dom元素的变化

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。

const cb = (mutations) => {
  mutations.forEach((element) => {
    console.log(element);
  });
};

var observer = new MutationObserver(cb);

observer.observe(el, {
  subtree: true,
  attributes: true,
  childList: true,  
});

observer

开始监听调用方法

  • el, 需要监听的dom对象
  • options
    • subtree boolean 元素下的所有子节点的添加删除、属性变化等
    • attributes boolean 属性变化
    • attributeFilter array 需要筛选的属性值数组
    • attributeOldValue boolean 属性变化的旧值
    • childList boolean 子孙节点的添加或删除
    • characterData boolean 节点中值的变化监听
    • characterDataOldValue boolean 节点中值的变化监听

disconnect

手动取消监听

takeRecords

获取所有已变更列表,但是还没有被回调函数调用的记录, 并清空之前的记录 此方法最常见的使用场景是在断开观察者之前立即获取所有未处理的更改记录,以便在停止观察者时可以处理任何未处理的更改。

使用

  • 监听Dom变化
  • 使用其执行微任务

observerPromise.then一样,属于micro task

Vue nextTick源码中使用方式

let callbacks = []

function flushCallback() {
  let copys = callbacks.copy(); // copy arrays

  let fn = copys.shift()

  while(copys.length > 0) {
    fn()
  }
}

let runFunc = () => {}

if(hasPromise) { // 如果有Promise, 优先使用Promsie

  let p = Promise.resolve()

  runFunc = () => {
    p.then(flushCallback)
  }
}
else if(hasMutationObserver) { // 如果有MutationObserver

  let count = 1

  let el = document.createTextNode(String(count))
  let o = new MutationObserver(flushCallback)
  o.observer(el, {
    characterData: true // 监听字符变化
  })
  
  runFunc = () => {
    count = (count + 1) % 2
    el.data = String(count) // 改变字符,以触发执行微任务
  }
}

// ....

const nextTick = (fn, ctx) => {

  callbacks.push(() => {
    // 更改调用上下文
    fn.call(ctx)
  })

  runFunc()
}

其内部实现,其实就是微任务的执行机制


欢迎欢迎~热烈欢迎~