七、事件处理函数、冒泡捕获、阻止冒泡默认事件
一、事件处理函数
事件是元素天生就具备的能力,比如可以被点击。
绑定事件——>绑定事件的处理函数。
事件 + 事件的反馈 == 前端交互,交互体验
1.1 如何绑定事件(绑定事件处理函数)
1.1.1 elem.onxxx = function(){} 句柄绑定形式
位置:
- js中,一个元素同一种事件只能绑定一个事件处理函数,后写覆盖先写。
- 内联事件监听器(行内事件监听器),每一个元素都有onxxx属性,属性值可以写代码或者函数。不推荐使用:结构和逻辑应该分开。
<button onclick="console.log(1)"></button>
<button onclick="test"></button>
<script>
function test(){
}
</script>
注:若同时使用这两种句柄的形式绑定事件,js中的会覆盖行内写的。
1.1.2 elem.addEventListener(事件类型,事件处理函数,false)
注册(绑定)事件监听器。
一个元素同一个事件绑定多个事件处理函数会有两种结果:
- 事件处理函数为匿名函数:使用不同的函数引用,属于不同的事件处理函数,都会执行。
- 外部的普通函数:相同的函数引用,属于相同的事件处理函数,相当于一个元素的一个事件绑定了一个事件处理函数,只执行一次。
var oBtn = document.getElementsByTagName('button')[0];
// 注册事件监听器,事件处理函数为匿名函数
// 绑定的多个事件处理函数都会执行
oBtn.addEventListener('click',function(){
console.log(1);
}, false);
oBtn.addEventListener('click',function(){
console.log(1);
}, false);
// 注册事件监听器,事件处理函数为外部普通函数
// 绑定的多个事件处理函数只执行一次,相当于只绑定了一个事件处理函数
oBtn.addEventListener('click',test,false);
oBtn.addEventListener('click',test,false);
function test(){
console.log(2);
}
1.1.3 elem.attachEvent(事件类型、事件处理函数)
IE8及以下绑定方法
var oBtn = document.getElementsByTagName('button')[0];
// IE8以下绑定方法(谷歌中没有)
oBtn.attachEvent('onclick',function(){
console.log(1);
});
oBtn.attachEvent('onclick',function(){
console.log(1);
});
oBtn.attachEvent('onclick',test);
oBtn.attachEvent('onclick',test);
function test(){
console.log(2);
}
1.2 绑定事件函数的兼容性写法
// 事件绑定函数的兼容性写法:
// 参数依次代表 事件源,事件类型,事件处理函数
function addEvent(el, type, fn){
// 先寻找注册监听器 IE9及以上兼容
if(el.addEventListener){
el.addEventListener(type, fn, false);
}else if(el.attachEvent){
// 再兼容IE8及以下
el.attachEvent('on' + type, fn);
}else{
// 还不行的话使用兼容最好的句柄形式
el['on'+ type] = fn;
}
}
验证通过!
1.3 笔试题
点击列表,输出列表的序号。
var oLi = document.getElementsByTagName('li'),
len = oLi.length;
for(var i = 0; i < len; i++){
// 产生了闭包现象
// 使用如下方法,点击时的i已经变成5,每次点击都是5
// oLi[i].onclick = function(){
// console.log(i);
// }
// 怎么解决这个问题 IIFE+闭包
(function(j){
oLi[j].addEventListener('click',function(){
console.log(j);
},false);
})(i)
}
浅了解一下使用IIFE的目的:
这里使用IIFE的目的在于使内部的函数变成一个闭包函数,可以访问外界的作用域;每次循环时,都可以获得一个新的立即执行函数,新的立即执行函数会拿到新的参数,闭包函数就拿到了新的参数j,每次循环以后j值就不一样了。
1.4 解除事件处理函数
-
element.onclick = null/false
-
-
element.removeEventListener(事件名,函数名,xx)
,函数为外部函数。 -
element.removeEventListener(事件名,arguments.callee,xx)
非严格模式下使用arguments.callee拿到此函数的引用。//绑定事件是什么样子,解除就什么样 element.addEventListener('click'、arguments.callee,false); element.removeEventListener('click'、arguments.callee,false);
-
-
element.detachEvent(事件名,事件处理函数)
1.4.1 解除事件函数的兼容性写法(与绑定事件对应)
// 事件解除函数的兼容性写法
// 参数依次为:事件源,事件类型,事件处理函数
function removeEvent(el, type, fn){
if(el.addEventListener){
el.removeEventListener(type, fn);
}else if(el.attachEvent){
el.detachEvent('on' + type, fn);
}else{
el['on' + type] = null;
}
}
注意:一般执行一次或多次后再解除事件处理函数,那么解除事件就应该写到事件绑定内部,或者根据一些条件成立后再解除事件。
// 事件绑定测试
addEvent(oBtn, 'click', function(){
test();
//...一些条件
// 事件解除测试
removeEvent(oBtn, 'click', arguments.callee);
});
二、冒泡捕获
标签是内联元素(行内元素inline),即使display变成块级元素,DOM结构解析出来也是非嵌套的。
原则上内联元素不应该嵌套内联元素
<style>
/* 属性选择器 */
[href="https://www.taobao.com"]{
display: block;
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>
<a href="https://www.taobao.com">淘宝
<a href="https://www.baidu.com">百度</a>
<p>11
<p>22</p>
</p>
</a>
2.1 事件冒泡
addEventListener(type, fn, false)
这种形式是事件冒泡。
DOM结构中存在嵌套关系,有了从内层到外层的事件冒泡现象:先执行事件源元素的事件处理函数,再执行父元素的同一种事件所触发的事件处理函数,直到顶层。(子元素的事件会从子元素起向父元素冒泡,从而触发父元素与子元素传递的同一事件,直到顶层)
2.2 事件捕获
addEventListener(type, fn, true)
这种形式是事件捕获。
DOM结构中存在嵌套关系,有了从外层到内层的事件捕获现象:先执行自身所在的顶层节点的相同事件,然后向下传递到自身。
2.3 DOM事件流
DOM事件流分为三个阶段:
- 捕获阶段
- 当前目标阶段
- 冒泡阶段
当一个元素又绑定了一个捕获事件,又绑定了一个冒泡事件,执行:由哪个元素触发事件,它就属于事件源,在DOM事件流中属于当前目标阶段。没有捕获和冒泡,就是单纯执行一个事件函数或多个事件函数,多个事件处理函数的具体执行顺序在不同浏览器,不同版本浏览器中不同;而其他元素的事件按照先捕获后冒泡执行。
简单来说:当一个元素又绑定了一个捕获事件,又绑定了一个冒泡事件,执行:事件源的事件处理函数正常执行(起一个传递事件的作用),其他元素的事件先捕获后冒泡。
2.4 其他
有些事件没有捕获和冒泡现象:
三、阻止冒泡默认事件(IE8中事件对象不在事件处理函数中,而在window上)
3.1 阻止冒泡事件
不想父级也执行同一个事件所引发的事件处理函数,所以要阻止冒泡默认事件。
W3C—>e.stopPropagation()
存在于Event.prototype上
IE——>e.cancelBubble = true
这里的e为事件对象,每个事件都有一个e在事件处理函数中。
阻止冒泡现象的兼容性写法:
// 消除事件默认冒泡现象,需要事件对象
// 而 IE8 中事件对象不在事件处理函数中,而在window上
// 需要使用兼容性写法
function cancelBubble(e){
var e = e || window.event;
// 首选W3C规则
if(e.stopPropagation){
e.stopPropagation()
}else{
e.cancelBubble = true;
}
}
使用位置:这个函数写到子元素的事件绑定函数中。
3.2 阻止默认事件
3.2.1 阻止右击浏览器默认出现菜单栏,兼容性写法
// 取消 右击浏览器默认出现菜单
document.oncontextmenu = function(e){
var e = e || window.event;
// 句柄形式中取消默认事件方法:
// 兼容性最好,addEventListener中不能用这个
// return false;
cancelPrevent(e);
}
// 取消默认事件兼容性写法
function cancelPrevent(e){
var e = e || window.event;
// w3c方法;一般 W3C 标准,IE9及以下都不支持
if(e.preventDefault){
e.preventDefault();
}else{
// IE9以下方法
e.returnValue = false;
}
}
3.2.2 阻止a链接跳转的方法
<body>
<a href="https://www.baidu.com" target="_blank">百度</a>
<!-- 方法 1: -->
<a href="javascript:;">淘宝</a>
<!-- 方法 2:void(0)相当于 return 0 -->
<a href="javascript:void(0);">京东</a>
<script>
var a = document.getElementsByTagName('a')[0];
// 方法3:
a.onclick = function(e){
var e = e || window.event;
// 点击百度不会跳转
e.preventDefault();
}
</script>
</body>
3.2.3 点击文字不跳转,点击文字外部跳转
<body>
<a href="https://www.baidu.com" class="link" target="_blank">
<div class="inner">点击</div>
</a>
<script>
// 要求:点击文字不跳转,点击文字外部跳转
var inner = document.getElementsByClassName('inner')[0];
// 在div中清除 a 标签默认跳转事件
inner.addEventListener('click',function(e){
var e = e || window.event;
e.preventDefault();
}, false)
</script>
</body>
3.2.4 阻止submit默认事件
// submit默认事件:刷新页面去提交数据(同步跳转提交)
// 通过id获取元素
var submit = document.getElementById('submit');
submit.onclick = function(e){
var e = e || window.event;
// 这里没有用兼容性写法
e.preventDefault();
console.log('提交了');
}
DOM中的兼容性写法更多是一个知识储备和面试相关,所以重点肯定在于DOM原型链;DOM增删改查;所有事件,包括键盘;冒泡捕获,事件代理
在DOM中的兼容性写法大部分针对IE浏览器各版本,firefox有些也是会考虑的;但是现在大部分项目不会考虑,因为工程化可以对兼容性进行配置。
而chrome浏览器是迭代的,99%API都兼容。