国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

clipboard.js代碼分析(2)-emitter

MoAir / 2444人閱讀

摘要:用于在同一主模塊下的不同子模塊以及不同主模塊之間的通信,支持動(dòng)態(tài)綁定作用域。如果用過的父子組件事件通信以及,對(duì)事件管理器應(yīng)該不會(huì)陌生的。而且支持指定作用域,可以遠(yuǎn)程調(diào)用任意模塊的函數(shù)。

上一篇文章介紹了clipboard.js這個(gè)工具庫(kù)中的第一個(gè)依賴select這個(gè)工具庫(kù)主要完成了對(duì)任意DOM元素的復(fù)制到粘貼板的功能。這次介紹一下clipboard.js源碼中的第二個(gè)依賴的輕型工具庫(kù)tiny-emitter這個(gè)工具庫(kù)主要用來實(shí)現(xiàn)一個(gè)簡(jiǎn)易的基于監(jiān)聽發(fā)布者模式的事件派發(fā)和接收器,代碼經(jīng)過我的es6改寫后只有40行,沒有依賴第三方庫(kù),實(shí)現(xiàn)的功能卻是比較強(qiáng)大的,而且可以根據(jù)實(shí)際情況方便的進(jìn)行擴(kuò)展。

快速上手

在研究源碼之前,先看一下最普遍的使用場(chǎng)景。

const Emitter = require("./emitter")

let emitter = new Emitter()

// on 一個(gè)事件

let sayHello = name => console.log(`hello, ${name}`)
emitter.on("helloName", sayHello)
// emit 一個(gè)事件

// emitter.emit("helloName", "dongzhe")

// on一個(gè)帶有作用域的同一個(gè)事件
let obj = {
    prefix: "smith",
    thankName (name) {
        console.log(`hello, ${this.prefix}.${name}`)
        return `hello, ${this.prefix}.${name}`
    }
}

emitter.on("helloName", obj.thankName, obj)
emitter.emit("helloName", "dongzhe")

// new other emitter 可以在這里分組 不同的組可以有同樣的eventName
let emitter1 = new Emitter()

let sayHaHa = name => console.log(`haha, ${name}`)
emitter1.on("helloName", sayHaHa)
// emit 一個(gè)事件

emitter1.emit("helloName", "dongzhe")

可以看出,每一個(gè)事件管理器都是一個(gè)對(duì)象,可以根據(jù)不同的業(yè)務(wù)場(chǎng)景模塊創(chuàng)建不同的事件管理器,事件管理器最基本功能就是動(dòng)態(tài)的訂閱事件和派發(fā)事件,當(dāng)然還可以取消事件。用于在同一主模塊下的不同子模塊以及不同主模塊之間的通信,支持動(dòng)態(tài)綁定作用域。如果用過vue的父子組件事件通信以及eventBus,對(duì)事件管理器應(yīng)該不會(huì)陌生的。

源碼實(shí)現(xiàn)

事件管理模型主要由4個(gè)函數(shù)構(gòu)成,

on 用于訂閱事件,一個(gè)事件訂閱多個(gè)觸發(fā)函數(shù)

emit 用于發(fā)布事件,發(fā)布時(shí)會(huì)以此觸發(fā)事件訂閱的函數(shù)

once 訂閱的事件只觸發(fā)一次

off 取消訂閱事件,支持指定取消,批量取消和全部取消

代碼結(jié)構(gòu)

class E {
    constructor () {
        this.eventObj = {}
    }
    on () {}
    once () {}
    emit () {}
    off () {}
}

module.exports = E

Emitter對(duì)象存在一個(gè)事件對(duì)象,以鍵值對(duì)的形式保存事件名稱和對(duì)應(yīng)的觸發(fā)事件。

訂閱事件 on

訂閱事件就是把要觸發(fā)的函數(shù)放到事件對(duì)應(yīng)的對(duì)象里面,如果事件不存在,需要初始化一下即可。一個(gè)事件可以動(dòng)態(tài)的訂閱多個(gè)觸發(fā)函數(shù)。而且支持指定作用域,可以遠(yuǎn)程調(diào)用任意模塊的函數(shù)。

on (eventName, callback, ctx) {
    // 一個(gè)eventName可以綁定多個(gè)事件
    (this.eventObj[eventName] || (this.eventObj[eventName] = [])).push({callback, ctx})
    return this
}
發(fā)布事件 emit

相對(duì)訂閱事件的就是發(fā)布事件,發(fā)布事件接收事件的事件名和觸發(fā)函數(shù)的參數(shù),將對(duì)應(yīng)事件訂閱的觸發(fā)函數(shù)依次執(zhí)行即可,參數(shù)可以使用es6rest操作符。

emit (eventName, ...args) {
    let eventArr = (this.eventObj[eventName] || []).slice()
    eventArr.forEach(ele => ele.callback.call(ele.ctx, args))
    return this
}
取消事件 off

相對(duì)訂閱事件,也應(yīng)該可以取消事件,取消事件可以有多種選擇,可以指定取消事件訂閱的某一個(gè)或者多個(gè)觸發(fā)函數(shù),也可以直接將整個(gè)事件都取消掉。取消事件接收取消的事件名稱,和一個(gè)可選的函數(shù)對(duì)象或者函數(shù)對(duì)象數(shù)組(我自己增加的),如果傳入了指定的觸發(fā)函數(shù)對(duì)象,通過遍歷所有觸發(fā)的函數(shù)來過濾掉需要取消的觸發(fā)函數(shù),最后重新賦值即可。如果沒有傳觸發(fā)函數(shù),那么就認(rèn)為取消整個(gè)訂閱的事件,直接從全局的事件對(duì)象中刪除訂閱對(duì)象即可

off (eventName, callback) {
    if (Object.prototype.toString.call(callback) === "[object Array]") {
        callback.forEach(func => this.off(eventName, func))
        return this
    } 
    let liveEvents = []
    let obj = this.eventObj
    let eventArr = obj[eventName]
    // 如果沒有callback 就刪除掉整個(gè)eventName對(duì)象
    if (eventArr && callback) {
        liveEvents = eventArr.filter(ele => (ele.callback !== callback && ele.callback._ !== callback))
    }
    (liveEvents.length) ? obj[eventName] = liveEvents : delete obj[eventName]
    return this
}

其中最主要的就是下面這一行代碼了,使用filter過濾掉需要取消的觸發(fā)函數(shù),ele.callback._ !== callback是為了兼容once后面馬上就說到。

liveEvents = eventArr.filter(ele => (ele.callback !== callback && ele.callback._ !== callback))
一次觸發(fā) once

有的時(shí)候我們只需要觸發(fā)一次訂閱的事件,比如用戶剛登錄進(jìn)來獲取歷史消息或者通知消息,觸發(fā)一次后就不需要了,所以有了once函數(shù),once函數(shù)主要的工作原理就是,在函數(shù)內(nèi)部添加一個(gè)代理函數(shù)listener代理函數(shù)用來為觸發(fā)函數(shù)做代理,做代理的目的是為了添加邏輯,這個(gè)邏輯就是在觸發(fā)函數(shù)第一次執(zhí)行的時(shí)候,就自動(dòng)執(zhí)行off函數(shù),用來取消觸發(fā)函數(shù)的邏輯。

let listener = (...args) => {
    this.off(eventName, listener)
    callback.apply(ctx, args)
}
// 因?yàn)閘istener是在callback上封裝了一層 所以要規(guī)定一個(gè)可以找到callbak的規(guī)則
listener._ = callback

因?yàn)?b>listener是在callback上封裝了一層代理 所以要規(guī)定一個(gè)可以找到callback的規(guī)則,這樣off函數(shù)在傳入取消函數(shù)的時(shí)候,我們可以順利的用兼容的方式找到。
最后其實(shí)訂閱的是這個(gè)代理函數(shù)listener

once (eventName, callback, ctx) {
    let listener = (...args) => {
        this.off(eventName, listener)
        callback.apply(ctx, args)
    }
    // 因?yàn)閘istener是在callback上封裝了一層 所以要規(guī)定一個(gè)可以找到callbak的規(guī)則
    listener._ = callback
    return this.on(eventName, listener, ctx)
}
完整代碼

我自己在原來的代碼基礎(chǔ)上用es6重新編寫,并添加了一些邏輯,可以對(duì)比原來的代碼來看,最后完整的代碼如下

class E {
    constructor () {
        this.eventObj = {}
    }
    on (eventName, callback, ctx) {
        // 一個(gè)eventName可以綁定多個(gè)事件
        (this.eventObj[eventName] || (this.eventObj[eventName] = [])).push({callback, ctx})
        return this
    }
    once (eventName, callback, ctx) {
        let listener = (...args) => {
            this.off(eventName, listener)
            callback.apply(ctx, args)
        }
        // 因?yàn)閘istener是在callback上封裝了一層 所以要規(guī)定一個(gè)可以找到callbak的規(guī)則
        listener._ = callback
        return this.on(eventName, listener, ctx)
    }
    emit (eventName, ...args) {
        let eventArr = (this.eventObj[eventName] || []).slice()
        eventArr.forEach(ele => ele.callback.call(ele.ctx, args))
        return this
    }
    off (eventName, callback) {
        if (Object.prototype.toString.call(callback) === "[object Array]") {
            callback.forEach(func => this.off(eventName, func))
            return this
        } 
        let liveEvents = []
        let obj = this.eventObj
        let eventArr = obj[eventName]
        // 如果沒有callback 就刪除掉整個(gè)eventName對(duì)象
        if (eventArr && callback) {
            liveEvents = eventArr.filter(ele => (ele.callback !== callback && ele.callback._ !== callback))
        }
        (liveEvents.length) ? obj[eventName] = liveEvents : delete obj[eventName]
        return this
    }
}

module.exports = E
結(jié)語(yǔ)

這只是一個(gè)比較簡(jiǎn)單的事件訂閱發(fā)布器,但包含的核心思想還是比較完整的,用到了面向?qū)ο螅嗛啺l(fā)布者模式,代理模式等,而且可以根據(jù)自己的需求進(jìn)行很方便的擴(kuò)展,比如我擴(kuò)展的批量取消,也可以添加批量訂閱,甚至使用promise來封裝異步觸發(fā),每一個(gè)函數(shù)都返回了對(duì)象本身,可以完成鏈?zhǔn)秸{(diào)用,比如訂閱完成后立刻觸發(fā)完成初始化等等。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/93133.html

相關(guān)文章

  • clipboard.js代碼分析(3)- good-listener

    摘要:上一篇文章介紹了這個(gè)工具庫(kù)中的第二個(gè)依賴,這個(gè)工具庫(kù)主要完成了一個(gè)簡(jiǎn)易的事件訂閱發(fā)布器。節(jié)點(diǎn)事件綁定判斷一個(gè)元素是否是節(jié)點(diǎn),是通過構(gòu)造函數(shù)和屬性來判斷的。 上一篇文章介紹了clipboard.js這個(gè)工具庫(kù)中的第二個(gè)依賴tiny-emitter,這個(gè)工具庫(kù)主要完成了一個(gè)簡(jiǎn)易的事件訂閱發(fā)布器。這次介紹一下clipboard.js源碼中的最后一個(gè)依賴的輕型工具庫(kù)good-listener,...

    objc94 評(píng)論0 收藏0
  • quill深入淺出

    摘要:與的關(guān)系既是表達(dá)文檔,又表達(dá)文檔修改。然后會(huì)監(jiān)聽事件,然后觸發(fā)的方法,傳入?yún)?shù),然后在的方法中,會(huì)依據(jù)構(gòu)建出對(duì)應(yīng)的數(shù)組,與已有的合并,使當(dāng)前保持最新。 背景分析/技術(shù)選型 quillAPI驅(qū)動(dòng)設(shè)計(jì),自定義內(nèi)容和格式化,跨平臺(tái),易用. CKEditor功能強(qiáng),配置靈活,ui漂亮,兼容性差 TinyMCE文檔好,功能強(qiáng),bug少,無外部依賴。 UEditor功能齊全,但是不維護(hù)了,依賴j...

    hlcfan 評(píng)論0 收藏0
  • clipboard.js代碼分析(1)-select

    摘要:下面對(duì)它的實(shí)現(xiàn)一一分析??梢允褂毛@取選中的內(nèi)容也可以使用獲取一個(gè)用戶選擇的范圍。在這里完成了對(duì)用戶選中內(nèi)容的一些操作,而且在不是表單無法觸發(fā)事件的時(shí)候,也可以在指定區(qū)域監(jiān)聽事件來實(shí)時(shí)獲取選中的內(nèi)容完成復(fù)制等功能。 項(xiàng)目中用到了選中復(fù)制功能 showImg(https://segmentfault.com/img/bVY7dH?w=400&h=78); 就是點(diǎn)擊按鈕,復(fù)制左側(cè)的內(nèi)容到剪切...

    li21 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<