JavaScript中三种观察者实现案例分享
作者:剪刀石头布啊
前面突然看到 Object.defineProperty,就顺道想到 Proxy,然后就想到了观察者案例,这边还没有用 javascript编写一个观察者的案例呢,顺道加入了一个 event-bus 监听事件案例,凑一起看一看不同的实现方式,需要的朋友可以参考下
event-bus
event-bus
最基础的事件监听,算是一个通知类型的,可以设置成一个单例全局使用,也可以用在局部
通过名字订阅监听,通过名字发布新消息,使用也比较简单
export default class EventBus { events: Record<string, Array<Function>> = {} //订阅 subscribe(name: string, callback: Function) { if (!this.events[name]) { this.events[name] = [] } this.events[name].push(callback) } //发布 -- data是往call中传递参数以便于使用的 publish(name: string, data?: any) { let event = this.events[name] if (event) { event.forEach(callback => callback(data)) } } //取消订阅,y unsubscribe(name: string, callback?: Function) { let event = this.events[name] if (!callback) { this.events[name] = [] }else if (event) { this.events[name] = event.filter(e => e !== callback) } } }
测试案例
const event = new EventBus() const subscribe = () => { event.subscribe('name', (value: any) => { console.log(value) }) console.log('已订阅') } const publish = () => { let content = "我发布消息了,内容是\"哈啊哈哈\"" event.publish('name', content) } const unsubscribe = () => { event.unsubscribe('name') console.log('取消订阅了') }
自动销毁观察者(event-bus版)
这里根据 event-bus
改变,参考 UI组件生命周期
变化,实现UI组件销毁时自动释放
的监听订阅案例,实际也就多了一个上下文,方便统一销毁罢了
///------自动释放组件---- //假设组件释放调用方法为onDestory const __ComponentDestoryName = 'onDestory' class EventObj { key: string fn: Function[] = [] constructor(key: string, fn: Function) { this.key = key this.fn.push(fn) } } class ContextEventObj<T = EventObj> { context: any events: T[] = [] constructor(context: any, event: T) { this.context = context this.events.push(event) } //假设组件释放调用方法为destory registerDestory(callback: Function) { let desFunc = this.context[__ComponentDestoryName] this.context[__ComponentDestoryName] = function() { callback() desFunc() } } } //根据上下文context自动释放版本( class EventBusByAutoRelease { events: ContextEventObj[] = [] //订阅,context为所属上下文,当上下文对象销毁时,响应监听随机销毁 subscribe(name: string, callback: Function, context: any = null) { let contextObj = this.events.find(e => e.context === context) if (contextObj) { let event = contextObj.events.find(e => e.key === name) event?.fn.push(callback) }else { let event = new EventObj(name, callback) contextObj = new ContextEventObj(context, event) contextObj.registerDestory(() => { this.events = this.events.filter(e => e.context !== context) }) this.events.push(contextObj) } } //发布 -- data是往call中传递参数以便于使用的 publish(name: string, data?: any) { this.events.forEach(item => item.events.forEach(e => e.key === name && e.fn.forEach(fn => fn(data)) ) ) } //取消订阅 unsubscribe(context?: any, name?: string) { if (context) { let ctx = this.events.find(e => e.context === context) if (!ctx) return if (name) { ctx.events = ctx.events.filter(e => e.key !== name) }else { this.events = this.events.filter(e => e.context !== context) } }else { this.events.length = 0 } } }
observer
这个观察者模式是通过Object.defineProperty
方法,冲定义指定对象的 set、get
方法以实现自动监听的效果,实现方法参考了一些以前看过的其他平台的部分实现案例(实际上实现可以更简洁)
ps
:自己也可以尝试写一个更好用的哈,这一主要是应用 Object.defineProperty
const symbol_observe = '__obs_map' const canObserve = (obj: any) => obj !== null && typeof obj === 'object' class ObserveObj { target: any key: string fn: Function[] = [] val: any constructor(target: any, key: string, fn: Function, val: any) { this.target = target this.key = key this.fn.push(fn) this.val = val this.defineProperty() } defineProperty() { let that = this Object.defineProperty(that.target, that.key, { get() { return that.val }, set(newVal) { that.notify(newVal) }, enumerable: true, // 可枚举 }) } notify(val: any) { this.val = val this.fn.forEach(e => e(val)) } removeObserver() { delete this.target[this.key] this.target = null } } class TargetObs { target: any observes: Array<Record<'key' | 'fn', string | Function>> = [] constructor(target: any) { this.target = target } } export default class Observer { targets: TargetObs[] = [] //用于保存target和callback信息,以便于后续清理 observe(target: any, key: string, callback: Function) { if (!canObserve(target)) return let targetObs = this.targets.find(e => e.target === target) if (!targetObs) { targetObs = new TargetObs(target) targetObs.observes.push({ key, fn: callback }) this.targets.push(targetObs) } if (!target[symbol_observe]) { target[symbol_observe] = new Map<string, ObserveObj>() } let observeMap: Map<string, ObserveObj> = target[symbol_observe] let observeObj = observeMap.get(key) if (observeObj) { observeObj.fn.push(callback) }else { observeObj = new ObserveObj(target, key, callback, target[key]) observeMap.set(key, observeObj) } } //清理某个target部分监听 unobserve(target: any, key: string, callback?: Function) { if (!canObserve(target)) return let observeMap: Map<string, ObserveObj> = target[symbol_observe] if (!observeMap) return let obj = observeMap.get(key) if (!obj) return let targetObs = this.targets.find(e => e.target = target) if (callback) { if (targetObs) { targetObs.observes = targetObs.observes.filter(e => e.fn !== callback) } obj.fn = obj.fn.filter(e => e !== callback) }else { let fns if (targetObs) { fns = targetObs.observes.filter(e => e.key === key) } fns?.forEach(item => { obj!.fn = obj!.fn.filter(e => e !== item.fn) }) } if (obj.fn.length < 1) { obj.removeObserver() observeMap.delete(key) } } //清理本观察者添加的指定监听 unobserveTarget(target: any) { if (!canObserve(target)) return let observeMap: Map<string, ObserveObj> = target[symbol_observe] if (!observeMap) return let targetObs = this.targets.find(e => e.target = target) for (let key in observeMap) { let fns if (targetObs) { fns = targetObs.observes.filter(e => e.key === key) } let obj = observeMap.get(key) if (!obj) continue fns?.forEach(item => { obj!.fn = obj!.fn.filter(e => e !== item.fn) }) if (obj.fn.length < 1) { obj.removeObserver() observeMap.delete(key) } } } //清理本观察者添加的指定监听 unobserveAll(target?: any) { if (target) { this.unobserveTarget(target) }else { this.targets.forEach(e => { this.unobserveTarget(e.target) }) this.targets.length = 0 } } }
测试案例
const event = new Observer() const subscribe = () => { event.observe(dataModel, 'name', (value: any) => { console.log(value) }) console.log('已订阅') } const publish = () => { let content = "我更改内容了,内容是\"哈啊哈哈\"" dataModel.name = content } const unsubscribe = () => { event.unobserve(dataModel, 'name') console.log('取消观察了') }
observer-proxy
本观察案例通过 Proxy
代理实现的,使用了里面的 Proxy.revocable
Proxy基础参考文档,可以取消监听
唯一缺陷更改方法需要使用新的 Proxy
对象来代替之前的,否则无法实现监听功能,实际使用不是很友好,这里面已经尽量让其更加又友好了😂
ps
:实际上 proxy 用在其他特殊场景才能发挥出其巨大的优势,这里面也是写出一种实现方案罢了,有更好的可以讨论哈
class ObserveObj { observer: string key: string fn: Function[] = [] constructor(key: string, observer: any, fn: Function) { this.key = key this.observer = observer this.fn.push(fn) } } export default class ObserverProxy<T extends Object> { target: any //用于回退原来对象 ori = proxy.target o: T rk: Function //用于取消代理 //存放回调的数组,一个对象的监听一般不会过多(例如成百上千),因为他存在性能可以考虑调整 observers: ObserveObj[] = [] //如果根据observer自动释放,可以重写observer的销毁属性,在哪里自动释放即可,由于很多组件的销毁方法不一样就不实现了 constructor(target: T) { this.target = target let that = this const {proxy, revoke} = Proxy.revocable(target, { //注意里面的 this 都会指向 target get: function (target, propKey, receiver) { return Reflect.get(target, propKey, receiver); }, //这里拦截set方法即可 set: function (target: any, propKey: string, value: any, receiver: any) { let observers = that.observers.filter(e => e.key === propKey) observers.forEach(e => e.fn.forEach(fn => fn(value))) //调动原来的set方法赋值 return Reflect.set(target, propKey, value, receiver); }, }) this.o = proxy this.rk = revoke } revoke() { this.rk() this.removeObserve() let target = this.target this.target = null return target } //callback回调,context观察者(参与观察的对象,即回调所在的类),responseByFirst初次是否响应回调 addObserve(key: string, callback: Function, observer: any, responseByFirst = false) { let observerObj = this.observers.find( e => e.observer === observer && e.key === key ) if (!observerObj) { observerObj = new ObserveObj(key, observer, callback) this.observers.push(observerObj) }else { observerObj.fn.push(callback) } responseByFirst && callback(this.target[key]) } removeObserve(observer?: any, key?: string) { if (observer) { if (key) { this.observers = this.observers.filter(e => e.observer !== observer || e.key !== key) }else { this.observers = this.observers.filter(e => e.observer !== observer) } }else { this.observers.length = 0 } } }
测试案例
const dataModel = new ObserverProxy(new DataModel()) const subscribe = () => { dataModel.addObserve('name', (value: any) => { console.log(value) }, this) console.log('已订阅') } const publish = () => { let content = "我更改内容了,内容是\"哈啊哈哈\"" //由于要用代理修改,使用.ob代替原对象即可 dataModel.o.name = content } const unsubscribe = () => { //撤销监听后,返回原对象,注意原对象没有监听 let res = dataModel.revoke() console.log('取消观察了') }
最后
最后两个本来想实现自动释放,由于一些平台存在引用计数(不只是垃圾回收),引用会存在内存泄露问题,
由于 js 对象没有析构函数,这里即使加上 WeakMap 也不行,因为它不可遍历,所以目前对于我来说实现自动释放的监听尚有困难(当然难不住大佬),当然这也是后续要解决的难题,大家要是有策略可以探讨,大家一起提升😂
以上就是JavaScript三种观察者实现案例分享的详细内容,更多关于JavaScript观察者实现的资料请关注脚本之家其它相关文章!