这里是修真院前端小课堂,每篇分享文从【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】八个方面深度解析前端知识/技能。
本篇分享的是:【 如何阻止事件冒泡和默认事件? 】
(1)背景介绍:
事件是监听在某个DOM元素上的,但是js的DOM事件有捕获和冒泡的机制,所以事件处理不是我们想的那样简单。
由于存在捕获和冒泡,所以事件的触发元素(目标源)不一定是当前的监听元素。于是就有一些问题
(2)知识剖析:
先来了解一下事件流
DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,
路径所经过的节点都会收到该事件,这个传播过程可称为DOM事件流。
简单理解为事件在页面的DOM节点之间传播的顺序,分为三个过程:事件捕获阶段 --> 事件目标阶段 --> 事件冒泡阶段
事件捕获:就是页面最外层的节点先接收事件,然后向内层元素逐级传播,比如从window ->document->html->body->div
事件冒泡:和捕获恰恰相反,让最内层先接受事件,然后向外层传播
事件目标阶段: target,不管是在传播阶段还是冒泡阶段,都必然经历目标阶段,就是对dom节点的事件进行处理
默认行为
浏览器的一些默认的行为。例如:点击超链接跳转,点击右键会弹出菜单,滑动滚轮控制滚动条
需要先了解一些关键点:1.event事件对象 2.目标源:target 3.当前目标源:currentTarget 4.元素element
1.事件对象
event对象代表事件的状态,比如事件在其发生的元素,键盘按键的状态,鼠标位置等。event对象只会在事件发生过程中存在。在触发的事件函数里,
我们会接收一个event对象,通过该对象去了解一些参数,比如要知道事件发生在谁身上,通过event的属性target来获取,方法是event.target。
2.target和currentTarget
target在事件流的目标阶段;currentTarget在事件流的捕获,目标及冒泡阶段。只有当事件流处在目标阶段的时候,两个的指向才是一样的,
而当处于捕获和冒泡阶段的时候,target指向被单击的对象而currentTarget指向当前事件活动的对象(一般为父级)。
点击外面
点击里面
function G(id){
return document.getElementById(id);
}
function addEvent(obj, ev, handler){
if(window.attachEvent){
obj.attachEvent("on" + ev, handler);
}else if(window.addEventListener){
obj.addEventListener(ev, handler, false);
}
}
function test(e){
console.log("e.target.tagName : " + e.target.tagName + "\n e.currentTarget.tagName : " + e.currentTarget.tagName);
}
var outer = G("outer");
var inner = G("inner");
//addEvent(inner, "click", test);
addEvent(outer, "click", test);
3.element对象
element对象表示html元素,可以拥有的类型为元素节点,文本节点,属性节点等
事件冒泡
在一个对象上触发某类事件(比如点击事件),如果此对象定义了此事件的处理程序,
那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回true,那么这个事件会向这个对象的父级对象传播,
从里到外,直至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层次的最顶层,即document对象(有些浏览器是window)。(demo)
简单的说,就是我鼠标点击一个元素上,这个事件会在这个元素所有祖先元素中触发,一直冒泡到dom最上层
事件冒泡有什么用?
想象一下现在我们有一个10列、100行的HTML表格,你希望在用户点击表格中的某一单元格的时候做点什么。
比如说我有一次就需要让表格中的每一个单元格在被点击的时候变成可编辑状态。如果把事件处理器加到这1000个单元格会产生一个很大的性能问题,
并且有可能导致内存泄露甚至是浏览器的崩溃。相反地,使用事件代理的话,
你只需要把一个事件处理器添加到table元素上就可以了,这个函数可以把点击事件给截下来,并且判断出是哪个单元格被点击了。
如何阻止冒泡
stopPropagation()
var html = document.documentElement;
var body = document.body;
var div = body.querySelector('div');
var ul = body.querySelector('ul');
var li = body.querySelector('li');
//下面这种方法就是事件流的实现方式
//事件名称,点击事件,事件处理函数。true是捕获阶段发生,false是冒泡阶段发生
ul.addEventListener('click',callback,true);//2
li.addEventListener('click',callback,true);//3
div.addEventListener('click',callbackDiv,true);
// div.addEventListener('click',callbackDiv2,false);
body.addEventListener('click',callback,false);//4
html.addEventListener('click',callback,false);//5
//event代表事件状态,比如触发event对象的元素,鼠标位置,按键等
//event只在事件发生过程中有效
function callback(event) {
var target = event.currentTarget;
console.log(target.tagName);
}
function callbackDiv(event) {
event.stopPropagation();
console.log('div callback')
}
2.return false
javascript的return false只会阻止默认行为,而是用jQuery的话则既阻止默认行为又防止对象冒泡。
如何阻止默认事件
可以使用preventDefault()方法,直接使用event对象调用即可
(3)常见问题:
1.阻止冒泡的这两种方法的区别?
(4)解决方案:
1.这两种方法是有区别的。return false不仅阻止了事件冒泡,还阻止了事件本身。
stopPropagation()方法只会阻止事件冒泡,不会阻止事件本身。
(5)编码实战:
见demo
(6)拓展思考:
是否所有事件都会触发冒泡 ?
focus、blur和scroll事件不会冒泡。文档元素上的load事件会冒泡,但它会在document对象上停止冒泡而不会传播到window对象
(7)参考文献:
参考一
参考2
参考3
(8)更多讨论:
Q1:onclick事件发生在哪个阶段?
A1:发生在冒泡阶段,如果对一个没有子元素的元素同时绑定冒泡和捕获,结果执行事件是遵循Javascript的执行顺序。如果有子元素,则是先执行捕获,然后再执行冒泡。
Q2:冒泡和捕获谁的优先级高?
A2:
先弹出ul,再弹出li,所以捕获的优先级高
Q3:不同浏览器停止冒泡的方法一样吗
A3:w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true