那些被忽略但很好用的 Web API / CustomEvent

自己的事件自己决定。

网页最重要的两件事,资讯显示与使用者交互,而使用者交互在页面中所代表的行为就是「监听事件」与「触发事件」,相信这是大家在熟悉不过的了,clickinputblurscroll...等等,几乎充斥在我们的网站中,但除了这些常见的、预设的事件之外,其实我们也可以自己创造出全新的事件。


# Window.CustomEvent

CustomEvent 本身是一个建构函式,也就是我们常讲的 class,当我们想要建立自订事件时,就透过 new 关键字来呼叫它即可,并且要记得传入代表事件名称的字串,另外还可以传入第二个参数来设定事件触发时传递的资料。

要注意的是,用来设定资料的第二个物件必须要是一个物件,且要传递的资料必须设定在该物件的 detail 属性底下。

const customEvent = new CustomEvent("myEvent", {
  detail: { customData: "maxLee" },
});

而当有 DOM 元素需要绑定这个事件时,一样使用 addEventListener 来处理即可,而其中事件 Callback 会拿到的 Event 物件就会多一个 detail 的属性,该属性就会是我们当初设定的事件传递资料。

const customEvent = new CustomEvent("myEvent", {
  detail: { customData: "maxLee" },
});
document.querySelector("#element").addEventListener("myEvent", function (event) {
  console.log(event.detail); // { customData: "maxLee" }
});

 

# EventTarget.dispatchEvent

与 CustomEvent 最极其相关的 API 就是 DispatchEvent 了,它是一个可以让我们主动触发事件的方法,当我们创建并绑定了一个事件後,就必须要倚靠它来帮我们启动事件了。

其中 EventTarget 是一个代称,它所指的是绑定事件的 DOM 对象,例如以下程序码中,div 就是 EventTarget:

const customEvent = new CustomEvent("myEvent", {
  detail: { customData: "maxLee" },
});
const div = document.querySelector("div");
div.addEventListener("myEvent", function (e) {
  console.log(event.detail);
});

此时上面的 div 已经被绑上了我们自订的 myEvent 事件,这时候我们就可以使用 dispatchEvent 来主动触发事件,只要在呼叫它时传入 CustomEvent 物件即可:

const customEvent = new CustomEvent("myEvent", {
  detail: { customData: "maxLee" },
});
const div = document.querySelector("div");
div.addEventListener("myEvent", function (e) {
  console.log(event.detail);
});
div.dispatchEvent(customEvent);

 

# 运用场景

认识了 CustomEvent 後,我们来假设一个需求:「今天有个页面,在进入时会向後端 request 资料,当资料回来後,我们要更改页面的标题及一个 list 的内容」,当然了,如果使用前端框架的话,这是一个非常简单的事情,但我们先假如这次专案不允许使用框架,那一般的写法可能会是这样:

function updateTitle(title) {
  const title = document.querySelector("h1");
  title.textContent = title;
}

function updateList(list) {
  const ul = document.querySelector("ul");
  ul.innerHtml = "";
  list.forEach((item) => {
    const li = document.createElement("li");
    li.textContent = item;
    ul.appendChild(li);
  });
}

function onDataFetch(res) {
  updateTitle(res.data.title);
  updateList(res.data.list);
}

// 如果不认识 axios,可以把它当成一个请求资料的 Promise 即可
axios.get("https://backend/data").then(onDataFetch);

以上这样的写法其实已经算是尽量避免耦合了,因为还额外包装了一支 onDataFetch 函式来独立处理取得资料後的事情,但如果未来还有其他的事情要处理,就必须再加进这个函式中,而且其他人在阅读时,可能会误以为里面执行内容可能有顺序性。那接下来我们看看使用 CustomEvent 可以怎麽写:

let dataFetchEventTarget = [];

function addDataFetchEvent(element, callback) {
  dataFetchEventTarget.push(element);
  element.addEventListener("dataFetch", callback);
}

addDataFetchEvent(document.querySelector("h1"), function (e) {
  this.textContent = e.detail.title;
});

addDataFetchEvent(document.querySelector("ul"), function (e) {
  this.innerHtml = "";
  e.detail.list.forEach((item) => {
    const li = document.createElement("li");
    li.textContent = item;
    this.appendChild(li);
  });
});

// 用 setTimeout 来模拟请求资料
setTimeout(() => {
  const dataFetchEvent = new CustomEvent("dataFetch", {
    detail: res.data,
  });
  dataFetchEventTarget.forEach((target) => {
    target.dispatchEvent(dataFetchEvent);
  });
}, 3000);

首先我们先宣告了一个阵列 dataFetchEventTarget,打算来存放所有有注册事件的元素,然後写了一个函式 addDataFetchEvent 来注册事件,并且同时将元素丢进阵列中,直到我们将资料请求回来後开始建立自订事件,并且把 dataFetchEventTarget 中的元素一一取出并 dispatchEvent 事件。

这样写法的好处在於,「取得资料」跟「後续行为」完全没有耦合,「注册事件」与「触发事件」完全是独立的两件事,所以未来如果有其他地方注册了这个事件,我们也不需要额外处理任何事,等到事件触发了,Callback 自然会去执行。

 

一般来说,没有特别去设计的话,大家都会使用第一种方式吧?但其实使用 CustomEvent 的话,会很接近 Design Patterns 中的观察者模式(Observer Pattern),其实是一个非常不错的撰写方式,大家可以在未来的开发中尝试看看。


<<:  VoK 系统功能权责划分 ( II ) - day14

>>:  [Day 24] 新功能又来了!时间相关的测试!

[Day28]-网路爬虫

上网不须要浏览器 Webbrowser模组 用地址查询地图的程序设计 下载网页资讯使用reques...

Day 24 : Jenkins 在Build完通知与好用套件

介绍Jenkins的章节即将进入尾声了。事实上你可能会想Jenkins默认介面这麽老气,怎麽就成为全...

[DAY 05] 盐水豆签羹

盐水豆签羹 地点:台南市盐水区朝琴路19号 时间:14:00~19:00 这一家的照片忘了拍 但是还...

强型闯入DenoLand[27] - Web API 介绍

强型闯入DenoLand[27] - Web API 介绍 终於来到本系列文的最终阶段 - Web...