浏览器对事件的处理-1冒泡-2捕获
浏览器对事件的处理
在浏览器的看来,用户的所有手势都是输入,鼠标滚动,悬置,点击等等都是。
当用户在屏幕上触发诸如 touch 等手势时,首先收到手势信息的是 Browser process, 不过 Browser process 只会感知到在哪里发生了手势,对 tab 内内容的处理是还是由渲染进程控制的。
事件发生时,浏览器进程会发送事件类型及相应的坐标给渲染进程,渲染进程随后找到事件对象并执行所有绑定在其上的相关事件处理函数。
前文中,我们提到过合成器可以独立于主线程之外通过合成栅格化层平滑的处理滚动。如果页面中没有绑定相关事件,组合器线程可以独立于主线程创建组合帧。如果页面绑定了相关事件处理器,主线程就不得不出来工作了。这时候合成器线程会怎么处理呢?
这里涉及到一个专业名词「非快速滚动区域(non-fast scrollable region)」由于执行 JS 是主线程的工作,当页面合成时,合成器线程会标记页面中绑定有事件处理器的区域为 non-fast scrollable region ,如果存在这个标注,合成器线程会把发生在此处的事件发送给主线程,如果事件不是发生在这些区域,合成器线程则会直接合成新的帧而不用等到主线程的响应。
解决页面中事件流(事件发生顺序)的问题
使用 addEventListener 方法的第三个参数,它是一个布尔值,通常被称为捕获标志(capture flag)。
false(默认值),则事件将以冒泡方式传播。
事件冒泡(微软),当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。
如果将这个标志设置为 true,则事件会以捕获方式传播,即从根元素开始,逐级向下到触发元素。
事件捕获分别由网景公司提出,它很少被用在实际开发中,但有时是有用的事件从最不精确的对象(document 对象)开始触发,然后到最精确(也可以在窗口级别捕获事件,不过必须由开发人员特别指定),与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素
用于停止冒泡的方法是 event.stopPropagation()
element.addEventListener(event, function, useCapture)
// 以冒泡方式添加事件监听器
element.addEventListener('click', eventHandlerFunction, false);
// 以捕获方式添加事件监听器
element.addEventListener('click', eventHandlerFunction, true);
事件冒泡的性能优化点
场景 1
子元素存在于父级元素,你点击子元素也是相当于点击了父元素,然后冒泡机制可用于事件委托,优化性能,比如长列表绑定事件;
长列表绑定,笨的做法:每个 li 上绑定事件,li 触发事件,如果 1k 条数据,这种做法肯定是不科学的。所以,优化性能的时候,将事件绑定在 ul 上,加入冒泡机制,代码量变少、性能又好
场景 2
案例 1: 2020/05/05 2019/5/5
哥问下你,我想实现微信点击聊天信息弹层(已经实现),然后点击信息外关闭弹层,,,怎么实现好?
给 document 绑定 click 事件,点击 document 关闭弹出的东西,
然后给弹出层绑定 click,阻止它冒泡:
componentDidMount() {
document.addEventListener('click', (e) => {
this.setState({
display: "none"
})
})
let modalIdChatListDoc = document.getElementById('modalLogId')
modalIdChatListDoc.addEventListener('click', (e) => {
console.log("----阻止ChatList事件冒泡------")
e.stopPropagation()
}, false)
}
场景 3:
例如一条购物车信息,在这条信息中,右下角有一个删除按钮。1.点击这条消息可查看详情;2.点击按钮可以删除;
此时需要阻止事件;
2-1.事件委托
事件流都会经过三个阶段: 捕获阶段 -> 目标阶段 -> 冒泡阶段,而事件委托就是在冒泡阶段完成
事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素
答案: 利用事件冒泡的原理,让自己的所触发的事件,让他的父元素代替执行!
对事件处理程序过多问题解决方案就是事件委托。思想是利用了事件冒泡,只指定一个事件处理程序,就可以处理某一类型的所有事件。
例如,click事件会一直冒泡到docment层次。可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素风别添加事件处理程序。
3-1.实战事件委托
mounted() {
const list = document.getElementById("myList");
list.addEventListener(
"click",
(event) => {
// function (event) {
event = event || window.event;
const target = event.target || event.srcElement;
switch (target.id) {
case "id1":
this.word = "本月";
this.initTime(0);
break;
case "id2":
this.word = "近三个月";
this.initTime(1);
break;
case "id3":
this.word = "近一年";
this.initTime(2);
break;
}
this.showPop = false;
},
false
);
this.initTime(0);
this.queryCustomerCreditByInit();
},
事件委托 2
<ul id="myLinks">
<li id="btn1">btn1</li>
<li id="btn2">btn2</li>
<li id="btn3">btn3</li>
</ul>
A.没使用事件委托:
let item1 = document.getElementById('btn1')
let item2 = document.getElementById('btn2')
let item3 = document.getElementById('btn3')
EventUtil.addHandler(item1,'click',fcuntion(event){
cosole.log('btn1')
})
EventUtil.addHandler(item2,'click',fcuntion(event){
cosole.log('btn2')
})
EventUtil.addHandler(item3,'click',fcuntion(event){
cosole.log('btn3')
})
B.使用事件委托: 只需要在 DOM 树中尽量高的层次中添加一个事件处理程序。由于所有列表都是这个元素的子节点,而且它们的事件会冒泡,所以单击事件最终会被这个函数处理。
let myLinks = document.getElementById('myLinks')
EventUtil.addHandler(myLinks,'click',fcuntion(event){
event = EventUtil.getEvent(event)
let target = EventUtil.getTarget(event)
switch(target.id){
case 'btn1':
console.log('btn1')
break;
case 'btn2':
console.log('btn2')
break;
case 'btn3':
console.log('btn3')
break;
}
})
2.web 开发中常用的事件处理模式是事件委托,基于事件冒泡,我们常常在最顶层绑定事件:
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault();
}
});
上述做法很常见,但是如果从浏览器的角度看,整个页面都成了 non-fast scrollable region 了。
这意味着即使操作的是页面无绑定事件处理器的区域,每次输入时,合成器线程也需要和主线程通信并等待反馈,流畅的合成器独立处理合成帧的模式就失效了。
为了防止这种情况,我们可以为事件处理器传递 `passive: true` 做为参数,这样写就能让浏览器即监听相关事件,又让组合器线程在等等主线程响应前构建新的组合帧。
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
不过上述写法可能又会带来另外一个问题,假设某个区域你只想要水平滚动,使用 `passive: true` 可以实现平滑滚动,但是垂直方向的滚动可能会先于`event.preventDefault()`发生,此时可以通过 `event.cancelable` 来防止这种情况。
document.body.addEventListener('pointermove', event => {
if (event.cancelable) {
event.preventDefault(); // block the native scroll
/*
* do what you want the application to do here
*/
}
}, {passive: true});
八.查找到事件对象
当组合器线程发送输入事件给主线程时,主线程首先会进行命中测试(hit test)来查找对应的事件目标,命中测试会基于渲染过程中生成的绘制记录( paint records )查找事件发生坐标下存在的元素。
九.事件的优化
一般我们屏幕的刷新速率为 60fps,但是某些事件的触发量会不止这个值,出于优化的目的,Chrome 会合并连续的事件(如 wheel, mousewheel, mousemove, pointermove, touchmove ),并延迟到下一帧渲染时候执行 。
而如 keydown, keyup, mouseup, mousedown, touchstart, 和 touchend 等非连续性事件则会立即被触发。
window.addEventListener("pointermove", (event) => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
});