扩展数组时发现不可调用的@@iterator

Found non-callable @@iterator when extending Array

当我尝试扩展数组时 class:

class Collection extends Array {
    constructor(array){
        super(...array)
    }

    getTimes(){
        return this.map(value=> value.getTime())
    }

    min(){
        return new Date(Math.min(...this.getTimes()))
    }
}



const array = ['2000/02/01','2000/02/02']
const dates = array.map(value => new Date(value))

const test = new Collection(dates)

console.log(test[Symbol.iterator]); //Iterator exists
console.log(test.min()); ///Error: Found non-callable @@iterator

出于某种原因出现此错误,但是当我们这样做时:

class Collection extends Array {
    static from(array){ /// Using a Factory instead of a constructor
        return new Collection(...array)
    }
...
...
}

const test = Collection.from(dates)

console.log(test.min()); //returns the Date

一切正常。为什么构造函数的迭代器有问题??

问题是 array.map() return 是一个 new 数组,并且会调用您的构造函数来完成此操作。

可以在此处看到此行为:

class CustomArray extends Array {
  constructor(...args) {
    super(...args)
    console.log('constructor called')
  }
}

const myArray = new CustomArray(1, 2, 3)
console.log('call .map()')
myArray.map(x => x)

因为您的自定义构造函数不采用与默认数组构造函数相同的参数,所以 .map() 函数在尝试使用它时会出错。以下实现将为您工作,因为它更改了构造函数以采用 .map() 期望的参数。

class Collection extends Array {
    constructor(...args){
        super(...args)
    }

    getTimes(){
        return this.map(value=> value.getTime())
    }

    min(){
        return new Date(Math.min(...this.getTimes()))
    }
}



const array = ['2000/02/01','2000/02/02']
const dates = array.map(value => new Date(value))

const test = new Collection(...dates)

console.log(test.min());

也许这不是您想要的,因为您可能想要更改构造函数的行为。事实证明,Javascript 为数组提供了一个特殊的 species symbol,专门用于帮助像您所做的那样制作数组 subclasses。你可以给这个物种符号一个不同的构造函数来使用,像 .map() 这样的函数将使用这个物种符号提供的任何东西。

class Collection extends Array {
    constructor(array){
        super(...array)
    }
    
    static [Symbol.species](...args) {
        return new Collection(args)
    }

    static get [Symbol.species]() {
      return function(...args) {
        return new Collection(args)
      }
    }

    getTimes(){
        return this.map(value=> value.getTime())
    }

    min(){
        return new Date(Math.min(...this.getTimes()))
    }
}



const array = ['2000/02/01','2000/02/02']
const dates = array.map(value => new Date(value))

const test = new Collection(dates)

console.log(test.min());

这使用了很多技巧,所以我建议尽可能避免使用这种特定的解决方案。但是,基本思想是物种 属性 应该设置为 class,然后将由 .map() 等函数更新。在上面的示例中,我将 species 属性 设置为 getter (否则我无法使其正常工作 - 我认为这与它的默认值设置方式有关).这个 getter returned 函数,在这种情况下会像 class 一样工作(您不必使用 class 关键字来创建 class).当一个普通函数被更新时,我只是 return 集合的一个实例 class,正确构造(即使这个匿名函数是正在构造的东西,它可以 return 完全不同的东西) .