07. Species Pattern

Sometimes a method creates new instances of its class. If you create a subclass – should the method return an instance of its class or an instance of the subclass? A few built-in ES6 methods let you configure how they create instances via the so-called species pattern.

The standard species pattern is used by Promise.prototype.then(), the filter() method of Typed Arrays and other operations. It works as follows:

  • If this.constructor[Symbol.species] exists, use it as a constructor for the new instance.

  • Otherwise, use a default constructor (e.g. Array for Arrays).

Implemented in JavaScript, the pattern would look like this:

function SpeciesConstructor(O, defaultConstructor) {
  const C = O.constructor;
  if (C === undefined) {
    return defaultConstructor;
  }
  if (!isObject(C)) {
    throw new TypeError();
  }
  const S = C[Symbol.species];
  if (S === undefined || S === null) {
    return defaultConstructor;
  }
  if (!isConstructor(S)) {
    throw new TypeError();
  }
  return S;
}

Symbol.species

This is the default getter for the property [Symbol.species]:

static get [Symbol.species]() {
    return this;
}

You can override the default species via a static getter (line A):

class MyArray1 extends Array {
  static get [Symbol.species]() {
    // (A)
    return Array;
  }
}

As a result, map() returns an instance of Array:

const result1 = new MyArray1().map(x => x);
console.log(result1 instanceof Array); // true

If you don’t override the default species, map() returns an instance of the subclass:

class MyArray2 extends Array {}

const result2 = new MyArray2().map(x => x);
console.log(result2 instanceof MyArray2); // true

Last updated