事件流
事件流分为两种:捕获事件流和冒泡事件流
- 捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点
- 冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点
- 事件流分为三个阶段:捕获阶段 -> 目标阶段 -> 冒泡阶段
- DOM事件流:事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行
“捕获”和“冒泡”执行顺序和事件的执行次数
- 按照W3C标准的事件:首先进入捕获阶段,直到达到目标元素,再进入冒泡阶段
- 事件执行次数(DOM2-addEventListener):元素上绑定事件的个数
- 注意1:前提是事件被确实触发
- 注意2:事件绑定几次就算几个事件,即使类型和功能完全一样也不会“覆盖”
- 事件执行顺序:判断的关键是否目标元素
- 非目标元素:根据W3C的标准执行:捕获->目标元素->冒泡(不依据事件绑定顺序)
- 目标元素:依据事件绑定顺序:先绑定的事件先执行(不依据捕获冒泡标准)
- 最终顺序:父元素捕获->目标元素事件1->目标元素事件2->子元素捕获->子元素冒泡->父元素冒泡
- 注意:子元素事件执行前提 事件确实“落”到子元素布局区域上,而不是简单的具有嵌套关系
DOM0,DOM2,DOM3 事件处理方式
- DOM0级事件处理方式:
- btn.onclick = func;
- btn.onclick = null;
- DOM2级事件处理方式:
- btn.addEventListener(‘click’, func, false);
- btn.removeEventListener(‘click’, func, false);
- btn.attachEvent(“onclick”, func);
- btn.detachEvent(“onclick”, func);
- DOM3级事件处理方式:
- eventUtil.addListener(input, “textInput”, func);
- eventUtil 是自定义对象,textInput 是DOM3级事件
注册事件
通常我们使用 addEventListener 注册事件
target.addEventListener(type, listener, options/useCapture);
- 参数1:必须,type 表示监听事件类型的字符串
- 参数2:必须,指定事件触发时执行的函数
参数3:可选,可是布尔值(useCapture)也可是对象(options)
阻止事件:在W3c中,使用stopPropagation()方法,可以阻止冒泡和捕获事件;在IE下设置cancelBubble = true;
- 阻止事件的默认行为,例如click - 后的跳转。在W3c中,使用preventDefault()方法,在IE下设置window.event.returnValue = false;
- stopImmediatePropagation同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。示例如下:
注意:不是所有的事件都能冒泡。以下事件不冒泡:blur、focus、load、unload。node.addEventListener(
'click',
event => {
event.stopImmediatePropagation()
console.log('冒泡')
},
false
)
// 点击 node 只会执行上面的函数,该函数不会执行
node.addEventListener(
'click',
event => {
console.log('捕获 ')
},
true
)
事件代理
当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到window,当然其传播的是事件,绑定的执行函数并不会传播,如果父级没有绑定事件函数,就算传递了事件,也不会有什么表现,但事件确实传递了。 事件冒泡的原因是事件源本身可能没有处理事件的能力,即处理事件的函数并未绑定在该事件源上。它本身并不能处理事件,所以需要将事件传播出去,从而能达到处理该事件的执行函数。
- 事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
- 使用事件代理的好处是
- 可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
- 可以将事件应用于动态添加的子元素上
- 缺点: 使用不当会造成事件在不应该触发时触发
如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上,示例
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('##ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
W3C事件的 target 与 currentTarget 的区别?
- target 只会出现在事件流的目标阶段
- currentTarget 可能出现在事件流的任何阶段
- 当事件流处在目标阶段时,二者的指向相同
- 当事件流处于捕获或冒泡阶段时:currentTarget 指向当前事件活动的对象(一般为父级)
《JavaScript高级程序设计》对这两个属性是这样解释的
event.target为事件的目标
event.currentTarget为事件处理程序正在处理的元素
个人理解:
event.target: 获取触发事件的标签元素(比如:点击了哪个元素,就是哪个元素);
event.currentTarget: 获取发起事件的标签元素(比如:那个元素绑定了事件,就是那个元素)
我们为一个元素绑定一个点击事件的时候,可以指定是要在捕获阶段绑定或者换在冒泡阶段绑定。 当addEventListener的第三个参数为true的时候,代表是在捕获阶段绑定,当第三个参数为false或者为空的时候,代表在冒泡阶段绑定。
示例;
<div id="a">
<div id="b">
<div id="c">
<div id="d"></div>
</div>
</div>
</div>
<script>
document.getElementById('a').addEventListener('click', function(e) {
console.log('aa', 'target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('b').addEventListener('click', function(e) {
console.log('bb', 'target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('c').addEventListener('click', function(e) {
console.log('cc', 'target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('d').addEventListener('click', function(e) {
console.log('dd', 'target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
上面例子绑定是在冒泡阶段,如果我们点击了id 是 d 的元素输出下面的结果:
因为点击的是id = ‘d’ , 的元素,所以target 一直是 d,哪个元素绑定了事件 currentTarget 就是哪个元素。