前言
前段时间在周会上被迫分享了一些 JavaScript
基础知识,后来听小伙伴反馈,组织性和逻辑性太乱,今天自己特意翻了下书本(JavaScript高级程序设计),以下是阅读后的整理,以便后续温故知新。
嗯,今天的主角是事件
事件
定义
以我的理解,事件就是指一系列的动作,像我们经常接触的click、dbclick
以及键盘的keydown、keyup
或者还有一些文档(DOM
)的加载、图片的加载,焦点事件等等。其实事件的主要目的就是为了JavaScript
和HTML
更好的交互。
课本上的定义是:
事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间
事件流
事件流是事件中一个重要的概念,由于历史(IE与Netscape巴拉巴拉~)的原因,出现了两种完全相反的事件流概念。
首先看下事件流的定义:
事件流是描述页面接收事件的顺序
顾名思义,事件流就是指事件的流向嘛,这个很好理解。前面说两位爸爸提出了两个截然相反事件流概念,是啥呢?我想大家都应该有听过 事件捕获和事件冒泡
事件捕获
直接上图:
举个例子
<html>
<body>
<div>今晚该点我了,小哥!</div>
</body>
</html>
单击div
元素,click
将会以以下顺序发生:
- document
- html
- body
- div
注意:
1.IE9一下不支持事件捕获;
2.DOM2级规范要求事件从document对象开始传播,但浏览器多数都是从window对象开始传播
事件冒泡
首先,说个重点因为浏览器兼容性问题,早期(IE9之前)是不支持事件捕获的,所以建议使用时间冒泡处理事件
上图
同样的代码(时间捕获的代码)
事件的顺序刚好相反:
- div
- body
- html
- document
DOM事件流
DOM2级事件 规定事件流包括3个阶段
- 事件捕获阶段
- 目标阶段
- 时间冒泡阶段
在DOM事件流中,实际的目标(div
)在捕获阶段是不会接收到事件的,这意味着事件从documen->html->body就停止了。在事件处理中,将"处于目标阶段"看做是冒泡阶段的一部分。
但是,多数支持DOM2事件流的现代浏览器都实现了在捕获阶段触发事件对象上的事件,so~
以上是书本原话,大家自行体会。
注意,IE9以下是不支持DOM2级事件流的
事件处理程序
用来响应某个事件的函数叫做事件处理程序(或事件侦听器)
为事件指定处理程序的方式有以下几种:
- HTML事件处理程序
- DOM0级时间处理程序
- DOM2级事件处理程序
- IE事件处理程序
HTML 事件处理程序
看代码
- 方法一
<input type='button' value='Click me' onclick='alert(123)' />
- 方法二
<script>
function showMessage(){
console.log(123);
}
</script>
<input type='button' value='Click me' onclick='showMessage()' />
这两种注册事件的方式,大家应该都不陌生,需要一提的是在方法一中,代码部分(alert(123)
)是作为JavaScript代码来处理的且是嵌套在html代码中的,如果代码中包含未经过转义的HTMl语法字符是有问题的,所以请转义OR用第二种方法。
代码执行时的作用域问题
- 在代码执行时以上两种方法都有权访问全局作用域中的任何代码
- 当前的
this
指向目标元素
作用域扩展
看实例代码:JSFiddeler代码示例
代码截图
- 在函数内部,你可以像访问局部变量一样访问document及该元素本身的成员
- 如果当前元素是一个表单的输入元素,则作用域中还会包含访问表单元素(父元素)的入口,这样可以更快的访问表单字段。
HTML事件处理程序的缺点
- 时差问题,当事件触发时,事件处理程序可能尚不具备执行条件
举个例子,前面的showMessage如果在按钮的下方,用户点击按钮时,showMessage还未加载完毕就会出现错误 - 作用域链可能因为浏览器的的不同产生差异
- HTML代码和JavaScript代码紧密耦合
DOM0 级时间处理程序
先说优点
- 简单
- 兼容性非常好,具有跨浏览器的优势
看看如何注册
var btn = document.getElementById('mybtn');
btn.onclick = function(){
console.log('clicked');
}
嗯,简单不需要多说什么。
几点需要注意的
- 处理程序中this指向当前元素
- 该种方式添加的事件处理程序都是在事件流的冒泡阶段被处理的
- 当
btn.onclick=null
时,可以去除事件绑定(有好处,日后展开)
DOM2 级处理程序
DOM2级事件定义了两个方法,用来处理和删除事件处理程序addEventListener()
、removeEventListener()
;所有实现DOM2级规范的dom节点都包含这两个方法;这两个函数有三个参数,依次顺序分别是:要处理的事件名称(click
,load
等)、处理函数、以及一个布尔值。最后这个布尔值如果是true
表示在捕获阶段调用时间处理程序,否则在冒泡阶段调用。
看代码
var btn = document.getElementById('mybtn');
btn.addEventListener('click',function(){
console.log(1111);
},true);
如果需要将事件处理程序移除,必须使用有签名的处理函数,上述代码的匿名函数则无法移除`
btn.removeEventListener("click",handler,true)
优点
- 可以添加多个处理程序
- 解耦
福利:查看代码 ,可以稍稍理解下这里的捕获阶段调用事件处理程序和冒泡阶段调用是啥意思
IE处理程序
说实话,我个人是不太想说这个的,因为它会让人混乱,我建议还是先了解标准之后再来阅读这个比较好。但为了知识的连贯性,我还是大概说一下下。
IE早期的版本(我试了ie11好像已经去除了,估计还是ie9之前,具体没有找到资料)中实现了类似DOM2中的两个方法attachEvent
、detachEvent
。
以下列举这两个方法的不同
- 因为IE8及更早的版本只支持时间冒泡,so 通过该方法注册的事件处理程序都会被添加到冒泡阶段
- 看下代码
多了个 onbtn.attachEvent('onclick',handler)
- this指向window
- 注册了的多个处理程序的执行顺序和DOM2顺序相反,DOM2是先注册的先执行
跨浏览器的事件处理程序
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;
}
}
}