事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定

(DOM2 级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。

一.传统事件绑定的问题
传统事件绑定有内联模型和脚本模型,内联模型我们不做讨论,基本很少去用。先来看
一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。

  1. var box = document.getElementById('box'); //获取元素
  2. box.onclick = function () { //元素点击触发事件
  3. alert('Lee');
  4. };

问题一:一个事件处理函数触发两次事件

  1. window.onload = function () { //第一组程序项目或第一个JS 文件
  2. alert('Lee');
  3. };
  4. window.onload = function () { //第二组程序项目或第二个JS 文件
  5. alert('Mr.Lee');
  6. };

当两组程序或两个JS 文件同时执行的时候,后面一个会把前面一个完全覆盖掉。导致
前面的window.onload 完全失效了。

解决覆盖问题,我们可以这样去解决:

  1. window.onload = function () { //第一个要执行的事件,会被覆盖
  2. alert('Lee');
  3. };
  4. if (typeof window.onload == 'function') { //判断之前是否有window.onload
  5. var saved = null; //创建一个保存器
  6. saved = window.onload; //把之前的window.onload 保存起来
  7. }
  8. window.onload = function () { //最终一个要执行事件
  9. if (saved) saved(); //执行之前一个事件
  10. alert('Mr.Lee'); //执行本事件的代码
  11. };

问题二:事件切换器

  1. box.onclick = toBlue; //第一次执行boBlue()
  2. function toRed() {
  3. this.className = 'red';
  4. this.onclick = toBlue; //第三次执行toBlue(),然后来回切换
  5. }
  6. function toBlue() {
  7. this.className = 'blue';
  8. this.onclick = toRed; //第二次执行toRed()
  9. }

这个切换器在扩展的时候,会出现一些问题:
1.如果增加一个执行函数,那么会被覆盖

  1. box.onclick = toAlert; //被增加的函数
  2. box.onclick = toBlue; //toAlert 被覆盖了

2.如果解决覆盖问题,就必须包含同时执行,但又出新问题

  1. box.onclick = function () { //包含进去,但可读性降低
  2. toAlert(); //第一次不会被覆盖,但第二次又被覆盖
  3. toBlue.call(this); //还必须把this 传递到切换器里
  4. };

综上的三个问题:覆盖问题、可读性问题、this 传递问题。我们来创建一个自定义的事
件处理函数,来解决以上三个问题。

  1. function addEvent(obj, type, fn) { //取代传统事件处理函数
  2. var saved = null; //保存每次触发的事件处理函数
  3. if (typeof obj['on' + type] == 'function') { //判断是不是事件
  4. saved = obj['on' + type]; //如果有,保存起来
  5. }
  6. obj['on' + type] = function () { //然后执行
  7. if (saved) saved(); //执行上一个
  8. fn.call(this); //执行函数,把this 传递过去
  9. };
  10. }
  11. addEvent(window, 'load', function () { //执行到了
  12. alert('Lee');
  13. });
  14. addEvent(window, 'load', function () { //执行到了
  15. alert('Mr.Lee');
  16. });

PS:以上编写的自定义事件处理函数,还有一个问题没有处理,就是两个相同函数名
的函数误注册了两次或多次,那么应该把多余的屏蔽掉。那,我们就需要把事件处理函数进
行遍历,如果有同样名称的函数名就不添加即可。(这里就不做了)

  1. addEvent(window, 'load', init); //注册第一次
  2. addEvent(window, 'load', init); //注册第二次,应该忽略
  3. function init() {
  4. alert('Lee');
  5. }

用自定义事件函数注册到切换器上查看效果:

  1. addEvent(window, 'load', function () {
  2. var box = document.getElementById('box');
  3. addEvent(box, 'click', toBlue);
  4. });
  5. function toRed() {
  6. this.className = 'red';
  7. addEvent(this, 'click', toBlue);
  8. }
  9. function toBlue() {
  10. this.className = 'blue';
  11. addEvent(this, 'click', toRed);
  12. }

PS:当你单击很多很多次切换后,浏览器直接卡死,或者弹出一个错误:too much
recursion(太多的递归)。主要的原因是,每次切换事件的时候,都保存下来,没有把无用的
移除,导致越积越多,最后卡死。

  1. function removeEvent(obj, type) {
  2. if (obj['on'] + type) obj['on' + type] = null; //删除事件处理函数
  3. }

以上的删除事件处理函数只不过是一刀切的删除了,这样虽然解决了卡死和太多递归的
问题。但其他的事件处理函数也一并被删除了,导致最后得不到自己想要的结果。如果想要
只删除指定的函数中的事件处理函数,那就需要遍历,查找。(这里就不做了)

二.W3C事件处理函数
“DOM2 级事件”定义了两个方法,用于添加事件和删除事件处理程序的操作:
addEventListener()和removeEventListener()。所有DOM 节点中都包含这两个方法,并且它
们都接受3 个参数;事件名、函数、冒泡或捕获的布尔值(true 表示捕获,false 表示冒泡)。

  1. window.addEventListener('load', function () {
  2. alert('Lee');
  3. }, false);
  4. window.addEventListener('load', function () {
  5. alert('Mr.Lee');
  6. }, false);

PS:W3C 的现代事件绑定比我们自定义的好处就是:1.不需要自定义了;2.可以屏蔽相
同的函数;3.可以设置冒泡和捕获。

  1. window.addEventListener('load', init, false); //第一次执行了
  2. window.addEventListener('load', init, false); //第二次被屏蔽了
  3. function init() {
  4. alert('Lee');
  5. }

事件切换器

  1. window.addEventListener('load', function () {
  2. var box = document.getElementById('box');
  3. box.addEventListener('click', function () { //不会被误删
  4. alert('Lee');
  5. }, false);
  6. box.addEventListener('click', toBlue, false); //引入切换也不会太多递归卡死
  7. }, false);
  8. function toRed() {
  9. this.className = 'red';
  10. this.removeEventListener('click', toRed, false);
  11. this.addEventListener('click', toBlue, false);
  12. }
  13. function toBlue() {
  14. this.className = 'blue';
  15. this.removeEventListener('click', toBlue, false);
  16. this.addEventListener('click', toRed, false);
  17. }

设置冒泡和捕获阶段
之前我们上一章了解了事件冒泡,即从里到外触发。我们也可以通过event 对象来阻止
某一阶段的冒泡。那么W3C 现代事件绑定可以设置冒泡和捕获。

  1. document.addEventListener('click', function () {
  2. alert('document');
  3. }, true); //把布尔值设置成true,则为捕获
  4. box.addEventListener('click', function () {
  5. alert('Lee');
  6. }, true); //把布尔值设置成false,则为冒泡

三.IE事件处理函数
IE 实现了与DOM 中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受
相同的参数:事件名称和函数。
在使用这两组函数的时候,先把区别说一下:1.IE 不支持捕获,只支持冒泡;2.IE 添加
事件不能屏蔽重复的函数;3.IE 中的this 指向的是window 而不是DOM 对象。4.在传统事
件上,IE 是无法接受到event 对象的,但使用了attchEvent()却可以,但有些区别。

  1. window.attachEvent('onload', function () {
  2. var box = document.getElementById('box');
  3. box.attachEvent('onclick', toBlue);
  4. });
  5. function toRed() {
  6. var that = window.event.srcElement;
  7. that.className = 'red';
  8. that.detachEvent('onclick', toRed);
  9. that.attachEvent('onclick', toBlue);
  10. }
  11. function toBlue() {
  12. var that = window.event.srcElement;
  13. that.className = 'blue';
  14. that.detachEvent('onclick', toBlue);
  15. that.attachEvent('onclick', toRed);
  16. }

PS:IE 不支持捕获,无解。IE 不能屏蔽,需要单独扩展或者自定义事件处理。IE 不能
传递this,可以call 过去。

  1. window.attachEvent('onload', function () {
  2. var box = document.getElementById('box');
  3. box.attachEvent('onclick', function () {
  4. alert(this === window); //this 指向的window
  5. });
  6. });
  7. window.attachEvent('onload', function () {
  8. var box = document.getElementById('box');
  9. box.attachEvent('onclick', function () {
  10. toBlue.call(box); //把this 直接call 过去
  11. });
  12. });
  13. function toThis() {
  14. alert(this.tagName);
  15. }

在传统绑定上,IE 是无法像W3C 那样通过传参接受event 对象,但如果使用了
attachEvent()却可以。

  1. box.onclick = function (evt) {
  2. alert(evt); //undefined
  3. }
  4. box.attachEvent('onclick', function (evt) {
  5. alert(evt); //object
  6. alert(evt.type); //click
  7. });
  8. box.attachEvent('onclick', function (evt) {
  9. alert(evt.srcElement === box); //true
  10. alert(window.event.srcElement === box); //true
  11. });

最后,为了让IE 和W3C 可以兼容这个事件切换器,我们可以写成如下方式:

  1. function addEvent(obj, type, fn) { //添加事件兼容
  2. if (obj.addEventListener) {
  3. obj.addEventListener(type, fn);
  4. } else if (obj.attachEvent) {
  5. obj.attachEvent('on' + type, fn);
  6. }
  7. }
  8. function removeEvent(obj, type, fn) { //移除事件兼容
  9. if (obj.removeEventListener) {
  10. obj.removeEventListener(type, fn);
  11. } else if (obj.detachEvent) {
  12. obj.detachEvent('on' + type, fn);
  13. }
  14. }
  15. function getTarget(evt) { //得到事件目标
  16. if (evt.target) {
  17. return evt.target;
  18. } else if (window.event.srcElement) {
  19. return window.event.srcElement;
  20. }
  21. }

PS:调用忽略,IE 兼容的事件,如果要传递this,改成call 即可。
PS:IE 中的事件绑定函数attachEvent()和detachEvent()可能在实践中不去使用,有几个
原因:1.IE9 就将全面支持W3C 中的事件绑定函数;2.IE 的事件绑定函数无法传递this;3.IE
的事件绑定函数不支持捕获;4.同一个函数注册绑定后,没有屏蔽掉;5.有内存泄漏的问题。
至于怎么替代,我们将在以后的项目课程中探讨。

四.事件对象的其他补充
在W3C 提供了一个属性:relatedTarget;这个属性可以在mouseover 和mouseout 事件
中获取从哪里移入和从哪里移出的DOM 对象。

  1. box.onmouseover = function (evt) { //鼠标移入box
  2. alert(evt.relatedTarget); //获取移入box 最近的那个元素对象
  3. } //span
  4. box.onmouseout = function (evt) { //鼠标移出box
  5. alert(evt.relatedTarget); //获取移出box 最近的那个元素对象
  6. } //span

IE 提供了两组分别用于移入移出的属性:fromElement 和toElement,分别对应mouseover
和mouseout。

  1. box.onmouseover = function (evt) { //鼠标移入box
  2. alert(window.event.fromElement.tagName); //获取移入box 最近的那个元素对象span
  3. }
  4. box.onmouseout = function (evt) { //鼠标移入box
  5. alert(window.event.toElement.tagName); //获取移入box 最近的那个元素对象span
  6. }

PS:fromElement 和toElement 如果分别对应相反的鼠标事件,没有任何意义。

剩下要做的就是跨浏览器兼容操作:

  1. function getTarget(evt) {
  2. var e = evt || window.event; //得到事件对象
  3. if (e.srcElement) { //如果支持srcElement,表示IE
  4. if (e.type == 'mouseover') { //如果是over
  5. return e.fromElement; //就使用from
  6. } else if (e.type == 'mouseout') { //如果是out
  7. return e.toElement; //就使用to
  8. }
  9. } else if (e.relatedTarget) { //如果支持relatedTarget,表示W3C
  10. return e.relatedTarget;
  11. }
  12. }

有时我们需要阻止事件的默认行为,比如:一个超链接的默认行为就点击然后跳转到指
定的页面。那么阻止默认行为就可以屏蔽跳转的这种操作,而实现自定义操作。
取消事件默认行为还有一种不规范的做法,就是返回false。

  1. link.onclick = function () {
  2. alert('Lee');
  3. return false; //直接给个假,就不会跳转了。
  4. };

PS:虽然return false;可以实现这个功能,但有漏洞;第一:必须写到最后,这样导致
中间的代码执行后,有可能执行不到return false;第二:return false 写到最前那么之后的自
定义操作就失效了。所以,最好的方法应该是在最前面就阻止默认行为,并且后面还能执行
代码。

  1. link.onclick = function (evt) {
  2. evt.preventDefault(); //W3C,阻止默认行为,放哪里都可以
  3. alert('Lee');
  4. };
  5. link.onclick = function (evt) { //IE,阻止默认行为
  6. window.event.returnValue = false;
  7. alert('Lee');
  8. };

跨浏览器兼容

  1. function preDef(evt) {
  2. var e = evt || window.event;
  3. if (e.preventDefault) {
  4. e.preventDefault();
  5. } else {
  6. e.returnValue = false;
  7. }
  8. }

上下文菜单事件:contextmenu,当我们右击网页的时候,会自动出现windows 自带的
菜单。那么我们可以使用contextmenu 事件来修改我们指定的菜单,但前提是把右击的默认
行为取消掉。

  1. addEvent(window, 'load', function () {
  2. var text = document.getElementById('text');
  3. addEvent(text, 'contextmenu', function (evt) {
  4. var e = evt || window.event;
  5. preDef(e);
  6. var menu = document.getElementById('menu');
  7. menu.style.left = e.clientX + 'px';
  8. menu.style.top = e.clientY + 'px';
  9. menu.style.visibility = 'visible';
  10. addEvent(document, 'click', function () {
  11. document.getElementById('myMenu').style.visibility = 'hidden';
  12. });
  13. });
  14. });

PS:contextmenu 事件很常用,这直接导致浏览器兼容性较为稳定。

卸载前事件:beforeunload,这个事件可以帮助在离开本页的时候给出相应的提示,“离
开”或者“返回”操作。

  1. addEvent(window, 'beforeunload', function (evt) {
  2. preDef(evt);
  3. });

鼠标滚轮(mousewheel)和DOMMouseScroll,用于获取鼠标上下滚轮的距离。

  1. addEvent(document, 'mousewheel', function (evt) { //非火狐
  2. alert(getWD(evt));
  3. });
  4. addEvent(document, 'DOMMouseScroll', function (evt) { //火狐
  5. alert(getWD(evt));
  6. });
  7. function getWD(evt) {
  8. var e = evt || window.event;
  9. if (e.wheelDelta) {
  10. return e.wheelDelta;
  11. } else if (e.detail) {
  12. return -evt.detail * 30; //保持计算的统一
  13. }
  14. }

PS:通过浏览器检测可以确定火狐只执行DOMMouseScroll。

DOMContentLoaded 事件和readystatechange 事件,有关DOM 加载方面的事件,关于这
两个事件的内容非常多且繁杂,我们先点明在这里,在项目课程中使用的时候详细讨论。

JavaScript的事件绑定及深入的更多相关文章

  1. javascript对象事件绑定方法

    javascript对象事件绑定方法 今天在做对象事件绑定的过程中出现了一点异外情况,由于事件方法是由参数传过来的,需要将当前对象call过去,方便方法体里直接调用this 错误写法 obj.oncl ...

  2. javascript之事件绑定

    曾经写过一篇随笔,attachEvent和addEventListener,跟本文内容有很多相似之处 本文链接:javascript之事件绑定 1.原始写法 <div onclick=" ...

  3. jQuery 事件绑定 和 JavaScript 原生事件绑定

    总结一下:jQuery 事件绑定 和 JavaScript 原生事件绑定 及 区别 jQuery 事件绑定 jQuery 中提供了四种事件监听绑定方式,分别是 bind.live.delegate.o ...

  4. 关于JavaScript的事件绑定

    js事件绑定的几种方式 JavaScript中有三种常用的绑定事件方法: 1. 在DOM元素中直接绑定: 2. 在JavaScript代码中绑定: 3. 绑定事件佳妮婷函数. 一.在DOM元素中直接绑 ...

  5. JavaScript之事件绑定多个序列执行方法

    //一种事件绑定多个方法:以加载事件为例 function addEventLoad(func,isLog) { var oldOnLoad = window.onload; if (typeof w ...

  6. javascript 的事件绑定和取消事件

    研究fabricjs中发现,它提供canvas.on('mousemove', hh) 来绑定事件, 提供 canvas.off()来取消绑定事件这样的接口,很是方便, 那我们就不妨探究一下内在的实现 ...

  7. JavaScript 中事件绑定的三种方式

    以下是在 JS 中事件绑定的三种方式.   1. HTML onclick attribute     <button type="button" id="uplo ...

  8. JavaScript中事件绑定的方法总结

    最近收集了一些关于JavaScript绑定事件的方法,汇总了一下,不全面,但是,希望便于以后自己查看. JavaScript中绑定事件的方法主要有三种: 1 在DOM元素中直接绑定 2 JavaScr ...

  9. JavaScript中事件绑定的三种方式

    JavaScript使得网页与用户友好交互,在使用 js 进行时间绑定的时候有三种绑定方式. 第一种:初学者以及普通写法 <div id="dom0"> <inp ...

随机推荐

  1. XCode之entitlement

    entitlement是codesign的一个输入,参见:codesign. entitlement的意思是权力,也就是表明应用所具有的权利,可以访问什么,不能访问什么等.这些信息会在codesign ...

  2. objectARX 获取ucs的X方向

    struct resbuf var; acedGetVar(_T("UCSXDIR"), &var);//获取用户坐标系下X方向量 ver = asVec3d(var.re ...

  3. linux基础命令学习(四)计划任务

    一.计划任务 crond服务简介 linux任务调度的工作主要分为以下两类: *系统执行的工作:系统周期性所要执行的工作,如备份系统数据.清理缓存 *个人执行的工作:某个用户定期要做的工作,例如每隔1 ...

  4. 未来WEB程序员

    作为一名程序员,如果你想在这个领域内继续向前进步或者在当前的经济形势下保持不被炒鱿鱼,那么你就决不应当自满自足,你需要继续学习.近日,著名IT评论员Justin James在他的博客中列出了未来五年程 ...

  5. UIWebView的缓存策略,清除cookie

    缓存策略 NSURLRequestCachePolicy NSURLRequestUseProtocolCachePolicy缓存策略定义在 web 协议实现中,用于请求特定的URL.是默认的URL缓 ...

  6. GIT之二 基础篇(1)

    GIT基础 取得项目的 Git 仓库 有两种取得 Git 项目仓库的方法.第一种是在现存的目录下,通过导入所有文件来创建新的 Git 仓库.第二种是从已有的 Git 仓库克隆出一个新的镜像仓库来. 在 ...

  7. 获取客户端ip并用正则表达式验证

    代理HTTP_VIA /// <summary> /// 获得请求的ip /// </summary> /// <returns></returns> ...

  8. 搬家后Magento前台只有产品的缩略图不显示

    第一种可能:缓存不足 http://blog.csdn.net/ddjohn/article/details/6648199 最近发现一个怪异的现象,Magento前台只有产品的缩略图不显示.我检查了 ...

  9. magento搬家步骤和可能遇到的问题

    将原来网站文件中的var文件中的cache和session文件删除,将media中的缓存文件删除.然后将所有文件制作成一个压缩包,以减少文件体积,方便转移. 将压缩包转移到新的服务器域名指向的文件夹, ...

  10. nginx源码学习资源(不断更新)

    nginx源码学习是一个痛苦又快乐的过程,下面列出了一些nginx的学习资源. 首先要做的当然是下载一份nginx源码,可以从nginx官方网站下载一份最新的. 看了nginx源码,发现这是一份完全没 ...