第一次传闻MutationObserver的时分,咱们应该都和我是相同的,经过VuenextTick的完成来知道的;

后来又有一个面试题,说是如何将一个使命放到微行列中履行,网上的解答方案中也会出现MutationObserver

可是直到现在我都很少看到有人去讲这个API,当我深入这个API之后,我发现这个API真很有用,所以他不讲,你不讲,那我来讲吧。

什么是 MutationObserver

能够看MDN的介绍MutationObserver

MutationObserver 都被传烂了,你怎样到现在都还不会?

简单点来说,MutationObserver是一个结构函数,能够用来监听某个节点的改变,当节点发生改变时,能够履行一些回调函数

可是它不会当即履行,首先你需求调用MutationObserverobserve办法,传入你想要监听的节点,以及一些装备,然后当节点发生改变时,就会履行你传入的回调函数。

MutationObserver 的运用

MutationObserver的运用十分简单,直接看代码:

const observer = new MutationObserver((mutations) => {
  console.log(mutations);
});
observer.observe(document.body, {
  childList: true,
  subtree: true,
  attributes: true,
  characterData: true,
});

上面的代码中,咱们创建了一个MutationObserver实例,然后调用observe办法,传入了document.body,以及一些装备,然后当document.body发生改变时,就会履行咱们传入的回调函数。

MutationObserver 都被传烂了,你怎样到现在都还不会?

知道咱们都不爱看文档,那我就直接开始整活了。

Vue 的 nextTick

下面是Vue2nextTick的完成源码,地址:next-tick.js

MutationObserver 都被传烂了,你怎样到现在都还不会?

能够看到我框出来的当地,运用的便是MutationObserver,传入的回调函数是flushCallbacks

接着创建了一个文本节点,然后调用observe办法,传入了这个文本节点,装备了characterDatatrue,这样当文本节点发生改变时,就会履行flushCallbacks

源码细节就不解释了,咱们能够自己去看;

Vue3现已全面拥抱Promise了,所以Vue3nextTick的完成便是直接运用Promise了,源码地址:scheduler.ts

MutationObserver 都被传烂了,你怎样到现在都还不会?

将使命放到微行列中履行

根据上面VuenextTick的完成,能够看到有一个标识isUsingMicroTask,见名知意,便是用来标识是否运用微行列的;

能够看到的是只要两种办法将使命放到微行列中履行,一种是Promise,一种是MutationObserver

下面是根据Vue2源码的完成,弄了一个十分简化的示例;

const fn = () => {
  console.log('fn');
};
// 优先运用 Promise
if (typeof Promise !== 'undefined') {
  Promise.resolve().then(fn);
} 
// 降级运用 MutationObserver
else if (typeof MutationObserver !== 'undefined') {
  const observer = new MutationObserver(fn);
  const textNode = document.createTextNode('1');
  observer.observe(textNode, {
    characterData: true,
  });
  textNode.data = '2';
}
// 再降级运用 setImmediate
else if (typeof setImmediate !== 'undefined') {
  setImmediate(fn);
}
// 最后降级运用 setTimeout
else {
  setTimeout(fn, 0);
}

其实看源码是能够学到很多东西的,比方VuenextTick的完成,上面写了很多的注释,咱们把注释翻译一下看看:

优先运用 Promise

The nextTick behavior leverages the microtask queue, which can be accessed via either native Promise.then or MutationObserver. MutationObserver has wider support, however it is seriously bugged in UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It completely stops working after triggering a few times… so, if native Promise is available, we will use it:

翻译如下:

nextTick的履行利用了能够拜访微行列的Promise.thenMutationObserver原生模块; MutationObserver的兼容性更好,可是在iOS >= 9.3.3UIWebView中, 它在接触事情处理程序中触发时会出现严重的错误,触发几回后就会彻底停止作业; 所以假如原生的Promise可用,咱们将运用它:

后面还有一段注释,用来修正Promise的一些问题:

In problematic UIWebViews, Promise.then doesn’t completely break, but it can get stuck in a weird state where callbacks are pushed into the microtask queue but the queue isn’t being flushed, until the browser needs to do some other work, e.g. handle a timer. Therefore we can “force” the microtask queue to be flushed by adding an empty timer.

翻译如下:

在有问题的UIWebView中,Promise.then不会彻底崩溃,可是它或许会堕入一种古怪的状态, 在这种状态下,回调被推入微使命行列,可是行列没有被改写,直到浏览器需求做一些其他的作业, 比方处理一个定时器;因而咱们能够经过增加一个空的定时器来“强制”改写微使命行列。

降级运用 MutationObserver

Use MutationObserver where native Promise is not available, e.g. PhantomJS, iOS7, Android 4.4 (#6466 MutationObserver is unreliable in IE11)

翻译如下:

在原生的Promise不可用的状况下运用MutationObserver, 比方PhantomJSiOS7Android 4.4; (#6466 MutationObserverIE11中不可靠)

再降级运用 setImmediate

Fallback to setImmediate. Technically it leverages the (macro) task queue, but it is still a better choice than setTimeout.

翻译如下:

回退到setImmediate; 从技术上讲,它利用了(宏)使命行列, 但它仍然是比setTimeout更好的挑选。

最后降级运用 setTimeout

Fallback to setTimeout.

就不翻译了,很无奈的一句话。

Node 环境下的微行列

在网上还看到有说Node环境下的微行列,面试题嘛,就得答复的全面一点,要考虑不同的环境;

Node环境下是不支持MutationObserver的,其他的都有,可是Node环境下还支持process.nextTick,所以加一个判别就好了:

if (typeof process !== 'undefined' && process.nextTick) {
  process.nextTick(fn);
}

MutationObserver 还有其他什么效果?

上面讲到了能够经过MutationObserver将使命放到微行列中履行,可是其他状况下,咱们好像很少用到MutationObserver

其实首要是因为咱们很少去监听节点的改变,而大多数状况咱们需求知道DOM发现改变的状况,通常都是会运用一些钩子函数来进行处理;

例如咱们要知道文本框的值发生了改变,咱们会运用oninput事情,而不是去监听文本框的改变;

假如咱们动态去切换主题,咱们会有切换的函数,然后能够经过这个函数调用来告诉主题发生了改变,而不是去监听DOM的办法来告诉主题发生了改变;

都被钩子之类的函数给替代了,那它还有用武之地吗?

我就引荐几种运用场景吧:

监听子节点的改变

在运用Vue的时分咱们经常会遇到一种场景,便是咱们获取不到某个节点,因为这个节点还没挂载上去,这个时分咱们通常会运用nextTick来获取这个节点;

const dom = ref(null);
nextTick(() => {
    console.log(dom.value);
});

这样必定是能够获取到的,可是假如这个dom节点的内容是动态的,咱们想要获取修改之后的内容;

或者说这个节点发生改变咱们需求做一些事情怎样办,通常是封装一个办法,然后如下:

const dom = ref(null);
const data1 = ref(null);
const data2 = ref(null);
const data3 = ref(null);
const handleChangeData = () => {
    nextTick(() => {
        console.log(dom.value);
    });
};
const fetchData1 = () => {
    fetch('xxx').then(res => {
        data1.value = res.data;
        handleChangeData();
    });
};
const fetchData2 = () => {
    fetch('xxx').then(res => {
        data2.value = res.data;
        handleChangeData();
    });
};
const fetchData3 = () => {
    fetch('xxx').then(res => {
        data3.value = res.data;
        handleChangeData();
    });
};

会玩一点的或许会运用useEffect来监听data1data2data3的改变,然后调用handleChangeData,这儿就不展开了;

可是假如咱们运用MutationObserver的话,就能够直接监听dom节点的改变,然后履行handleChangeData,代码如下:

const dom = ref(null);
const observer = new MutationObserver(() => {
    console.log(dom.value);
});
const handleChangeData = () => {
    nextTick(() => {
        console.log(dom.value);
    });
};
// 需求在 dom 节点挂载之后履行
nextTick(() => {
    observer.observe(dom.value, {
        childList: true,
        subtree: true,
        attributes: true,
        characterData: true,
    });
});

这样就算再加几个data,也不需求再去调用handleChangeData了,直接监听dom节点的改变就好了。

监听 DOM 被移除

有时分有些网站会加上一些水印,或者一些广告,作为一个站主必定是不希望这些东西被人运用一些脚本给移除掉的;

这个时分就能够运用MutationObserver来监听DOM被移除,然后重新增加上去,代码如下:

const watermark = document.createElement('div');
watermark.innerHTML = '水印';
document.body.appendChild(watermark);
const observer = new MutationObserver(() => {
    if (!document.body.contains(watermark)) {
        document.body.appendChild(watermark);
    }
});
observer.observe(document.body, {
    childList: true,
    subtree: true,
});

也能够经过这个办法来监听是否被增加DOM,或许你的站点上的广告被人替换了,也有或许是你想要监听某个DOM是否被增加上去了,都能够运用这个办法。

监听 DOM 的特点改变

有时分咱们需求监听某个DOM的特点改变,比方说咱们要监听DOMstyle特点,然后根据style特点的改变来做一些事情,代码如下:

const dom = document.querySelector('.dom');
const observer = new MutationObserver((mutations) => {
    console.log(mutations);
});
observer.observe(dom, {
    attributes: true,
    attributeFilter: ['style'],
});

总结

MutationObserver是一个很有用的API,它首要能够用于监听某个节点的改变,当节点发生改变时,能够履行一些回调函数;

其间的节点改变包含:子节点的改变、特点的改变、文本内容的改变等等;

可根据不同的装备项来监听不同的改变,能够说是十分灵活了;

MutationObserver还有很多其他的用法,首要效果仍是监听dom的改变;

例如还能够做监控,元素发生改变记载日志的改变;跟踪一些元素的改变,例如广告,采取相应的办法;做输入检测,尽管说能够用input事情代替;

当然必定还有其他的应用场景,感兴趣的能够自行探索。