注册事件
注册事件概述
给元素添加事件,称为注册事件或者绑定事件。注册事件有两种方式:传统方式和方法监听注册方式。
传统方式注册事件
- 利用 on 开头的事件,例如:onclick、ondblclick、onmouseover等
- 可看作给元素的事件属性赋值。<button οnclick=“alert(‘hi~’)”></button>
- btn.onclick = function() {}
- 同一个元素同一事件只能注册一个处理程序,最后注册的处理程序会覆盖前面注册的处理程序。
方法监听注册方式
addEventListener()
是事件监听的一个方法,但 ie9 之前不支持此方法。attachEvent()
可以在 ie9 之前的 ie 浏览器使用的一个事件监听方法。- 同一个元素同一个事件可以注册多个监听器,它们可以按照注册的顺序依次执行。
addEventListener 事件监听方式
eventTarget.addEventListener(type, listener[, useCapture])
addEventListener() 方法将指定的监听器注册到 eventTarget
(目标对象)上,当该对象触发指定事件时,就会执行事件处理程序。
- type:事件类型字符串,比如 click、mouseenter 等。这里不要带 on 。
- listener:事件处理函数,事件触发时会调用该监听函数。
- useCapture:可选参数,默认是 false ,表示在事件冒泡阶段调用事件处理程序;为 true 则表示在事件捕获阶段调用事件处理程序。
attachEvent 事件监听方式
eventTarget.attachEvent(eventNameWithOn, callbak)
attachEvent() 方法将指定的监听器注册到 eventTarget
(目标对象) 上,当该对象触发指定的事件时,指定的回调函数就会被执行。
- eventNameWithOn:事件类型字符串,比如 onclick、onblur 等,这里要带 on 。
- callback:事件处理函数,当目标触发事件时回调函数被调用。
- 该方法只有 ie8 及早期 ie 浏览器才支持。
兼容性注册事件处理
// 封装一个注册事件的函数
function addEventListener(eventTarget, eventName, fn) {
// 判断当前浏览器是否支持 addEventListener() 方法
if (eventTarget.addEventlistener) {
eventTarget.addEventlistener(eventName, fn);
} else if (eventTarget.attachEvent) {
eventTarget.attachEvent('on' + eventName, fn);
} else {
eventTarget['on' + eventName] = fn;
}
}
删除事件
- 传统注册方式事件的移除
eventTarget.onclick = null;
eventTarget.removeEventListener(type, listener[, useCapture]);
eventTarget.detachEvent(eventNameWithOn, callback);
移除事件兼容性方案:
function removeEventListener(element, eventName, fn) {
// 判断当前浏览器是否支持 removeEventListener 方法
if (element.removeEventListener) {
element.removeEventListener(eventName, fn);
} else if (element.detachEvent) {
element.detachEvent('on' + eventName, fn);
} else {
element['on' + eventName] = null;
}
}
DOM事件流
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即 DOM 事件流。事件流描述的是从页面接收事件的顺序。 传播路径所经过的所有节点都会收到该事件并触发相应的监听器(若存在)。
DOM 事件流分为 3 个阶段:
- 捕获阶段
- 当前目标阶段
- 冒泡阶段
- 事件冒泡:IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到 DOM 最顶层节点的过程。
- 事件捕获:网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到最具体的元素接收的过程。
如图:点击按钮,点击事件首先处于捕获阶段从最顶层节点开始向最具体的元素传播。到达目标后又开始由最具体的元素向最顶层的节点传播,这是冒泡阶段。
- 每个元素都触发事件两次,但是 js 代码中只能执行捕获或者冒泡其中的一个阶段。
- 传统方式注册的事件和使用 attachEvent() 方法注册的监听器只能处理事件冒泡阶段。
- addEventListener() 方法第三个布尔型参数为 true 表示在事件捕获阶段调用事件处理程序;如果为 false 表示在事件冒泡阶段调用事件处理程序。
- 有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave 。
- 事件冒泡是我们主要关注的,它有时候会带来麻烦,有时候又可以帮助我们便捷高效地处理某些事情。
代码演示:
<body>
<div>
<span>
<button id="btn">按钮</button>
</span>
</div>
</body>
<script>
var btn = document.querySelector('#btn');
var span = document.querySelector('span');
var div = document.querySelector('div');
btn.addEventListener('click', function() {
console.log('btn冒泡阶段')
})
btn.addEventListener('click', function() {
console.log('btn捕获阶段')
}, true)
// 注意是事件捕获阶段先输出。
div.addEventListener('click', function() {
console.log('div冒泡阶段')
})
div.addEventListener('click', function() {
console.log('div捕获阶段')
}, true)
span.addEventListener('click', function() {
console.log('span冒泡阶段')
})
span.addEventListener('click', function() {
console.log('span捕获阶段')
}, true)
</script>
点击按钮时的输出:div捕获阶段 span捕获阶段 btn捕获阶段 btn冒泡阶段 span冒泡阶段 div冒泡阶段
点击span时输出:div捕获阶段 span捕获阶段 span冒泡阶段 div冒泡阶段
这样就会有一个问题(只考虑冒泡):如果我想在点击按钮时只触发按钮绑定的事件而不会触发 span 和 div 的点击事件;只有点击 span(除去按钮部分)时才会触发 span 上的点击事件处理程序,怎么办?这就是事件冒泡带来的问题。
事件对象
事件对象概述
事件对象代表事件的状态,比如鼠标事件触发时鼠标的位置、鼠标按钮的状态;键盘事件触发时按键的状态、是哪个键按下或者弹起等等。事件对象代表事件的状态,跟事件相关的一系列信息的集合。
事件对象是事件触发时系统自行创建和传入事件处理函数的一个参数,我们只需要在声明事件处理函数时设置一个形参接收即可。代码示例如下:
var btn = document.querySelector('#btn');
btn.onclick = function(event) {
console.log(event); // MouseEvent
} // 这个形参 event 就是事件对象,根据个人喜好可以写成 e 或者 evt
document.onkeyup = function(e) {
console.log(e); // KeyboardEvent
}
// 可以看看这两个事件对象大概具有哪些属性
事件对象的兼容性方案
事件对象本身的获取存在兼容性问题。在标准浏览器中是浏览器给方法传递的参数,我们只需要定义新参就可以获取到,但是在 IE6~8 中,浏览器不会默认给方法传参,需要使用事件对象要使用 window.event
获取。可以使用 e = e || window.event;
的方式解决兼容性问题。
事件对象的常用属性和方法
e.target
: 返回触发事件的对象,即目标对象(捕获过程的尾,冒泡过程的头☺)。e.srcElement
: 同 e.target 的作用,是非标准,在 ie6~8 使用。e.type
: 返回事件的类型,比如鼠标点击 (click) ,键盘按键弹起 (keyup)。e.cancelBubble
: 该属性阻止事件冒泡,非标准,在 ie6~8 中使用。e.returnValue
: 该属性阻止默认事件(默认行为),非标准,在 ie6~8 中使用。比如链接跳转,提交按钮提交等。e.preventDefault()
: 该方法阻止默认事件(默认行为),标准。e.stopPropagation()
: 阻止事件冒泡,标准。
ie6~8 阻止事件冒泡和默认事件都使用事件对象的属性,而标准使用的是事件对象的方法。
阻止事件冒泡
- 利用事件对象里面的 stopPropagation() 方法阻止事件冒泡,这是标准方式,可在绝大多数浏览器使用。
- 利用事件对象的 cancelBubble 属性,这是非标准方式,在 ie6~8中使用。
btn.onclick = function(e) {
// e.stopPropagation();
// e.cancelBubble = true;
// 兼容性写法:
if (e && e.stopPropagation()) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
}
事件委托
事件委托是事件冒泡特性的一个利用场景,事件委托也称为事件代理。当某个 dom 元素需要监听事件时,我们会自然而然地给它注册相应地监听器。但是当处于同一元素中的后代元素中有很多 dom 元素同时需要注册一样的事件和事件处理程序(事件处理程序可以不同)时,我们是否需要给他们分别去注册事件呢?比如:简单版的发布删除留言案例。
<body>
<div class="comment">
<textarea name="" id="" cols="30" rows="10"></textarea>
<button id="btn">提交</button>
<ul>
<li>这是原来就有的第3条评论<span>删除</span></li>
<li>这是原来就有的第2条评论<span>删除</span></li>
<li>这是原来就有的第1条评论<span>删除</span></li>
</ul>
</div>
</body>
<script>
var btn = document.querySelector('#btn');
var content = document.querySelector('textarea');
var ul = document.querySelector('ul');
var spans = ul.querySelectorAll('span');
// 使用for循环给每个span注册事件
for (var i = 0; i < spans.length; i++) {
spans[i].onclick = function() {
ul.removeChild(this.parentNode);
}
}
// 发布留言按钮绑定事件
btn.onclick = addComment;
function addComment() {
var li = document.createElement('li');
var str = content.value;
if (str.replace(/\s*/g, "")) {
li.innerHTML = str + '<span>删除</span>';
ul.insertBefore(li, ul.children[0]);
// 给新的li中的span绑定事件
li.children[0].onclick = function() {
ul.removeChild(this.parentNode);
}
} else {
alert('输入为空');
}
}
</script>
这是不是很麻烦,麻烦还是次要的。因为事件监听器是占用内存的,如果有几百条甚至上千条评论时这…就会很影响性能。所以我们想到事件不是可以冒泡吗?所有的 span 元素不是都在 ul 内吗?事件对象不是有 target 属性可以指向目标对象吗?这就引出了事件委托的原理。
事件委托原理:不是每个子节点单独设置事件监听器,而是将事件监听器设置在其父节点上,委托他们的父级代为执行事件,然后利用冒泡原理影响设置每个子节点。
以上案例我们可以给 ul 注册点击事件,因为点击 span 事件会冒泡到 ul 上触发监听器,然后利用事件对象的 target 属性找到当前点击的 span ,然后执行相应操作。
var btn = document.querySelector('#btn');
var content = document.querySelector('textarea');
var ul = document.querySelector('ul');
// 无需使用for循环为所有span添加事件
ul.onclick = function(e) {
ul.removeChild(e.target.parentNode);
}
btn.onclick = addComment;
function addComment() {
var li = document.createElement('li');
var str = content.value;
if (str.replace(/\s*/g, "")) {
li.innerHTML = str + '<span>删除</span>';
ul.insertBefore(li, ul.children[0]);
// 无需再为动态生成的li中的span添加点击事件
} else {
alert('输入为空');
}
很多文章都是用代收快递的例子来描述事件委托。我想使用提交成绩案例来描述一下。比如学校通知每个班的同学提交成绩总分前几名的同学的成绩用来评奖学金。方案一是每个班里面成绩靠前的同学自行登录教务系统提交成绩,这样这些同学都会有一个提交成绩的事件。方案二是帮主任按成绩单这些同学(target)并提交相应成绩即可。
事件委托的意义:减少 dom 操作次数和内存的占用提高程序的性能。
常用的鼠标事件
常用的鼠标事件
鼠标事件类型 | 触发条件 |
---|---|
click | 鼠标点击左键 |
mouseover | 鼠标经过 |
mouseout | 鼠标离开 |
focus | 元素获得鼠标焦点 |
blur | 元素失去鼠标焦点 |
mousemove | 鼠标移动 |
mouseup | 鼠标弹起 |
mousedown | 鼠标按下 |
mouseenter | 鼠标进入(不会冒泡) |
mouseleave | 鼠标离开(不会冒泡) |
contextmenu | 禁止鼠标右键菜单(默认行为) |
selectstart | 禁止鼠标选中(默认行为) |
鼠标事件对象(MouseEvent)
鼠标事件对象属性 | 描述 |
---|---|
e.clientX | 返回鼠标相对于浏览器窗口可视区的X坐标。 |
e.clientY | 返回鼠标相对于浏览器窗口可视区的Y坐标。 |
e.pagX | 返回鼠标相对于文档页面的X坐标。IE9+支持 |
e.pagY | 返回鼠标相对于文档页面的Y坐标。IE9+支持 |
e.screenX | 返回鼠标相对于电脑屏幕的X坐标。 |
e.screenY | 返回鼠标相对于电脑屏幕的Y坐标。 |
常用的键盘事件
- keyup : 键盘按键弹起,如果是表单则内容已经落入表单中。
- keydown : 键盘按键被按下。
- keypress : 键盘按键被按下并弹起时触发。
注意: 他们的触发顺序为 keydown、keypress、keyup ,其中前两个触发时文字还没有落入表单元素内。keypress 不能识别功能键,例如左右箭头,shift 键等,但是它可以识别字母大小写,这是它与其他两个的区别。
键盘事件对象的 keyCode 属性返回按键字母的 ASCII 码值,key 属性直接返回按键的内容。