@6pm/emit 中文文档教程
6pm/emit
具有惰性初始化、零内存和 cpu 的零依赖事件发射器 初始状态,并完全支持 Symbol
事件类型!
Installation
通过 npm 添加 @6pm/emit
到你的项目。
$ npm install @6pm/emit --save
Testing
首先获取 github 存储库的本地克隆。
$ git clone https://github.com/6pm-js/emit.git
$ cd emit
运行测试套件:
$ npm test
或使用 istanbul 生成覆盖率报告。
$ npm run cover
A note on ES2015
@6pm/emit
是使用 ES2015 特性编写的,因此直接兼容 仅适用于 Node 版本 >= 4.0.0 或最新的常青浏览器。
理论上,Babel 或者 Traceur 可用于 transpile 以与相当旧的 JavaScript 引擎兼容 - 如果 必需的。
@6pm/emit
模块也通过 ES2015 模块公开,尽管 NPM 包在 dist/emit.js 中包含一个 UMD 包装构建, 适合包含在常青浏览器中,或直接通过 require
在节点中。
当直接在网络浏览器中使用 UMD 包装构建时,Emit
类是 通过全局 sixpm.emit.Emit
公开。
Usage
与核心 NodeJS 大部分兼容 EventEmitter, except:
- addition of explicit context extensions, as defined by eventemitter3
- removal of the 'uncaught
error
event throws' behaviour - this is very counterintuitive - throw or emit, pick one, or explicitly do both. - removal of the expensive and pointless
newListener
andremoveListener
events. - Total ignorance of the max listeners setting and its very arbitrary warning.
要将类变成事件发射器,有两种主要方法:
- by extension
- by prototypical enhancement
By extension
扩展 Emit
类为子类提供事件发射器功能。
import { Emit } from '@6pm/emit';
class SomeNewEmitter extends Emit {}
let instance = new SomeNewEmitter(),
EVENT = Symbol('my.event');
instance.on(EVENT, () => { /* Do some work. */ });
instance.emit(EVENT, 1, 2, 3);
By prototypical enhancement
静态 Emit.assign()
方法将事件发射器功能分配给 目标对象,可以是静态对象,也可以是类原型。
因为 6pm/emit
最初是无状态的,所以没有构造函数重载 必需 - assign
ing Just Works™。
import { Emit as EventEmitter } from '@6pm/emit';
class SomeNewEmitter {
constructor() {
// Note: no Emit based custom construction required.
}
}
EventEmitter.assign(SomeNewEmitter.prototype);
let instance = new SomeNewEmitter(),
instance.on('some-event', () => { /* Do some work. */ });
instance.emit('some-event', result, false);
Context enhancement
以下方法支持将 context
作为可选的第三个传递 参数,作为避免 bind
函数到 a 的开销的快捷方式 context - 从 eventemitter3 中无耻地窃取的想法。
on
once
off
addListener
removeListener
因此,以下:
someEmitter.on('event', callback, context);
等同于但快于:
someEmitter.on('event', callback.bind(context));
Design
@6pm/emit
旨在解决 JavaScript 中非常常见的设计模式。
即,事件发射和消费明显不相交 - 图书馆 通常会发出许多事件,以便公开托管的完整生命周期 信息,但应用程序很少使用它们——而是挑选一个 必要的很少。
Zero initial state
该模块不假定任何给定事件发射器的初始事件状态,而是 仅在首次注册听众时增加发射器, 具有可以对隐藏的发射器状态做出安全假设的“快速路径”。
这意味着没有 cpu 开销或事件的空间要求 发射器,除非应用程序实际上监听它们。
权衡是增强的“快速路径”方法需要额外的 注册监听器后的空间 - 但对于有许多对象的应用程序 暴露事件发射器接口,但很少使用,这有很大的影响 关于资源使用和初始化性能。
Encapsulated internals
所有发射器状态都通过 ES2015 Symbol
隐藏,以防止冲突,并且 在设计中提供一定程度的封装 - 虽然显然不是完美的 防止信息泄露的方法 (getOwnPropertySymbols 允许揭开面纱)
- this is generally safer than using named properties.
Symbol support
Symbol
s 作为事件类型在整个过程中都得到支持和显式测试 模块 - 作为个人喜好的有效分离 / Symbol
作为事件的命名空间和可读性似乎是一个很好的方法。
事件生产:
import { Emit } from '@6pm/emit';
const START = Symbol('someclass.start.event');
export class SomeClass {
start() {
this.emit(START);
}
}
SomeClass.START = START;
事件消费:
import { SomeClass } from 'someclass.js';
let instance = SomeClass();
// No potential for event name collision, elegant reference to source of event.
instance.on(SomeClass.START, () => { /* ... */ });
Performance
这个模块的主要目的是提供一个(大部分)与Node兼容的 API,为未订阅的发射器强制执行零初始状态,以确保强大的 Symbol
支持(由于替代库中的混合体验),并使用 干净的 ES2015 代码风格。
性能优化是次要问题,尽管通过折叠 从 eventemitter3 的出色工作中吸取的教训 团队,然后继续削减发现的任何多余部分,此实施具有 变得异常高效 - 微基准测试是一个相当糟糕的衡量标准 真实世界的性能 - 但如果需要,添加 @6pm/emit
是相当简单的 到 eventemitter3 的基准套件来评估原始开销 - 和 结果 (与适量的盐一起食用)显示 @6pm/emit
没有 特定的弱点,并在大约一半的情况下取得最高绩效 测试。
TODO:添加性能测试和资源监控工具以允许 对此在野外进行综合测试。
Contributing
欢迎所有评论、批评、PR 和问题!
License
在 MIT 许可下发布
6pm/emit
A zero dependency event emitter with lazy initialisation, zero memory and cpu initial state, and full support for Symbol
event types!
Installation
Add @6pm/emit
to your project via npm.
$ npm install @6pm/emit --save
Testing
First grab a local clone of the github repo.
$ git clone https://github.com/6pm-js/emit.git
$ cd emit
The run the test suite:
$ npm test
Or use istanbul to produce a coverage report.
$ npm run cover
A note on ES2015
@6pm/emit
is written using ES2015 features, and as such is directly compatible only with Node versions >= 4.0.0, or recent evergreen browsers.
In theory, Babel or Traceur could be used to transpile for compatibility with considerably older JavaScript engines - if required.
The @6pm/emit
module is also exposed via a ES2015 module, though the NPM package includes a UMD wrapped build in dist/emit.js, suitable for inclusion in evergreen browsers, or directly accessing via require
in Node.
When using the UMD wrapped build directly in a web browser, the Emit
class is exposed via the global sixpm.emit.Emit
.
Usage
Mostly compatible with the core NodeJS EventEmitter, except:
- addition of explicit context extensions, as defined by eventemitter3
- removal of the 'uncaught
error
event throws' behaviour - this is very counterintuitive - throw or emit, pick one, or explicitly do both. - removal of the expensive and pointless
newListener
andremoveListener
events. - Total ignorance of the max listeners setting and its very arbitrary warning.
To turn a class into an event emitter, there are two primary approaches:
- by extension
- by prototypical enhancement
By extension
Extending the Emit
class provides event emitter capabilities to the sub class.
import { Emit } from '@6pm/emit';
class SomeNewEmitter extends Emit {}
let instance = new SomeNewEmitter(),
EVENT = Symbol('my.event');
instance.on(EVENT, () => { /* Do some work. */ });
instance.emit(EVENT, 1, 2, 3);
By prototypical enhancement
The static Emit.assign()
method assigns event emitter capabilities to the target object, which can be a static object, or a class prototype.
Because 6pm/emit
is initially stateless, no constructor overloading is required - assign
ing Just Works™.
import { Emit as EventEmitter } from '@6pm/emit';
class SomeNewEmitter {
constructor() {
// Note: no Emit based custom construction required.
}
}
EventEmitter.assign(SomeNewEmitter.prototype);
let instance = new SomeNewEmitter(),
instance.on('some-event', () => { /* Do some work. */ });
instance.emit('some-event', result, false);
Context enhancement
The following methods support a context
being passed as an optional third argument, as a shortcut to avoid the overhead of bind
ing a function to a context - an idea brazenly stolen from eventemitter3.
on
once
off
addListener
removeListener
So, the following:
someEmitter.on('event', callback, context);
is equivalent to, but considerably faster than:
someEmitter.on('event', callback.bind(context));
Design
@6pm/emit
is designed to address a very common design pattern in JavaScript.
Namely, that event emission, and consumption are notably disjoint - libraries typically emit many events, in order to expose complete lifecycles of managed information, but applications rarely consume them all - instead cherry picking a necessary few as required.
Zero initial state
This module assumes no initial event state for any given event emitter, instead augmenting emitters only when listeners are registered for the first time, with a 'fast path' that can make safe assumptions about hidden emitter state.
This means that there is no cpu overhead, or space requirements for event emitters, unless an application actually listens to them.
The trade off is that the augmented 'Fast path' methods require additional space once listeners are registered - but for applications where many objects expose event emitter interfaces, but few are used, this has a significant impact on resource usage, and initialisation performance.
Encapsulated internals
All emitter state is hidden via ES2015 Symbol
s, to prevent clashes, and provide a degree of encapsulation in the design - though obviously not a perfect approach to preventing information leakage (getOwnPropertySymbols allows peering behind the veil)
- this is generally safer than using named properties.
Symbol support
Symbol
s as event types are supported, and explicitly tested for, throughout the module - as a matter of personal preference the effective separation / namespacing and readability of Symbol
s as events seems like a great approach.
Event production:
import { Emit } from '@6pm/emit';
const START = Symbol('someclass.start.event');
export class SomeClass {
start() {
this.emit(START);
}
}
SomeClass.START = START;
Event consumption:
import { SomeClass } from 'someclass.js';
let instance = SomeClass();
// No potential for event name collision, elegant reference to source of event.
instance.on(SomeClass.START, () => { /* ... */ });
Performance
The primary purpose of this module is to provide a (mostly) Node compatible API, to enforce zero initial state for unsubscribed emitters, to ensure strong Symbol
support (due to mixed experiences in alternative libraries), and to use a clean ES2015 code style.
Performance optimisation is a secondary concern, though by folding in the lessons learned by the sterling work of the eventemitter3 team, and then continuing to cut any excesses found, this implementation has become exceptionally efficient - microbenchmarks are a fairly poor measure of real world performance - but if desired, it is fairly trivial to add @6pm/emit
to eventemitter3's benchmark suite to asses raw overheads - and the results (to be consumed with a suitable serving of salt) show @6pm/emit
as having no particular weaknesses, and achieving the highest performance in around half of the tests.
TODO: Add performance testing and resource monitoring tooling to allow comprehensive testing of this in the wild.
Contributing
All comments, criticisms, PRs, and Issues welcome!
License
Release under the MIT license