PixiJS 源码解读:Runner 事件通知类
创始人
2025-07-03 18:30:37
0

大家好,我是前端西瓜哥。

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 不一样,有一些特别的点:

  • 一个 Runner 只能绑定一个事件,不像 EventEmitter 的 on 方法,还能多指定一个事件名。
  • 绑定的监听器是一个对象,并会在触发事件时调用 Runner 初始化时设置的 name 对应的函数。这样做的优点是监听器执行时 this 不会丢失。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 内部模块的风格。

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
30分钟搞定iOS自定义相机 最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究...
Intel将Moblin社区控... 本周二,非营利机构Linux基金会宣布,他们将担负起Moblin社区的管理工作,而这之前,Mobli...