Here's a quick JavaScript bug story. I'm working on Infinite Scroll v4, updating older ES5 code to ES2018.
If you create a class using the traditional function
class definition, you can use mix it in to another class' prototype with Object.assign()
. I use this pattern with EvEmitter and my plugins.
function EvEmitter() {}
EvEmitter.prototype.emit = function() {};
EvEmitter.prototype.on = function() {};
EvEmitter.prototype.off = function() {};
function InfiniteScroll() {}
Object.assign( InfiniteScroll.prototype, EvEmitter.prototype );
InfiniteScroll.prototype.create = function() {
this.emit( 'load', function() {} );
this.on( 'request', function() {} );
};
But, if you use ES6 classes expressions, you can no longer use the Object.assign()
mix-in pattern.
class EvEmitter {
emit() {}
on() {}
off() {}
}
function InfiniteScroll() {}
Object.assign( InfiniteScroll.prototype, EvEmitter.prototype );
InfiniteScroll.prototype.create = function() {
this.emit( 'load', function() {} );
this.on( 'request', function() {} );
};
Whaaa? The core issue is that class methods are non-enumerable. If you try iterating over Class.prototype
that was set with a class expression, you'll get nothing.
class EvEmitter {
emit() {}
on() {}
off() {}
}
console.log( Object.keys( EvEmitter.prototype ) );
I suppose this is an improvement
Thatโs good, because if we for..in over an object, we usually donโt want its class methods.
Except I've been iterating over prototype
for years.
The obvious solution is to define the inherited class with a class expression as well, using extend
to inherit the superclass.
class InfiniteScroll extends EvEmitter {
create() {
this.emit( 'load', function() {} );
this.on( 'request', function() {} );
}
}
But it's a bummer that I lose a feature by opting-in to the new syntax. EvEmitter
really is a mix-in and I would like to be able to use it like one. Read Angus Croll about why mixins are a good JavaScript pattern.. There is an ES6 approach for mix-ins and class expressions, but I'm not a fan.
So I reverted using class
and switched back to the original function expression & prototype
setting.
function EvEmitter() {}
EvEmitter.prototype.emit = function() {};
EvEmitter.prototype.on = function() {};
EvEmitter.prototype.off = function() {};
YAY back in business.