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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 之前通过深入学习DOM的相关知识,看了慕课网DOM探索之基础详解篇这个视频(在最近看第三遍的时候,准备记录一点东西...
    微醺岁月阅读 4,449评论 2 62
  • (续jQuery基础(1)) 第5章 DOM节点的复制与替换 (1)DOM拷贝clone() 克隆节点是DOM的常...
    凛0_0阅读 1,316评论 0 8
  • 真不明白为什么你们这么喜欢玩暧昧,说骚话,按我说就是贱………平时就不要口嘴不对…说出来的都是道理,没意思!
    fbad912d56d6阅读 1,027评论 0 1
  • 干燥纸味的被子包裹热气蜷曲静栖,身体在被卷的庇护里几乎触不到凛冽的空气,头脑线路失连。蹿隙晨光钻开眼皮擦拭瞳...
    聂臻阅读 246评论 0 0