我会尽量用最抽象的方式来解释。真正的实现可能要复杂得多。因此,我将要使用的名称是假设性的,但它们确实可以很好地解释事物,我希望 ;)
浏览器中的每个节点都是EventEmitter 类的实现。此类维护一个对象 events,其中包含 key:value 对 eventType(键):一个包含 listener 函数(值)的数组。
EventEmitter 类中定义的两个函数是addEventListener 和fire。
class EventEmitter {
constructor(id) {
this.events = {};
this.id = id;
}
addEventListener(eventType, listener) {
if (!this.events[eventType]) {
this.events[eventType] = [];
}
this.events[eventType].push(listener);
}
fire(eventType, eventProperties) {
if (this.events[eventType]) {
this.events[eventType].forEach(listener => listener(eventProperties));
}
}
}
addEventListener 被程序员用来注册他们想要的listener 函数,以便在执行他们想要的eventType 时被触发。
请注意,对于每个不同的eventType,都有一个不同的数组。该数组可以为同一个eventType 保存多个listener 函数。
fire 由浏览器调用以响应用户交互。浏览器知道已经执行了什么样的交互以及在哪个节点上执行了交互。它使用该知识在适当的节点上调用fire,并使用适当的参数eventType 和eventProperties。
fire 循环遍历与特定 eventType 关联的数组。遍历数组,它调用数组中的每个listener 函数,同时将eventProperties 传递给它。
这就是 listener 函数(仅使用特定 eventType 注册)在调用 fire 后被调用的方式。
以下是演示。此演示中有 3 个演员。程序员、浏览器和用户。
let button = document.getElementById("myButton"); // Done by the Programmer
let button = new EventEmitter("myButton"); // Done by the Browser somewhere in the background.
button.addEventListener("click", () =>
console.log("This is one of the listeners for the click event. But it DOES NOT need the event details.")
); // Done By the Programmer
button.addEventListener("click", e => {
console.log(
"This is another listener for the click event! However this DOES need the event details."
);
console.log(e);
}); // Done By the Programmer
//User clicks the button
button.fire("click", {
type: "click",
clientX: 47,
clientY: 18,
bubbles: true,
manyOthers: "etc"
}); // Done By the Browser in the background
在用户点击按钮后,浏览器在按钮上调用fire,将“点击”作为eventType 和持有eventProperties 的对象传递。这会导致调用“单击”eventType 下所有已注册的listener 函数。
如您所见,浏览器总是让eventProperties 着火了。作为程序员,您可能会也可能不会在 listener 函数中使用这些属性。
我发现对 stackoveflow 有帮助的一些答案:
Where is an event registered with addEventListener stored?
Where are Javascript event handlers stored?