神刀安全网

event emitter, Object.defineProperty get and set, and combining them

JS event emitter, get and set, and combining them.

How to write a simple event emitter, how to use object property’s getters and setters, and how to mix them together for something way cooler.

In this article we will learn how to build a simple event emitter. If you think that’s a piece of cake — you are right! Jump to the second part and read about ES5 getters and setters, using Object.defineProperty and other methods. If you already know that — cool, your’e a champ! Jump to the third part, and see a new and interesting way to combine them both, I promise it will be thought-provoking.

Event emitter in Javascript

Event emitter is sort of a pattern widely all over the JS , from jQuery to angular, flux or node. If you’ve written JS you’ve probably used it already. But how does it work? Native JS does not support events!

First, lets look at the interface for a regular emitter:

myEmitter.on('dinner', (mainDish) => {
console.log('dinner is served with: ' + mainDish );
});
myEmitter.emit('dinner', 'lasagna');
setTimeout( function(){
myEmitter.emit('dinner', 'pasta');
}, 1000);

First, you have an object that will be your instance of the emitter ( myEmitter ), then you subscribe to “dinner with on method and provide a callback to handle it. Every time your emitter will emit this event, the callback function will get called.

In order to build an emitter like that, we just need to create an emitter object, that will have on and emit methods, and store all of the callbacks in a property named _subscriptions.

_subscriptions will be an object whose keys are the event names, and their values will be an array of all the registered callbacks for that event.

const emitter = {
_subscriptions:{
//someEvent:[cb1, cb2] //
},
}

on method will add keys and values to _subscriptions:

on(name, func){
//check for the existence of
// this event in _subscriptions or init it
this._subscriptions[name] = this._subscriptions[name] || [];
//push the callback that array
this._subscriptions[name].push(func);
},

last, add emit that will call all callbacks when triggered:

emit(name, …args){
if(!this._subscriptions[name]){return; }
this._subscriptions[name].forEach(f=>f(…args))
}

full code for this simple event emitter.

event emitter, Object.defineProperty get and set, and combining them

Property definition

Normally you would create objects and initiate their properties like so:

let obj = {
key1: 'john'
};
obj.key2 = 'smith';

‘key1’ and ‘key2’ are writable (you can override their value by another assignment), and enumerable (you’ll see all of the keys when calling Object.keys(obj) and you’ll iterate over the with for..in loop). Read more about it here .

They also have normal read and write:

obj.key1 // ‘john’
obj.key1 = ‘adam’;
obj.key1 // 'adam'

The cool thing here is that you can change all that! that’s done with Object.defineProperty/ies :

Object.defineProperty(obj, 'key1', descriptor);
//OR
Object.defineProperties(obj, {
"key1": descriptor
},

descriptor is another object, that will allow you to configure exactly how this property should behave. You can choose value, set it to be non-writable , or non-enumerable , and whether this configuration is later configurable . In this article I’ll focus at two other properties of the descriptor, read more about the descriptor (and about configurable, enumerable, value and writable) here .

The descriptor has two more methods: get and set . Normally, when you call obj.prop, you expect to get the value of that prop, but you can alter that:

var obj = {key1: 'john'}
Object.defineProperty(obj, 'key2', {
get: function(){
return "John is way too normal for me";
}
});
// OR when you init obj:
var obj = {
key1: 'john',
get key2(){return '...'}
}
obj.key2 = "I'm John too";
console.log(obj.key2) // logs: "John is way too normal for me";

we can also play with the assignment:

var obj = {};
var name = ‘John’;
Object.defineProperty(obj, ‘key2’, {
set: function(val){
name = val + val;
},
get: function(){return name}
});
console.log(obj.key2) // logs name: "John";
obj.key2 = "John"; // changes name;
console.log(obj.key2) // logs: "JohnJohn";

Notice that name is not internal to obj.key2, you cannot have value of a property if you define setters and getters. Also pay attention thatObject.defineProperty sets enumerable and configurable to false unless otherwise specified.

event emitter, Object.defineProperty get and set, and combining them

Mixing getters-setters with eventEmitter = cool event emitter

Lets use what we’ve learned about property descriptors in order to create a new interface for our event emitter. What I’d like to see, is something like this:

//use assignment (set) as 'on'
myEmitter.dinner = function(){
console.log('dinner is served' );
};
//second assignment
myEmitter.dinner = function(mainDish){console.log(mainDish)}
// getters will be 'emit'
myEmitter.dinner('pasta');
// logs:
// 'dinner is served'
// 'pasta'

To do this, let’s create a function that will generate those unique emitter-events. This function will also present a closured variable that will hold all of the subscribed callbacks:

function emitize(obj, eventName) { 
var _subscriptions = [];
Object.defineProperty(obj, eventName, getterSetterDescriptor)
};
//usage:
let myEmitter = {};
emitize(myEmitter, 'dinner')

Now, in getterSetterDescriptor , we’ll add get and set with logic from the simple eventEmitter above:

function emitize(obj, eventName) { 
var _subscriptions = [];
Object.defineProperty(obj, eventName, {
get(){
return function emit(...args){
if (!_subscriptions) {
return function () {};
}
_subscriptions.forEach(f=>f(...args));
};
},
set(cb) {
_subscriptions.push(cb);
},
})
};

get returns a function, so it will be able to receive arguments. If we want to add off functionality to this, we can just use Object.defineProperty again, this time over the internal emit function! Here’s the full code ( gist here ):

function emitize(obj, eventName) {
var _subscriptions = [];
Object.defineProperty(obj, eventName, {
set(func) {
_subscriptions.push(func);
},
get() {
var emit = (…args)=>{
if (!_subscriptions) { return function () {}; }
_subscriptions.forEach(f=>f(…args)); };
Object.defineProperty(emit, ‘off’, {
set(func) {
_subscriptions = _subscriptions.filter(f => f!==func);},
get() { _subscriptions = []; }
});
return emit;
}
});
}
//usage:
var obj = {};
emitize(obj, 'x');
function log(v) {
console.log(v);
}
function logPlus1(v) {
console.log(v + 1);
}
obj.x = log; //subscribe to events (named 'x') with cb (log)
//another subscription won't override the previous one
obj.x = logPlus1;
obj.x(9); //emits '9' to all listeners;
//logs:
//'9'
//'10'
obj.x.off = logPlus1; //unsubscribe logPlus1
obj.x.off; //or unsubscribe from all callbacks
event emitter, Object.defineProperty get and set, and combining them

Hope you enjoyed this article, and maybe even learned something new. Is this interface for eventEmitter really usable or is it too non-regular and confusing? I don’t know, it’s up to you to decide, but when you learn a new tool—cool implementations starts to follow.

If you liked it — Share, subscribe and follow my twitter .

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » event emitter, Object.defineProperty get and set, and combining them

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮