Как я могу расширить класс Array и сохранить его реализации - PullRequest
0 голосов
/ 04 февраля 2019

Я хотел бы добавить некоторые функции в класс Array (я бы предпочел не иметь их в качестве функций, внешних по отношению к классу, поскольку в идеале это можно было бы обнаружить при вводе . после объекта).Это то, что я до сих пор:

export class List<T> extends Array<T> {
    constructor(items?: Array<T>) {
        super(...items)
        Object.setPrototypeOf(this, List.prototype);
    }

    get first(): T {
        return this[0]
    }
}

это нормально работает:

const list = new List([1,2,3]);
console.log(list.first)

, но если я пытаюсь запустить это:

const list = new List([1,2,3]);
console.log(list.map(x=>x*2))

я получаюследующая ошибка:

        super(...items)
        ^
TypeError: Found non-callable @@iterator

В идеале я хотел бы получить объект обратно, эквивалентный new List(this.map(x=>x*2)) Как я могу расширить класс Array без необходимости переписывать все методы Array?

Ответы [ 2 ]

0 голосов
/ 05 февраля 2019

Думаю, проблема в том, что ваш конструктор List не ожидает те же аргументы, что и конструктор Array.

Когда встроенные методы, такие как map(), создают новый массив, они конструируют его, используя конструктор, найденный в static Symbol.species свойстве класса .По умолчанию это то же самое, что и сам конструктор класса ... если вы не переопределите его.Так что List[Symbol.species] это ListList.prototype.map() в итоге вызовет new List(...).Я почти уверен, что эти методы ожидают, что конструктор в [Symbol.species] примет те же аргументы, что и конструктор Array , а именно одну из следующих перегрузок:

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

Но ваш *Конструктор 1020 * ожидает, что его первый (и единственный) аргумент items будет повторяться (поскольку он использует распространяемый синтаксис для него в вызове super(...items). Когда list.map(x=>x*2) выполняется, он вызывает что-то вродеnew List(3), и вы получаете сообщение о том, что 3 не повторяется.


Итак, что вы можете сделать, чтобы это исправить? Безусловно, самый простой способ - убедиться, что ваш ListКонструктор совместим с типом ArrayConstructor, так как он принимает те же типы аргументов.

Следующий простой способ - переопределить List[Symbol.species] и вернуть конструктор Array:

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

Но это будет означать, что list.map(x => x*2) возвращает Array, а не List.

Предполагая, что вам действительно нужен ваш конструктор List для выполнения одной итерацииаргумент вместо того же самого variadic-или-возможно-может-единственного числааргументы как Array, и, предполагая, что вам нужно list.map(), чтобы возвратить List, вы можете переопределить свойство List[Symbol.species] чем-то более сложным:

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

Это по существу вызывает вызов собственных методовnew List(new Array(x,y,z)) вместо new List(x,y,z).

Хорошо, надеюсь, что это имеет смысл и дает вам некоторое направление.Удачи!

0 голосов
/ 05 февраля 2019

Нет необходимости устанавливать прототип.Ошибка возникает из-за того, что конструктор запускается второй раз, когда вызывается карта, а длина массива передается в качестве аргумента, поэтому, когда вы пытаетесь распространить аргумент в супер вызове, он выдает ошибку, потому что число не повторяется..

 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);

 }

Почему это происходит?Без понятия!У меня пока недостаточно очков, иначе я бы поставил это как комментарий!: -)

Если вы измените конструктор на использование ... для сбора аргументов, ничего не взорвется:

 constructor(...items: Array<T>) { //...
...