对于不同对象之间的通信,有没有比事件监听更好的方法?

Is there a better way than event listening for communication between different objects?

假设我们有一个发射器对象实现了一些逻辑,另一个不同类型的对象根据发射器对象触发的事件实现了一些其他逻辑。我想,我们可以通过在发射器中使用 function[pointer]s 来简单地解决这个问题,并通过使用添加侦听器对象的侦听器函数的函数来更改它们的目标,如下面的代码所示。即使我们可以删除它。就像 DOM 事件一样,我可以说。

你能为这个以前从事其他行业的新手推荐一个更好的方法吗?提前致谢。

class Emitter {
  constructor() {
    this.event1 = this.empty;
    this.event2 = this.empty;
  }

  empty(){}

  someEmmitterLogic(input) {
    // arbitrary logic here before event1 occurs
    this.event1({message:"produced at event1", b:1});
    // maybe event2 can also occur here
    this.event2({message:"produced at event2", d:2});
  }

  addEvent(event, listener) {
    switch (event) {
      case "event1":
        this.event1 = listener;
        break;
      case "event2":
        this.event2 = listener;
        break;
      default:
        console.log("Sorry, we don't emit this event.");
    }
  }

  removeEvent(event) {
    switch (event) {
      case "event1":
        this.event1 = this.empty;
        break;
      case "event2":
        this.event2 = this.empty;
        break;
      default:
        console.log("Sorry, cannot remove an event we are not emitting.");
    }
  }  
}

class Listener{
    constructor(){
// Simple listeners outputting the event object to the console 
    this.listener1 = (e) => console.log(e.message);
    this.listener2 = (e) => console.log(e.message);
}
}
///////////////////////////////////////////////////////////////////////////
const emitter = new Emitter();
const listener = new Listener();

console.log("STATUS: not listening emitter");
emitter.someEmmitterLogic();

// Add listeners
emitter.addEvent("event1", listener.listener1);
emitter.addEvent("event2", listener.listener2);

console.log("STATUS: started listening emitter");
emitter.someEmmitterLogic();

console.log("say we don't need event1 anymore, let's remove it");
emitter.removeEvent("event1");
console.log("STATUS: listening event2 only");
emitter.someEmmitterLogic();
.as-console-wrapper {
    max-height: 100% !important;
}

Fiddle of the code above

活动

以这种方式使用事件是提供此类通信的一种 well-established 方式。如果您寻找事件发射器的现有实现,例如 Node.js's 或搜索“publish/subscribe”,您会发现许多可以借鉴的现有技术。

一些注意事项:

  • 通常,您希望拥有一组事件处理程序,而不是只允许一个。
  • 通常,发射器会将对事件处理程序的调用包装在 try/catch 块中,以便抛出错误的处理程序不会阻止发射器代码继续执行其工作(这通常只是为了通知事件的侦听器)。
  • 一些系统(包括 DOM 的系统)为所有侦听器提供相同的事件对象,允许它们之间有一些 cross-talk。不受控制的 cross-talk 可能不是一个好主意,但某种形式的受控 cross-talk 可能会有用。
  • 类似地,一些系统(包括 DOM 的系统)为事件侦听器提供了一种取消事件的方法,防止它到达其他侦听器。

协程

当要观察序列(从广义上讲)时,另一种沿着这些线进行通信的方法是使用 coroutines. In JavaScript, coroutines can be implemented using generators,这最容易通过生成器函数创建。生成器是一个对象,它会响应对其 next 方法的调用而生成和使用值。

这是一个非常简单的生成器,它只产生(不消耗)值:

// Generator function (note the `*` after `function`) returns
// a generator. This generator provides a series of values in
// a given range (inclusive).
function* range(from = 1, to = 10) {
    console.log("Generator: Starting");
    for (let value = from; value <= to; ++value) {
        console.log(`Generator: Ready with value ${value}`);
        yield value;
    }
}

// A popular way to consume generators (and iterables in general) is
// the `for-of` loop. When the generator/iterable is infinite
// as in our case, you need to be sure to break out of it at some point.
for (const value of range(1, 5)) {
    console.log(`for-of:    Received ${value}`);
}
console.log(`Done`);
.as-console-wrapper {
    max-height: 100% !important;
}

注意生成器的逻辑(在这种情况下非常简单的逻辑,一个 for 循环)和它的消费者(同样非常简单,for-of 循环)是如何混合的。

Generators 是生成像这样的可迭代对象的好方法,但它们也可以 consume 推送给它们的值。要将值推送到生成器,您可以直接调用 next 而不是隐式调用。这是生成器的另一个简单示例,但是这个生成器同时生成 消耗值 — 这是一个简单的 1-100 数字猜谜游戏,生成器负责选择数字并告诉您如何好吧,你的猜测是:

function* hiddenNumber(from = 1, to = 100) {
    console.log("Generator: Starting");
    const size = (to - from + 1);
    const num = Math.floor(Math.random() * size) + from;
    // console.log("cheat: " + num);
    let guess = yield `Pick a number between ${from} and ${to} (inclusive)`;
    while (guess !== num) {
        if (guess < num) {
            if (Math.abs(num - guess) >= size / 2) {
                guess = yield "REALLY low";
            } else {
                guess = yield "too low";
            }
        } else if (guess > num) {
            if (Math.abs(num - guess) >= size / 2) {
                guess = yield "REALLY high";
            } else {
                guess = yield "too high";
            }
        }
    }
    return "you got it!";
}

const prompt = document.getElementById("prompt");
const input = document.getElementById("input");
const button = document.getElementById("button");
const doGuess = () => {
    const num = input.valueAsNumber;
    const result = game.next(num);
    const answer = result.value;
    prompt.textContent = answer;
    if (result.done) {
        button.disabled = true;
    } else {
        input.focus();
        input.select();
    }
};
const game = hiddenNumber();
prompt.textContent = game.next().value;
button.addEventListener("click", doGuess);
input.addEventListener("keypress", event => {
    if (event.key === "Enter") {
        doGuess();
    }
});
input.select();
<p id="prompt"></p>
<input id="input" type="number" value="0" step="1">
<input id="button" type="button" value="Guess">

您可以看到使用它的代码 (doGuess) 与生成器逻辑的交互。

协程确实很有用,但(恕我直言)可能不如事件发射器通用。