How can I extend the Array class and keep its implementations

时光毁灭记忆、已成空白 提交于 2019-12-08 18:53:29

I think the problem here is that your List constructor does not expect the same arguments as the Array constructor.

When built-in methods like map() create a new array, they construct it using a constructor found in the static Symbol.species class property. This is, by default, the same as the class constructor itself... unless you override it. So List[Symbol.species] is List. And List.prototype.map() will end up calling new List(...). I'm pretty sure these methods expect the constructor at [Symbol.species] to take the same arguments as the Array constructor, namely one of these overloads:

new Array(element0, element1[, ...[, elementN]]); // variadic arguments, one per item in array
new Array(arrayLength); // one numeric argument specifying length 

But your List constructor expects to treat its first (and only) argument items as an iterable (since it uses spread syntax on it in the call to super(...items). When list.map(x=>x*2) executes it calls something like new List(3), and you get an error about 3 not being iterable.


So, what can you do to fix this? By far the easiest way is to make sure that your List constructor is compatible with the ArrayConstructor type, by having it take the same argument types.

The next easiest thing to do is to override List[Symbol.species] and return the Array constructor:

  static get [Symbol.species](): ArrayConstructor {
    return Array;
  }

But that would mean that list.map(x => x*2) returns an Array and not a List.

Assuming you really need your List constructor to take a single iterable argument instead of the same variadic-or-maybe-a-single-number arguments as Array, and assuming that you need list.map() to return a List, you can override the List[Symbol.species] property with something more complicated:

  static get [Symbol.species](): ArrayConstructor {
    return Object.assign(function (...items: any[]) {
      return new List(new Array(...items))
    }, List) as any;
  }

That essentially causes native methods to call new List(new Array(x,y,z)) instead of new List(x,y,z).

Okay, hope that makes sense and gives you some direction. Good luck!

There is no need to set the prototype. The error occurs because the constructor runs a second time when the map is called and the length of the array is passed as an argument, so when you try to spread the argument on the super call, it throws an error because a number is not iterable.

 constructor(items?: Array<T>) {

    console.log(`I've received `, items);
    items = items || [];
    super(...items);
    console.log(`Now i'm this`, this); //
    // Object.setPrototypeOf(this, List.prototype);

 }

Why does it happen? No idea! I do not have enough points yet, otherwise I'd put this as a comment! :-)

If you change the constructor to use ... to gather the arguments nothing will blow up:

 constructor(...items: Array<T>) { //...
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!