事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。

 

一.传统事件绑定的问题

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

var box = document.getElementById('box'); //获取元素

box.onclick = function () { //元素点击触发事件

alert('Dkf');

};

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

window.onload = function () { //第一组程序项目或第一个JS文件

alert('Dkf');

};

window.onload = function () { //第二组程序项目或第二个JS文件

alert('Mr.Dkf');

};

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

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

window.onload = function () { //第一个要执行的事件,会被覆盖

alert('Dkf');

};

if (typeof window.onload == 'function') { //判断之前是否有window.onload

var saved = null; //创建一个保存器

saved = window.onload; //把之前的window.onload保存起来

}

window.onload = function () { //最终一个要执行事件

if (saved) saved(); //执行之前一个事件

alert('Mr.Dkf'); //执行本事件的代码

};

问题二:事件切换器

box.onclick = toBlue; //第一次执行boBlue()

function toRed() {

this.className = 'red';

this.onclick = toBlue; //第三次执行toBlue(),然后来回切换

}

function toBlue() {

this.className = 'blue';

this.onclick = toRed; //第二次执行toRed()

}

这个切换器在扩展的时候,会出现一些问题:

1.如果增加一个执行函数,那么会被覆盖

box.onclick = toAlert; //被增加的函数

box.onclick = toBlue; //toAlert被覆盖了

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

box.onclick = function () { //包含进去,但可读性降低

toAlert(); //第一次不会被覆盖,但第二次又被覆盖

toBlue.call(this); //还必须把this传递到切换器里

};

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

function addEvent(obj, type, fn) { //取代传统事件处理函数

var saved = null; //保存每次触发的事件处理函数

if (typeof obj['on' + type] == 'function') { //判断是不是事件

saved = obj['on' + type]; //如果有,保存起来

}

obj['on' + type] = function () { //然后执行

if (saved) saved(); //执行上一个

fn.call(this); //执行函数,把this传递过去

};

}

addEvent(window, 'load', function () { //执行到了

alert('Dkf');

});

addEvent(window, 'load', function () { //执行到了

alert('Mr.Dkf');

});

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

addEvent(window, 'load', init); //注册第一次

addEvent(window, 'load', init); //注册第二次,应该忽略

function init() {

alert('Dkf');

}

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

addEvent(window, 'load', function () {

var box = document.getElementById('box');

addEvent(box, 'click', toBlue);

});

function toRed() {

this.className = 'red';

addEvent(this, 'click', toBlue);

}

function toBlue() {

this.className = 'blue';

addEvent(this, 'click', toRed);

}

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

function removeEvent(obj, type) {

if (obj['on'] + type) obj['on' + type] = null; //删除事件处理函数

}

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

 

 

 

二.W3C事件处理函数

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

window.addEventListener('load', function () {

alert('Dkf');

}, false);

window.addEventListener('load', function () {

alert('Mr.Dkf');

}, false);

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

window.addEventListener('load', init, false); //第一次执行了

window.addEventListener('load', init, false); //第二次被屏蔽了

function init() {

alert('Dkf');

}

事件切换器

window.addEventListener('load', function () {

var box = document.getElementById('box');

box.addEventListener('click', function () { //不会被误删

alert('Dkf');

}, false);

box.addEventListener('click', toBlue, false); //引入切换也不会太多递归卡死

}, false);

function toRed() {

this.className = 'red';

this.removeEventListener('click', toRed, false);

this.addEventListener('click', toBlue, false);

}

function toBlue() {

this.className = 'blue';

this.removeEventListener('click', toBlue, false);

this.addEventListener('click', toRed, false);

}

设置冒泡和捕获阶段

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

document.addEventListener('click', function () {

alert('document');

}, true); //把布尔值设置成true,则为捕获

box.addEventListener('click', function () {

alert('Dkf');

}, true); //把布尔值设置成false,则为冒泡

 

三.IE事件处理函数

IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的参数:事件名称和函数。

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

window.attachEvent('onload', function () {

var box = document.getElementById('box');

box.attachEvent('onclick', toBlue);

});

function toRed() {

var that = window.event.srcElement;

that.className = 'red';

that.detachEvent('onclick', toRed);

that.attachEvent('onclick', toBlue);

}

function toBlue() {

var that = window.event.srcElement;

that.className = 'blue';

that.detachEvent('onclick', toBlue);

that.attachEvent('onclick', toRed);

}

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

window.attachEvent('onload', function () {

var box = document.getElementById('box');

box.attachEvent('onclick', function () {

alert(this === window); //this指向的window

});

});

window.attachEvent('onload', function () {

var box = document.getElementById('box');

box.attachEvent('onclick', function () {

toBlue.call(box); //把this直接call过去

});

});

function toThis() {

alert(this.tagName);

}

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

box.onclick = function (evt) {

alert(evt); //undefined

}

box.attachEvent('onclick', function (evt) {

alert(evt); //object

alert(evt.type); //click

});

box.attachEvent('onclick', function (evt) {

alert(evt.srcElement === box); //true

alert(window.event.srcElement === box); //true

});

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

function addEvent(obj, type, fn) { //添加事件兼容

if (obj.addEventListener) {

obj.addEventListener(type, fn);

} else if (obj.attachEvent) {

obj.attachEvent('on' + type, fn);

}

}

function removeEvent(obj, type, fn) { //移除事件兼容

if (obj.removeEventListener) {

obj.removeEventListener(type, fn);

} else if (obj.detachEvent) {

obj.detachEvent('on' + type, fn);

}

}

function getTarget(evt) { //得到事件目标

if (evt.target) {

return evt.target;

} else if (window.event.srcElement) {

return window.event.srcElement;

}

}

PS:调用忽略,IE兼容的事件,如果要传递this,改成call即可。

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

 

四.事件对象的其他补充

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

box.onmouseover = function (evt) { //鼠标移入box

alert(evt.relatedTarget); //获取移入box最近的那个元素对象

} //span

box.onmouseout = function (evt) { //鼠标移出box

alert(evt.relatedTarget); //获取移出box最近的那个元素对象

} //span

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

box.onmouseover = function (evt) { //鼠标移入box

alert(window.event.fromElement.tagName); //获取移入box最近的那个元素对象span

}

box.onmouseout = function (evt) { //鼠标移入box

alert(window.event.toElement.tagName); //获取移入box最近的那个元素对象span

}

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

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

function getTarget(evt) {

var e = evt || window.event; //得到事件对象

if (e.srcElement) { //如果支持srcElement,表示IE

if (e.type == 'mouseover') { //如果是over

return e.fromElement; //就使用from

} else if (e.type == 'mouseout') { //如果是out

return e.toElement; //就使用to

}

} else if (e.relatedTarget) { //如果支持relatedTarget,表示W3C

return e.relatedTarget;

}

}

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

取消事件默认行为还有一种不规范的做法,就是返回false。

link.onclick = function () {

alert('Dkf');

return false; //直接给个假,就不会跳转了。

};

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

link.onclick = function (evt) {

evt.preventDefault(); //W3C,阻止默认行为,放哪里都可以

alert('Dkf');

};

link.onclick = function (evt) { //IE,阻止默认行为

window.event.returnValue = false;

alert('Dkf');

};

跨浏览器兼容

function preDef(evt) {

var e = evt || window.event;

if (e.preventDefault) {

e.preventDefault();

} else {

e.returnValue = false;

}

}

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

addEvent(window, 'load', function () {

var text = document.getElementById('text');

addEvent(text, 'contextmenu', function (evt) {

var e = evt || window.event;

preDef(e);

var menu = document.getElementById('menu');

menu.style.left = e.clientX + 'px';

menu.style.top = e.clientY + 'px';

menu.style.visibility = 'visible';

addEvent(document, 'click', function () {

document.getElementById('myMenu').style.visibility = 'hidden';

});

});

});

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

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

addEvent(window, 'beforeunload', function (evt) {

preDef(evt);

});

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

addEvent(document, 'mousewheel', function (evt) { //非火狐

alert(getWD(evt));

});

addEvent(document, 'DOMMouseScroll', function (evt) { //火狐

alert(getWD(evt));

});

function getWD(evt) {

var e = evt || window.event;

if (e.wheelDelta) {

return e.wheelDelta;

} else if (e.detail) {

return -evt.detail * 30; //保持计算的统一

}

}

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

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

JavaScript(第二十五天)【事件绑定及深入】的更多相关文章

  1. 探究JavaScript中的五种事件处理程序

    探究JavaScript中的五种事件处理程序 我们知道JavaScript与HTML之间的交互是通过事件实现的.事件最早是在IE3和Netscape Navigator 2中出现的,当时是作为分担服务 ...

  2. javaSE第二十五天

    第二十五天    399 1:如何让Netbeans的东西Eclipse能访问.    399 2:GUI(了解)    399 (1)用户图形界面    399 (2)两个包:    399 (3) ...

  3. NeHe OpenGL教程 第二十五课:变形

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  4. Gradle 1.12用户指南翻译——第二十五章. Scala 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  5. SQL注入之Sqli-labs系列第二十五关(过滤 OR & AND)和第二十五A关(过滤逻辑运算符注释符)

    开始挑战第二十五关(Trick with OR & AND) 第二十五关A(Trick with comments) 0x1先查看源码 (1)这里的or和and采用了i正则匹配,大小写都无法绕 ...

  6. “全栈2019”Java多线程第二十五章:生产者与消费者线程详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. centos lamp/lnmp阶段复习 以后搬迁discuz论坛不需要重新安装,只需修改配置文件即可 安装wordpress 安装phpmyadmin 定时备份mysql两种方法 第二十五节课

    centos  lamp/lnmp阶段复习 以后搬迁discuz论坛不需要重新安装,只需修改配置文件即可 安装wordpress  安装phpmyadmin  定时备份mysql两种方法  第二十五节 ...

  8. “全栈2019”Java第二十五章:流程控制语句中循环语句while

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. (转)第二十三节 inotify事件监控工具

    第二十三节 inotify事件监控工具 标签(空格分隔): Linux实战教学笔记-陈思齐 原文:http://www.cnblogs.com/chensiqiqi/p/6542268.html 第1 ...

随机推荐

  1. Excel 转 vCard格式、CSV格式

    Excel 转vCard格式(常用于Gmail, Yahoo, 163等).CSV格式(常用于Outlook, Foxmail等) 最近公司邮件通讯录需要更新,我就将原来的通讯录给删除了,准备重新导入 ...

  2. js小括号的作用

    js中小括号()的用法详解:对于小括号无论是菜鸟还是高手一定都不会陌生,可以说它几乎是随处可见,虽然熟悉但并非真正的理解,由此可能会产生很多莫名其妙的错误,下面就通过代码实例详细介绍一下小括号的用法. ...

  3. npm包管理器小节一下

    淘宝npm镜像cnpm设置 npm install -g cnpm --registry=https://registry.npm.taobao.org 更新npm的版本 npm install np ...

  4. 使用sourceTree向码云提交代码时 push 错误 (或认证失败)解决办法

     如果出现push不进去或者使用命令push认证失败时,很可能是你密码有误或者用户冲突,解决办法如下: 1.进入目录,找到文件后先备份一下 注意:appData可能隐藏了,若是隐藏,先让其显示  2. ...

  5. requests库使用

    介绍: 发送HTTP请求的第三方库,比起之前用到的urllib,requests模块的api更加便捷(本质就是封装了urllib3) 安装:pip3 install requests 学习reques ...

  6. shell 脚本下执行Mongodb命令

    最近项目中搭建了两台mongodb的服务器,由于服务器只有两台的情况下,目前只是搭建了主从模式架构(官方目前并不推荐主从模式),缺点就是故障转移不变等等原因,而是推荐副本集模式(这里就不多说了)... ...

  7. Android中Activity.this,getApplicationContext(),getBaseContext()和this详解

    转自:http://android.tgbus.com/Android/tutorial/201103/346236.shtml 在使用Android上下文参数的时候经常分不清Activity.thi ...

  8. 解决linux安装软件:/lib/ld-linux.so.2: bad ELF interpreter问题

    问题:64位系统中安装了32位程序解决办法 是因为64位系统中安装了32位程序 解决方法: yum install glibc.i686

  9. 夹缝中求生存-在一无所有的php虚拟主机环境下利用smtp发送邮件(二)

    夹缝中求生存 前言:在上一篇随笔中,以163个人邮箱作为发送邮箱地址,当收件邮箱为QQ邮箱时,极有可能会被直接扔进邮件垃圾箱里,为了解决这个问题,申请注册企业邮箱,可以减少发出的邮件被当作垃圾邮件的可 ...

  10. 属性动画 ValueAnimator 运行原理全解析

    最近下班时间都用来健身还有看书了,博客被晾了一段时间了,原谅我~~~~ 提问环节 好,废话不多说,之前我们已经分析过 View 动画 Animation 运行原理解析,那么这次就来学习下属性动画的运行 ...