`
chaoyi
  • 浏览: 309611 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

第二十六章:事件绑定及深入

阅读更多

学习要点:
1.传统事件绑定的问题
2.W3C 事件处理函数
3.IE 事件处理函数
4.事件对象的其他补充

事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2 级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。
一.传统事件绑定的问题
传统事件绑定有内联模型和脚本模型, 内联模型我们不做讨论, 基本很少去用。 先来看一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。

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

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

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

 
当两组程序或两个 JS 文件同时执行的时候,后面一个会把前面一个完全覆盖掉。导致前面的 window.onload 完全失效了。
解决覆盖问题,我们可以这样去解决:

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

 
问题二:事件切换器

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('Lee');
});
addEvent(window, 'load', function () { //执行到了
alert('Mr.Lee');
});

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

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

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

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 muchrecursion(太多的递归)。主要的原因是,每次切换事件的时候,都保存下来,没有把无用的移除,导致越积越多,最后卡死。

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('Lee');
}, false);
window.addEventListener('load', function () {
alert('Mr.Lee');
}, false);

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

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

 
事件切换器

window.addEventListener('load', function () {
var box = document.getElementById('box');
box.addEventListener('click', function () { //不会被误删
alert('Lee');
}, 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('Lee');
}, 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('Lee');
return false; //直接给个假,就不会跳转了。
};

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

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

 
跨浏览器兼容

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 加载方面的事件, 关于这两个事件的内容非常多且繁杂,我们先点明在这里,在项目课程中使用的时候详细讨论。

分享到:
评论

相关推荐

    ASP.NET从入门到精通(源码及数据库)

    - **第十五章至第二十三章**:可能包含多个实际项目,如在线商城、博客系统等,让学习者将所学知识应用于实践中,提升开发技能。 6. **数据库部分** - 数据库文件可能包含SQL Server或SQLite等数据库的脚本和结构...

    北大青鸟 ACCP 之 asp.net 8 - 14 章

    **第二章:C#语言** C#是ASP.NET的主要编程语言,其语法清晰,支持面向对象编程。这一章可能深入讲解C#的基础语法,如变量、数据类型、控制结构(如if语句、循环)、类和对象、继承、多态等。 **第三章:ASP.NET...

    ASP.NET4.0从入门到精通课件(18章)

    ### 第十六章至第十八章:部署与发布 - **部署准备**:部署前需要注意的事项,如环境检查、配置文件调整等。 - **自动化部署工具**:介绍常用的自动化部署工具,如MSBuild、Visual Studio部署项目等。 - **发布与...

    C#程序开发范例宝典1(共20章)

    - **第二十章:性能优化** - 分析代码性能瓶颈,讲解内存管理、缓存策略以及并发优化的方法。 这本《C#程序开发范例宝典1》全面覆盖了C#编程的各个方面,通过实例演示和解析,使读者能够快速上手并深入理解C#语言...

    Asp.net夜话(word版)共11章

    第二章:ASP.NET内置对象 在这一章,作者详细讲解了ASP.NET中的一些重要内置对象,如Request、Response、Session、Application等,这些对象在实际开发中起着至关重要的作用,帮助开发者处理HTTP请求、响应、会话管理...

    疯狂Android讲义(第2版)源代码

    2. **第二章:Hello, Android** - 第一个Android程序:讲解如何编写“Hello, World”程序,理解AndroidManifest.xml的作用。 - 活动(Activity)与生命周期:介绍Activity的基本概念,以及启动、暂停、恢复和销毁...

    asp.net+深入编程中文教程

    - **第8章:ASP.NET的数据库编程**:涵盖数据库编程的基础知识、ADO.NET的核心概念、数据集的使用方法及数据绑定技术,帮助开发者构建高效的数据处理应用程序。 #### 九、应用程序配置与管理 - **第9章:应用程序*...

    C#与.NET3.5高级程序设计高清版(01-23)章

    8. **第二十三章:ADO.NET之二:断开连接层** - 在这个章节中,重点讨论了 disconnected模式的ADO.NET操作,如DataSet,DataTable,以及DataAdapter,这些在离线操作或批量处理数据时非常有用。 9. **第十六章:...

    《Android应用开发揭秘》源代码

    源代码部分涵盖了从第二章到第十六章,这意味着我们可以学习到从基础到进阶的Android开发知识。以下是这些章节源代码所涉及的关键知识点: 1. **第二章:Android开发环境搭建** - 这一章通常会介绍如何安装并配置...

    HTML5 The Definitive Guide to HTML5

    - **第26章:操作文档对象**:介绍如何使用JavaScript来访问和修改DOM树中的元素。 - **第27章:操作窗口对象**:探讨如何通过窗口对象来控制浏览器窗口的行为,如弹出新窗口、调整大小等。 - **第28章:操作DOM元素...

    Pro-WPF-Chapter21-30.rar_C# EDI

    6. **第26章:资源和依赖属性** 这一章重点介绍了资源字典的概念,以及依赖属性的工作原理,这两者都是WPF中复用和管理UI元素的重要机制。 7. **第27章:数据验证** 本章探讨了如何在WPF中实现数据验证,包括...

    Marco Cantu - Mastering Delphi 7

    #### 第二十四章:从Delphi视角看Microsoft .NET架构 .NET是微软推出的一套开发框架。本章介绍了Delphi如何与.NET架构融合,包括CLR(Common Language Runtime)和IL(Intermediate Language)等概念。 #### 第二...

    Visual C#.NET 程序设计课件

    第二章:C#语言基础 这一章主要讲解C#的基本语法,包括变量、常量、数据类型、运算符和表达式。学习者将了解如何声明和初始化变量,以及不同类型的数据如何在C#中使用。 第三章:控制流程 本章介绍了C#中的控制流程...

    [深入浅出Ext.JS.徐会生&何启伟&康爱媛)第1——6章.pdf

    文档标题提到该资料覆盖了《深入浅出Ext JS》的第1至第6章,这意味着内容将涵盖Ext JS的基础入门到进阶技巧。接下来,我们将对这些章节可能涉及的知识点进行总结: #### 第1章:Ext JS概述 - **Ext JS的历史与发展...

    Learn JavaFX 8

    ##### 第 9 章:事件处理 事件处理是 GUI 开发的核心。本章详细介绍了 JavaFX 的事件模型及其实现方式。 ##### 第 10 章:理解布局面板(Layout Panes) 布局面板是组织和排列 UI 组件的关键组件。本章介绍了 ...

    JSF核心编程(第3版)源码

    "第二章:组件模型"可能会介绍如何声明和使用内置组件,以及自定义组件的创建过程。"第三章:生命周期与渲染"则可能包含关于JSF请求处理周期的代码,让你理解从用户发起请求到响应返回的整个流程。 此外,"第四章:...

    ASP.NET课件(ppt)

    第二章:.NET Framework 深入探讨了.NET Framework的结构,包括Common Language Runtime(CLR)、类库以及如何使用Visual Studio作为开发环境。 第三章:Web Forms 介绍了ASP.NET Web Forms,它是ASP.NET中的主要...

Global site tag (gtag.js) - Google Analytics