10分鐘,手把手帶你用typescript實現一個發佈訂閱模式,完成測試用例。

  • 先來看咱們要實現的功能
    • 訂閱,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'
複製代碼

取消發佈,在發佈以前調用,不能取消匿名函數。node

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], }數組

    • 寫數組的緣由:
      • 一個事件名,可能會傳入多個函數,調用屢次
  • onbash

    • 若是傳進新的函數名,初始化
    • 將函數存入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,如今咱們來給全部參數加上類型定義ui

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
        }
    }
}
複製代碼

測試代碼使用TypeScriptthis

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編寫的發佈訂閱模式,感謝各位的閱讀 ~spa

歡迎你們留下問題一塊兒探討 ~

但願能夠在面試、工做中助你一臂之力!

相關文章
相關標籤/搜索