了解JavaScript中原生观察者接口

975 阅读3分钟

前言

观察者模式是一种设计模式,定义了一种一对多的依赖关系,使得当一个对象状态发生变化时,所有依赖于它的对象都会得到通知并被自动更新。

JavaScript中有多种原生接口实现了观察者模式,涵盖了从 DOM 变化、元素可见性、性能监控到消息传递等多个方面。本文将全面介绍这些原生观察者接口,给大家一个直观的感受。

1. 什么是观察者模式

用一张图直观的感受下:

image.png

可以参考下我之前写的一篇文章【源码共读】第25期 | mitt、tiny-emitter,中间写了观察者和发布订阅的区别,源码实现的方式。

下面进入正题,介绍js中原生观察者接口。

2. 原生观察者接口

2.1 EventTarget (事件监听器)

EventTarget 是一个基础接口,几乎所有的 DOM 元素都继承自它,用于实现事件监听和触发机制,是观察者模式的基础。

document.addEventListener('click', (event) => {
  console.log('Document was clicked', event);
});

2.2 MutationObserver

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。

用于监听DOM对象的变化,通过配置(需要观察什么变动),包括节点属性发生变化、增删改时执行相应的回调。

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    console.log(mutation);
  });
});

const config = { attributes: true, childList: true, subtree: true };
observer.observe(document.body, config);

// 停止观察
// observer.disconnect();

2.3 IntersectionObserver

IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。

当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。

实现懒加载、无限滚动等功能时非常有用。

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      console.log('Element is in viewport');
    } else {
      console.log('Element is out of viewport');
    }
  });
});

const target = document.querySelector('#myElement');
observer.observe(target);

// 停止观察
// observer.unobserve(target);
// observer.disconnect();

2.4 ResizeObserver

ResizeObserver 接口监视 Element 内容盒或边框盒或者 SVGElement 边界尺寸的变化。 用于观察元素尺寸变化。

const observer = new ResizeObserver((entries) => {
  entries.forEach((entry) => {
    console.log('Element size changed:', entry.contentRect);
  });
});

const target = document.querySelector('#myElement');
observer.observe(target);

// 停止观察
// observer.unobserve(target);
// observer.disconnect();

2.5 PerformanceObserver

PerformanceObserver()  构造函数使用给定的观察者 callback 生成一个新的 PerformanceObserver 对象。当通过 observe() 方法注册的 条目类型 的 性能条目事件 被记录下来时,调用该观察者回调。

const observer = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  entries.forEach((entry) => {
    console.log(`Entry name: ${entry.name}`);
    console.log(`Entry type: ${entry.entryType}`);
    console.log(`Start time: ${entry.startTime}`);
    console.log(`Duration: ${entry.duration}`);
  });
});

observer.observe({ type: 'resource', buffered: true });

2.6 ReportingObserver

ReportingObserver 用于监控浏览器报告的问题,如弃用 API 使用、浏览器干预等。这有助于提前发现和解决潜在问题。

const observer = new ReportingObserver((reports) => {
  reports.forEach((report) => {
    console.log('Report type:', report.type);
    console.log('Report URL:', report.url);
    console.log('Report body:', report.body);
  });
}, { buffered: true });

observer.observe();

image.png

2.7 BroadcastChannel

BroadcastChannel 接口允许在同一源下不同浏览器上下文(如不同的浏览器窗口、标签页、iframe 等)之间进行简单的消息传递。

const channel = new BroadcastChannel('my_channel');
channel.onmessage = (event) => {
  console.log('Received:', event.data);
};

// 在另一个上下文中发送消息
const channel2 = new BroadcastChannel('my_channel');
channel2.postMessage('Hello from another context');

2.8 Channel Messaging API

MessageChannelMessagePort 允许在同一页面内不同浏览器上下文之间传递消息。

const channel = new MessageChannel();

channel.port1.onmessage = (event) => {
  console.log('Received:', event.data);
};

channel.port2.postMessage('Hello from port2');

2.9 WebSocket

WebSocket 提供了在浏览器和服务器之间创建持久连接的接口,以便双向通信。

const socket = new WebSocket('ws://localhost:8080');

socket.onopen = () => {
  console.log('WebSocket connection established');
};

socket.onmessage = (event) => {
  console.log('Received:', event.data);
};

socket.send('Hello, Server');

2.10 Server-Sent Events (SSE)

EventSource 接口用于从服务器接收推送事件。它是一种服务器向浏览器推送消息的单向通信。

const eventSource = new EventSource('/events');

eventSource.onmessage = (event) => {
  console.log('Received:', event.data);
};

eventSource.onerror = (error) => {
  console.error('EventSource failed:', error);
};

总结

最后总结一波,能够发现观察者模式其实很多地方应用。JavaScript 提供了很多的原生观察者接口,通过理解和应用这些观察者接口,在实际开发中,更加得心应手。

如有错误,请指正O^O!