浏览器事件模型

94 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第N天,点击查看活动详情 >>

EventTarget

EventTarget 不仅仅 只有 HTMLElement ,其他的需要通讯的,比如 XHR 也是 EventTarget

addEventListener

target.addEventListener(type, listener[, useCapture])
target.removeEventListener(type,listener[, options]);

Listener:

  • 函数:

    • 添加了多个 callback,则按添加的顺序调用
    • 添加了多个同一个 callback,则只调用一次
  • 实现了 handleEvent 的对象

useCapture:是一个布尔值代表是否在捕获阶段触发,还可以是一个options 对象

capture:布尔值,如果设为true,表示监听函数在捕获阶段触发,默认为false,在冒泡阶段触发。

  • once:布尔值,如果设为true,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为false
  • passive:布尔值,设为true时,表示禁止监听函数调用preventDefault()方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为false
  • signal:该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。

removeEventListener

target.removeEventListener(type,listener[, options]);
target.removeEventListener(type,listener[,useCapture]);

useCapture

指定需要移除的 [EventListener](<https://developer.mozilla.org/zh-CN/docs/conflicting/Web/API/EventTarget/addEventListener_380cb5f366307beb2c072f74e561ee98>) 函数是否为捕获监听器。如果无此参数,默认值为 false

如果同一个监听事件分别为“事件捕获”和“事件冒泡”注册了一次,这两次事件需要分别移除。两者不会互相干扰。

target.addEventListener("click", listener, true);

target.removeEventListener("click", listener, false); // 不会生效哦

options

合法的属性只有 capture 其实和直接使用 useCapture 无异

dispatchEvent

target.dispatchEvent(event)

这里的 dispatch(派发) 的目标就是 target,相当于手动去触发一些方法;

该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true

para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);

事件调用的三种方法

HTML: on - xx

<body onload="doSomething()">
<div onclick="console.log('触发事件')">

原生的Value是 JS片段(要执行的代码),而React 的 OnXX 的 Value 是一个 函数对象

使用这个方法指定的监听代码,只会在 冒泡阶段 触发。

HTMLElement 的 JS 属性

div.onclick = function(){}

使用这个方法指定的监听函数,也是只会在 冒泡阶段 触发。

addEventListener

不再赘述

This 指向问题

普通函数:执行触发的元素

箭头函数:外部作用域的 thi

事件的传播模型

三个阶段划分

  • capture phase : 从window对象传导到目标节点(上层传到底层),称为“捕获阶段”。
  • target phase:在目标节点上触发,称为“目标阶段”。
  • bubbling phase:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”。

比如

上使用 addEventListener 添加了 两个阶段的 click 事件;

但是实际上真正点击的时候 是在 target phase 阶段触发了两个事件

<p id="pp">PP</p>

let p = document.getElementById('pp')

p.addEventListener('click',()=>{
  console.log("CLICK BuBBle")
}, false)

p.addEventListener('click',()=>{
  console.log("CLICK Capture")
}, true)

/*
    先打印 Capture, 再打印 Bubble
*/

事件委托

<div id="fa">
    <p id="pp">PP</p>
 </div>

let fa = document.getElementById('fa')

fa.addEventListener('click',(e)=>{
  e.stopImmediatePropagation();
  console.log(e);
})

stopPropagation:当前元素的回调还是会被触发

stopImmediatePropagation:接下来的元素的回调会被砍掉,但是目前执行的回调还会继续执行

Event 对象

构造函数

event = new Event(type, options);

type: 事件名称

options:

  • Bubbles: false
  • Concelable: false -> 表示事件是否可以被取消,即能否用Event.preventDefault() 取消这个事件。

preventDefault

告诉 user agent :如果此事件没有被显式处理,它默认的动作也不应该照常执行。此事件还是继续传播,除非碰到事件侦听器调用[stopPropagation()](<https://developer.mozilla.org/zh-CN/docs/Web/API/Event/stopPropagation>)[stopImmediatePropagation()](<https://developer.mozilla.org/zh-CN/docs/Web/API/Event/stopImmediatePropagation>),才停止传播。

React 自定义事件

面试题:

为什么要提出浏览器的事件模型?在当前的事件模型中,哪些事件可以冒泡,哪些不会冒泡,为什么?不冒泡的元素,如何来实现事件代理?

  • 在GUI程序中,可以触发某个特定的元素的某个事件,比如点击一个嵌套的同心div,那么到底哪一个div会拥有这个点击事件?实际上难以确定点击者的意图,团队给出的解决方式是所有div都将拥有这个事件,于是产生了事件流模型。
  • 不能冒泡的事件,如error、blur、load等;
  • 能知道通过event.bubbles来判断事件是否支持冒泡,也能说出自定义事件通过设置该属性修改是否支持冒泡,其次也要知道对于不能冒泡的事件可以尝试用捕获的方式,如在addEventListener的第三个参数设置为true。

事件代理有哪些优点?

把事件委托到其父对象上,借助事件冒泡机制,实现对节点的事件代理。

优点

  • 可以大量节省内存占用,减少事件注册
  • 当新增子对象时无需再次对其绑定事件,对于动态内容部分尤为合适

React 事件的实现机制?与原生 DOM 事件有什么区别?

  • react 17中事件就不再注册在document上,而是你的组件所绑定的container
  • React 绑定的是 冒泡阶段,所以如果在React中混入了一些原生的事件,在捕获阶段或者捕获阶段就阻止他传播的话,React的回调就不执行了
  • React 通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对象内存的分配,提高了性能