javascript快速入门17—事件
事件(上)
问题一:每个事件只能注册一个函数
解决方案一:
fn1();
fn2();
fn3();
};
缺陷一:需要将所有函数一次添加进去,不能在运行时添加
缺陷二:在事件处理函数中this将指向window,而不是obj
解决方案二:
function addEvent(fn,evtype,obj) { //obj是要添加事件的HTML元素对象
//evtype是事件名字,不包含on前缀,因为每个都有on,所以写个on是多余的
//fn是事件处理函数
var oldFn; if (obj["on"+evtype] instanceof Function) {
oldFn = obj["on"+evtype];//当添加函数时,如果已注册过了,则将其保存起来
}
obj["on"+evtype]=function () { if (oldFn) {
oldFn.call(this);
}
fn.call(this);//使用call方法,使事件处理函数中的this仍指向obj
};
}
这样已经解决了问题,但如何删除事件呢?如果直接将对象的onevtype这类的属性赋值为null将会删除所有的事件处理函数!
解决方案二的修改版:先将事件存储起来,存储在对象的__EventHandles属性里面
eventHandlesCounter=1;//计数器,将统计所有添加进去的函数的个数,0位预留作其它用
function addEvent(fn,evtype,obj) { if (!fn.__EventID) {//__EventID是给函数加的一个标识,见下面给函数添加标识的部分
fn.__EventID=eventHandlesCounter++; //使用一个自动增长的计数器作为函数的标识以保证不会重复
} if (!obj.__EventHandles) {
obj.__EventHandles=[];//当不存在,也就是第一次执行时,创建一个,并且是数组
} if (!obj.__EventHandles[evtype]) {//将所有事件处理函数按事件类型分类存放
obj.__EventHandles[evtype]=[];//当不存在时也创建一个数组
if (obj["on"+evtype] instanceof Function) { //查看是否已经注册过其它函数
//如果已经注册过,则将以前的事件处理函数添加到数组下标为0的预留的位置
obj.__EventHandles[evtype][0]=obj["on"+evtype];
obj["on"+evtype]=handleEvents;//使用handleEvents集中处理所有的函数
}
}
obj.__EventHandles[evtype][fn.__EventID]=fn; //如果函数是第一次注册为事件处理函数,那么它将被添加到数组中,函数的标识作为下标
//如果函数已经注册过相同对象的相同事件了,那么将覆盖原来的而不会被添加两次
function handleEvents() { var fns = obj.__EventHandles[evtype]; for (var i=0;i< fns.length;i++) {
fns[i].call(this);
}
}
}
使用上面的函数已经可以在一个对象添加多个事件处理函数,在函数内部this关键字也指向了相应的对象,并且这些函数都被作了标识,那么移除某个事件处理函数就是轻而易举的了!
事件(下)
事件对象是用来记录一些事件发生时的相关信息的对象。事件对象只有事件发生时才会产生,并且只能是事件处理函数内部访问,在所有事件处理函数运行结束后,事件对象就被销毁!
//W3C DOM把事件对象作为事件处理函数的第一个参数传入进去
document.onclick = function (evt) {//这样,事件对象只能在对应的事件处理函数内部可以访问到
alert(evt);
}; //IE将事件对象作为window对象的一个属性(相当于全局变量)
//貌似全局对象,但是只有是事件发生时才能够访问
alert(window.event);//null
alert(window.event);
};
其它
| 常量 | 值 || —- | —- || Event.CAPTURING_PHASE(捕获阶段) | 1 || Event.AT_TARGET(在目标对象上) | 2 || Event.BUBBLING_PHASE(冒泡阶段) | 3 |
|| timeStamp (仅W3C) | Long | R | 返回一个时间戳。指示发生事件的日期和时间(从 epoch 开始的毫秒数)。epoch 是一个事件参考点。在这里,它是客户机启动的时间。并非所有系统都提供该信息,因此,timeStamp 属性并非对所有系统/事件都是可用的。 |
取得事件对象及取得事件目标对象
document.onclick =function (evt) {
evt = evt || window.event;//在IE中evt会是undefined
//而支持W3C DOM事件的浏览器中事件对象将会作为事件处理函数的第一个参数
};
阻止事件发生时浏览器的默认行为
document.onclick = function (evt) {
evt = evt || window.event; var target = evt.target || evt.srcElement; if (!target) { return;
} if (target.tagName=="A" && target.href) { //使用传统的方法取消事件默认行为必须使用return false
//但使用了return ,函数便终止了运行,可以使用事件对象来取消
if (window.event) {//IE
window.event.returnValue = false;
} else {
evt.preventDefault();
}
window.open(target.href,"newWindow"); //这样让所有的链接在新窗口打开
}
};
DOM事件标准定义了两种事件流,这两种事件流有着显著的不同并且可能对你的应用有着相当大的影响。这两种事件流分别是捕获和冒泡。和许多Web技术一样,在它们成为标准之前,Netscape和微软各自不同地实现了它们。Netscape选择实现了捕获事件流,微软则实现了冒泡事件流。幸运的是,W3C决定组合使用这两种方法,并且大多数新浏览器都遵循这两种事件流方式。
默认情况下,事件使用冒泡事件流,不使用捕获事件流。然而,在Firefox和Safari里,你可以显式的指定使用捕获事件流,方法是在注册事件时传入useCapture参数,将这个参数设为true。
当事件在某一DOM元素被触发时,例如用户在客户名字节点上点击鼠标,事件将跟随着该节点继承自的各个父节点冒泡穿过整个的DOM节点层次,直到它遇到依附有该事件类型处理器的节点,此时,该事件是onclick事件。在冒泡过程中的任何时候都可以终止事件的冒泡,在遵从W3C标准的浏览器里可以通过调用事件对象上的stopPropagation()方法,在Internet Explorer里可以通过设置事件对象的cancelBubble属性为true。如果不停止事件的传播,事件将一直通过DOM冒泡直至到达文档根。
事件的处理将从DOM层次的根开始,而不是从触发事件的目标元素开始,事件被从目标元素的所有祖先元素依次往下传递。在这个过程中,事件会被从文档根到事件目标元素之间各个继承派生的元素所捕获,如果事件监听器在被注册时设置了useCapture属性为true,那么它们可以被分派给这期间的任何元素以对事件做出处理;否则,事件会被接着传递给派生元素路径上的下一元素,直至目标元素。事件到达目标元素后,它会接着通过DOM节点再进行冒泡。
W3C DOM
- obj.addEventListener(evtype,fn,useCapture)——W3C提供的添加事件处理函数的方法。obj是要添加事件的对象,evtype是事件类型,不带on前缀,fn是事件处理函数,如果useCapture是true,则事件处理函数在捕获阶段被执行,否则在冒泡阶段执行
obj.removeEventListener(evtype,fn,useCapture)——W3C提供的删除事件处理函数的方法
微软IE方法obj.attachEvent(evtype,fn)——IE提供的添加事件处理函数的方法。obj是要添加事件的对象,evtype是事件类型,带on前缀,fn是事件处理函数,IE不支持事件捕获
- obj.detachEvent(evtype,fn,)——IE提供的删除事件处理函数的方法,evtype包含on前缀
整合两者的方法
其它兼容性问题:IE不支持事件捕获?很抱歉,这个没有办法解决!但IE的attach方法有个问题,就是使用attachEvent时在事件处理函数内部,this指向了window,而不是obj!当然,这个是有解决方案的!
function addEvent(obj,evtype,fn,useCapture) { if (obj.addEventListener) {
obj.addEventListener(evtype,fn,useCapture);
} else {
obj.attachEvent("on"+evtype,function () {
fn.call(obj);
});
} else {
obj["on"+evtype]=fn;//事实上这种情况不会存在
}
}
但IE的attachEvent方法有另外一个问题,同一个函数可以被注册到同一个对象同一个事件上多次,解决方法:抛弃IE的attachEvent方法吧!IE下的attachEvent方法不支持捕获,和传统事件注册没多大区别(除了能绑定多个事件处理函数),并且IE的attachEvent方法存在内存泄漏问题!
addEvent,delEvent现代版
function addEvent(obj,evtype,fn,useCapture) { if (obj.addEventListener) {//优先考虑W3C事件注册方案
obj.addEventListener(evtype,fn,!!useCapture);
} else {//当不支持addEventListener时(IE),由于IE同时也不支持捕获,所以不如使用传统事件绑定
if (!fn.__EventID) {fn.__EventID = addEvent.__EventHandlesCounter++;} //为每个事件处理函数分配一个唯一的ID
if (!obj.__EventHandles) {obj.__EventHandles={};} //__EventHandles属性用来保存所有事件处理函数的引用
//按事件类型分类
if (!obj.__EventHandles[evtype]) {//第一次注册某事件时
obj.__EventHandles[evtype]=[]; if (obj["on"+evtype]) {//以前曾用传统方式注册过事件处理函数
(obj.__EventHandles[evtype][0]=obj["on"+evtype]).__EventID=0;//添加到预留的0位
//并且给原来的事件处理函数增加一个ID
}
obj["on"+evtype]=addEvent.execEventHandles; //当事件发生时,execEventHandles遍历数组obj.__EventHandles[evtype]并执行其中的函数
}
}
}
addEvent.__EventHandlesCounter=1;//计数器,0位预留它用
addEvent.execEventHandles = function (evt) {//遍历所有的事件处理函数并执行
if (!this.__EventHandles) {return true;}
fns[i].call(this);
}
}
}; function delEvent(obj,evtype,fn,useCapture) { if (obj.removeEventListener) {//先使用W3C的方法移除事件处理函数
} else { if (obj.__EventHandles) { var fns = obj.__EventHandles[evtype]; if (fns) {delete fns[fn.__EventID];}
}
}
}
IE的事件对象与W3C DOM的事件对象有许多不一样的地方,解决的最好的方法就是调整IE的事件对象,以使它尽可能的与标准相似!下表列出了IE事件对象中一些和W3C DOM名称或值不一样但含义相同的属性
总结出fixEvent函数
function fixEvent(evt) { if (!evt.target) {
evt.target = evt.srcElement;
evt.preventDefault = fixEvent.preventDefault;
evt.stopPropagation = fixEvent.stopPropagation; if (evt.type == "mouseover") {
evt.relatedTarget = evt.fromElement;
} else if (evt.type =="mouseout") {
evt.relatedTarget = evt.toElement;
}
evt.charCode = (evt.type=="keypress")?evt.keyCode:0;
evt.eventPhase = 2;//IE仅工作在冒泡阶段
evt.timeStamp = (new Date()).getTime();//仅将其设为当前时间
} return evt;
}
fixEvent.preventDefault =function () { this.returnValue = false;//这里的this指向了某个事件对象,而不是fixEvent
};
fixEvent.stopPropagation =function () { this.cancelBubble = true;
};
fixEvent函数不是单独执行的,它必须有一个事件对象参数,而且只有事件发生时它才被执行!最好的方法是把它整合到addEvent函数的execEventHandles里面
function addLoadEvent(fn) { var init = function() { if (arguments.callee.done) return;
arguments.callee.done = true;
fn.apply(document,arguments);
}; //注册DOMContentLoaded事件,如果支持的话
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", init, false);
} //但对于Safari,我们需要使用setInterval方法不断检测document.readyState
//当为loaded或complete的时候表明DOM已经加载完毕
if (/WebKit/i.test(navigator.userAgent)) { var _timer = setInterval(function() { if (/loaded|complete/.test(document.readyState)) {
clearInterval(_timer);
init();
}
},10);
} //对于IE则使用条件注释,并使用script标签的defer属性
//IE中可以给script标签添加一个defer(延迟)属性,这样,标签中的脚本只有当DOM加载完毕后才执行
/*@cc_on @*/
/*@if (@_win32)
document.write("<script id=\"__ie_onload\" defer=\"defer\" src=\"javascript:void(0)\"><\/script>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
if (this.readyState == "complete") {
init();
}
};
/*@end @*/
return true;
}