I have a custom array class that extends the base array class. I have a custom method for ease of use
export class ExampleArray extends Array {
includesThing(thing) {
...
return false
}
}
However the existing methods of filter
, map
etc return an instance of an array. I would like to return an instance of ExampleArray
with these methods.
I can find the interface for these methods, but not their implementation. How do I call the parent method and return my custom EampleArray instead? Something like the following
export class ExampleArray extends Array {
filter() {
result = Array.filter()
array = new ExampleArray()
array.push(...result)
return array
}
Or is this even the correct way to extend an Array to make a custom array?
I have a custom array class that extends the base array class. I have a custom method for ease of use
export class ExampleArray extends Array {
includesThing(thing) {
...
return false
}
}
However the existing methods of filter
, map
etc return an instance of an array. I would like to return an instance of ExampleArray
with these methods.
I can find the interface for these methods, but not their implementation. How do I call the parent method and return my custom EampleArray instead? Something like the following
export class ExampleArray extends Array {
filter() {
result = Array.filter()
array = new ExampleArray()
array.push(...result)
return array
}
Or is this even the correct way to extend an Array to make a custom array?
Share Improve this question edited Oct 5, 2018 at 13:47 M.A.K. Ripon 2,1483 gold badges30 silver badges48 bronze badges asked Sep 27, 2018 at 3:41 myolmyol 9,83823 gold badges95 silver badges158 bronze badges 4- 1 I think you might have better success using object position – Rafael Commented Sep 27, 2018 at 3:45
- Good question. I agree with @Rafael. You could have an array object as a property in ExampleArray. And, write glue logic to ferry data between Array and ExampleArray. i.e. If the method does not exist in Array then its the custom implementation else wrap results from Array methods to return ExampleArray type. – trk Commented Sep 27, 2018 at 3:48
- Could you provide an example of what you mean? – myol Commented Sep 27, 2018 at 4:00
- By using .prototype you can extend Array and add your custom method or override exist one. – Mohammad Rajabloo Commented Oct 5, 2018 at 13:37
4 Answers
Reset to default 11You will need to shadow the existing .filter
and .map
so that, when called on an instance of ExampleArray
, your new functions will be called, rather than the Array.prototype
functions. Inside ExampleArray
, you can access super.map
and super.filter
in order to get to the Array.prototype
methods. For example:
class ExampleArray extends Array {
constructor(...args) {
super(...args);
}
hasMoreThanTwoItems() {
// example custom method
return this.length > 2;
}
isExampleArray() {
return true;
}
// Shadow Array.prototype methods:
filter(...args) {
return new ExampleArray(
// Spread the result of the native .filter into a new ExampleArray instance:
...super.filter.apply(this, args)
);
}
map(...args) {
return new ExampleArray(
...super.map.apply(this, args)
);
}
}
const exampleArray = new ExampleArray(3, 4, 5, 6, 7);
// true, filtering will result in 3 items
console.log(
exampleArray
.filter(e => e > 4)
.hasMoreThanTwoItems()
);
// false, filtering will result in zero items
console.log(
exampleArray
.filter(e => e > 10)
.hasMoreThanTwoItems()
);
// true, is an ExampleArray
console.log(
exampleArray
.map(e => e * 2)
.isExampleArray()
);
Note that there are also other Array methods which return arrays, including splice
, slice
, and (experimental) flat
and flatMap
. If you want those to return a custom class instantiation rather than the default Array
instance, follow the same pattern: shadow the Array.prototype
function name, and return a new ExampleArray
populated with the result of apply
ing the Array.prototype
method:
<fnName>(...args) {
return new ExampleArray(
...super.<fnName>.apply(this, args)
);
}
You don't need to rewrite or override any of Array's methods. Just make sure you have a proper constructor.
The reason why this works is in the ES6 spec here (emphasis added):
9.4.2.3 ArraySpeciesCreate(originalArray, length)
...
Let C be Get(originalArray, "constructor"). ReturnIfAbrupt(C). If IsConstructor(C) is true, then
...
This is what Array.filter
uses to create the new array - it gets the constructor of the original object and uses that to construct the filtered array.
Here is the code from another answer with the redefined filter and map methods removed, and it works the same way without them. This code (using Chrome's console):
class ExampleArray extends Array {
constructor(...args) {
super(...args);
}
hasMoreThanTwoItems() {
// example custom method
return this.length > 2;
}
isExampleArray() {
return true;
}
}
const exampleArray = new ExampleArray(3, 4, 5, 6, 7);
// true, filtering will result in 3 items
console.log(
exampleArray
.filter(e => e > 4)
.hasMoreThanTwoItems()
);
// false, filtering will result in zero items
console.log(
exampleArray
.filter(e => e > 10)
.hasMoreThanTwoItems()
);
// true, is an ExampleArray
console.log(
exampleArray
.map(e => e * 2)
.isExampleArray()
);
produces this output:
true
false
true
I must add, this is not a good model for extending javascript classes in general, but Array, apparently, is extensible by design.
Much simpler is to create static functions that take any array as first argument.
That way the Array is still extended (feature wise). Also extending Array is confusing and does not scale in large teams. The constructor will also be lost whenever the array is transferred (for example JSON in HTTP request) without extra care.
Create new method name filter, and use inside with the 'super' key word, like so: super.filter(func)