JavaScript学习之事件原理和实践
1 基本概念
1.1 事件
JavaScript与HTML之间的交互是通过事件实现的。
事件是文档或浏览器窗口中发生的一些特定的交互瞬间,在事件上可以注册处理程序,以便当事件发生时,处理程序中的代码得到执行(这种模型对应设计模式中的观察者模式)。
1.2 事件流
事件流描述的是从页面接受事件的顺序。
2 事件流
2.1 事件冒泡
IE事件流叫做事件冒泡,即事件开始由最具体的元素接受,然后逐渐向上传播到较为不具体的节点。
事件冒泡能够被所有浏览器支持。注意IE5.5及更早版本的事件冒泡会跳过<html>元素(从<body>直接跳到document)。IE9,Firefox,Chrome和Safari则将事件一直冒泡到window对象。
2.2 事件捕获
事件捕获是由不太具体的节点最早接受到事件,而最具体的元素最后接受到事件。事件捕获的用意在于在事件到达预定的目标之前捕获它。
事件捕获不支持老版本的浏览器,但IE9,Firefox,Chrome,Opera和Safari目前都支持这种事件流模型。
2.3 DOM事件流
"DOM2事件流"包括三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段;在DOM事件流中,实际的目标在捕获阶段不会接受到事件(但IE9、Safari、Chrome、Firefox和Opera 9.5及更高版本都会在捕获阶段触发事件对象上的事件,即有两个机会在目标对象上面操作事件)。
IE9,Firefox,Chrome,Opera和Safari都支持DOM事件流;IE8及更早版本不支持DOM事件流。
3 事件处理程序
事件是用户或浏览器自身执行的某种动作,常见的事件有click,focus,load;而响应事件的函数叫做事件处理程序(或事件侦听器)。事件处理程序的名字以"on"开头,例如click事件对应的事件处理程序就是onclick,focus事件对应的事件处理程序就是onfocus,load事件对应的事件处理程序就是onload。
3.1 HTML事件处理程序
HTML元素支持的每种事件,都可以用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行的JavaScript代码。例如:
<input type="button" value="click me" onclick="showMessage()"/>
HTML中指定事件处理程序有两个缺点:
首先存在时差问题。如果在HTML元素一出现在页面就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。具体说来,上例中如果事件处理程序showMessage是在按钮下方,页面底部定义的,当用户在页面解析该函数之前就单击了按钮,就会引发错误;
另一个缺点是,这样扩展事件处理程序的作用链在不同浏览器中会导致不同结果。
通过HTML指定事件处理程序的另一个缺点是HTML和JavaScript代码紧密耦合。如果要更换事件处理程序,需要同时改动两个地方:HTML代码和JavaScript代码。
3.2 DOM0级事件处理程序
指定事件处理程序的传统方式就是将一个函数赋值给一个事件处理程序属性。这种方式优点:1.简单,2.具有跨浏览器的优势。为元素指定事件处理程序分两步:
(1)取得要操作对象的引用;
(2)将该元素的事件处理程序属性如onclick设置为一个函数。示例如下:
<input type="button" value="click me" id="myBtn"/>
<script>
var btn = document.getElementById('myBtn');
btn.onclick = function(){ alert("I am clicked!"); }
</script>
使用DOM0级方法指定的事件处理程序被认定为是元素的方法,即事件处理程序是在元素的作用域中运行,此时this引用当前元素。
删除DOM0级事件处理程序:btn.onclick = null;
3.3 DOM2级事件处理程序
DOM2级事件定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。这两个方法都接受三个参数:要处理的事件名称,事件处理程序函数,布尔值。其中布尔值为true表示在捕获阶段调用事件处理程序,否则表示在冒泡阶段调用事件处理程序。
与DOM0级事件处理程序一样,DOM2级事件处理程序也是在依附的元素的作用域中运行。使用DOM2级方法添加事件处理程序的好处是可以添加多个事件处理程序。示例如下:
<input type="button" value="click me" id="myBtn"/>
<script>
var btn = document.getElementById('myBtn'); btn.addEventListener("click", function(){
alert("first");
}, false);
btn.addEventListener("click", function(){
alert("second");
}, false);
</script>
运行后,先后弹内容为"first"和"second"的对话框,说明可以通过addEventListener为元素添加多个事件处理程序。
通过addEventListener()添加的事件处理程序只能通过removeEventListener()来移除;移除时传入的参数与添加时传入的参数相同。注意通过addEventListener()添加的匿名函数将无法移除。
<input type="button" value="click me" id="myBtn"/>
<script>
var btn = document.getElementById('myBtn'); btn.addEventListener("click", function(){
alert("first");
}, false); btn.removeEventListener("click", function(){ //失效:删除匿名函数并不是同一个函数
alert("first");
}, false); var secondFunc = function(){
alert("second");
};
btn.addEventListener("click", secondFunc, false);
btn.removeEventListener("click", secondFunc, false);//成功:删除同一个处理函数
</script>
运行后只有一个弹窗,内容为"first",说明在使用removeEventListener()删除addEventListener()添加的事件处理函数时,必须保证第二个参数非匿名函数。
DOM2级事件处理程序的添加大多数都是在事件的冒泡阶段(第三个参数为false),这样可以最大限度地兼容各种浏览器。
IE9,Firefox,Chrome,Opera和Safari支持DOM2级事件处理程序。
3.4 IE事件处理程序
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的两个参数:事件处理程序名称和事件处理程序函数。由于IE8及更早的版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
在IE中使用attachEvent()与使用DOM0级方法的主要区别是事件处理程序的作用域,使用attachEvent()方法的情况下,事件处理程序会在全局作用域运行,因此this === window。
attachEvent()添加多个事件处理程序,执行顺序与添加顺序相反。
使用attachEvent()添加的事件可以通过detachEvent()来移除,条件是必须提供相同的参数。与DOM方法一样,添加的匿名函数不能被移除。
支持IE事件处理程序的浏览器有IE和Opera。
3.5 跨浏览器的事件处理程序
要保证事件处理程序在大多数浏览器中一致地运行,只需关注冒泡阶段。
var EventUtil = {
	addHandler: function(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;
		}
	},
	removeHandler: function(element, type, handler){
		if(element.removeEventListener){
			element.removeEventListener(type, handler, false);
		}
		else if(element.detachEvent){
			element.detachEvent("on" + type, handler);
		}
		else{
			element["on" + type] = null;
		}
	}
};
4 事件对象
4.1 DOM事件对象
兼容DOM(DOM0级和DOM2级)的浏览器会将一个event对象传入到事件处理程序中。
要阻止特定的默认行为用preventDefault()方法,要立即停止事件在DOM层次中的传播用stopPropagation()方法。
在事件处理程序中,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。
4.2 IE事件对象
与访问DOM中的event对象不同,IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。
使用DOM0级方法添加事件处理程序时,event作为window对象的一个属性存在;如果使用attachEvent()添加事件处理程序,那么就会有一个event对象作为参数被传入事件处理函数中(也可以通过window对象来访问event对象)。
IE的event对象包含于创建它的事件相关的属性和方法。
其中cancelBubble设置为true与DOM中stopPropagation()方法的作用相同;
returnValue设置为false与DOM中preventDefault()方法的作用相同。
srcElement表示事件的目标,与DOM中target属性相同。
type被触发的事件类型与DOM中type属性相同。
4.3 跨浏览器的事件对象
在前面的EventUtil中添加方法来处理DOM和IE中event对象的差异(以下只展示添加的代码):
getEvent: function(event){
  return event ? event : window.event;
},
getTarget: function(event){
  return event.target || event.srcElement;
},
preventDefalut: function(event){
  if(event.preventDefalut){
    event.preventDefalut();  
	}
  else{
    event.returnValue = false;
  }
},
stopPropagation: function(event){
  if(event.stopPropagation){
    event.stopPropagation();
  }
  else{
    event.cancelBubble = true;
  }
}
5 事件委托
5.1 内存和性能
在JavaScript中,添加到页面上的事件处理程序的数量直接关系到页面的整体运行性能:
首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。
其次,必须事先指定所有事件处理程序,会导致DOM访问次数增加,会延迟整个页面的交互就绪时间。
5.2 事件委托
事件委托就是事件目标本身不处理事件,而是把处理任务委托给其父元素或祖先元素。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
通过将事件处理程序委托给父元素或祖先元素,事件委托解决了"事件处理程序过多"的问题。
假定现在有个ul无序列表(id为items),其中li(id为item+序号)代表每个项,当点击项时需弹窗显示项的基本信息。
<ul id="items">
<li id="item1">无序列表1</li>
<li id="item2">无序列表2</li>
<li id="item3">无序列表3</li>
</ul>
最简单的实现方式就是直接在li元素上进行事件绑定(addHandler是跨浏览器的添加事件处理程序函数,具体实现见JavaScript事件章节):
var item1 = document.getElementById('item1');
var item2 = document.getElementById('item2');
var item3 = document.getElementById('item3');
EventUtil.addHandler(item1, "click", function(){
	alert("I am item1!");
});
EventUtil.addHandler(item2, "click", function(){
	alert("I am item2!");
});
EventUtil.addHandler(item3, "click", function(){
	alert("I am item3!");
});
使用事件委托,只需在DOM树种尽量最高的层次添加一个事件处理程序。
var items = document.getElementById('items');
EventUtil.addHandler(items, "click", function(event){
	var event = EventUtil.getEvent(event);
	var target = EventUtil.getTarget(event);
	alert("I am " + target.id);
});
这里,通过事件委托只为ul元素添加了一个onclick的事件处理程序。由于所有li都是这个ul的子节点,而且它们的事件会冒泡,所以单击事件最终会被这个函数处理。
对比普通的事件绑定代码和使用事件委托后的代码,会发现使用事件委托后消耗更小:只获取了一个DOM节点,只添加了一个事件处理程序,占用的内存更小。
所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用委托的技术。
5.3 移除事件处理程序
减少浏览器代码和支持页面交互的JavaScript代码之间的连接,通常有两种方法:
1.使用事件委托技术,限制建立的连接数量;
2.在不需要的时候,移除事件处理程序。
如果没有很好地移除事件处理程序,会导致"空事件处理程序",进而也会影响程序内存和性能,造成空事件处理程序的原因:
1.从文档中移除了带有事件处理程序的元素(removeChild(),replaceChild());
2.带有事件处理程序的元素被innerHTML删除了。解决办法在innerHTML操作前通过给事件赋值null移除事件处理程序。
3.页面卸载的时候(IE8-),如果在页面被卸载前没有清理干净事件处理程序,那么它们就会滞留在内存。解决办法通过onunload事件处理程序移除所有事件处理程序。
JavaScript学习之事件原理和实践的更多相关文章
- 【JavaScript学习】-事件响应,让网页交互
		什么是事件: JavaScript 创建动态页面.事件是可以被 JavaScript 侦测到的行为. 网页中的每个元素都可以产生某些可以触发 JavaScript 函数或程序的事件. 比如说,当用户单 ... 
- JavaScript学习笔记——事件
		javascript事件基础和事件绑定 一.事件驱动 1.事件 javascript侦测到的用户的操作或是页面的一些行为(怎么发生的) 2.事件源 引发事件的元素.(发生在谁的身上)3.事件处理程序 ... 
- 8. JavaScript学习笔记——事件
		8. 事件 8.1 事件基础 /// 事件就是用户或浏览器自身执行的某种动作.诸如 click.load 和 mouseover,都是事件的名字.而响应某个事件的函数就叫做事件处理程序(或事件侦听器) ... 
- JavaScript学习——JS事件总结
		回顾之前已经使用过的事件 (onsubmit.onclick.onload.onfocus.onblur.onmouseover.onmouseout) onfocus/onblur:聚焦离焦事件,用 ... 
- 《浏览器工作原理与实践》<11>this:从JavaScript执行上下文的视角讲清楚this
		在上篇文章中,我们讲了词法作用域.作用域链以及闭包,接下来我们分析一下这段代码: var bar = { myName:"time.geekbang.com", printName ... 
- 《浏览器工作原理与实践》<10>作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?
		在上一篇文章中我们讲到了什么是作用域,以及 ES6 是如何通过变量环境和词法环境来同时支持变量提升和块级作用域,在最后我们也提到了如何通过词法环境和变量环境来查找变量,这其中就涉及到作用域链的概念. ... 
- 《浏览器工作原理与实践》<08>调用栈:为什么JavaScript代码会出现栈溢出?
		在上篇文章中,我们讲到了,当一段代码被执行时,JavaScript 引擎先会对其进行编译,并创建执行上下文.但是并没有明确说明到底什么样的代码才算符合规范. 那么接下来我们就来明确下,哪些情况下代码才 ... 
- 《浏览器工作原理与实践》<07>变量提升:JavaScript代码是按顺序执行的吗?
		讲解完宏观视角下的浏览器后,从这篇文章开始,我们就进入下一个新的模块了,这里我会对 JavaScript 执行原理做深入介绍. 今天在该模块的第一篇文章,我们主要讲解执行上下文相关的内容.那为什么先讲 ... 
- JavaScript学习06 JS事件对象
		JavaScript学习06 JS事件对象 事件对象:当事件发生时,浏览器自动建立该对象,并包含该事件的类型.鼠标坐标等. 事件对象的属性:格式:event.属性. 一些说明: event代表事件的状 ... 
随机推荐
- Qt ------ CSS 长度单位
			1. css相对长度单位 Ø em 元素的字体高度 Ø ex 字体x的高度 Ø px ... 
- 给阿里云ECS主机添加IPV6地址
			阿里云公开的CentOS镜像将IPv6支持给去掉了,需要加载相关模块.通过HE的tunnelbroker开启IPv6隧道使国内VPS支持IPv6地址. 1. vim /etc/modprobe.d ... 
- 在服务器上运行Jar包
			在服务器上运行Jar包 并且该Jar包依赖其他的Jar文件的时候,采用如下格式 java -Djava.ext.dirs=你依赖的Jar文件路径 -jar 你要运行的Jar文件 包名+类名 例如: j ... 
- JS函数大全 莫名其妙找到的
			1 .document.write(""); 输出语句 2 .JS中的注释为// 3 .传统的HTML文档顺序是:document->html->(head,body) ... 
- NOIP模拟7
			期望得分:100+100+20=220 实际得分:100+95+20=215 T1 洛谷 P1306 斐波那契公约数 #include<cstdio> #include<cstrin ... 
- 重构改善既有代码设计--重构手法05:Introduce Explaining Variable (引入解释性变量)
			发现:你有一个复杂的表达式. 解决:将该复杂的表达式(或其中的部分)的结果放进一个临时变量,并以此变量名称来解释表达式用途. //重构前 if((platform.toUpperCase().in ... 
- 【CodeForces】713 C. Sonya and Problem Wihtout a Legend
			[题目]C. Sonya and Problem Wihtout a Legend [题意]给定n个数字,每次操作可以对一个数字±1,求最少操作次数使数列递增.n<=10^5. [算法]动态规划 ... 
- Go语言 5 函数
			文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/ 今天,我们来学习Go语言编程的第五章,函数.首先简单说一下函数的概念和作用.函数是一 ... 
- linux设备驱动模型-浅析-转
			1. typeof typeof并非ISO C的关键字,而是gcc对C的一个扩展.typeof是一个关键字(类似sizeof),用于获取一个表达式的类型. 举个简单的例子: char tt; typ ... 
- Android快速入门(转自 农民伯伯: http://www.cnblogs.com/over140/)
			前言 这是前段时间用于公司Android入门培训的资料,学习Android三周时间收集整理的,时间仓促,希望能对像我这样还没入门就直接上项目的人一点帮助 :) 声明 欢迎转载,但请保留文章原始出处: ... 
