源码仓库
一、EventHub
是什么
EventHub
又叫 发布/ 订阅模式
,是用于多个模块之间进行通信的
比如我们有两个文件,分别是:1.js
和 2.js
1.js
里有一个函数 fn1
,2.js
里有个函数 fn2
fn1
想要调佣 fn2
,怎么才能做到呢?
如果不用全局变量,那我们就可以使用 EventHub
那么就可以这么写:
1 2 3 4 5 6 7 8 9 10 11 12 fn1 = () => { eventhub.emit ('轮到fn2' ) } eventhub.on ('轮到fn2' , () => { fn2 () })
二、确定 API
先确认各个 API 的功能,再去实现,再去写代码
API 列表
为什么我用井号?因为 EventHub
是一个类,井号表示是一个对象的属性
EventHub#on
注册/监听事件
EventHub#off
取消事件
EventHub#emit
触发事件
三、源码书写过程
采用测试驱动开发:添加测试用例,想办法让测试通过 不使用库,而是用非常简单的 console
方式 需要全局安装 ts-node@8.3.0
1. 在根目录下创建 test/index.ts
和 src/index.ts
1 2 export class EventHub {}
1 2 3 4 import { EventHub } from '../src/index' const eventhub = new EventHub ()console .assert (eventHub instanceof Object === true , 'eventHub 是一个对象' )
然后在当前目录下执行 ts-node test/index.ts
,如果没有报错,则测试通过
2. on
和 emit
添加测试用例 on
和 emit
1 2 3 4 5 6 7 8 9 10 import { EventHub } from '../src/index' const eventhub = new EventHub ()let called = false eventhub.on ('xxx' , () => { called = true console .log ('called:' + called) }) eventhub.emit ('xxx' )
实现 on
和 emit
on
接受两个参数,第一个是 事件名
、第二个是一个 回调事件
emit
接受一个参数,为 事件名
on
:当有人订阅了事件之后,我们需要把 事件名
和 事件回调
存在一个 map
里,这里我放在 cache
这个对象1 2 3 4 5 cache = { 事件1 : [fn1, fn2, fn3], 事件2 : [fn1, fn2, fn3] }
emit
:触发 事件回调
,读取事件名相对应的函数,并且依次调用这些函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export class EventHub { cache = {} on (eventName, fn ) { if (this .cache [eventName] === undefined ) { this .cache [eventName] = [] } const array = this .cache [eventName] array.push (fn) } emit (eventName ) { let array = this .cache [eventName] if (array === undefined ) { array = [] } array.forEach (fn => { fn () }) } }
运行 ts-node test/index.ts
,如果控制台输出 called:true
那么就测试通过
3. 优化代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export class EventHub { cache = {} on(eventName, fn) { - if (this.cache[eventName] === undefined) { - this.cache[eventName] = [] - } + this.cache[eventName] = this.cache[eventName] || [] - const array = this.cache[eventName] - array.push(fn) + this.cache[eventName].push } emit(eventName) { - let array = this.cache[eventName] - if (array === undefined) { - array = [] - } - let array = this.cache[eventName] || [] - array.forEach(fn => { - fn() - }) + (this.cache[eventName] || []).forEach(fn => fn()) } }
运行 ts-node test/index.ts
,如果不报任何错误,则测试通过
4. 实现 emit
的第二个参数 添加测试用例 1 2 3 4 5 6 7 8 9 10 11 import { EventHub } from '../src/index' const eventhub = new EventHub ()let called = false eventhub.on ('xxx' , data => { called = true console .log ('called:' + called) console .assert (data === '接受的数据' ) }) eventhub.emit ('xxx' , '接受的数据' )
实现功能 很简单,把 接受的数据
传给每一个函数即可
1 2 3 4 5 6 7 8 9 10 11 export class EventHub { cache = {} on (eventName, fn ) { this .cache [eventName] = this .cache [eventName] || [] this .cache [eventName].push } emit (eventName, data? ) { (this .cache [eventName] || []).forEach (fn => fn (data)) }
运行 ts-node test/index.ts
,如果不报任何错误,则测试通过
5. 实现 off
测试用例 想象在淘宝买东西:我下单后,在发货前,马上取消订单,这样子货就不会送到家里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import { EventHub } from '../src/index' const eventhub = new EventHub ()let called = false eventhub.on ('xxx' , data => { called = true console .log ('called:' + called) console .assert (data === '接受的数据' ) }) eventhub.emit ('xxx' , '接受的数据' ) const eventHub2 = new EventHub ()let called2 = false const fn1 = ( ) => { called2 = true } eventHub.on ('yyy' , fn1) eventHub.off ('yyy' , fn1) eventHub.emit ('yyy' ) setTimeout (() => { console .log (called2) }, 1000 )
实现功能 把 map
里相对应的函数删掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 export class EventHub { cache = {} on (eventName, fn ) { this .cache [eventName] = this .cache [eventName] || [] this .cache [eventName].push } emit (eventName, data? ) { ;(this .cache [eventName] || []).forEach (fn => fn (data)) } off (eventName, fn ) { this .cache [eventName] = this .cache [eventName] || [] let index = undefined for (let i = 0 ; i < this .cache [eventName].length ; i++) { if (this .cache [eventName][i] === fn) { index = i break } } if ((index = undefined )) { return } else { this .cache [eventName].splice (index, 1 ) } } }
运行 ts-node test/index.ts
,输出 false
则测试成功
6. 优化代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 export class EventHub { cache = {} on(eventName, fn) { this.cache[eventName] = this.cache[eventName] || [] this.cache[eventName].push } emit(eventName, data?) { (this.cache[eventName] || []).forEach(fn => fn(data)) } off(eventName, fn) { - this.cache[eventName] = this.cache[eventName] || [] - let index = undefined - for(let i = 0; i < this.cache[eventName].length; i++) { - if(this.cache[eventName][i] === fn) { - index = i - break - } - } + let index = indexOf(this.cache[eventName], fn) - if(index = undefined) { - return - } else { - this.cache[eventName].splice(index, 1) - } + if(index = -1) return + this.cache[eventName].splice(index, 1) } } +function indexOf(array, item) { + if (array === undefined) return -1 + let index = -1 + for (let i = 0; i < array.length; i++) { + if (array[i] === item) { + index = i + } + } + return index +}
五、重构代码
重构!优化!
1. 重构优化测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import { EventHub } from '../src/index' type TestCase = (message: string ) => void const test1 : TestCase = message => { const eventHub = new EventHub () console .assert (eventHub instanceof Object === true , 'eventHub 是一个对象' ) console .log (message) } const test2 : TestCase = message => { const eventHub = new EventHub () let called = false eventHub.on ('xxx' , data => { called = true console .assert (data[0 ] === '接受的数据1' ) console .assert (data[1 ] === '接受的数据2' ) }) eventHub.emit ('xxx' , ['接受的数据1' , '接受的数据2' ]) console .assert (called) console .log (message) } const test3 : TestCase = message => { const eventHub = new EventHub () let called = false const fn1 = ( ) => { called = true } eventHub.on ('yyy' , fn1) eventHub.off ('yyy' , fn1) eventHub.emit ('yyy' ) console .assert (called === false ) console .log (message) } test1 ('EventHub 可以创建对象' )test2 ('.on 之后 .emit 会触发 .on 的函数' )test3 ('.off 是有用的' )
2. 重构优化 EventHub
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export class EventHub { private cache : { [key : string ]: Array <(data: unknown ) => void > } = {} on (eventName: string , fn: (data: any ) => void ) { this .cache [eventName] = this .cache [eventName] || [] this .cache [eventName].push (fn) } emit (eventName: string , data?: unknown ) { (this .cache [eventName] || []).forEach (fn => fn (data)) } off (eventName: string , fn: (data: unknown ) => void ) { let index = indexOf (this .cache [eventName], fn) if (index === -1 ) return this .cache [eventName].splice (index, 1 ) } } function indexOf (array, item ) { if (array === undefined ) return -1 let index = -1 for (let i = 0 ; i < array.length ; i++) { if (array[i] === item) { index = i } } return index }
感谢阅读,下次见 :)