You can do some sort of pubsub implementation.
var events = {};
var pubsub = {
on: function(event, handler) {
if(event in events) {
events[event].push(handler);
} else {
events[event] = [handler];
}
},
emit: function(event) {
if(event in events) {
events[event].forEach(function(handler) {
handler.call(null, event);
});
}
}
};
This sort of implementation ties custom events to a global object, so elements don't have to "know" about each other, like you mentioned. The way you'd implement the above would be something like:
document.getElementById('some-element').addEventListener('click', pubsub.emit.bind(null, 'custom-event'));
The click will emit an event, and any handlers will be called. So in some other module, or whatever, based on whatever action you prescribe, you can tie a handler to that event emission:
pubsub.on('custom-event', doSomething);
function doSomething() { ... }
I think this is a pretty normal, language-agnostic global event-handling implementation.
There are lots of ways that JavaScript developers have figured out how to handle this.