89 lines
3.2 KiB
Markdown
89 lines
3.2 KiB
Markdown
|
|
# Extending built-in classes
|
|
|
|
Built-in classes like Array, Map and others are extendable also.
|
|
|
|
For instance, here `PowerArray` inherits from the native `Array`:
|
|
|
|
```js run
|
|
// add one more method to it (can do more)
|
|
class PowerArray extends Array {
|
|
isEmpty() {
|
|
return this.length === 0;
|
|
}
|
|
}
|
|
|
|
let arr = new PowerArray(1, 2, 5, 10, 50);
|
|
alert(arr.isEmpty()); // false
|
|
|
|
let filteredArr = arr.filter(item => item >= 10);
|
|
alert(filteredArr); // 10, 50
|
|
alert(filteredArr.isEmpty()); // false
|
|
```
|
|
|
|
Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type `PowerArray`. Their internal implementation uses object `constructor` property for that.
|
|
|
|
In the example above,
|
|
```js
|
|
arr.constructor === PowerArray
|
|
```
|
|
|
|
When `arr.filter()` is called, it internally creates the new array of results using exactly `arr.constructor`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result.
|
|
|
|
Even more, we can customize that behavior.
|
|
|
|
We can add a special static getter `Symbol.species` to the class. If exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on.
|
|
|
|
If we'd like built-in methods like `map` or `filter` to return regular arrays, we can return `Array` in `Symbol.species`, like here:
|
|
|
|
```js run
|
|
class PowerArray extends Array {
|
|
isEmpty() {
|
|
return this.length === 0;
|
|
}
|
|
|
|
*!*
|
|
// built-in methods will use this as the constructor
|
|
static get [Symbol.species]() {
|
|
return Array;
|
|
}
|
|
*/!*
|
|
}
|
|
|
|
let arr = new PowerArray(1, 2, 5, 10, 50);
|
|
alert(arr.isEmpty()); // false
|
|
|
|
// filter creates new array using arr.constructor[Symbol.species] as constructor
|
|
let filteredArr = arr.filter(item => item >= 10);
|
|
|
|
*!*
|
|
// filteredArr is not PowerArray, but Array
|
|
*/!*
|
|
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
|
|
```
|
|
|
|
As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further.
|
|
|
|
```smart header="Other collections work similarly"
|
|
Other collections, such as `Map` and `Set`, work alike. They also use `Symbol.species`.
|
|
```
|
|
|
|
## No static inheritance in built-ins
|
|
|
|
Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc.
|
|
|
|
As we already know, native classes extend each other. For instance, `Array` extends `Object`.
|
|
|
|
Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the chapter [](info:static-properties-methods#statics-and-inheritance).
|
|
|
|
But built-in classes are an exception. They don't inherit statics from each other.
|
|
|
|
For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no `Array.keys()` and `Date.keys()` static methods.
|
|
|
|
Here's the picture structure for `Date` and `Object`:
|
|
|
|

|
|
|
|
As you can see, there's no link between `Date` and `Object`. They are independent, only `Date.prototype` inherits from `Object.prototype`.
|
|
|
|
That's an important difference of inheritance between built-in objects compared to what we get with `extends`.
|