一、自定义事件可用于创建“图形组件”。
【示例1】我们自己的基于JavaScript的菜单的根元素可能会触发open(打开菜单),select(有一项被选中)等事件来告诉菜单发生了什么。另一个代码可能会监听事件,并观察菜单发生了什么。
二、我们不仅可以生成处于自身目的而创建的全新事件,还可以生成如click和mousedown等内建事件。
1、这可能会有助于自动化测试。
2、可以生成原生事件的场景
(1)如果第三方程序库不提供其他交互方式,那么这是使第三方程序库工作所需的一种肮脏手段
(2)对于自动化测试,要在脚本中“点击按钮”并查看接口是否正确响应。
三、我们不应该生成浏览器事件,因为这是运行处理程序的一种怪异(hacky)方式。大多数时候,这都是糟糕的架构。
四、使用我们自己的名称的自定义事件通常是出于架构的目的而创建的,以指示发生在菜单(menu),滑块(slider),轮播(carousel)等内部发生了什么。

创建自定义事件的步骤

事件构造器

事件构造器new Event

一、内建事件形成一个层级结构(hierachy),类似于DOM元素类。根是内建的Event类。
二、我们可以像这样创建Event对象

  1. let event = new Event(type[, options]);

1、参数
(1)type:事件类型,可以是像这样’click’的字符串,或者我们自己的像这样’my-event’的参数。
(2)options:具有两个可选属性的对象。默认情况下,以下两者都为false: { bubbles: false, cancelabel: false }
① bubbles:true / false:如果为true,那么事件会冒泡。
② cancelable: true / false,如果为true,那么“默认行为”就会被阻止。

event.preventDefault()

一、许多浏览器事件都有“默认行为”。如,导航到链接,开始一个选择等。
二、对于新的,自定义的事件,绝对没有默认的浏览器行为,但是分派(dispatch)此类事件的代码可能有自己的计划,触发该事件之后应该做什么。
三、通过调用event.preventDefault(),事件处理程序可以发出一个信号,指出这些行为应该被取消。
1、在这种情况下,elem.dispatchEvent(event)的调用会返回false。那么分派(dispatch)该事件的代码就会知道不应该再继续。
【示例1】一只隐藏的兔子(可以是关闭菜单或其他)

<pre id="rabbit">
  |\   /|
   \|_|/
   /. .\
  =\_Y_/=
   {>o<}
</pre>
<button onclick="hide()">Hide()</button>

<script>
  function hide() {
    let event = new CustomEvent("hide", {
      cancelable: true // 没有这个标志,preventDefault 将不起作用
    });
    if (!rabbit.dispatchEvent(event)) {
      alert('The action was prevented by a handler');
    } else {
      rabbit.hidden = true;
    }
  }

  rabbit.addEventListener('hide', function(event) { // 任何处理程序都可以使用rabbit.addEventListener('hide', ...)来监听该事件,并在需要时使用event.preventDefault()来取消该行为。然后兔子就不会藏起来了
    if (confirm("Call preventDefault?")) {
      event.preventDefault();
    }
  });
</script>

三、事件必须有cancelable: true标志,否则event.preventDefault()调用将会被忽略。

UI事件规范

一、UI事件规范
UIEvent
FocusEvent
MouseEvent
WheelEvent
KeyboardEvent

二、如果我们想要创建这样的事件,我们应该使用它们而不是new Event。
【示例1】new MouseEvent(‘click’),而不是new Event(‘click’)
三、正确的构造器允许为该类型的事件指定标准属性。
【示例1】就像鼠标的clientX / clientY一样

let event = new MouseEvent('click', {
    bubbles: true,
    cancelable: true,
    clientX: 100,
    clientY: 100
})

alert(event.clientX); // 100

【示例2】通用的Event构造器不允许像上述这样做

let event = new Event('click', {
    bubbles: true, // 构造器Event 中只有bubbles和cancelable可以工作
    cancelable: true,
    clientX: 100,
    clientY: 100
})

alert(event.clientx); // undefined,未知的属性被忽略了。技术上,我们可以通过在创建后直接分配event.clientX = 100来解决这个问题

自定义事件 new CustomEvent

一、对于我们自己的全新事件类型,例如’hello’,我们应该使用new CustomEvent。
从技术上讲,CustomEvent 和Event一样。除了一点不同。
二、在第二个参数(对象)中,我们可以为我们想要与事件一起传递的任何自定义信息添加一个附加的属性detail
【示例1】

<h1 id="elem"> hello from john!</h1>

<script>
  let event = new CustomEvent('hello', {
      detail: { name: 'John' }
  })
  // 事件附带给处理程序的其他详细信息
  elem.addEventListener('hello', function(event) {
      alert(event.detail.name);
  });
  elem.dispatchEvent(event);
</script>

三、detail属性可以有任何数据。从技术上讲,我们可以不用,因为我们可以再创建后将任何属性分配给常规的new Event对象中。但是CustomEvent提供了特殊的detail字段,以避免与其他事件属性的冲突。
四、事件类描述了它是“什么类型的事件”,如果事件是自定义的,那么我们应该使用CustomEvent来明确它是什么。

dispatchEvent

一、事件对象被创建后,我们应该使用elem.dispatchEvent(event)调用在元素上“运行”它。
二、然后,处理程序会对它作出反应,就好像它是一个常规的浏览器事件一样。如果事件是用bubbles标志创建的,那么它会冒泡。
【示例1】click事件是用JavaScript创建初始化创建的。处理程序工作方式和点击按钮的方式相同

<button id="elem" onclick="alert('Click!');">Autoclick</button>

<script>
  let event = new Event("click");
  elem.dispatchEvent(event);
</script>

三、有一种方法可以区分“真实”用户事件和通过脚本生成的事件
1、对于来自真实用户操作的事件,event.isTrusted属性为true,对于脚本生成的事件,event.isTrusted属性为false。
【示例1】冒泡示例。创建一个名为”hello”的冒泡事件,并在document上捕获它。这需要将bubbles设置为true

<h1 id="elem">Hello from the script</h1>
<script>
    // 在document上捕获...
  document.addEventListener('hello', function(event) { // 我们应该对我们的自定义事件使用addEventListener,因为on<event>仅存在于内建事件中,document.onhello无法运行
      alert('hello from ' + event.target.tagName); // hello from H1
  });

  // 在elem上dispatch
  let event = new Event('hello', { bubbles: true }); // 必须设置bubbles: true,否则事件不会向上冒泡
  elem.dispatchEvent(event);

  // 在document上的处理程序将被激活,并显示消息
</script>

1、内建事件(click)和自定义事件(hello)的冒泡机制相同。自定义事件也有捕获阶段和冒泡阶段。

事件中的事件是同步的

一、通常事件是在队列中处理的。也就是说,如果浏览器正在处理onclick,这时发生了一个新的事件,例如鼠标移动了,那么它的处理程序也会被排入队列,相应的mousemove处理程序将在onclick事件处理完成后被调用。
二、例外的情况是,一个事件是在另一个事件中发起的。如使用dispatchEvent,这类事件将会被立即处理,即在新的事件处理程序被调用之后,恢复到当前的事件处理程序。
【示例1】menu-open是在onclick时间执行过程中被调用的。它会被立即执行,而不必等地啊onclick处理程序结束

<button id="menu">Menu (click me)</button>

<script>
  menu.onclick = function() {
    alert(1);

    menu.dispatchEvent(new CustomEvent("menu-open", {
      bubbles: true
    }));

    alert(2);
  };

  // 在 1 和 2 之间触发
  document.addEventListener('menu-open', () => alert('nested'));
</script>

// ------输出结果为:
// 1
// nested
// 2

1、嵌套事件menu-open会在document上被捕获。嵌套事件的传播(propagation)和处理先被完成,然后处理过程才会返回到外部代码(onclick)。
2、这不只是与diapatchEvent有关,还有其他情况。如果一个事件处理程序调用了触发其他事件的方法,它们同样也会被以嵌套的方式同步处理。
3、有时候这不是我们期望结果。文末想让onclick不受menu-open或者其它嵌套事件的影响,优先被处理完毕。
【示例2】可以将dispatchEvent(或另一个触发事件的调用)放在onclick末尾,或者最好将其包装到零延迟的setTimeout中

<button id="menu">Menu (click me)</button>

<script>
  menu.onclick = function() {
    alert(1);

    setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
      bubbles: true
    })));

    alert(2);
  };

  document.addEventListener('menu-open', () => alert('nested'));
</script>

// ------输出结果为:
// 1
// 2
// nested

4、这样,dispatchEvent在当前代码执行完成后一步运行,包括mouse.onclick,因此,事件处理程序是完全独立的