一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

3.自定義事件

 行者花雕 2021-09-12

上一篇提到了模塊化的思想,并且引入了按需加載模塊的技術(shù)。然而對(duì)于實(shí)現(xiàn)細(xì)節(jié)卻沒(méi)有講解,因?yàn)榘葱杓虞d有點(diǎn)復(fù)雜,如果不引入觀察者模式的設(shè)計(jì)思想,就會(huì)比較難實(shí)現(xiàn)。

使用觀察者模式,我們實(shí)現(xiàn)dom事件類似的注冊(cè)觸發(fā),這種方式可以很好的解耦,讓模塊間沒(méi)有依賴。我們先討論一下觀察者模式是怎么回事,我們先引入幾個(gè)場(chǎng)景:

  • 如果兩個(gè)模塊之間交互,通常我們用a變量和b變量引用,如果a更改了b的屬性,可以通過(guò)代碼b.property = someValue; 如果a要更改b的行為,那么可以通過(guò)b.doSomething(); 更改屬性還好,因?yàn)閎是個(gè)對(duì)象,更改屬性不會(huì)報(bào)錯(cuò)。如果觸發(fā)方法,b沒(méi)有dosomething的方法,那么代碼就會(huì)報(bào)錯(cuò)。這個(gè)場(chǎng)景只有在a和b完全知道對(duì)方的時(shí)候可以使用,比如上一個(gè)例子,名為static的Page對(duì)象更改顯示頁(yè)面的方法(currentPage),因?yàn)闊o(wú)法知道currentPage是什么,這種場(chǎng)景不適合直接使用;

  • 如果一個(gè)模塊發(fā)生了變動(dòng),它要讓另一個(gè)模塊做一系列的事情,這種場(chǎng)景比上一個(gè)場(chǎng)景更加嚴(yán)苛,更加不適合直接使用;

  • 如果一個(gè)模塊發(fā)生了變動(dòng),讓另一個(gè)模塊更改事情,同時(shí)另一個(gè)模塊要求當(dāng)前模塊發(fā)生交互??赡茈p方需要交互一系列操作,這種場(chǎng)景很明顯也是無(wú)法做到的。

我們聯(lián)想一下dom事件,當(dāng)dom被點(diǎn)擊的時(shí)候,如果監(jiān)聽(tīng)了點(diǎn)擊事件,那就觸發(fā)了點(diǎn)擊的回調(diào)方法。如果沒(méi)有注冊(cè),啥事情也不會(huì)發(fā)生。這時(shí)我們假設(shè)兩個(gè)模塊注冊(cè)事件和觸發(fā)事件,試一下來(lái)解決上述的場(chǎng)景問(wèn)題:

  • 如果模塊a需要模塊b發(fā)生變化,那么在a中調(diào)用b的觸發(fā)事件,b.dispatchEvent(eventname, data); data是傳遞的數(shù)據(jù)如果b監(jiān)聽(tīng)了b.addEventListener(eventName, fn); 那么會(huì)調(diào)用fn的回調(diào)方法,因?yàn)閒n是在b的作用域定義的,它可以訪問(wèn)在b模塊下的私有變量,如果b沒(méi)有監(jiān)聽(tīng)eventName事件,那么什么都不會(huì)發(fā)生;

  • 如果b監(jiān)聽(tīng)了若干個(gè)b.addEventListener(eventName, fn1); b.addEventListener(eventName, fn2); ...
    那么當(dāng)a中調(diào)用b.dispatchEvent的時(shí)候,就會(huì)觸發(fā)b的一系列方法;

  • 在監(jiān)聽(tīng)的回調(diào)函數(shù)中,可以調(diào)用a.dispatchEvent方法。同理,a中的也可以調(diào)用b中的回調(diào)方法。

通過(guò)以上談?wù)?,如果使用事件的方式是可以完美的解決之前的幾個(gè)場(chǎng)景問(wèn)題。

需求

開發(fā)一個(gè)觀察者模式的對(duì)象,該對(duì)象有以下特點(diǎn):

  1. 可以注冊(cè)事件,將名稱和回調(diào)方法們管理起來(lái),有時(shí)候回調(diào)方法只觸發(fā)一次也很常用;

  2. 可以觸發(fā)事件,針對(duì)名稱以及一個(gè)對(duì)象,讓回調(diào)方法們依次觸發(fā);

  3. 可以移除某個(gè)回調(diào)方法,也可以針對(duì)名稱移除該名稱對(duì)應(yīng)的所有回調(diào)方法;

  4. 可以銷毀對(duì)象的destroy方法。

實(shí)現(xiàn)

代碼如下

function EventDispatcher() {
    this.listeners = {};
}
EventDispatcher.prototype = {
    constructor: EventDispatcher,
    // 訂閱自定義事件
    addEventListener: function (type, listener) {
        this._addEventListener(type, listener);
    },
    // 訂閱某個(gè)類型的事件
    _addEventListener: function (type, listener, isOnce) {
        var listeners = this.listeners;
        if (typeof listeners[type] == "undefined") {
            listeners[type] = [];
        }
        if (isOnce) listener.once = true;
        listeners[type].push(listener);
    },
    // 注冊(cè)一次綁定事件,觸發(fā)后自動(dòng)清除
    addOnce: function (type, listener) {
        this._addEventListener(type, listener, true);
    },
    // 移除某個(gè)事件類型的訂閱事件
    removeEventListener: function (type, listener) {
        var listeners = this.listeners;
        var handlers = listeners[type];
        if (Array.isArray(handlers)) {
            var i = handlers.indexOf(listener);
            handlers.splice(i, 1);
        }
    },
    //  根據(jù)名稱移除所有的回調(diào)函數(shù)
    clearListenerByType: function (type) {
        var handlers = this.listeners[type];
        if (Array.isArray(handlers)) handlers.length = 0;
    },
    // 觸發(fā)某個(gè)事件類型
    dispatchEvent: function (event) {
        var listeners = this.listeners;
        if (!event.target) {
            event.target = this;
        }
        var handlers = listeners[event.type];
        // 如果有事件注冊(cè)
        if (Array.isArray(handlers)) {
            var onceIndexs = [];
            for (var i = 0, len = handlers.length; i < len; i++) {
                handlers[i](event);
                if (handlers[i].once) {
                    onceIndexs.push(i);
                }
            }
            // 移除一次觸發(fā)就銷毀的回調(diào)方法
            if (onceIndexs.length > 0) {
                for (var i = onceIndexs.length - 1; i >= 0; i--) {
                    handlers.splice(onceIndexs[i], 1);
                }
            }
        }
    },
    // 銷毀自定義事件
    destroy: function () {
        for (var str in this.listeners) {
            this.listeners[str].length = 0;
            delete this.listeners[str];
        }
    }
};

可以通過(guò)繼承或者組合方式來(lái)使用這個(gè)對(duì)象

var a = {
    eventDispatcher: new EventDispatcher(),
    attachDiyEvent: function (type, fn) { 
        this.eventDispatcher.addEventListener(type, fn);
    },
    dispatchEvent: function (data) {
        this.eventDispatcher.dispatchEvent(data)
    }
}

如果每個(gè)模塊都擁有EventDispatcher對(duì)象,就可以使用自定義事件進(jìn)行跨模塊交互了,上一篇說(shuō)的App對(duì)象,Page對(duì)象,以及后面的Component和PopUp對(duì)象,都是間接繼承了EventDispatcher對(duì)象。

實(shí)現(xiàn)按需加載

實(shí)現(xiàn)方法 App.require(list, bk),App.define(name, obj), 思路如下:

  1. require方法第一個(gè)參數(shù)中的name必須要在該name配置項(xiàng)的js中,有對(duì)應(yīng)的App.define(name, obj),
    就是說(shuō) App.require(["strTool"], function (strTool) {});
    存在配置項(xiàng) { strTool: { js: "/public/services/strTool.js "}}
    在/public/services/strTool.js中必須要有定義 App.define("strTool", {});
    App.require代表數(shù)組全部被require后,才會(huì)執(zhí)行回調(diào)方法, App.define代表某一個(gè)模塊已經(jīng)被加載了;

  2. 如果數(shù)組中加載的模塊都沒(méi)有引用別的模塊,等模塊全部加載完,執(zhí)行回調(diào)是沒(méi)有問(wèn)題的。如果引入的模塊還引用了其它模塊,比如App.require(["m1", "m2"], function (m1, m2) {}); m2中還引用了其它模塊。 因?yàn)閙2的定義是在回調(diào)函數(shù)中定義的,所以當(dāng)m2的其它模塊全部引入后,才會(huì)觸發(fā)m2的引用完成,因此這個(gè)場(chǎng)景也是成立的。同理,只要不存在循環(huán)引用,或自引用, 就不會(huì)出現(xiàn)加載不出來(lái)的問(wèn)題;

  3. 因?yàn)橐檬钱惒降模嬖谝粋€(gè)文件同時(shí)被多個(gè)文件同時(shí)引用的情況,為了避免js被多次加載,需要做一下判斷,可以用上面介紹的觀察者模式。

實(shí)現(xiàn)App.require方法

App.require = function (list, bk) {
    if (typeof list === "function") return list.call(this);
    var len = list.length, mods = [], that = this;
    if (len == 0) bk.call(this);
    var func = function (obj, index) {
        mods[index] = obj;
        if (--len === 0) bk.apply(that, mods);
    }

    list.forEach(function (item, index) {
        // 獲取對(duì)應(yīng)的name的js文件,內(nèi)由App.define(name, obj)方法
        var config = that._contains(item); 
        // 這個(gè)方法要定義自定義事件,在App.define觸發(fā)回調(diào),所有觸發(fā)后才會(huì)全部加載完畢
        loadUnit._getUnit({ name: item, js: config.js }, function (obj) {
            func(obj, index);
        })
    })
};

實(shí)現(xiàn)App.define方法

App.define = function (name, bk) {
    if (typeof bk === "function") loadUnit.units[name] = bk();
    else loadUnit.units[name] = bk;
    // 告訴模塊我已經(jīng)加載完了。
    loadUnit.dispatchEvent({ type: name, detail: { component: loadUnit.units[name] }})
}

通過(guò)繼承的方式,擁有自定義事件的所有方法,實(shí)現(xiàn)LoadUnit對(duì)象

function LoadUnit() {
    EventDispatcher.call(this);
    // 存已經(jīng)加載的模塊,如果模塊已加載,就不再加載js,直接返回結(jié)果
    this.units = {};
    // 存放某個(gè)模塊的加載狀態(tài),如果正在加載中,某個(gè)模塊名為true,否則為false或undefined
    this.loadObj = {};
}
LoadUnit.prototype = create(EventDispatcher.prototype, {
    constructor: LoadUnit,
    _getUnit: function (config, next) {
        var that = this;
        var name = config.name, url = config.js;
        // 如果已經(jīng)加載鍋,直接返回加載完畢
        if (this.units[name]) next(this.units[name], config);
        else {
            // 監(jiān)聽(tīng)觸發(fā)一次的事件,再第二點(diǎn)里面觸發(fā),觸發(fā)后才算加載完畢。
            this.addOnce(name, function (obj) {
                next(obj.detail.component, obj.detail.config || config);
            });
            // 如果不是加載狀態(tài),那就加載js,反之不加載
            if (!this.loadObj[name]) {
                this.loadObj[name] = true;
                var script = document.createElement("script"),
                    head = document.head;
                script.src = url;
                script.onload = function () {
                    that.loadObj[name] = false;
                }
                head.appendChild(script);
            }
        }
    }
};
// 創(chuàng)建一個(gè)loadUnit,來(lái)管理模塊化
var loadUnit = new LoadUnit();

后面的pageLoadUnit, componentLoadUnit, popUpLoadUnit都是LoadUnit的實(shí)例對(duì)象。

簡(jiǎn)單案例

為了更好的理解將上一章節(jié)的內(nèi)容代碼拷貝下來(lái),并作以下修改

  1. 修改static.js文件

     getDomObj: function () {
         this.attachDom(".page-container", "pageContainer")
             .attachDom("header", "header")
             .attachDiyEvent("changelayout", this.changeLayoutHandler)
             .attachDom("footer", "footer");
     },
     changeLayoutHandler: function (ev) {
         this.domList.header.textContent = ev.header;
         this.domList.footer.textContent = ev.footer;
     }
  2. 修改home.js, second.js文件

    與app.staticPage交互改為自定義事件交互

     app.staticPage.dispatchEvent("changelayout", {
         header: "次頁(yè)",
         footer: "從首頁(yè)跳轉(zhuǎn)到次頁(yè)"
     });
  3. 查看效果(主要針對(duì)移動(dòng)端,可以通過(guò)手機(jī)端查看或者用瀏覽器模擬移動(dòng)端)
    將代碼放在可以訪問(wèn)的服務(wù)器或本地服務(wù)上,啟動(dòng)服務(wù),通過(guò)瀏覽器訪問(wèn),訪問(wèn)地址

結(jié)語(yǔ)

模塊開發(fā)是當(dāng)前web開發(fā)中多人合作開發(fā)的主流方式,如何去管理模塊,以及有序的準(zhǔn)確的加載是非常重要的。自定義事件是框架的核心之一,熟悉原理并熟練使用會(huì)讓項(xiàng)目更容易維護(hù)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    99久久国产亚洲综合精品| 亚洲国产成人久久99精品| 正在播放玩弄漂亮少妇高潮| 日韩性生活视频免费在线观看 | 欧美日韩亚洲巨色人妻| 亚洲综合色在线视频香蕉视频| 久久精品久久久精品久久| 欧美午夜性刺激在线观看| 欧美人与动牲交a精品| 日韩精品视频香蕉视频| 天海翼高清二区三区在线| 美国欧洲日本韩国二本道| 国产精品日韩精品最新| 日韩精品一区二区亚洲| 91人妻久久精品一区二区三区| 高清一区二区三区大伊香蕉| 欧美日韩综合在线精品| 男女一进一出午夜视频| 久久精品国产在热亚洲| 成人精品一级特黄大片| 日本 一区二区 在线| 欧美午夜一级特黄大片| 五月天综合网五月天综合网| 欧美午夜不卡在线观看| 91偷拍视频久久精品| 亚洲国产精品无遮挡羞羞| 日韩国产欧美中文字幕| 色老汉在线视频免费亚欧| 果冻传媒精选麻豆白晶晶| 欧洲日韩精品一区二区三区| 激情亚洲一区国产精品久久| 肥白女人日韩中文视频| 欧美极品欧美精品欧美| 国产精品内射视频免费| 自拍偷女厕所拍偷区亚洲综合| 中文字幕精品少妇人妻| 精品久久久一区二区三| 亚洲日本久久国产精品久久| 在线播放欧美精品一区| 丰满少妇被猛烈插入在线观看| 色小姐干香蕉在线综合网|