成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

擼一個JS的事件管理模塊

harryhappy / 2843人閱讀

摘要:列舉一個生活中的例子來幫助大家理解這一種模式。例子中的小明就是訂閱者訂閱的是飯涼了,而媽媽則是發(fā)布者將信號飯涼了發(fā)布出去。這樣就不用把小明和媽媽強耦合在一起,當(dāng)小明的弟弟妹妹都想在飯涼了在吃飯,只需告訴媽媽一聲。

關(guān)于事件
在我們使用javascript開發(fā)時,我們會經(jīng)常用到很多事件,如點擊、鍵盤、鼠標(biāo)等等,這些物理性的事件。而我們今天所說的我稱之為事件的,是另一種形式的事件,訂閱---發(fā)布,又叫做觀察者模式,他定義了一對多的依賴關(guān)系,當(dāng)一個對象狀態(tài)發(fā)生改變時,所有依賴于它的對象都會收到通知,而在javascript中,一般習(xí)慣性的用事件模型來替代發(fā)布---訂閱模式。

列舉一個生活中的例子來幫助大家理解這一種模式。炎熱的夏天,媽媽燒好了飯盛上桌,冒著熱氣,這時媽媽喊小明吃飯(小明在旁邊的屋子里餓著肚子大吉大利晚上吃雞...),小明出來一看,跟媽媽說,等一會 ‘飯涼了’ 再叫我,太燙了...十分鐘后...媽媽喊你 ‘飯涼了’,快來吃飯,而這時小明聽到了媽媽的喊話說 ‘飯涼了’,便快速的出來吃完了。這個例子,就是以上介紹的訂閱---發(fā)布模式。例子中的小明就是訂閱者(訂閱的是 ‘飯涼了’),而媽媽則是發(fā)布者(將信號 ‘飯涼了’ 發(fā)布出去)。

使用訂閱---發(fā)布模式的有著顯而易見的優(yōu)點:訂閱者不用每時每刻都詢問發(fā)布者飯是否涼了,在合適的事件點,發(fā)布者會通知這些訂閱者,告訴他們飯涼了,他們可以過來吃了。這樣就不用把小明和媽媽強耦合在一起,當(dāng)小明的弟弟妹妹都想在飯涼了在吃飯,只需告訴媽媽一聲。就像每個看官肯定都接觸過的一種訂閱---發(fā)布:DOM事件的綁定

document.body.addEventListener("click", function (e) {
     console.log("我執(zhí)行了...")
}, false)
回歸正題:
*event-mange 通過訂閱-發(fā)布模式實現(xiàn)的*
一步一步的實現(xiàn)

event-mange 模塊的主要方法

on:訂閱者,添加事件

emit:發(fā)布者, 出發(fā)事件

once: 訂閱者,添加只能監(jiān)聽一次之后就失效的事件

removeListener:刪除單個訂閱(事件)

removeAllListener: 刪除單個事件類型的訂閱或刪除全部訂閱

getListenerCount:獲得訂閱者的數(shù)量

event-mange 模塊的主要屬性

MaxEventListNum: 設(shè)置單個事件最多訂閱者數(shù)量(默認(rèn)為10)

基本骨架

首先,我們希望通過 event.on , event.emit 來訂閱和發(fā)布,通過構(gòu)造函數(shù)來創(chuàng)建一個event實例,而on,emit分別為這個實例的兩個方法, 同樣的,以上列出的所有主要方法,都是event的對象的原型方法。

function events () {};

// 列舉去我們想要實現(xiàn)的event對象的方法

event.prototype.on = function () {};

event.prototype.emit = function () {};

event.prototype.once = function () {};

event.prototype.removeListener = function () {};

event.prototype.removeAllListener = function () {};

event.prototype.getListenerCount = function () {};

似乎丟了什么,沒錯,是event對象我們上面列出來的MaxEventListNum屬性,我們給他補上

function event () {
    //因為MaxEventListNum屬性是可以讓開發(fā)者設(shè)置的
    //所以在沒有set的時候,我們將其設(shè)置為 undefind
    this.MaxEventListNum = this.MaxEventListNum || undefined;

    //如果沒有設(shè)置set,我們不能讓監(jiān)聽數(shù)量無限大
    //這樣有可能會造成內(nèi)存溢出
    //所以我們將默認(rèn)數(shù)量設(shè)置為10(當(dāng)然,設(shè)置成別的數(shù)量也是可以的)
    this.defaultMaxEventListNum = 10;
}

到這里,基本上我們想實現(xiàn)的時間管理模塊屬性和方法的初態(tài)也就差不多了,也就是說,骨架出來了,我們就需要填飽他的代碼邏輯,讓他變的有血有肉(看似像個生命...)

值得思考的是,骨架我們構(gòu)建完了,我們要做的是一個訂閱--發(fā)布模式,我們應(yīng)該怎么去記住眾多的訂閱事件呢? 首先,對于一個訂閱,我們需要有一個訂閱的類型,也就是topic,針對此topic我們要把所有的訂閱此topic的事件都放在一起,對,可以選擇Array,初步的構(gòu)造

event_list: {
    topic1: [fn1, fn2, fn3 ...]
    ...
}

那么接下來我們將存放我們事件的event_list放入代碼中完善,作為event的屬性

function event () {
    // 這里我們做一個簡單的判斷,以免一些意外的錯誤出現(xiàn)
    if(!this.event_list) {
        this.event_list = {};
    }

    this.MaxEventListNum = this.MaxEventListNum || undefined;
    this.defaultMaxEventListNum = 10;
}
on 方法實現(xiàn)
event.prototype.on = function () {};

通過分析得出on方法首先應(yīng)該接收一個訂閱的topic,其次是一個當(dāng)此topic響應(yīng)后觸發(fā)的callback方法

event.prototype.on = function (eventName, content) {};

eventName作為事件類型,將其作為event_list的一個屬性,所有的事件類型為eventName的監(jiān)聽都push到eventName這個數(shù)組里面。

event.prototype.on = function (eventName, content) {
    ...
    var _event, ctx;
    _event = this.event_list;
    // 再次判斷event_list是否存在,不存在則重新賦值
    if (!_event) {
      _event = this.event_list = {};
    } else {
      // 獲取當(dāng)前eventName的監(jiān)聽
      ctx = this.event_list[eventName];
    }
    // 判斷是否有此監(jiān)聽類型
    // 如果不存在,則表示此事件第一次被監(jiān)聽
    // 將回調(diào)函數(shù) content 直接賦值
    if (!ctx) {
      ctx = this.event_list[eventName] = content;
      // 改變訂閱者數(shù)量
      ctx.ListenerCount = 1;
    } else if (isFunction(ctx)) {
      // 判斷此屬性是否為函數(shù)(是函數(shù)則表示已經(jīng)有且只有一個訂閱者)
      // 將此eventName類型由函數(shù)轉(zhuǎn)變?yōu)閿?shù)組
      ctx = this.event_list[eventName] = [ctx, content];
      // 此時訂閱者數(shù)量變?yōu)閿?shù)組長度
      ctx.ListenerCount = ctx.length;
    } else if (isArray(ctx)) {
      // 判斷是否為數(shù)組,如果是數(shù)組則直接push
      ctx.push(content);
      ctx.ListenerCount = ctx.length;
    }
    ...
};
once 方法實現(xiàn)
event.prototype.once = function () {};

once方法對已訂閱事件只執(zhí)行一次,需執(zhí)行完后立即在event_list中相應(yīng)的訂閱類型屬性中刪除該訂閱的回調(diào)函數(shù),其存儲過程與on方法幾乎一致,同樣需要一個訂閱類型的topic,以及一個響應(yīng)事件的回調(diào) content

event.prototype.once = function (eventName, content) {};

在執(zhí)行完本次事件回調(diào)后立即取消注冊此訂閱,而如果此時同一類型的事件注冊了多個監(jiān)聽回調(diào),我們無法準(zhǔn)確的刪除當(dāng)前once方法所注冊的監(jiān)聽回調(diào),所以通常我們采用的遍歷事件監(jiān)聽隊列,找到相應(yīng)的監(jiān)聽回調(diào)然后將其刪除是行不通的。還好,偉大的javascript語言為我們提供了一個強大的閉包特性,通過閉包的方式來裝飾content,包裝成一個全新的函數(shù)。

events.prototype.once = function (event, content) {
    ...
    // once和on的存儲事件回調(diào)機制相同
    // dealOnce 函數(shù) 包裝函數(shù)
    this.on(event, dealOnce(this, event, content));
    ...
  }

// 包裝函數(shù)
function dealOnce(target, type, content) {
    var flag = false;
    // 通過閉包特性(會將函數(shù)外部引用保存在作用域中)
    function packageFun() {
      // 當(dāng)此監(jiān)聽回調(diào)被調(diào)用時,會先刪除此回調(diào)方法
      this.removeListener(type, packageFun);
      if (!flag) {
        flag = true;
        // 因為閉包,所以原監(jiān)聽回調(diào)還會保留,所以還會執(zhí)行
        content.apply(target, arguments);
      }
      packageFun.content = content;
    }
    return packageFun;
  }

once的實現(xiàn)其實將我們自己傳遞的回調(diào)函數(shù)做了二次封裝,再綁定上封裝后的函數(shù),封裝的函數(shù)首先執(zhí)行了removeListener()移除了回調(diào)函數(shù)與事件的綁定,然后才執(zhí)行的回調(diào)函數(shù)

emit 方法實現(xiàn)
event.prototype.emit = function () {};

emit方法用來發(fā)布事件,驅(qū)動執(zhí)行相應(yīng)的事件監(jiān)聽隊列中的監(jiān)聽回調(diào),故我們需要一個事件type的topic

event.prototype.emit = function (eventName[,message][,message1][,...]) {};

當(dāng)然,發(fā)布事件是,也可以像該事件監(jiān)聽者傳遞參數(shù),數(shù)量不限,則會依次傳遞給所有的監(jiān)聽回調(diào)

event.prototype.emit = function (eventName[,message]) {
    var _event, ctx;
    //除第一個參數(shù)eventNmae外,其他參數(shù)保存在一個數(shù)組里
    var args = Array.prototype.slice.call(arguments, 1);
    _event = this.event_list;
    // 檢測存儲事件隊列是否存在
    if (_event) {
      // 如果存在,得到此監(jiān)聽類型
      ctx = this.event_list[eventName];
    }
    // 檢測此監(jiān)聽類型的事件隊列
    // 不存在則直接返回
    if (!ctx) {
      return false;
    } else if (isFunction(ctx)) {
      // 是番薯則直接執(zhí)行,并將所有參數(shù)傳遞給此函數(shù)(回調(diào)函數(shù))
      ctx.apply(this, args);
    } else if (isArray(ctx)) {
      // 是數(shù)組則遍歷調(diào)用
      for (var i = 0; i < ctx.length; i++) {
        ctx[i].apply(this, args);
      }
    }
};

emit從理解程度上來說應(yīng)該是更容易一些,只是從存儲事件的對象中找到相應(yīng)類型的監(jiān)聽事件隊列,然后執(zhí)行隊列中的每一個回調(diào)

removeListener 方法實現(xiàn)
event.prototype.removeListener = function () {};

刪除某種監(jiān)聽類型的某一個監(jiān)聽回調(diào),顯然,我們?nèi)匀恍枰粋€事件type,以及一個監(jiān)聽回調(diào),當(dāng)事件對列中的回調(diào)與該回調(diào)相同時,則移除

event.prototype.removeListener = function (eventName, content) {};

需要注意的是,如果我們確實存在要移除某個監(jiān)聽事件的回調(diào),在on方法時一定不要使用匿名函數(shù)作為回調(diào),這樣會導(dǎo)致在removeListener是無法移除,因為在javascript中匿名函數(shù)是不相等的。

// 如果需要移除

// 錯誤
event.on("eatting", function (msg) {

});

// 正確
event.on("eatting", cb);
// 回調(diào)
function cb (msg) {
    ...
}
event.prototype.removeListener = function (eventName, content) {
    var _event, ctx, index = 0;
    _event = this.event_list;
    if (!_event) {
      return this;
    } else {
      ctx = this.event_list[eventName];
    }
    if (!ctx) {
      return this;
    }
    // 如果是函數(shù)  直接delete
    if (isFunction(ctx)) {
      if (ctx === content) {
        delete _event[eventName];
      }
    } else if (isArray(ctx)) {
      // 如果是數(shù)組 遍歷
      for (var i = 0; i < ctx.length; i++) {
        if (ctx[i] === content) {
          // 監(jiān)聽回調(diào)相等
          // 從該監(jiān)聽回調(diào)的index開始,后面的回調(diào)依次覆蓋掉前面的回調(diào)
          // 將最后的回調(diào)刪除
          // 等價于直接將滿足條件的監(jiān)聽回調(diào)刪除
          this.event_list[eventName].splice(i - index, 1);
          ctx.ListenerCount = ctx.length;
          if (this.event_list[eventName].length === 0) {
            delete this.event_list[eventName]
          }
          index++;
        }
      }
    }
};
removeAllListener 方法實現(xiàn)
event.prototype.removeAllListener = function () {};

此方法有兩個用途,即實現(xiàn)當(dāng)有參數(shù)事件類型eventName時,則刪除該類型的所有監(jiān)聽(清空此事件的監(jiān)聽回調(diào)隊列),當(dāng)沒有參數(shù)時,則將所有類型的事件監(jiān)聽對壘全部移除,還是比較好理解的直接上代碼

event.prototype.removeAllListener = function ([,eventName]) {
    var _event, ctx;
    _event = this.event_list;
    if (!_event) {
      return this;
    }
    ctx = this.event_list[eventName];
    // 判斷是否有參數(shù)
    if (arguments.length === 0 && (!eventName)) {
      // 無參數(shù)
      // 將key 轉(zhuǎn)成 數(shù)組  并遍歷
      // 依次刪除所有的類型監(jiān)聽
      var keys = Object.keys(this.event_list);
      for (var i = 0, key; i < keys.length; i++) {
        key = keys[i];
        delete this.event_list[key];
      }
    }
    // 有參數(shù) 直接移除
    if (ctx || isFunction(ctx) || isArray(ctx)) {
      delete this.event_list[eventName];
    } else {
      return this;
    }
};

其主要實現(xiàn)思路大致如上所述,貌似還漏了一些什么,哦,是對于是否超過艦艇數(shù)量的最大限制的處理
在on方法中

...
// 檢測回調(diào)隊列是否有maxed屬性以及是否為false
if (!ctx.maxed) {
      //只有在是數(shù)組的情況下才會做比較
      if (isArray(ctx)) {
        var len = ctx.length;
        if (len > (this.MaxEventListNum ? this.MaxEventListNum : this.defaultMaxEventListNum)) { 
        // 當(dāng)超過最大限制,則會發(fā)除警告
          ctx.maxed = true;
          console.warn("events.MaxEventListNum || [ MaxEventListNum ] :The number of subscriptions exceeds the maximum, and if you do not set it, the default value is 10");
        } else {
          ctx.maxed = false;
        }
      }
    }

...

現(xiàn)在Vue可謂是紅的發(fā)紫,沒關(guān)系,events-manage也可以在Vue中掛在到全局使用哦

events.prototype.install = function (Vue, Option) {
    Vue.prototype.$ev = this;
  }

不用多解釋了吧,想必看官都明白應(yīng)該怎么使用了吧(在Vue中)

關(guān)于本庫更具體更詳細(xì)的使用文檔,趕緊戳這里

碼字不易啊,如果覺得對您有一些幫助,還請給一個大大的贊

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/96402.html

相關(guān)文章

  • 手把手教你一個網(wǎng)頁聊天室

    摘要:前端邏輯搞定之后,思考一下這個聊天室的交互是怎么實現(xiàn)的。在前端監(jiān)聽一個事件,這個事件的觸發(fā)條件是成功和服務(wù)端建立連接。攜帶一個參數(shù),即用戶的輸入。別人發(fā)送的消息現(xiàn)在就需要在前端建立一個響應(yīng)服務(wù)端有新消息的監(jiān)聽事件了。 一些廢話:) 最近在學(xué)校比較閑,終于有這么一塊時間可以自由支配了,所以內(nèi)心還是十分的酸爽舒暢的。當(dāng)然了,罪惡的事情也是有的,比如已經(jīng)連續(xù)一周沒有吃早飯了,其實現(xiàn)在回頭想想...

    nemo 評論0 收藏0
  • 手把手教你一個網(wǎng)頁聊天室

    摘要:前端邏輯搞定之后,思考一下這個聊天室的交互是怎么實現(xiàn)的。在前端監(jiān)聽一個事件,這個事件的觸發(fā)條件是成功和服務(wù)端建立連接。攜帶一個參數(shù),即用戶的輸入。別人發(fā)送的消息現(xiàn)在就需要在前端建立一個響應(yīng)服務(wù)端有新消息的監(jiān)聽事件了。 一些廢話:) 最近在學(xué)校比較閑,終于有這么一塊時間可以自由支配了,所以內(nèi)心還是十分的酸爽舒暢的。當(dāng)然了,罪惡的事情也是有的,比如已經(jīng)連續(xù)一周沒有吃早飯了,其實現(xiàn)在回頭想想...

    leiyi 評論0 收藏0
  • 手摸手,帶你用vue后臺 系列一(基礎(chǔ)篇)

    摘要:詳細(xì)具體的使用可以見文章手摸手,帶你優(yōu)雅的使用。為了加速線上鏡像構(gòu)建的速度,我們利用源進(jìn)行加速并且將一些常見的依賴打入了基礎(chǔ)鏡像,避免每次都需要重新下載。 完整項目地址:vue-element-admin系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺 系列四(vueAdmin 一...

    xiaotianyi 評論0 收藏0
  • 手摸手,帶你用vue后臺 系列三(實戰(zhàn)篇)

    摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開源項目了,體現(xiàn)出了社區(qū)對其的認(rèn)可。監(jiān)聽事件手動維護(hù)列表這樣我們就簡單的完成了拖拽排序。 完整項目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺 系列...

    Channe 評論0 收藏0
  • 手摸手,帶你用vue后臺 系列三(實戰(zhàn)篇)

    摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開源項目了,體現(xiàn)出了社區(qū)對其的認(rèn)可。監(jiān)聽事件手動維護(hù)列表這樣我們就簡單的完成了拖拽排序。 完整項目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺 系列...

    zgbgx 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<