UNPKG

@vitarx/responsive

Version:
458 lines (457 loc) 20.9 kB
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _a, _Observer_accessLock, _Observer_batchSubscribers, _Observer_immediateSubscribers, _Observer_pendingChanges, _Observer_isProcessingQueue, _Observer_flushChangeQueue, _Observer_notifyTargetChanges, _Observer_notifySubscribers, _Observer_acquireLock; import { isArray, isFunction, isObject, microTaskDebouncedCallback } from '@vitarx/utils'; import { Subscriber } from './subscriber.js'; /** * 全局属性变更标识符 * * 用于订阅对象的所有属性变更,作为通配符使用。 * 当使用此标识符订阅时,对象的任何属性变更都会触发通知。 */ export const ALL_PROPERTIES_SYMBOL = Symbol('ALL_PROPERTIES_SYMBOL'); /** * 目标对象标识符 * * 用于在代理对象上标识原始目标对象的Symbol。 * 可将此标识符作为对象的属性,属性值为实际用于订阅的原始目标对象。 */ const OBS_TARGET_SYMBOL = Symbol('OBSERVER_TARGET_SYMBOL'); /** * # 观察者管理器 * * 负责管理订阅关系并在数据变更时通知相关订阅者。 * * @class ObserverManager */ export class Observer { /** * 获取订阅存储 * * 根据批处理模式参数返回相应的订阅存储。 * * @param {boolean} [batch=true] - 是否获取批处理模式的存储 * @returns {Readonly<SubscriberStore>} - 只读的订阅存储实例 */ static getSubscriberStore(batch = true) { return batch ? __classPrivateFieldGet(this, _a, "f", _Observer_batchSubscribers) : __classPrivateFieldGet(this, _a, "f", _Observer_immediateSubscribers); } /** * 触发变更通知 * * 通知订阅者目标对象的指定属性已变更。该方法会将变更添加到队列中, * 并通过微任务异步处理,以提高性能。对于即时模式的订阅者,会立即触发通知。 * * @template T - 目标对象类型 * @param {T} target - 变更的目标对象 * @param {keyof T | Array<keyof T>} property - 变更的属性名或属性名数组 * @returns {void} - 无返回值 */ static notify(target, property) { // 如果队列未在处理中,初始化处理流程 if (!__classPrivateFieldGet(this, _a, "f", _Observer_isProcessingQueue)) { __classPrivateFieldSet(this, _a, new Map(), "f", _Observer_pendingChanges); __classPrivateFieldSet(this, _a, true, "f", _Observer_isProcessingQueue); // 使用微任务处理队列 Promise.resolve().then(() => __classPrivateFieldGet(this, _a, "m", _Observer_flushChangeQueue).call(this)); } // 获取原始目标对象 target = this.getOriginalTarget(target); const properties = isArray(property) ? property : [property]; // 获取订阅存储 const immediateSubscribers = __classPrivateFieldGet(this, _a, "f", _Observer_immediateSubscribers).get(target); const batchSubscribers = __classPrivateFieldGet(this, _a, "f", _Observer_batchSubscribers).get(target); if (immediateSubscribers || batchSubscribers) { for (const prop of properties) { // 立即通知即时模式的订阅者 __classPrivateFieldGet(this, _a, "m", _Observer_notifySubscribers).call(this, target, immediateSubscribers?.get(prop), [prop]); // 将变更添加到队列中,用于批处理模式 if (__classPrivateFieldGet(this, _a, "f", _Observer_pendingChanges).has(target)) { __classPrivateFieldGet(this, _a, "f", _Observer_pendingChanges).get(target).add(prop); } else { __classPrivateFieldGet(this, _a, "f", _Observer_pendingChanges).set(target, new Set([prop])); } } // 通知关注全局变更的即时订阅者 __classPrivateFieldGet(this, _a, "m", _Observer_notifySubscribers).call(this, target, immediateSubscribers?.get(this.ALL_PROPERTIES_SYMBOL), properties); } } /** * 检查对象是否有订阅者 * * 检查指定对象的特定属性是否有订阅者,包括批处理和即时模式。 * * @template T - 目标对象类型 * @param {T} target - 目标对象 * @param {keyof T | ALL_PROPERTIES_SYMBOL} property - 属性名,默认为全局变更标识符 * @returns {boolean} - 如果存在订阅者返回true,否则返回false */ static hasSubscribers(target, property = ALL_PROPERTIES_SYMBOL) { target = this.getOriginalTarget(target); return !!(__classPrivateFieldGet(this, _a, "f", _Observer_batchSubscribers).get(target)?.has(property) || __classPrivateFieldGet(this, _a, "f", _Observer_immediateSubscribers).get(target)?.has(property)); } /** * 订阅对象变化 * * 为指定对象注册变更订阅,返回可用于管理订阅生命周期的订阅者实例。 * * @template T - 目标对象类型 * @template CB - 回调函数类型 * @param {T} target - 目标对象 * @param {CB|Subscriber<CB>} callback - 回调函数或订阅者实例 * @param {SubscriptionOptions} [options] - 订阅选项 * @param {boolean} [options.batch=true] - 是否使用批处理模式 * @param {number} [options.limit=0] - 触发次数限制,0表示无限制 * @param {boolean} [options.scope=true] - 是否自动添加到当前作用域 * @returns {Subscriber<CB>} - 订阅者实例 */ static subscribe(target, callback, options) { const subscriber = this.createSubscriber(callback, options); this.addSubscriber(target, this.ALL_PROPERTIES_SYMBOL, subscriber, { batch: options?.batch }); return subscriber; } /** * 订阅对象属性变更 * * 为指定对象的属性注册变更订阅,返回可用于管理订阅生命周期的订阅者实例。 * * @template T - 目标对象类型 * @template CB - 回调函数类型 * @param {T} target - 目标对象 * @param {CB|Subscriber<CB>} callback - 回调函数或订阅者实例 * @param {keyof T} property - 属性名。 * @param {SubscriptionOptions} [options] - 订阅选项 * @param {boolean} [options.batch=true] - 是否使用批处理模式 * @param {number} [options.limit=0] - 触发次数限制,0表示无限制 * @param {boolean} [options.scope=true] - 是否自动添加到当前作用域 * @returns {Subscriber<CB>} - 订阅者实例 */ static subscribeProperty(target, property, callback, options) { const subscriber = this.createSubscriber(callback, options); this.addSubscriber(target, property, subscriber, { batch: options?.batch }); return subscriber; } /** * 同时订阅多个属性 * * 为目标对象的多个属性注册相同的订阅者,当任何一个属性变更时触发回调。 * 内部会优化处理方式,避免重复通知。 * * @template T - 目标对象类型 * @template CB - 回调函数类型 * @param {T} target - 目标对象 * @param {Array<keyof T>|Set<keyof T>} properties - 属性名集合 * @param {CB|Subscriber<CB>} callback - 回调函数或订阅者实例 * @param {SubscriptionOptions} [options] - 订阅选项 * @param {boolean} [options.batch=true] - 是否使用批处理模式 * @param {number} [options.limit=0] - 触发次数限制,0表示无限制 * @param {boolean} [options.scope=true] - 是否自动添加到当前作用域 * @returns {Subscriber<CB>} - 订阅者实例 */ static subscribeProperties(target, properties, callback, options) { if (!target || typeof target !== 'object') { throw new TypeError('Target must be an object'); } if (Array.isArray(properties)) { properties = new Set(properties); } if (properties.size === 0) { throw new TypeError('Properties must be a non-empty array or set'); } // 创建订阅者 const subscriber = this.createSubscriber(callback, options); // 对单个属性或禁用批处理时,为每个属性单独添加订阅 if (options?.batch === false || properties.size === 1) { for (const property of properties) { this.addSubscriber(target, property, subscriber, { batch: options?.batch }); } } else { // 使用批处理模式,创建一个过滤器回调 const propertySet = properties instanceof Set ? properties : new Set(properties); // 添加一个不进行批处理的回调,但内部使用微任务防抖 const unsubscribe = this.addSyncSubscriber(target, microTaskDebouncedCallback((properties) => { const relevantChanges = []; // 过滤出订阅的属性 for (const prop of new Set(properties)) { if (propertySet.has(prop)) { relevantChanges.push(prop); } } // 如果有相关变更,触发订阅者 if (relevantChanges.length) { ; subscriber.trigger(relevantChanges, target); } }, (latest, previous) => { if (!previous) return latest; previous[0].push(...latest[0]); return previous; })); // 订阅者销毁时取消订阅 subscriber.onDispose(unsubscribe); } return subscriber; } /** * 添加即时回调函数 * * 添加一个不使用批处理的回调函数,用于需要立即响应变更的高级场景。 * 每次属性变更都会立即触发回调,而不会等待微任务队列处理。 * * @template T - 目标对象类型 * @template CB - 回调函数类型 * @param {T} target - 目标对象 * @param {CB} callback - 回调函数 * @param {keyof T | ALL_PROPERTIES_SYMBOL} property - 属性名,默认为全局变更标识符 * @returns {() => void} - 取消订阅函数 */ static addSyncSubscriber(target, callback, property = this.ALL_PROPERTIES_SYMBOL) { this.addSubscriber(target, property, callback, { batch: false }); return () => this.removeSubscriber(target, property, callback, false); } /** * 同时为多个对象注册相同的订阅者 * * 为多个目标对象注册相同的订阅者,当任何一个对象发生变更时触发回调。 * 返回的订阅者实例可用于统一管理所有订阅。 * * @template T - 目标对象类型 * @template CB - 回调函数类型 * @param {Set<T>|T[]} targets - 目标对象集合 * @param {CB|Subscriber<CB>} callback - 回调函数或订阅者实例 * @param {SubscriptionOptions} [options] - 订阅选项 * @returns {Subscriber<CB>} - 订阅者实例 */ static subscribes(targets, callback, options) { if (Array.isArray(targets)) { targets = new Set(targets); } if (!(targets instanceof Set) || targets.size === 0) { throw new TypeError('Targets must be a non-empty array or set collection'); } const subscriber = this.createSubscriber(callback, options); // 为每个目标添加订阅 for (const target of targets) { // 跳过非对象目标 if (!isObject(target)) { targets.delete(target); continue; } this.addSubscriber(target, this.ALL_PROPERTIES_SYMBOL, subscriber, { batch: options?.batch, autoRemove: false }); } // 订阅者销毁时取消所有订阅 subscriber.onDispose(() => { for (const target of targets) { this.removeSubscriber(target, this.ALL_PROPERTIES_SYMBOL, subscriber, options?.batch); } }); return subscriber; } /** * 创建订阅者实例 * * 根据提供的回调函数或现有订阅者创建新的订阅者实例。 * 如果传入的是函数,则创建新实例;如果是订阅者实例,则直接返回。 * * @template C - 回调函数类型 * @param {C|Subscriber<C>} callback - 回调函数或订阅者实例 * @param {SubscriberOptions} [options] - 订阅选项 * @returns {Subscriber<C>} - 订阅者实例 */ static createSubscriber(callback, options) { return isFunction(callback) ? new Subscriber(callback, options) : callback; } /** * 获取响应式对象的原始目标 * * 从可能是代理的响应式对象中获取原始目标对象。 * 如果对象上存在TARGET_SYMBOL属性,则返回该属性值;否则返回对象本身。 * * @template T - 对象类型 * @returns {T} - 原始目标对象 * @param obj */ static getOriginalTarget(obj) { return Reflect.get(obj, this.TARGET_SYMBOL) ?? obj; } /** * 添加订阅者 * * 为目标对象的指定属性添加订阅者或回调函数。 * 内部实现方法,用于支持各种订阅API。 * * @template T - 目标对象类型 * @template C - 回调函数类型 * @param {T} target - 目标对象 * @param {keyof T | ALL_PROPERTIES_SYMBOL} property - 属性名 * @param {Subscriber<C>|C} subscriber - 订阅者或回调函数 * @param {object} [options] - 是否使用批处理模式 * @param {boolean} [options.batch=true] - 是否使用批处理模式 * @param {boolean} [options.autoRemove=true] - 是否自动移除订阅者 * @param options * @returns {void} */ static addSubscriber(target, property, subscriber, options) { const { batch = true, autoRemove = true } = options || {}; // 获取存储 const store = this.getSubscriberStore(batch); // 获取原始目标对象 const originalTarget = this.getOriginalTarget(target); // 加锁防止并发修改 const unlock = __classPrivateFieldGet(this, _a, "m", _Observer_acquireLock).call(this, originalTarget); try { // 确保目标在存储中有对应的映射 if (!store.has(originalTarget)) { store.set(originalTarget, new Map()); } // 获取属性映射 const propertyMap = store.get(originalTarget); // 确保属性有对应的订阅者集合 if (!propertyMap.has(property)) { propertyMap.set(property, new Set()); } // 添加订阅者 propertyMap.get(property).add(subscriber); // 如果是Subscriber实例,添加清理回调 if (autoRemove && subscriber instanceof Subscriber) { subscriber.onDispose(() => this.removeSubscriber(originalTarget, property, subscriber, batch)); } } finally { // 解锁 unlock(); } } /** * 移除订阅者 * * 从目标对象的指定属性中移除订阅者或回调函数。 * 会自动清理空的集合和映射,释放内存。 * * @template C - 回调函数类型 * @param {AnyObject} target - 目标对象 * @param {AnyKey} property - 属性名 * @param {Subscriber<C>|AnyCallback} subscriber - 订阅者或回调函数 * @param {boolean} [batch=true] - 是否使用批处理模式 * @returns {void} */ static removeSubscriber(target, property, subscriber, batch = true) { const store = this.getSubscriberStore(batch); target = this.getOriginalTarget(target); const unlock = __classPrivateFieldGet(this, _a, "m", _Observer_acquireLock).call(this, target); const subscriberSet = store.get(target)?.get(property); if (!subscriberSet) return; try { subscriberSet.delete(subscriber); // 清理空集合 if (subscriberSet.size === 0) { store.get(target)?.delete(property); } // 清理空映射 if (store.get(target)?.size === 0) { store.delete(target); } } finally { unlock(); } } } _a = Observer, _Observer_flushChangeQueue = function _Observer_flushChangeQueue() { // 重置处理状态 __classPrivateFieldSet(this, _a, false, "f", _Observer_isProcessingQueue); const queue = __classPrivateFieldGet(this, _a, "f", _Observer_pendingChanges); // 处理每个目标的变更 for (const [target, properties] of queue) { __classPrivateFieldGet(this, _a, "m", _Observer_notifyTargetChanges).call(this, target, properties); } }, _Observer_notifyTargetChanges = function _Observer_notifyTargetChanges(target, properties) { // 获取批处理订阅者 const subscribers = __classPrivateFieldGet(this, _a, "f", _Observer_batchSubscribers).get(target); if (!subscribers) return; // 通知每个属性的订阅者 for (const property of properties) { __classPrivateFieldGet(this, _a, "m", _Observer_notifySubscribers).call(this, target, subscribers.get(property), [property]); } // 如果没有全局变更订阅,则通知全局订阅者 if (!properties.has(this.ALL_PROPERTIES_SYMBOL)) { __classPrivateFieldGet(this, _a, "m", _Observer_notifySubscribers).call(this, target, subscribers.get(this.ALL_PROPERTIES_SYMBOL), Array.from(properties)); } }, _Observer_notifySubscribers = function _Observer_notifySubscribers(target, subscribers, changedProperties) { if (subscribers?.size) { for (const subscriber of subscribers) { if (typeof subscriber === 'function') { subscriber(changedProperties, target); } else { subscriber.trigger(changedProperties, target); } } } }, _Observer_acquireLock = function _Observer_acquireLock(target) { // 等待直到目标没有被锁定 while (__classPrivateFieldGet(this, _a, "f", _Observer_accessLock).has(target)) { } // 锁定目标 __classPrivateFieldGet(this, _a, "f", _Observer_accessLock).add(target); // 返回解锁函数 return () => { __classPrivateFieldGet(this, _a, "f", _Observer_accessLock).delete(target); }; }; /** * 全局属性变更标识符 * * 用于订阅对象的所有属性变更,作为通配符使用。 * 当使用此标识符订阅时,对象的任何属性变更都会触发通知。 * * @type {ALL_PROPERTIES_SYMBOL} */ Object.defineProperty(Observer, "ALL_PROPERTIES_SYMBOL", { enumerable: true, configurable: true, writable: true, value: ALL_PROPERTIES_SYMBOL }); /** * 目标对象标识符 * * 用于在代理对象上标识原始目标对象的Symbol。 * 可将此标识符作为对象的属性,属性值为实际用于订阅的原始目标对象。 * * @type {OBS_TARGET_SYMBOL} */ Object.defineProperty(Observer, "TARGET_SYMBOL", { enumerable: true, configurable: true, writable: true, value: OBS_TARGET_SYMBOL }); // 防止并发修改的锁 _Observer_accessLock = { value: new WeakSet() }; // 批处理模式的订阅存储 _Observer_batchSubscribers = { value: new WeakMap() }; // 即时模式的订阅存储 _Observer_immediateSubscribers = { value: new WeakMap() }; // 微任务变更队列 _Observer_pendingChanges = { value: new Map() }; // 是否正在处理队列 _Observer_isProcessingQueue = { value: false }; export { Observer as ObserverManager };