方案背景

在刀耕火种的jQuery时代,我们都是对DOM直接进行操作,但往往也会面临一个问题:DOM被不明来源的操作修改了。而且这种改动大都不是顺着一条线的逻辑,而是一个犄角旮旯的事件监听,或是一次没有关键词的AOE操作,非常难排查。到了Vue时代,这种情况改善了很多,但如果有人乱写ref,或者随意的使用Watch,依然会碰到类似的问题。那么这种问题我们该怎么排查呢?

核心思路

浏览器其实有一个监听DOM发生任意变动的API:MutationObserver,不论是DOM上的属性,还是其内部的子元素,所有变动都可以监听到。不过不知为何这个东西并没有全面推广,Vue的$nextTick之前还使用该API充当过底层,但之后也替换成了Promise了,不过这倒是不影响咱们使用它
有了MutationObserver,我们就可以在目标DOM的变动事件中设置debugger,当然这个注册动作至少要早于未知操作,然后通过Chrome的Call Stack就能逐步找到操作的来源

具体实现

本方案思路是尽可能设计为外挂逻辑,操作都是在控制台而非代码中进行的

  1. 全局声明以MutationObserver为核心的debugger工具方法,Vue项目需要显示挂载到window上
// 针对找不到DOM是谁操作的情况专用debugger工具
// debugger卡住后可以从任务栈入手看上一层是谁导致的
function listenerMutationObserver (dom) {
  const config = { attributes: true, childList: true, subtree: true };
  // 当观察到变动时执行的回调函数
  const callback = function(mutationsList, observer) {
    debugger;
  };
  // 创建一个观察器实例并传入回调函数
  const observer = new MutationObserver(callback);
  // 以上述配置开始观察目标节点
  observer.observe(dom, config);
}

// 全局挂载是为了咱们在控制台进行操作
window.listenerMutationObserver = listenerMutationObserver;
  1. 在一个尽可能早的节点将目标DOM传进来
    a. 在Vue项目中可以选择mounted的第一行,在jQuery项目中可以选择DOM Ready回调的第一行
    b. 如果不想在代码里修改,可以直接把断点打到如上的位置,然后在控制台里传入目标DOM

    // 控制台执行
    window.listenerMutationObserver(document.getElementById('test'))

    c. 如果目标DOM没有明显的抓取标志,那就在卡住断点后通过控制台动态给DOM设置一个
    下载.png
    d. 如果不想在代码里写debugger,Sources里又找不到想卡断点的代码文件,那可以考虑通过发接口的来源入手
    image.png

  2. debugger卡住不明DOM操作后,通过Call Stark确认可疑的帧
    image.png
    image.png
    Vue的栈会比较多,各路Watch也因为在观察者队列中,很难直接观测,但至少可以找到变动的根源。如果是jQuery类项目,在Call Stack中会非常直观

魔芋药丸
15 声望0 粉丝