JavaScript - 响应式
在 Web 开发中,响应式是指系统如何响应数据的变化。这一概念对于创建现代动态 Web 应用程序至关重要,因为用户操作需要快速响应,例如在不重新加载的情况下更新页面,或者在数据更新时动态更改用户看到的内容。
本章介绍一些强大的 JavaScript 模式,可帮助您构建响应式系统。这些模式通常用于 React 和 Vue 等框架,但理解这些模式的简单 JavaScript 知识可以帮助您理解基本概念。
为什么要使用响应式?
如今的 Web 应用程序交互性极强。当用户点击按钮、浏览页面或输入数据时,应用程序必须快速响应并更改屏幕上显示的内容。手动处理此类更新可能很困难,但响应式模式可以自动响应数据变化,从而简化流程。
JavaScript 中的关键响应式模式
本节将探讨 JavaScript 中的一些关键响应式模式。这些模式使我们能够快速响应数据变化以及页面如何响应用户操作。许多流行的库和框架都使用了它们,但我们将展示如何在简单的 JavaScript 中使用它们。
发布/订阅:简化的数据流管理
发布/订阅(发布者/订阅者)模式是管理数据流的常用方法。它区分了更改数据的代码(发布者)和响应这些更新的代码(订阅者)。这使得分别管理应用程序的各个部分变得容易。
示例
以下是此响应式模式的简单演示 -
class PS {
constructor() {
this.t = {}; // Tracks events and subscribers
}
sub(tp, cb) {
if (!this.t[tp]) {
this.t[tp] = [];
}
this.t[tp].push(cb);
}
pub(tp, d) {
if (this.t[tp]) {
this.t[tp].forEach((cb) => cb(d));
}
}
}
const ps = new PS();
ps.sub('n', (msg) => console.log(`News: ${msg}`));
ps.pub('n', 'New update available!');
此方法用于像 Redux 这样的系统中,组件会感知到变化并进行相应更新。
输出
这将生成以下结果 -
News: New update available!
自定义事件:浏览器内置的发布/订阅功能,实现响应式响应
浏览器提供了一个 API,用于使用 CustomEvent 类和 dispatchEvent 方法触发和订阅自定义事件。后者不仅允许我们创建事件,还可以将任何所需的数据附加到事件中。
示例
以下是此响应式模式的一个简单示例 -
const evt = new CustomEvent('cEvt', {
detail: 'Event data',
});
const el = document.getElementById('target');
el.addEventListener('cEvt', (e) => console.log(`Event: ${e.detail}`));
el.dispatchEvent(evt);
此技术适用于无需库的简单事件触发。
输出
这将产生以下结果 -
Event: Event data
自定义事件目标
如果您不想在窗口对象上全局调度事件,您可以创建自己的事件目标。
通过修改原生 EventTarget 类,您可以将事件传输到新实例。这确保您的事件仅针对新类触发,从而防止全局传播。您也可以将处理程序直接连接到此实例。
示例
以下代码展示了此响应式模式的简单用法 -
class CET extends EventTarget {
trig(evtName, evtData) {
const evt = new CustomEvent(evtName, { detail: evtData });
this.dispatchEvent(evt);
}
}
const ct = new CET();
ct.addEventListener('cEvt', (e) => console.log(`Event: ${e.detail}`));
ct.trig('cEvt', 'Hello from event!');
输出
这将创建以下结果 -
Event: Hello from event!
观察者模式:解耦代码的灵活更新
观察者模式与发布/订阅模式非常相似。您可以订阅主题,它会将更改通知其订阅者(观察者),并允许它们进行相应的回复。此方法对于设计一个断开连接的灵活架构至关重要。
示例
以下是此响应式模式工作原理的基本演示 -
class Sub {
constructor() {
this.obs = [];
}
sub(o) {
this.obs.push(o);
}
notify(d) {
this.obs.forEach((o) => o.up(d));
}
}
class Obs {
constructor(n) {
this.n = n;
}
up(d) {
console.log(`${this.n} got: ${d}`);
}
}
const s = new Sub();
const o1 = new Obs('Obs 1');
s.sub(o1);
s.notify('New info!');
输出
这将创建以下结果 −
Obs 1 got: New info!
使用代理实现响应式属性
代理对象允许您拦截对象内的属性访问操作(get 和 set)。这使您可以创建响应式,并在每次检索或更新属性值时运行代码。
示例
这是一个简单示例,展示了响应式模式的实际应用 −
let d = { n: 'Akash', a: 25 };
let rData = new Proxy(d, {
get(t, p) {
console.log(`Read: "${p}" = ${t[p]}`);
return t[p];
},
set(t, p, v) {
console.log(`Changed: "${p}" from ${t[p]} to ${v}`);
t[p] = v;
return true;
}
});
rData.n = 'Vikas';
输出
这将导致以下结果 -
Changed: "n" from Akash to Vikas
单个属性的反应性
如果您不需要跟踪对象的所有字段,可以使用 Object.defineProperty 选择一个字段,或使用 Object.defineProperties 将它们分组。
示例
以下代码清晰地演示了这种反应性模式 -
let u = { _n: 'Akash' };
Object.defineProperty(u, 'n', {
get() {
return this._n;
},
set(v) {
console.log(`Name changed from ${this._n} to ${v}`);
this._n = v;
}
});
u.n = 'Vikas';
输出
这将产生以下结果 -
Name changed from Akash to Vikas
使用 MutationObserver 实现响应式 HTML 属性
MutationObserver 是一个 JavaScript API,用于监控 DOM(文档对象模型)的变化,例如属性更改、元素添加或删除等。它特别适用于运行代码来响应 DOM 更改,而无需主动监控这些更改。
示例
以下是此特定响应式模式的一个简单实现 -
const el = document.getElementById('obs-el');
const obs = new MutationObserver((muts) => {
muts.forEach(m => {
if (m.attributeName === 'class') {
console.log('Class changed!');
}
});
});
obs.observe(el, { attributes: true });
输出
这将生成以下结果 -
Class changed!
使用 IntersectionObserver 实现响应式滚动
IntersectionObserver API 允许您异步监控目标元素与其祖先元素或顶级文档视口相交处的变化。这意味着您可以在元素进入或退出视口或其他特定元素时随时执行代码。
这对于延迟图像加载、无限滚动、基于滚动位置的动画和其他功能特别有用。
示例
以下是此响应式模式的一个示例 -
// 当被观察元素相交时调用的函数
function onIntersect(entries, obs) {
entries.forEach(e => {
e.target.style.backgroundColor = e.isIntersecting ? 'lightgreen' : 'grey';
});
}
// 创建一个带有回调的 IntersectionObserver
const obs = new IntersectionObserver(onIntersect);
// 开始观察元素
const el = document.getElementById('spec-el');
obs.observe(el);
输出
以下是元素进入视口时的输出 -
Element is now in view.
以下是元素退出视口时的输出 -
Element is now out of view.

