Javascript, objects, object keys, named functions, and chaos
.name
propertyWe can use this to our advantage if we wish to perform lookups based on functions instead of by keys
function setNamedFunction(obj, fn) {
var keys = Object.keys(obj),
name = fn.name;
//
// If `fn` __isn't__ a named function
// then this will never work, so return.
//
if (!name) {
return;
}
//
// No need to return `obj` since it is
// passed by reference
//
obj[name] = fn;
}
When a function is declared without a name it is an anonymous function. e.g.:
$.on('load', function () {
console.log('I am inside an anonymous function');
});
Anonymous functions are OK but they have two serious drawbacks:
name
property so they can be difficult to reflect acrossThe solution is to use named functions:
$.on('load', function onReady() {
console.log('I am inside the onReady function');
});
Lets suppose that instead of wanting to trigger a linear set of functions such as the core Node.js EventEmitter, you wanted to be able to trigger events as a (potentially cyclic) directed graph.
Scenario 1. A causes B & C to fire
A
├─B
│
└────C
Scenario 2. A causes B & C to fire. B causes D to fire
A
├──B
│ └─D
└────C
This could be implemented by modifying the underlying EventEmitter
(in this example EventEmitter3 thusly:
EventEmitter.prototype.connect = function (in, out) {
var self = this;
this.on(in, function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(out);
self.emit.apply(self, args);
});
return this;
};
In this way we could then setup the above scenarios:
Scenario 1. A causes B & C to fire
var emitter = new EventEmitter();
emitter.on('A', function a() { console.log('A fired'); });
emitter.on('B', function b() { console.log('B fired'); });
emitter.on('C', function c() { console.log('C fired'); });
emitter.connect('A', 'B');
emitter.connect('A', 'C');
Scenario 2. A causes B & C to fire. B causes D to fire
var emitter = new EventEmitter();
emitter.on('A', function a() { console.log('A fired'); });
emitter.on('B', function b() { console.log('B fired'); });
emitter.on('C', function c() { console.log('C fired'); });
emitter.on('D', function c() { console.log('C fired'); });
emitter.connect('A', 'B');
emitter.connect('A', 'C');
emitter.connect('B', 'D');
We could implement disconnect but we would have to add a naming convention to our functions in .connect()
and implment .disconnect()
:
EventEmitter.prototype.connect = function (in, out) {
var self = this,
listener;
listener = function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(out);
self.emit.apply(self, args);
};
listener.name = [in, out].join('_');
this.on(in, listener);
return this;
};
EventEmitter.prototype.unconnect = function (in, out) {
var name = [in, out].join('-'),
self = this;
this.listeners(in)
.filter(function (fn) {
return fn.name === name;
})
.forEach(function (fn) {
self.removeListener(in, fn);
});
return this;
};