深入浅出js事件

一.事件流

事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念是为了解决页面中事件流(事件发生顺序)的问题。

<div id="outer">
<p id="inner">Click me!</p>
</div>

上面的代码当中一个div元素当中有一个p子元素,如果两个元素都有一个click的处理函数,那么我们怎么才能知道哪一个函数会首先被触发呢?

为了解决这个问题微软和网景提出了两种几乎完全相反的概念。

1.事件冒泡

微软提出了名为事件冒泡的事件流。事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。

因此上面的例子在事件冒泡的概念下发生click事件的顺序应该是p -> div -> body -> html -> document

2.事件捕获

网景提出另一种事件流名为事件捕获与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。

上面的例子在事件捕获的概念下发生click事件的顺序应该是document -> html -> body -> div -> p

3.W3C事件阶段(event phase):

当一个DOM事件被触发的时候,他并不是只在它的起源对象上触发一次,而是会经历三个不同的阶段。简而言之:事件一开始从文档的根节点流向目标对象(捕获阶段),然后在目标对向上被触发(目标阶段),之后再回溯到文档的根节点(冒泡阶段)如图所示(图片来自W3C):

事件捕获阶段(Capture Phase)

事件从文档的根节点出发,随着DOM树的结构向事件的目标节点流去。途中经过各个层次的DOM节点,并在各节点上触发捕获事件,直到到达时间的目标节点。捕获阶段的主要任务是简历传播路径,在冒泡阶段,时间会通过这个路径回溯到文档根节点。

例如,通过下面的这个函数来给节点设置监听,可以通过将;设置成true来为事件的捕获阶段添加监听回调函数。

element.removeEventListener(&ltevent-name>, <callback>, <use-capture>);

而,在实际应用中,我们并没有太多使用捕获阶段监听的用例,但是通过在捕获阶段对事件的处理,我们可以阻止类似click事件在某个特定元素上被触发。如下:

var form=document.querySeletor('form');
form.addEventListener('click',function(e){
e.stopPropagation();
},true);

如果你对这种用法不是很了解的话,建议设置为false或者undefined,从而在冒泡阶段对事件进行监听,这也是常用的方法。

目标阶段(Target Phase)

当事件到达目标节点时,事件就进入了目标阶段。事件在目标节点上被触发,然后逆向回流,知道传播到最外层的文档节点。

对于多层嵌套的节点,鼠标和指针事件经常会被定位到最里层的元素上。假设,你在一个div元素上设置了click的监听函数,而用户点击在了这个div元素内部的p元素上,那么p元素就是这个时间的目标元素。事件冒泡让我们可以在这个div或者更上层的元素上监听click事件,并且时间传播过程中触发回调函数。

冒泡阶段(Bubble Phase)

事件在目标事件上触发后,并不在这个元素上终止。它会随着DOM树一层层向上冒泡,直到到达最外层的根节点,一直向上传播,直到document对象。也就是说,同一事件会一次在目标节点的父节点,父节点的父节点...直到最外层的节点上触发。

绝大多数事件是会冒泡的,但并非所有的。

See the Pen dojdVG by jingwhale (@jingwhale) on CodePen.

二.事件代理(event delegation)

在JavaScript中,经常会碰到要监听列表中多项li的情形,假设我们有一个列表如下:

<ul id="list">
<li id="item1">item1</li>
<li id="item2">item2</li>
<li id="item3">item3</li>
<li id="item4">item4</li>
</ul>

如果我们要实现以下功能:当鼠标点击某一li时,alert输出该li的内容,我们通常的写法是这样的:

-当列表项比较少时,直接在html里给每个li添加onclick事件
-列表项比较多时,在onload时就给每个列表项调用监听

第一种方法比较简单直接,但是没有顾及到html与JavaScript的分离,不建议使用,第二种方法的代码如下:

window.onload=function(){
var ulNode=document.getElementById("list");
var liNodes=ulNode.childNodes||ulNode.children;
for(var i=0;i<liNodes.length;i++){
liNodes[i].addEventListener('click',function(e){
alert(e.target.innerHTML);
},false);
}
}

由上可以看出来,假如不停的删除或添加li,则function()也要不停的更改操作,易出错,因此推荐使用事件代理。

在传统的事件处理中,你按照需要为每一个元素添加或者是删除事件处理器。然而,事件处理器将有可能导致内存泄露或者是性能下降——你用得越多这种风险就越大。JavaScript事件代理则是一种简单的技巧,通过它你可以把事件处理器添加到一个父级元素上,这样就避免了把事件处理器添加到多个子级元素上。

事件代理机制

事件代理用到了两个在JavaSciprt事件中两个特性:事件冒泡以及目标元素。使用事件代理,我们可以把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,并且可以得知这个事件是从哪个元素开始的。

事件代理实现

例如,有一个table元素,ID是“report”,我们为这个表格添加一个事件处理器以调用editCell函数。

第一步,找到目标元素

editCell函数需要判断传到table来的事件的目标元素。考虑到我们要写的几个函数中都有可能用到这一功能,所以我们把它单独放到一个名为getEventTarget的函数中:

function getEventTarget(e) {
e = e || window.event;//事件对象
return e.target || e.srcElement;
}

在IE里目标元素放在srcElemnt属性中,而在其它浏览器里则是target属性。

第二步,判断目标元素,进行相关操作

接下来就是editCell函数了,这个函数调用到了getEventTarget函数。一旦我们得到了目标元素,剩下的事情就是看看它是否是我们所需要的那个元素了。

function editCell(e){
var target = getEventTarget(e);
if(target.tagName.toLowerCase() =='td')
{
// DO SOMETHING WITH THE CELL
}
}

由上叙述,我们可以使用事件代理来实现本小结开始对每一个li的监听。第三种方法,代码如下:

window.onload=function(){
e = e || window.event;
target = e.target || e.srcElement;
var ulNode=document.getElementById("list");
ulNode.addEventListener('click',function(e){
if(target&&target.nodeName.toUpperCase()=="LI"){/*判断目标事件是否为li*/
alert(e.target.innerHTML);
}
},false);
};

注:

-tagName 属性返回元素的标签名。在 HTML 中,tagName 属性的返回值始终是大写的。

-nodeName 属性指定节点的节点名称。如果节点是元素节点,则 nodeName 属性返回标签名。如果节点是属性节点,则 nodeName 属性返回属性的名称。对于其他节点类型,nodeName 属性返回不同节点类型的不同名称。试一试

三.事件兼容处理

现代绑定中W3C 使用的是:addEventListener 和removeEventListener。IE 使用的是attachEvent 和detachEvent。我们知道IE 的这两个问题多多,并且伴随内存泄漏。所以,解决这些问题非常有必要。

那么我们希望解决非IE 浏览器事件绑定哪些问题呢?

1)支持同一元素的同一事件句柄可以绑定多个监听函数;
2)如果在同一元素的同一事件句柄上多次注册同一函数,那么第一次注册后的所有注册
都被忽略;
3)函数体内的this 指向的应当是正在处理事件的节点(如当前正在运行事件句柄的节
点);
4)监听函数的执行顺序应当是按照绑定的顺序执行;
5)在函数体内不用使用event = event || window.event; 来标准化Event 对象;

设计原理

1.通过使用传统事件绑定对IE 进行封装,模拟现代事件绑定(不使用attachEvent/detachEvent)。

2.把IE常用的Event对象配对到W3C中去

//跨浏览器添加事件绑定
function addEvent(obj, type, fn) {
if (typeof obj.addEventListener != 'undefined') {
obj.addEventListener(type, fn, false);
} else {
//创建一个存放事件的哈希表(散列表)
if (!obj.events) obj.events = {};
//第一次执行时执行
if (!obj.events[type]) {
//创建一个存放事件处理函数的数组
obj.events[type] = [];
//把第一次的事件处理函数先储存到第一个位置上
if (obj['on' + type]) obj.events[type][0] = fn;
} else {
//同一个注册函数进行屏蔽,不添加到计数器中
if (addEvent.equal(obj.events[type], fn)) return false;
}
//从第二次开始我们用事件计数器来存储
obj.events[type][addEvent.ID++] = fn;
//执行事件处理函数
obj['on' + type] = addEvent.exec;
}
} //为每个事件分配一个计数器
addEvent.ID = 1; //执行事件处理函数
addEvent.exec = function (event) {
var e = event || addEvent.fixEvent(window.event);
var es = this.events[e.type];
for (var i in es) {
es[i].call(this, e);
}
}; //同一个注册函数进行屏蔽
addEvent.equal = function (es, fn) {
for (var i in es) {
if (es[i] == fn) return true;
}
return false;
} //把IE常用的Event对象配对到W3C中去
addEvent.fixEvent = function (event) {
event.preventDefault = addEvent.fixEvent.preventDefault;
event.stopPropagation = addEvent.fixEvent.stopPropagation;
event.target = event.srcElement;
return event;
}; //IE阻止默认行为
addEvent.fixEvent.preventDefault = function () {
this.returnValue = false;
}; //IE取消冒泡
addEvent.fixEvent.stopPropagation = function () {
this.cancelBubble = true;
}; //跨浏览器删除事件
function removeEvent(obj, type, fn) {
if (typeof obj.removeEventListener != 'undefined') {
obj.removeEventListener(type, fn, false);
} else {
if (obj.events) {
for (var i in obj.events[type]) {
if (obj.events[type][i] == fn) {
delete obj.events[type][i];
}
}
}
}
}

addEvent的优点
-可以在所有浏览器中工作,就算是更古老无任何支持的浏览器
-this关键字可以在所有的绑定函数中使用,指向的是当前元素
-中和了所有防止浏览器默认行为和阻止事件冒泡的各种浏览器特定函数
-不管浏览器类型,事件对象总是作为第一个对象传入

addEvent的缺点
-仅工作在冒泡阶段(因为它深入使用事件绑定的传统方式)

知识说明:

添加、移除事件,代码如下:

var addEvent = document.addEventListener ?
function(elem,type, listener, useCapture) {
elem.addEventListener(type, listener, useCapture);
}:
function(elem, type, listener, useCapture) {
elem.attachEvent('on' + type, listener);
}; var delEvent = document.removeEventListener ?
function(elem, type, listener, useCapture) {
elem.removeEventListener(type, listener, useCapture)
}:
function(elem, type, listener, useCapture) {
elem.detachEvent('on' + type, listener);
};

阻止事件继续传播,代码如下:

function stopEvent (evt) {
var evt = evt || window.event;
if (evt.stopPropagation) {
evt.stopPropagation();
} else {
evt.cancelBubble = true;
}
}

取消默认行为,代码如下:

function stopEvent (evt) {
var evt = evt || window.event;
if (evt.preventDefault) {
evt.preventDefault();
} else {
evt.returnValue = false;
}
}

四.事件类型

事件类型有:UI(用户界面)事件,用户与页面上元素交互时触发 ;焦点事件:当元素获得或失去焦点时触发 ; 文本事件:当在文档中输入文本时触发;键盘事件:当用户通过键盘在页面上执行操作时触发;鼠标事件:当用户通过鼠标在页面上执行操作时触发;滚轮事件:当使用鼠标滚轮(或类似设备)时触发。它们之间是继承的关系,如下图:

1.

常用:window、image。例如设置默认的图片:

<img src="photo" alt="photo.jpg" onerror="this.src='defualt.jpg'">

2.

3.

4.

5.

6.

MouseEvent 顺序
从元素A上方移过
-mousemove-> mouseover (A) ->mouseenter (A)-> mousemove (A) ->mouseout (A) ->mouseleave (A)
点击元素
-mousedown-> [mousemove]-> mouseup->click

7.

详细使用,请参考js事件手册。如:http://www.w3school.com.cn/jsref/dom_obj_event.asp

-------------------------------------------------------------------------------------------------------------------------------------

转载需注明转载字样,标注原作者和原博文地址。

更多阅读:

http://blog.sina.com.cn/s/blog_5f54f0be0100cy49.html

http://www.cnblogs.com/luhangnote/archive/2012/08/16/2642657.html

深入浅出js事件的更多相关文章

  1. js事件循环机制辨析

     对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...

  2. dynamic-css 动态 CSS 库,使得你可以借助 MVVM 模式动态生成和更新 css,从 js 事件和 css 选择器的苦海中脱离出来

    dynamic-css 使得你可以借助 MVVM 模式动态生成和更新 css,从而将本插件到来之前,打散.嵌套在 js 中的修改样式的代码剥离出来.比如你要做元素跟随鼠标移动,或者根据滚动条位置的变化 ...

  3. 什么是JS事件冒泡?

    什么是JS事件冒泡?: 在一个对象上触发某类事件(比如单击onclick事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理 程序或者事件返回true,那么 ...

  4. js事件技巧方法整合

    window.resizeTo(800,600); //js设置浏览器窗口尺寸 window.open (function(){ resizeTo(640,480);//设置浏览器窗口尺寸 moveT ...

  5. js事件浅析

    js中关于DOM的操作很多,因此js事件机制也就尤为重要. 事件绑定形式: 一. 内联形式 耦合度高,不利于维护 <button onclick="alert('你点击了这个按钮'); ...

  6. js 事件大全

    Js事件大全一般事件 事件 浏览器支持 描述onClick IE3|N2|O3 鼠标点击事件,多用在某个对象控制的范围内的鼠标点击onDblClick IE4|N4|O 鼠标双击事件onMouseDo ...

  7. 原生JS事件绑定方法以及jQuery绑定事件方法bind、live、on、delegate的区别

    一.原生JS事件绑定方法: 1.通过HTML属性进行事件处理函数的绑定如: <a href="#" onclick="f()"> 2.通过JavaS ...

  8. JS事件

    JS事件:  声明:为了事件对象event跨浏览器兼容: var oEvent==ev||event;      所以在下面用到 event 的地方都用 oEvent 代替  1)doucument的 ...

  9. 原生js事件和jquery事件的执行顺序问题

    场景:近日,写前端页面时候,在针对输入框input操作时,用到了jquery的插件,插件中使用了jquery的focus()和blur()方法.但是同时,又需要在插件之外再针对输入框的获取焦点和失去焦 ...

随机推荐

  1. sql 更新多条记录

    转载:https://www.cnblogs.com/hfultrastrong/p/6905057.html 如果你想更新多行数据,并且每行记录的各字段值都是各不一样,你会怎么办呢?本文以一个示例向 ...

  2. Spark 实践——音乐推荐和 Audioscrobbler 数据集

    本文基于<Spark 高级数据分析>第3章 用音乐推荐和Audioscrobbler数据 完整代码见 https://github.com/libaoquan95/aasPractice/ ...

  3. We are a 团队

    虽然在团队中只是拖后腿的存在,但是几个人一起摸索着前进也确实有着不一样的感觉. 我们队伍共有五个人:董强强.张振鑫.王鼎.高庆阳还有我(排名不分先后) 我们有自己的关于软件工程的QQ群,会在群里讨论一 ...

  4. QT试验(1)

    今天,用Qt Creator写第一个简单的小程序“HelloQt”,运行时报错:error: cannot open C:\Users\敏\AppData\Local\Temp\main.obj.51 ...

  5. Java反射的用法

    Class类 在Java中,每个class都有一个相应的Class对象.也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息. 反射 ...

  6. ES6学习笔记(四):异步操作

    Promise Promise三种状态 pending.resolved.rejected 使用语法 var promis = new Promise(function(resolve,reject) ...

  7. Linux中dpkg工具update-alternatives实现符号链接软件版本的切换(转)

    一.功能作用 update-alternatives是dpkg的实用工具,用来维护系统命令的符号链接,以决定系统默认使用什么命令. 在Debian系统中,我们可能会同时安装有很多功能类似的程序和可选配 ...

  8. UVA - 10129 Play on Words (欧拉回路+并查集)

    思路: 分别存下每个字符串的首尾字符,以字符为结点,单词看作一条变,就变成了求欧拉回路了,先判断下图是否连通,然后根据欧拉回路的结论:最多只能有两个点的入读不等于初读,而且必须是一个点的出度恰好比入度 ...

  9. Spring定时服务QuartZ

    在JavaEE系统中,我们会经常用到定时任务,比如每天凌晨生成前天报表,每一小时生成汇总数据等等. 我们可以使用java.util.Timer结合java.util.TimerTask来完成这项工作, ...

  10. 浅谈范德蒙德(Vandermonde)方阵的逆矩阵的求法以及快速傅里叶变换(FFT)中IDFT的原理

    浅谈范德蒙德(Vandermonde)方阵的逆矩阵与拉格朗日(Lagrange)插值的关系以及快速傅里叶变换(FFT)中IDFT的原理 标签: 行列式 矩阵 线性代数 FFT 拉格朗日插值 只要稍微看 ...