上一次,了解了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;
}
自定义事件添加容易,但是如何触发它们呢?——考虑到自定义事件与浏览器行为无关,同时浏览器没有直接的触发事件的方法。
自定义事件的触发
- 对于标准浏览器,其提供了可供元素触发的方法:element.dispatchEvent()。不过,在使用该方法之前,我们还需要做其他两件事,及创建和初始化。因此,总结说来就是:
document.createEvent()
event.initEvent()
element.dispatchEvent()
createEvent()方法返回新创建的Event对象,支持一个参数,表示事件类型
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);
- 对于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>
运行结果如下:
当我们点击图片,会出现下面三个弹窗
当我们点击按钮: