I have some ES6 class inherited from Array
:
class Cache extends Array {
add(item) {
if(!item.doNotRemove)
this.push(item)
}
printLast() {
if(this.length > 0)
console.log(this[this.length - 1].text)
}
}
The following code works fine
const myCache = new Cache()
myCache.add({text: 'hello'})
myCache.add({text: 'world'})
myCache.add({text: '!!!', doNotRemove: true})
myCache.printLast() // world
But I can't transpile it to ES5 with Babel (I know there is an issue), and currently as a workaround I apply the following approach:
const CacheProto = {
add(item) {
if(!item.doNotRemove)
this.push(item)
},
printLast() {
if(this.length > 0)
console.log(this[this.length - 1].text)
}
}
function Cache() {
return Object.assign(Object.create(Array.prototype), CacheProto)
}
This satisfies the code above (myCache = new Cache()
etc). But as you can see, it's just an Array instance extending.
The question
Is it possible to have a workaround with original class? Of course, without extends Array
. Is it possible to have add
and printLast
methods and all Array.prototype
methods on the prototype chain, not on instance?
I have made a little plunker for possible research.
You only really have to extend Array
if you want to the magic .length
-affecting property assignment (arr[42] = 21;
) behavior or most of the array methods. If you don't need that, using an array as internal data structure seems to be the simplest (and most compatible) solution:
class Cache {
constructor() {
this._data = [];
}
add(item) {
if(!item.doNotRemove)
this._data.push(item)
}
printLast() {
if(this.length > 0)
console.log(this._data[this._data.length - 1].text)
}
}
You can easily expose .length
and other methods.
An easy way to pull in multiple methods from Array.prototype
would be:
['reduce', 'filter', 'find', ...].forEach(method => {
Cache.prototype[method] = function(...args) {
return this._data[method](...args);
};
});
// Or if you want them to be non-enumerable
// (like they would if they were defined via `class` syntax)
Object.defineProperties(
Cache.prototype,
['reduce', 'filter', 'find', ...].reduce((obj, method) => {
obj[method] = {
value: function(...args) { return this._data[method](...args); },
enumerable: false,
configurable: true,
writeable: true,
};
return obj;
}, {})
);
You can manipulate the prototype directly using __proto__
, it's also now kind of been standardised for backward compatibility reasons so should be safe to use.
More info here -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
edit: Like @Bergi pointed out, using a shim with Object.setPrototypeOf
would future proof this technique too.
function Cache() {
var arr = [];
arr.push.apply(arr, arguments);
arr.__proto__ = Cache.prototype;
//if using a shim, (better option).
//Object.setPrototypeOf(arr, Cache.prototype);
return arr;
}
Cache.prototype = new Array;
Cache.prototype.printLast = function () {
if(this.length > 0)
console.log(this[this.length - 1].text)
}
Cache.prototype.add = function (item) {
if(!item.doNotRemove)
this.push(item)
}
const myCache = new Cache()
myCache.add({text: 'hello'})
myCache.add({text: 'world'})
myCache.add({text: '!!!', doNotRemove: true})
myCache.printLast() // world
myCache.forEach(function (item) { console.log(item); });
console.log("is Array = " + Array.isArray(myCache));
This is a little hacky, but I'm pretty sure it does what you are looking for.
function Cache() {}
Cache.prototype = new Array;
Cache.prototype.add = function(item) {
if (!item.doNotRemove) {
this.push(item);
}
};
Cache.prototype.printLast = function() {
if (this.length <= 0) { return }
console.log(this[this.length - 1].text);
}
let test = new Cache();
test.add({foo:'bar', text: 'cake' });
test.add({baz:'bat', doNotRemove: true});
test.add({free:'hugs', text: 'hello'});
test.printLast();
console.log(test);
After some discussions here I was able to build a solution satisfied both of the requirements: keep original ES6 class as a state for new functionality and have this new functionality on the prototype as well as it is for the Array.prototype methods.
class CacheProto {
add(item) {
if(!item.doNotRemove)
this.push(item)
}
printLast() {
if(this.length > 0)
console.log(this[this.length - 1].text)
}
}
function Cache() {
const instance = [];
instance.push.apply(instance, arguments);
Object.setPrototypeOf(instance, Cache.prototype);
return instance;
}
Cache.prototype = Object.create(Array.prototype);
Object.getOwnPropertyNames(CacheProto.prototype).forEach(methodName =>
Cache.prototype[methodName] = CacheProto.prototype[methodName]
);
The only difference between this CacheProto
and the original class from the Question is that the CacheProto
class does not extend the Array
.
The end plunker could be obtained here. It contains this solution and all intermediate variants.