JavaScript事件详解

JavaScript与HTML之间的交互是通过事件来实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以用侦听器来预订事件,以便事件发生的时候执行相应的代码。

 

事件流

事件流描述了从页面中接收事件的顺序,包括事件冒泡和事件捕获。

事件冒泡

事件最开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

譬如有如下嵌套的HTML页面:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Event</title>
</head>
<body><div><p>点击</p></div>
</body>
</html>

如果点击p元素,那么click事件首先在p元素上发生,这个元素是我们单击的元素。然后,click事件沿着DOM树向上传播,在每一级节点上都会发生,直到传播到document对象。传播顺序如下:p -> div -> body -> html -> document

事件捕获

事件捕获的思想是不太具体的节点应该更早接收事件,最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。

由于老版本浏览器不支持,因此很少有人使用事件捕获。

DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段
document -> html -> body -> div -> p-> div -> body -> html -> document

IE8及更早版本不支持DOM事件流

事件处理程序

响应事件的函数叫做事件处理程序或事件侦听器,我们可以通过如下方式为事件指定事件处理程序。

HTML事件处理程序

某个元素支持的每种事件都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行的JavaScript代码。

<input type="button" value="click me" onclick="alert('clicked')">

这样指定事件处理程序具有一些独到之处。首先,这样会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。

<!-- 输出 'click' -->
<input type="button" value="click me" onclick="alert(event.type)">

通过event变量,可以直接访问事件对象,不需要自己定义或者从函数的参数列表中读取。

在这个函数内部,this指向事件的目标元素,例如:

<!-- 输出 click me-->
<input type="button" value="click me" onclick="alert(this.value)">

关于这个动态创建的函数,另一个有意思的地方是它扩展作用域的方式。在这个函数内部,可以像访问局部变量一样访问document以及该元素本身的成员。这个函数使用with想下面这样扩展作用域:

function() {with(document) {with(this) {//元素属性}}
}

这样一来,我们就可以更简单的访问自己的属性,如下和前面的例子效果相同。

<!-- 输出 click me-->
<input type="button" value="click me" onclick="alert(value)">

如果当前元素是个表单输入元素,则表用域中还会包含访问表单元素(父元素)的入口,这个函数就变成了如下所示:

function() {with(document) {with(this.form) {with(this) {//元素属性}}}
}
<!-- username中的值 -->
<form action="bg.php"><input type="text" name="username"><input type="password" name="password"><input type="button" value="Click Me" onclick="alert(username.value)">
</form>

使用HTML事件处理程序的缺点

时差问题:用户可能会在HTML元素一出现在页面上就触发相应的事件,但是当时事件处理程序可能不具备执行条件。譬如:

<input type="button" value="click me" onclick="clickFun();">

 

假设clickFun函数是在页面最底部定义的,那么在页面解析该函数之前点击都会引发错误。因此,很多HTML事件处理程序都会被封装到try-catch之中:

<input type="button" value="click me" onclick="try{clickFun();}catch(ex){}">

浏览器兼容问题:这样扩展事件处理程序的作用域链在不同浏览器中会导致不同的结果。不同JavaScript引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。

代码耦合:HTML事件处理程序会导致HTML代码和JavaScript代码紧密耦合。如果要更改事件处理成程序需要同时修改HTML代码和JavaScript代码。

DOM0级事件处理程序

通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这样的优势一是简单,二是浏览器兼容性好。

var btn = document.getElementById('btn');
btn.onclick = function() {alert('clicked');
}

通过DOM0级方式指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this引用当前元素:

var btn = document.getElementById('btn');
btn.onclick = function() {alert(this.id); //输出 'btn'
}

我们可以在事件处理程序中通过this访问元素的任何属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。也可以删除通过DOM0级方法指定的事件处理程序:

btn.onclick = null;

如果我们使用HTML指定事件处理程序,那么onclick属性的值就是一个包含着在同名HTML特性中指定的代码的函数。

<input id="btn" type="button" value="click me" onclick="alert(123);">
<script>var btn = document.getElementById('btn');//输出function onclick(event) {  alert(123);} alert(btn.onclick); //单击按钮没反应btn.onclick = null;
</script>

DOM2级事件处理程序

“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener和removeEventListener。所有DOM节点中都包含这两个方法,并且都接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。如果这个布尔值参数为true,表示在捕获阶段调用事件处理函数;如果是false,表示在冒泡阶段调用事件处理函数。

var btn = document.getElementById('btn');
btn.addEventListener('click',function() {alert(this.id);
},false);

 

与DOM0级方法一样,添加的事件处理程序也是在其依附的元素的作用域中运行 ,另外,通过这种方式可以添加多个事件处理程序,添加的事件处理程序会按照添加它们的顺序出发。

var btn = document.getElementById('btn');
btn.addEventListener('click',function() {alert(this.id);
},false);
btn.addEventListener('click',function() {alert(this.type);
},false);

问题

我们给一个dom同时绑定两个点击事件,一个用捕获,一个用冒泡,那么事件的执行顺序是怎么样的?

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Event</title><style>div {padding: 30px;border: 1px solid #000;}</style>
</head>
<body><div id="one"><div id="two"><div id="three"><div id="four">Click Me</div></div></div></div><script>window.onload = function() {one.addEventListener('click',function(){alert('one');},true);two.addEventListener('click',function(){alert('two,bubble');},false);two.addEventListener('click',function(){alert('two,capture');},true);three.addEventListener('click',function(){alert('three,capture');},true);four.addEventListener('click',function(){alert('four');},true);}</script>
</body>
</html>

点击two,执行结果:one   two,bubble   two,capture

点击three,执行结果:one   two,capture   three,capture   two,bubble

分析:

绑定在被点击元素的事件是按照代码顺序发生,其他元素通过冒泡或者捕获“感知”的事件,按照W3C的标准,先发生捕获事件,后发生冒泡事件。所有事件的顺序是:其他元素捕获阶段事件 -> 本元素代码顺序事件 -> 其他元素冒泡阶段事件 。

 

通过addEventListener添加的事件处理程序只能用removeEventListener来移除;移除时传入的参数与添加处理程序时使用的参数相同,这也就意味着通过addEventListener添加的匿名函数无法移除。

var btn = document.getElementById('btn');btn.addEventListener('click',function() {alert(this.id);
},false);
btn.addEventListener('click',function() {alert(this.type);
},false);
//不能移除
btn.removeEventListener('click',function() {alert(this.type);
},false)

 大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。

 IE9+、Firefox、Safari、Chrome、Opera支持DOM2级事件处理程序。

IE事件处理程序

IE实现了类似的两个方法:attachEvent和detachEvent。这两个方法接收两个参数:事件处理程序名称和事件处理程序函数。由于IE8及更早版本只支持事件冒泡,所以通过attachEvent添加的事件处理程序都会被添加到冒泡阶段。

var btn = document.getElementById('btn');
btn.attachEvent('onclick',function() {alert('clicked');
})

注意第一个参数是onclick而不是click。

使用attachEvent与使用DOM0级方法的主要区别在于事件处理程序的作用域,使用attachEvent时,事件处理程序会在全局作用域中运行,因此this等于window。

var btn = document.getElementById('btn');
btn.attachEvent('onclick',function() {alert(this === window);  //true
})

利用attachEvent也可以为一个元素添加多个事件处理程序,但是这些事件处理程序并不是以添加它们的顺序执行,而是以相反的顺序被执行。

使用attachEvent添加的事件可以通过detachEvent来移除,条件是必须提供相同的参数,所以匿名函数将不能被移除。

支持IE事件处理程序的浏览器有IE和Opera,IE11开始将不再支持attachEvent和detachEvent。

跨浏览器的事件处理程序

function addEvent(element, type, handler) {if (element.addEventListener) {//事件类型、需要执行的函数、是否捕捉(false表示冒泡)//IE9+支持addEventListener,IE8及以下不支持addEventListenerelement.addEventListener(type, handler, false);} else if (element.attachEvent) {//IE11之后不再支持attachEvent//attachEvent添加的时间函数中this指向window//IE6-8只支持事件冒泡不支持事件捕获element.attachEvent('on' + type, handler);} else {element['on' + type] = handler;}
}// 移除事件
function removeEvent(element, type, handler) {if (element.removeEventListener) {element.removeEventListener(type, handler, false);} else if (element.datachEvent) {element.detachEvent('on' + type, handler);} else {element['on' + type] = null;}
}

事件对象

在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。

DOM中的事件对象

兼容DOM的浏览器会将一个event对象传入到事件处理程序中

var btn = document.getElementById('btn');
btn.onclick = function(event) {alert(event.type);
}
btn.addEventListener('click',function(event) {alert(event.type);
},false);
<input id="btn" type="button" value="click me" onclick="alert(event.type)">

常用属性和方法

属性方法      类型  读/写  说明

cancelable     Boolean 只读  表明是否可以取消事件的默认行为

currentTarget  Element 只读  其事件处理程序当前正在处理事件的那个元素、

eventPhase    Integer  只读  调用事件处理程序的阶段:1-捕获阶段,2-处于目标,3-冒泡阶段

preventDefault   Function  只读  取消事件默认行为,如果cancelable是true则可以使用这个方法

stopPropagation   Function 只读  取消事件的进一步捕获或者冒泡,同时阻止任何事件处理程序被调用(DOM3级事件中新增)

target      Element 只读  事件的目标

type       String  只读  被触发的事件的类型

在事件处理程序内部,this始终等于currentTarget的值,而target则只包含事件的实际目标

如果直接将事件处理程序指定给了目标元素,则this、currentTarget和target包含相同的值。

如果需要通过一个函数处理多个事件时,可以使用type属性:

var btn = document.getElementById('btn');
var handler = function(event) {switch(event.type) {case 'click':alert('click');break;case 'mouseover':alert('mouseover');break;case 'mouseout':alert('mouseout');break;}
}
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;

事件对象的eventPhase属性表示事件当前正位于事件流的哪个阶段,需要注意的是尽管“处于目标”发生在冒泡阶段,但是eventPhase仍然一支等于2,当eventPhase等于2时,this、target、currentTarget始终是相等的。

注意:只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完成,event对象就会被销毁。

 

IE中的事件对象

与访问DOM中的event对象不同,要访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。

var btn = document.getElementById('btn');
btn.onclick = function() {var event = window.event;alert(event.type);
}

IE9+中event对象也会作为参数被传入到事件处理程序中,但是IE9和IE10中参数event和window.event并不是同一个对象,而IE11中参数event和window.event为同一个对象。

var btn = document.getElementById('btn');
btn.onclick = function(event) {var event1 = window.event;alert(event === event1);  //IE11中为true
}

如果事件处理程序是使用attachEvent添加的,那么就会有一个event对象传入事件处理函数中,同时我们也可以通过window对象来访问event对象,但是它们是不相等的。

常用属性和方法

属性方法      类型  读/写  说明

cancelBubble   Boolean 读/写  默认值为false,将其设置为true可以消除事件冒泡

returnValue     Element 读/写   默认值为true,将其设置为false可以取消事件的默认行为

srcElement    Element 只读  事件的目标(相当于DOM中target属性)

type        String  只读  被触发的事件的类型

因为使用attachEvent添加的事件处理程序中this指向window,所以我们通常使用srcElement来代替this。

跨浏览器的事件对象

var EventUtil = {// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)stopPropagation : function(ev) {if (ev.stopPropagation) {ev.stopPropagation();} else {ev.cancelBubble = true;}},// 取消事件的默认行为preventDefault : function(event) {if (event.preventDefault) {event.preventDefault();} else {event.returnValue = false;}},// 获取事件目标getTarget : function(event) {return event.target || event.srcElement;},// 获取event对象的引用getEvent : function(event) {return event ? event : window.event;}
}

事件代理

 

因为事件有冒泡机制,所有子节点的事件都会顺着父级节点跑回去,所以我们可以通过监听父级节点来实现监听子节点的功能,这就是事件代理。

使用事件代理主要有两个优势:

  • 减少事件绑定,提升性能。之前你需要绑定一堆子节点,而现在你只需要绑定一个父节点即可。减少了绑定事件监听函数的数量。
  • 动态变化的 DOM 结构,仍然可以监听。当一个 DOM 动态创建之后,不会带有任何事件监听,除非你重新执行事件监听函数,而使用事件监听无须担忧这个问题。
addEvent(ul2, 'click', handler)
function addEvent(element, type, handler) {if (element.addEventListener) {element.addEventListener(type, handler, false);} else if (element.attachEvent) {element.attachEvent('on' + type, handler);} else {element['on' + type] = handler;}
}
function handler(ev) {var ev = ev || event;var target = ev.target || ev.srcElement;//找到a元素if (target.nodeName.toLowerCase() == 'a') {//a添加的事件}
}

jQuery的写法:

$('#ul1 a').on('click', function(){alert('正在监听');
});
//改为
$('#ul2').on('click', 'a', function(){alert('正在监听');
});

总结:

1. addEventListener()和attachEvent()的区别

  • addEventListener(type,handler,capture)有三个参数,其中type是事件名称,如click,handler是事件处理函数,capture是否使用捕获,是一个布尔值,一般为false,这是默认值,所以第三个参数可以不写。attachEvent('on'+type,handler)有两个参数,其中type是事件名称,如click,第一个参数必须是onxxxx,handler是事件处理函数,IE6 IE7 IE8不支持事件捕获,只支持事件冒泡。
  • addEventListener绑定的事件是先绑定先执行,attachEvent绑定的事件是先绑定后执行
  • 使用了attachEvent或detachEvent后事件处事函数里面的this指向window对象,而不是事件对象元素

2. 解决attchEvent事件处理函数中 this指向window的方法

1) 使用事件处理函数.apply(事件对象,arguments)
这种方式的缺点是绑定的事件无法取消绑定,原因上面已经说了,匿名函数和匿名函数之间是互不相等的。

var object=document.getElementById('xc');
function handler(){alert(this.innerHTML);
}
object.attachEvent('onclick',function(){handler.call(object,arguments);
});

2) 使用事件源代替this关键字
以下代码仅适用于IE6 IE7 IE8,这种方式完全忽略this关键字,但写起来稍显麻烦。

function handler(e){e = e||window.event;var _this = e.srcElement||e.target;alert(_this.innerHTML);
}
var object = document.getElementById('xc');
object.attachEvent('onclick',handler);

3) 写一个函数完全代替attachEvent/detachEvent,并且支持所有主流浏览器、解决IE6 IE7 IE8事件绑定导致的先绑定后执行问题。
注意,本函数是全局函数,而不是DOM对象的成员方法。

/** 添加事件处理程序* @param object object 要添加事件处理程序的元素* @param string type 事件名称,如click* @param function handler 事件处理程序,可以直接以匿名函数的形式给定,或者给一个已经定义的函数名。* @param boolean remove 是否是移除的事件,本参数是为简化下面的removeEvent函数而写的,对添加事件处理程序不起任何作用
*/
function addEvent(object,type,handler,remove){if(typeof object != 'object' || typeof handler != 'function') return;try{object[remove ? 'removeEventListener' : 'addEventListener'](type,handler,false);} catch( e ){var i, l, xc = '_' + type;object[xc] = object[xc] || [];if(remove){l = object[xc].length;for(i = 0;i < l;i++){if(object[xc][i].toString() === handler.toString()){object[xc].splice(i,1);}}} else{l = object[xc].length;var exists = false;for(i = 0;i < l;i++){                                                if(object[xc][i].toString() === handler.toString()) {exists = true;}}if(!exists) object[xc].push(handler);}object['on' + type] = function(){l = object[xc].length;for(i = 0;i < l;i++){object[xc][i].apply(object,arguments);}}}
}
/*
* 移除事件处理程序
*/
function removeEvent(object,type,handler){addEvent(object,type,handler,true);
}
转自:https://www.cnblogs.com/MarcoHan/p/5804362.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/249306.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript基础01

JavaScript查漏补缺 JavaScript有几种数据类型&#xff1f; 0. String(字符串) 1. Number(数值) 2. Boolean(布尔) 3. Null(空值) 4. Undefined(未定义) 5. Object(对象)前 5 种是基本类型 Null类型和Undefined类型的定义和区别&#xff1f; Null类型的值只有一个(null)&#…

.Net Core应用框架Util介绍(五)

上篇简要介绍了Util在Angular Ts方面的封装情况&#xff0c;本文介绍Angular封装的另一个部分&#xff0c;即Html的封装。 标准组件与业务组件 对于管理后台这样的表单系统&#xff0c;你通常会使用Angular Material或Ng-Zorro这样的UI组件库&#xff0c;它们提供了标准化的U…

SpringBoot中处理的转发与重定向

https://blog.csdn.net/yubin1285570923/article/details/83796003

scrapy爬虫系列之三--爬取图片保存到本地

功能点&#xff1a;如何爬取图片&#xff0c;并保存到本地 爬取网站&#xff1a;斗鱼主播 完整代码&#xff1a;https://files.cnblogs.com/files/bookwed/Douyu.zip 主要代码&#xff1a; douyu.py import scrapy import json from Douyu.items import DouyuItemclass DouyuSp…

glup server 报错 Task function must be specified

解决方案 今天像往常一样&#xff0c;编写文章&#xff0c;并使用gulp bulid压缩代码&#xff0c;但是一运行&#xff1a;gulp build 就出现了这个错误&#xff1a;AssertionError: Task function must be specified。 gulp项目需要全局安装gulp和项目内安装gulp&#xff0c;…

mybatis Example 使用方法

一、mapper接口中的方法解析 mapper接口中的函数及方法 方法 功能说明 int countByExample(UserExample example) thorws SQLException 按条件计数 int deleteByPrimaryKey(Integer id) thorws SQLException 按主键删除 int deleteByExample(UserExample example) thorws SQLE…

gulp + browsersync实现页面自动刷新

写习惯了vue&#xff0c;特别喜欢vue的自动刷新功能&#xff0c;于是琢磨在node中如何自动刷新&#xff0c;使用过nodemon&#xff0c; 但是感觉效果差点&#xff0c;看到网上有gulp livereload的方案和gulp browsersync的方案&#xff0c;但都是褒贬不一&#xff0c;先简单记…

[JZOJ5836] Sequence

Problem 题目链接 Solution 吼题啊吼题&#xff01; 首先如何求本质不同的子序列个数就是 \(f[val[i]]1\sum\limits_{j1}^k f[j]\) 其中 \(f[i]\) 表示的是以 \(i\) 结尾的子序列个数 先把原数列的不同子序列个数求出来&#xff0c;然后观察一下这个转移&#xff0c;贪心的发现…

numpy和pandas的基础索引切片

Numpy的索引切片 索引 In [72]: arr np.array([[[1,1,1],[2,2,2]],[[3,3,3],[4,4,4]]]) In [73]: arr Out[73]: array([[[1, 1, 1],[2, 2, 2]],[[3, 3, 3],[4, 4, 4]]])In [74]: arr.nd…

mybatis的Example[Criteria]的使用

https://blog.csdn.net/u014756578/article/details/86490052

Thunar 右键菜单等自定义

Thunar 右键菜单等自定义 可以使用图形界面或者直接编辑配置文件&#xff0c;二者是等价的。 图形界面&#xff1a; 以给“zip&#xff0c;rar&#xff0c;7z”等文件添加“在此位置使用unar解压缩”的右键菜单为例&#xff1a;&#xff08;unar可以很好地处理编码问题&#xf…

JavaScript设计模式(二)之单例模式

一、单例模式的定义 单例就是保证一个类只有一个实例&#xff0c;实现的方法一般是先判断实例存在与否&#xff0c;如果存在直接返回&#xff0c;如果不存在就创建后再返回&#xff0c;这就确保了一个类只有一个实例对象。在JavaScript里&#xff0c;单例作为一个命名空间的提…

python全栈开发_day10_函数的实参和形参

一&#xff1a;函数的实参和形参 实参是在调用函数时()出现的外界的实际的值 形参不能再函数外部直接使用 1&#xff09;实参的两种形式 实参是调用函数时()中传入的参数 1.位置实参 def a(a):print(a)a(1)#得到返回值:1 2.关键字实参 def a(a,b):print(a,b)a(b3,a5)#得到返回值…

JAVA的(PO,VO,TO,BO,DAO,POJO)解释

JAVA的(PO,VO,TO,BO,DAO,POJO)解释 O/R Mapping 是 Object Relational Mapping&#xff08;对象关系映射&#xff09;的缩写。通俗点讲&#xff0c;就是将对象与关系数据库绑定&#xff0c;用对象来表示关系数据。在O/R Mapping的世界里&#xff0c;有两个基本的也是重要的东东…

使用wsimport命令生成webService客户端代码实例

https://blog.csdn.net/qq_39459412/article/details/79079865

学习网站大汇集

一.综合类学习网站&#xff08;中文&#xff09; 1.网易公开课&#xff1a;https://open.163.com/。上面有TED、可汗学院、国内外高校公开课的免费资源。站内内容完全免费&#xff0c;良心推荐。 2.网易云课堂&#xff1a;http://study.163.com/。网易旗下付费学习平台&#…

ios怎样在一个UIImageButton的里面加一些自己定义的箭头

能够採用例如以下方法&#xff0c;写一个函数&#xff1a; -(UIImage*) getOneImageButtonWithArrow{//tmpView做附控件UIView *tmpView [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 38.0f, 32.0f)];tmpView.backgroundColor [UIColor clearColor];//bgImg作为背景…

vue从入门到精通之基础篇(一)语法概要

(1).vue起步 1:引包2:启动 new Vue({el:目的地,template:模板内容});options 目的地 el内容 template数据 data 保存数据属性 数据驱动视图 (2).插值表达式 {{ 表达式 }} 对象 (不要连续3个{{ {name:‘jack’} }})字符串 {{ ‘xxx’ }}判断后的布尔值 {{ true }}三元表达式…

dede 文章列表页如何倒序排列

{dede:arclist row6 typeid18 orderwayasc} <li>;<a href"[field:arcurl/]">[field:title/]</a></li> {/dede:arclist} 正常排列&#xff1a;orderwayasc倒序排列&#xff1a;orderwaydesc转载于:https://www.cnblogs.com/php-qiuwei/p/1062…

Chapter 5 Blood Type——24

"Shes just a little faint," he reassured the startled nurse. "Theyre blood typing in Biology." "她只是有点头晕&#xff0c;" 他让护士放心的说道。“他们再生物课上测血型。” The nurse nodded sagely. "Theres always one."…