JavaScript 自定義事件(二)——Dom事件

上一次,了解了JS自定義事件。今天在DOM上進行事件方法擴展。

1.基于DOM擴展自定義方法(了解即可)

我們一起來添加一個addEvent方法

if (window.HTMLElement) {
    // 使用原型擴展DOM自定義事件
    
    HTMLElement.prototype.addEvent = function(type, fn, capture) {
        var el = this;
        if (window.addEventListener) {
            el.addEventListener(type, function(e) {
                fn.call(el, e);
            }, capture);
        } else if (window.attachEvent) {
            el.attachEvent("on" + type, function(e) {
                fn.call(el, e);
            });
        } 
    };
} else {
    // 如果是不支持HTMLElement擴展的瀏覽器
    // 通過遍歷所有元素擴展DOM事件
    
    var elAll = document.all, lenAll = elAll.length;
    for (var iAll=0; iAll<lenAll; iAll+=1) {
        elAll[iAll].addEvent = function(type, fn) {
            var el = this;
            el.attachEvent("on" + type, function(e) {
                fn.call(el, e);
            });
        };
    }
    
}

HTMLElement 接口表示所有的 HTML 元素(nodeType==1)。以一個<p>標簽元素舉例,其向上尋找原型對象用過會是這樣:HTMLParagraphElement.prototype → HTMLElement.prototype → Element.prototype → Node.prototype → Object.prototype → null。上述代碼HTMLElement直接換成Element也是可以的,但是會讓其他元素(例如文本元素等)也擴展addEvent方法,有些浪費了。

通過上面的擴展,element上就有了addEvent()方法。我們可以像下面展示的那樣使用

<div id="content-wrap" class="content-wrap">這是pattyzzh的領地</div>
document.getElementById("content-wrap").addEvent("click", function() {
         alert("歡迎光臨pattyzzh");    
});

基于DOM擴展缺點有:缺少標準無規律、提高沖突可能性、性能以及瀏覽器支持。擴展名字任意命,很有可能就會與未來DOM瀏覽器本身支持的方法相互沖突;擴展無規律,很有可能出現A和B同名不同功能的擴展而造成沖突;IE6-7瀏覽器下所有擴展都要通過遍歷支持,其性能開銷可想而知;另外IE8對DOM擴展的支持并不完整,例如其支持Element.prototype,卻沒有HTMLElement.prototype.


2.偽DOM自定義事件

這里的“偽DOM自定義事件”是自己定義的一個名詞,用來區分DOM自定義事件的。例如jQuery庫,其是基于包裝器(一個包含DOM元素的中間層)擴展事件的,既與DOM相關,又不直接是DOM,因此,稱之為“偽DOM自定義事件”。

如果只考慮事件添加,我們的工作其實很簡單,根據支持情況,addEventListener與attachEvent方法分別添加事件即可:

addEvent: function(type, fn,  capture) {
    var el = this.el;
    if (window.addEventListener) {
        el.addEventListener(type, fn, capture);        
    } else if (window.attachEvent) {
        el.attachEvent("on" + type, fn);
    }
    return this;
}

自定義事件添加容易,但是如何觸發它們呢?——考慮到自定義事件與瀏覽器行為無關,同時瀏覽器沒有直接的觸發事件的方法。

自定義事件的觸發
  1. 對于標準瀏覽器,其提供了可供元素觸發的方法:element.dispatchEvent()。不過,在使用該方法之前,我們還需要做其他兩件事,及創建和初始化。因此,總結說來就是:
document.createEvent()
event.initEvent()
element.dispatchEvent()

createEvent()方法返回新創建的Event對象,支持一個參數,表示事件類型

para.png

initEvent()方法用于初始化通過DocumentEvent接口創建的Event的值。支持三個參數:initEvent(eventName, canBubble, preventDefault). 分別表示事件名稱是否可以冒泡是否阻止事件的默認操作
dispatchEvent(eventObj)就是觸發執行了.
舉個例子

$(dom).addEvent("sayHello", function() {
alert("Hello");
});

// 創建
var event = document.createEvent("HTMLEvents");
// 初始化
event.initEvent("sayHello",true, false);
// 觸發, 即彈出文字
dom.dispatchEvent(event);
  1. 對于IE瀏覽器,由于向下很多版本的瀏覽器都不支持document.createEvent()方法)。IE瀏覽器有不少自給自足的東西,例如下面要說的這個"propertychange"事件,顧名思義,就是屬性改變即觸發的事件。例如文本框value值改變,或是元素id改變,或是綁定的事件改變等等。
    當我們添加自定義事件的時候,順便給元素添加一個自定義屬性即可。例如,我們添加自定義名為"sayHello"的自定義事件,順便我們可以對元素做點小手腳:
    dom.listener = 0;
    再順便把自定義事件fn塞到"propertychange"事件中:
dom.attachEvent("onpropertychange", function(e) {
    if (e.propertyName == "listener") {
        fn.call(this);   //fn是事件處理函數
    }
});

這個,當我們需要觸發自定義事件的時候,只要修改DOM上自定義的listener屬性的值即可:
dom.listener = Math.random(); // 值變成隨機數
此時就會觸發dom上綁定的onpropertychange事件,又因為修改的屬性名正好是"listener", 于是自定義的fn就會被執行。這就是IE瀏覽器下事件觸發實現的完整機制。

自定義事件的刪除

與觸發事件不同,事件刪除,各個瀏覽器都提供了對于的時間刪除方法,如removeEventListener和detachEvent。不過呢,對于IE瀏覽器,還要多刪除一個事件,就是為了實現觸發功能額外增加的onpropertychange事件:
dom.detachEvent("onpropertychange", event);

綜合
var $ = function(el) {
    return new _$(el);    
};
var _$ = function(el) {
    this.el = (el && el.nodeType == 1)? el: document;
};
_$.prototype = {
    constructor: _$,
    addEvent: function(type, fn, capture) {
        var el = this.el;
        if (window.addEventListener) {
            el.addEventListener(type, fn, capture);
            var ev = document.createEvent("HTMLEvents");
            ev.initEvent(type, capture || false, false);
            
            if (!el["ev" + type]) { 
                el["ev" + type] = ev; //將自定義事件存儲在該元素屬性下,觸發時使用
            }  
        } else if (window.attachEvent) {
            el.attachEvent("on" + type, fn);    
            if (isNaN(el["cu" + type])) {
                // 自定義屬性,用來間接觸發觸發自定義事件
                el["cu" + type] = 0; 
            }   
            var fnEv = function(event) {
                if (event.propertyName == "cu" + type) { fn.call(el); }
            };
            el.attachEvent("onpropertychange", fnEv);     
            if (!el["ev" + type]) {
                el["ev" + type] = [fnEv];
            } else {
                el["ev" + type].push(fnEv);    //同一事件的多個處理函數
            }
        }
        return this;
    },
    fireEvent: function(type) {
        var el = this.el;
        if (typeof type === "string") {
            if (document.dispatchEvent) {
                if (el["ev" + type]) {
                    el.dispatchEvent(el["ev" + type]);
                }
            } else if (document.attachEvent) {
                el["cu" + type]++;
            }    
        }    
        return this;
    },
    removeEvent: function(type, fn, capture) {
        var el = this.el;
        if (window.removeEventListener) {
            el.removeEventListener(type, fn, capture || false);
        } else if (document.attachEvent) {
            el.detachEvent("on" + type, fn);
            var arrEv = el["ev" + type];
            if (arrEv instanceof Array) {
                for (var i=0; i<arrEv.length; i+=1) {
                     //這里還需要再次過濾,否則會刪除該事件所有處理函數(ie下)
                     //條件?這里提示:1.對象的比較是比較引用  2.el["ev" + type]里裝的fnEv而不是fn 這里就難辦了  
                    el.detachEvent("onpropertychange", arrEv[i]);
                }
            }
        }
        return this;    
    }
};

測試

var fnClick = function(e) {
    e = e || window.event;
    var target = e.target || e.srcElement;
    if (target.nodeType === 1) {
        alert("點擊類型:" +  e.type);
        $(target).fireEvent("sayHello");  //觸發自定義事件
    }
}, sayHello1 = function() {
    alert("Hello 1");    
}, sayHello2 = function() {
    alert("Hello 2");    
};

//  梅西圖片
var elImage = document.getElementById("myImg");
$(elImage)
    .addEvent("click", fnClick)
    .addEvent("sayHello", sayHello1)
    .addEvent("sayHello", sayHello2);

// 刪除自定義事件按鈕
var elButton = document.getElementById("myBut");
$(elButton).addEvent("click", function() {
    $(elImage)
        .removeEvent("sayHello", sayHello1)
        .removeEvent("sayHello", sayHello2);    

       alert("清除成功!");
});

html:
     <div class="box" id="box">
        ![](./meixi.jpg)
        <input type="button" value="點擊清除自定義事件 " id="myBut">
     </div>  

運行結果如下:
當我們點擊圖片,會出現下面三個彈窗


alert1.png

alert2.png

alert3.png

當我們點擊按鈕:


del.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 之前通過深入學習DOM的相關知識,看了慕課網DOM探索之基礎詳解篇這個視頻(在最近看第三遍的時候,準備記錄一點東西...
    微醺歲月閱讀 4,515評論 2 61
  • (續jQuery基礎(1)) 第5章 DOM節點的復制與替換 (1)DOM拷貝clone() 克隆節點是DOM的常...
    凜0_0閱讀 1,363評論 0 8
  • 真不明白為什么你們這么喜歡玩曖昧,說騷話,按我說就是賤………平時就不要口嘴不對…說出來的都是道理,沒意思!
    fbad912d56d6閱讀 1,029評論 0 1
  • 干燥紙味的被子包裹熱氣蜷曲靜棲,身體在被卷的庇護里幾乎觸不到凜冽的空氣,頭腦線路失連。躥隙晨光鉆開眼皮擦拭瞳...
    聶臻閱讀 261評論 0 0