- 先来看我们要实现的功能
- 订阅,eventHub.$on(方法名,function(接收数据的参数){用该组件的数据接收传递过来的数据})
- 发布,eventHub.$emit(方法名,传递的数据)
- 取消发布,eventHub.$off(方法名,函数名称)取消订阅,要给函数一个名字,不能为匿名函数
- 使用console.assert 完成测试用例
let eventHub = new Vue()
// 订阅
eventHub.$on('eventName', (data) => {
console.log('😍' + data)
})
// 发布
eventHub.$emit('eventName', 'cats') // '😍 cats'
取消发布,在发布之前调用,不能取消匿名函数。
let eventHub = new Vue()
let fn = (data) => {
console.log('😍' + data)
}
// 订阅
eventHub.$on('eventName', fn)
// 取消发布
eventHub.$off('eventName', fn)
// 发布
eventHub.$emit('eventName', 'cats') // 没有结果
先来实现第一版eventHub
-
文件结构
-
eventHub/ src/ index.ts test/ index.ts 放测试代码
-
将函数存在一个对象中,命名为cache,格式为,
js { 'xxx': [fn1, fn2], 'xxx2': [fn1, fn2], }
- 写数组的原因:
- 一个事件名,可能会传入多个函数,调用多次
- 写数组的原因:
-
on
- 如果传进新的函数名,初始化
- 将函数存入cache中,之后可以从off调用函数
-
off
- 根据传递的函数名,从cache中调用函数
class EventHub {
cache = {}
// ts 语法 === js的 this.cache={}
// 对象的属性
on(eventName, fn) {
// 如果没有,初始化
if (this.cache[eventName] === undefined){
this.cache[eventName] = []
}
// 把fn 推进 this.cache[eventName] 数组
let array = this.cache[eventName]
array.push(fn)
}
emit(eventName) {
let array = this.cache[eventName]
if (array === undefined) {
array = []
}
// 把this.cache中函数依次执行
array.forEach(fn => {
fn()
})
}
}
export default EventHub
测试代码ts
- console.assert
- 如果断言为false,则将错误消息写入控制台。如果断言为真,则没有任何反应。
- 运行ts-node test.ts, 没有中断, 测试通过
import EventHub from '../src/index'
const eventHub = new EventHub()
console.assert(eventHub instanceof Object === true, 'eventHub 是个对象')
// on emit
let called = false
eventHub.on('xxx', () => {
called = true
console.log('called:' + called)
})
eventHub.emit('xxx')
第二版 优化on、emit
on(eventName, fn) {
// this.cache[eventName] 如果不存在,为[];否则为他自己
// if (this.cache[eventName] === undefined) {
// this.cache[eventName] = []
// }
// 将上面的代码优化为 下面一句(下同)
this.cache[eventName] = this.cache[eventName] || []
// let array = this.cache[eventName]
// array.push(fn)
this.cache[eventName].push(fn)
}
emit(eventName) {
// let array = this.cache[eventName]
// if (array === undefined) {
// array = []
// }
// 此处与on优化方式相同 --> this.cache[eventName] || []
// this.cache[eventName].forEach(fn =>
// fn()
// )
// 上方的代码优化为下面一句
(this.cache[eventName] || []).forEach(fn => fn())
}
第三版 emit实现传递参数
// ? 可以不传递参数 ...拓展运算符 可以传递多个参数
emit(eventName, ...args?) {
(this.cache[eventName] || []).forEach(fn => fn(...args))
}
测试 传递参数代码
// 测试 on emit
eventHub.on('xxx', (y) => {
console.log('called:' + called)
console.assert( y === 'hello')
})
eventHub.emit('xxx', 'hello')
第四版 添加off功能
off(eventName, fn) {
// 把fn从this.cache[eventName] 删掉
this.cache[eventName] = this.cache[eventName] || []
// 找一个元素是数组的第几项 array.indexOf(searchElement)
// 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
// 但是兼容性不好,不兼容IE11,这里自己实现一个函数
let index
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)
}
}
第五版 优化off
off(eventName, fn) {
// this.cache[eventName] = this.cache[eventName] || [] 优化为下面一句
let index = indexOf(this.cache[eventName], fn)
// if(index === undefined) {
// return
// } else {
// this.cache[eventName].splice(index, 1)
// } 优化为下面一句
if (index === undefined) return
this.cache[eventName].splice(index, 1)
}
}
export default EventHub
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
break
}
}
}
最终版 完整使用TypeScript重构
TypeScript 可以简单理解为 Type + JavaScript,现在我们来给所有参数加上类型定义
class EventHub {
// 声明为一个cache 对象
// - key: value
// - key是string : value 数组
// - 数组中是函数 参数(未知) 和 返回值(为空)
private cache: { [key: string]: Array<(data: unknown) => void>} = {}
// unknow 第一次传递的为什么类型,以后只能传递此种类型,不能修改
// void 返回值为空
on(eventName: string, fn: (data: unknown) => void) {
this.cache[eventName] = this.cache[eventName] || []
this.cache[eventName].push(fn)
}
// data? 表示可以为空,不传递
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 === undefined) return
this.cache[eventName].splice(index, 1)
}
}
export default EventHub
function indexOf(array: Array<unknown>, item: unknown) {
if (array === undefined) return -1
let index = -1
for (let i = 0; i < array.length; i++) {
if (array[i] === item) {
index = i
break
}
}
}
测试代码使用TypeScript
import EventHub from '../src/index'
// 定义一种类型 TestCase 传递string参数,返回值为空
type TestCase = (message: string) => void
// 第一个测试用例,起名为test1 在最后调用
// 断言 eventHub是个对象
const test1: TestCase = message => {
const eventHub = new EventHub()
console.assert(eventHub instanceof Object === true, 'eventHub 是个对象')
// 确定成功之后打出message
console.log(message)
}
const test2: TestCase = message => {
const eventHub = new EventHub()
// on emit
let called = false
eventHub.on('xxx', y => {
called = true
console.assert(y === 'hello')
})
eventHub.emit('xxx', 'hello')
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有用')
这就是使用TypeScipt编写的发布订阅模式,感谢各位的阅读 ~
欢迎大家留下问题一起探讨 ~
希望可以在面试、工作中助你一臂之力!