DOM

监听页面 DOM 变动并高效响应.👿

Posted by huangqing on August 9, 2017

监听页面 DOM 变动并高效响应

JavaScript 中事件的发生阶段(捕获-命中-冒泡):

Graphical representation of an event dispatched in a DOM tree using the DOM event flow

Graphical representation of an event dispatched in a DOM tree using the DOM event flow

开始的时候我一直在 window 状态改变涉及到的事件中寻找,一圈搜寻下来发现也就 onload 事件最接近了,所以我们看看 MDN 对该事件的定义:

The load event is fired when a resource and its dependent resources have finished loading.

怎么理解资源及其依赖资源已加载完毕呢?简单来说,如果一个页面涉及到图片资源,那么 onload 事件会在页面完全载入(包括图片、css文件等等)后触发。一个简单的监听事件用 JavaScript 应该这样书写(注意不同环境下 load 和 onload 的差异):

  window.addEventListener("load", function(event) {
    console.log("All resources finished loading!");
  });
  
  // or
  window.onload=function(){
    console.log("All resources finished loading!");
  };
  
  // jQuery
  $( window ).on( "load", handler )
 < body onload="SomeJavaScriptCode">

说到 onload 事件,有一个 jQuery 中相似的事件一定会被提及—— ready 事件。jQuery 中这样定义这个事件:

Specify a function to execute when the DOM is fully loaded.

需要知道的是 jQuery 定义的 ready 事件实质上是为 DOMContentLoaded 事件设计的,所以当我们谈论加载时应该区分的事件其实是 onload(接口 UIEvent) 以及 DOMContentLoaded(接口 Event),MDN 这样描述 DOMContentLoaded:

当初始HTML文档被完全加载和解析时,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架完成加载。另一个不同的事件 load 应该仅用于检测一个完全加载的页面。

所以可以知道,当一个页面加载时应先触发 DOMContentLoaded 然后才是 onload. 类似的事件及区别包括以下几类:

  • DOMContentLoaded: 当初始HTML文档被完全加载和解析时,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架完成加载;
  • readystatechange: 一个document 的 Document.readyState 属性描述了文档的加载状态,当这个状态发生了变化,就会触发该事件;
  • load: 当一个资源及其依赖资源已完成加载时,将触发load事件;
  • beforeunload: 当浏览器窗口,文档或其资源将要卸载时,会触发beforeunload事件。
  • unload: 当文档或一个子资源正在被卸载时, 触发 unload事件。

细心点会发现上面在介绍事件时提到了 UIEvent 以及 Event,这是什么呢?这些都是事件——可以被 JavaScript 侦测到的行为。其他的事件接口还包括 KeyboardEventVRDisplayEvent 等等:

DOM3 级事件规定了以下几类事件 :

  • UI 事件
  • 焦点事件
  • 鼠标事件
  • 滚轮事件
  • 文本事件
  • 键盘事件
  • 合成事件
  • 变动事件
  • 变动名称事件

其他:

  • HTML5 事件
  • 设备事件
  • 触摸与手势事件

Web API 接口

监听页面中动态生成的元素,应该在变动事件中做出选择, DOM2 定义的如下变动事件:事件类型一览表

  • DOMSubtreeModified: 在DOM结构发生任何变化的时候。这个事件在其他事件触发后都会触发;
  • DOMNodeInserted: 当一个节点作为子节点被插入到另一个节点中时触发;
  • DOMNodeRemoved: 在节点从其父节点中移除时触发;
  • DOMNodeInsertedIntoDocument: 在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在 DOMNodeInserted 之后触发;
  • DOMNodeRemovedFromDocument: 在一个节点被直接从文档移除或通过子树间接从文档移除之前触发。这个事件在 DOMNodeRemoved 之后触发;
  • DOMAttrModified: 在特性被修改之后触发;
  • DOMCharacterDataModified: 在文本节点的值发生变化时触发;

Mutation Observer API :

Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。

概念上,它很接近事件,可以理解为当 DOM 发生变动,就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发。

这样设计是为了应付 DOM 变动频繁的特点。举例来说,如果在文档中连续插入1000个<p>元素,就会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 Mutation Observer 完全不同,只在1000个段落都插入结束后才会触发,而且只触发一次。

Mutation Observer 有以下特点:

  • 它等待所有脚本任务完成后,才会运行,即采用异步方式。
  • 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条地个别处理 DOM 变动。
  • 它既可以观察发生在 DOM 的所有类型变动,也可以观察某一类变动。

MutationObserver 的构造函数比较简单,传入一个回调函数即可(回调函数接受两个参数,第一个是变动数组,第二个是观察器实例):

let observer = new MutationObserver(callback);

JavaScript 中的截流/节流函数:

在前端开发中,页面有时会绑定scroll或resize事件等频繁触发的事件,也就意味着在正常的操作之内,会多次调用绑定的程序,然而有些时候javascript需要处理的事情特别多,频繁出发就会导致性能下降、成页面卡顿甚至是浏览器奔溃。

形像的比喻是水龙头或机枪,你可以控制它的流量或频率。throttle 的关注点是连续的执行间隔时间。

函数节流的原理也挺简单,一样还是定时器。当我触发一个时间时,先setTimout让这个事件延迟一会再执行,如果在这个时间间隔内又触发了事件,那我们就清除原来的定时器,再setTimeout一个新的定时器延迟一会执行。函数节流的出发点,就是让一个函数不要执行得太频繁,减少一些过快的调用来节流。