大家好,我是前端西瓜哥。
PixiJS 的 Runner 类是高性能的事件通知类。其实就是一个简易的发布订阅库。
发布订阅库,我们比较熟悉的就是 Nodejs 的 EventEmitter。
不过这个 Runner 的逻辑稍微有点特殊,后面会说它怎么特殊。
import { Runner } from "@pixi/runner";const loadedRunner = new Runner("loaded");const listener = { loaded(n1: number, n2: number) { console.log("前端西瓜哥", n1, n2); }};loadedRunner.add(listener);loadedRunner.emit(1, 2); // 输出:前端西瓜哥 1 2
首先通过 new Runner(name) 创建一个 Runner 实例,这里需要传入一个字符串类型的 name。
之后通过 runner.add 方法添加一个监听器对象 listener。
最后通过 runner.emit 方法触发事件,之前绑定的监听器的 listener[name] 方法会被执行。
和我们熟悉的 Nodejs 的 EventEmitter 不一样,有一些特别的点:
然后它和 EventEmitter 一样,是类型不安全的:emit 传的参数并没有限定。
首先是构造函数。
export class Runner { public items: any[]; private _name: string; private _aliasCount: number; constructor(name: string) { this.items = []; this._name = name; this._aliasCount = 0; } // ...}
简单的初始化操作,这个 name 我们要保存下来,之后我们执行监听器对象,需要这个 name 作为 key 去找到方法去执行。
items 是保存监听器对象的数组。
_aliasCount 是一个标识,标识是否在 emit(触发事件)阶段,用于防止 emit 时改变了 items,导致不可预期的行为。
然后是 add 方法,用于添加监听器。
public add(item: unknown): this { if ((item as any)[this._name]) { this.ensureNonAliasedItems(); this.remove(item); // 如果存在,先删除 this.items.push(item); // 添加的末尾 } return this;}
监听器对象必须有对应的 key 才能被添加进去。
为了保证 this.items 不出现多个相同的对象,会将其删除。然后把监听器对象放到 this.items 末尾。
返回 this,是为了实现链式调用。
this.ensureNonAliasedItems() 方法用于处理一些特殊 case。
比如在 emit 阶段发生了 add 操作,PixiJS 会防止其在本轮 emit 被执行,为此会拷贝一份新的 items。
private ensureNonAliasedItems(): void { if (this._aliasCount > 0 && this.items.length > 1) { this._aliasCount = 0; this.items = this.items.slice(0); }}
emit 会触发事件,别名有 dispatch、run。
public emit( a0?: unknown, a1?: unknown, a2?: unknown, a3?: unknown, a4?: unknown, a5?: unknown, a6?: unknown, a7?: unknown): this { if (arguments.length > 8) { throw new Error('max arguments reached'); } const { name, items } = this; this._aliasCount++; for (let i = 0, len = items.length; i < len; i++) { items[i][name](a0, a1, a2, a3, a4, a5, a6, a7); } if (items === this.items) { this._aliasCount--; } return this;}
核心逻辑:遍历 this.items 数组,顺序执行监听器的 key 为 this.name 的方法。
remove,删除监听器。
public remove(item: unknown): this { const index = this.items.indexOf(item); if (index !== -1) { this.ensureNonAliasedItems(); this.items.splice(index, 1); } return this;}
contains:查看指定对象是已经是被绑定为监听器。
public contains(item: unknown): boolean { return this.items.includes(item);}
removeAll:删除所有监听器.
public removeAll(): this { this.ensureNonAliasedItems(); this.items.length = 0; return this;}
destory:销毁。销毁后就不能再用了,否则会报错。
public destroy(): void { this.removeAll(); this.items = null; this._name = null;}
empty,是一个 getter,判断是否存在监听器集合。PixiJS 确实喜欢用 getter
public get empty(): boolean { return this.items.length === 0;}
通常我们会在 PixiJS 的类中看到名为 disposeRunner 的成员属性,说明这个类会通过事件订阅的方式和其他模块通信。
发布订阅库我实在是分析得够多了,基本的套路就 3 个:把监听器函数放到有序表中,触发事件时顺序调用,支持删除监听器(3 种风格)。
PixiJS 的 Runner 功能并不多,其中特殊的调用逻辑(调用监听器的特定 key)显然是用于 PixiJS 内部模块的风格。
本文链接:http://www.28at.com/showinfo-26-13254-0.htmlPixiJS 源码解读:Runner 事件通知类
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 如何更优雅的编程?面向接口编程四大法宝!