深入浅出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. PAT甲题题解-1061. Dating (20)-字符串处理,水水

    #include <iostream> #include <cstdio> #include <algorithm> #include <string.h&g ...

  2. PAT甲题题解-1109. Group Photo (25)-(模拟拍照排队)

    题意:n个人,要拍成k行排队,每行 n/k人,多余的都在最后一排. 从第一排到最后一排个子是逐渐增高的,即后一排最低的个子要>=前一排的所有人 每排排列规则如下: 1.中间m/2+1为该排最高: ...

  3. LeetCode-----算法448.找到所有数组中消失的数字

    题目: 给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次. 找到所有在 [1, n] 范围之间没有出现在数组中的数字. ...

  4. 冲刺Two之站立会议2

    今天我们进行了主界面部分的设置,因为它包含的部分有很多,所以就只能它拆分进行一一突破.今天主要完成了主界面的框架搭建,以及添加了需要的按钮,包括好友管理,退出登录,开启聊天通信界面的内容等.

  5. React组件继承的由来

    没有显式继承的时候我们这么写: import * as React from "react"; export interface HelloProps { compiler: st ...

  6. 《Toward an SDN-Enabled Big Data Platform for Social TV Analysis》--2015--Han Hu

    <面向应用于社会TV分析的应用了SDN的大数据平台> Abstract social TV analytics 是什么,就是说很多TV观众在微博.微信和推特等这些地方分享他们的观感时,然后 ...

  7. 新安装的Ubuntu设置root密码

    一.问题描述 新安装的Ubuntu切换到root用户时如果没有设置root用户密码会操作失败.此时需要先设置root用户密码. 二.解决办法 打开终端执行 sudo passwd 命令. 输入设置的密 ...

  8. Linux命令(二十三) 磁盘管理命令(一) df,du,tune2fs

    一. 查看磁盘占用空间情况 df df 命令用于查看硬盘空间的使用情况,还可以查看硬盘分区的类型或 inode 节点的使用情况等. df 命令常用参数如下: -a 显示所有文件系统的磁盘使用情况,包括 ...

  9. asp、asp.net、.aspx、.ascx、.ashx的简单说明

    ASP是动态server页面(Active Server Page)的英文缩写.[1]是微软公司开发的取代CGI脚本程序的一种应用.它能够与数据库和其他程序进行交互,是一种简单.方便的编程工具.ASP ...

  10. 洛谷P3385 【模板】负环(DFS求环)

    洛谷题目传送门 HNOI爆零前回刷模板题 非常不正经的题目,目前并没有合适的优秀算法,就算是大家公认的dfs(还是不要强行叫dfs-spfa吧,概念应该不一样,这就是暴力dfs松弛答案) 但是对于随机 ...