4

I am trying to create a subclass of javascript's Array. I want to initiate subclass with array-type argument and add a method to remove an element from the array (subclass).

My code looks like this:

class CustomArray extends Array {
        
  constructor(array) {
    console.log('Initiating array:', array)
    super(...array);
  }

  remove(element) {
    let index = this.indexOf(element);
    if (index > -1) {
      return this.splice(index, 1);
    } 
    return [];
  }

}

var a = ['a', 'b', 'c', 'd', 'e'];

var list = new CustomArray(a)
console.log('list:', list);
console.log('remove:', list.remove('c'));
console.log('list:', list);

The problem is that when I call .splice() it removes element from array, but also returns array of deleted elements, actually it returns new instance of my subclass CustomArray, which should be initiated with argument of array type, but .splice() initiates it with arguments of integer type.

Here is an example of what I think happens when I call .splice():

Let's say we have instance list of CustomArray class initiated with argument ['a','b','c','d','e'] and then we call method list.remove('c'). (Just like in code snippet).
Method remove of CustomArray class checks the index of c is in array ['a','b','c','d','e'], which is 2 and then calls method this.splice(2,1) which should remove 1 element at index 2 in array ['a','b','c','d','e']. Method splice removes element c form array, but also returns something like new CustomArray(1) because one element was removed form array so it tries to create array of length 1, but that failes because class CustomArray is expectiong array.

I want to prevent splice method from initiating a new instance of CustomArray class and instead return normal array (an instance of Array object).

Jsbin link.

4
  • FWIW, I just noticed a bug in the code in my answer which I've fixed (missing ++index; line). Commented Mar 18, 2018 at 19:23
  • CustomArray or CostomArray? Commented Mar 18, 2018 at 19:30
  • Thanks for the fix on ++index, but I used for loop instead of while. And it is CustomArray, I edited that in the question. Commented Mar 18, 2018 at 22:34
  • Your constructor is broken. Commented Dec 16, 2019 at 22:12

2 Answers 2

3

It is possible have splice return a standard array -- so without it calling your constructor. It is by changing the @@species property of your custom class, which determines which constructor will be used. But be aware that this will not only affect splice, but also all other methods that would create a new instance, including map, filter, slice, ...

You can change the @@species property by overwriting the corresponding static getter:

class CustomArray extends Array {
  static get [Symbol.species]() { return Array; } // <-----------

  constructor(array) {
    console.log('Initiating array:', array)
    super(...array);
  }

  remove(element) {
    let index = this.indexOf(element);
    if (index > -1) {
      return this.splice(index, 1); // Now calls Array constructor, not CustomArray
    } 
    return [];
  }

}

var a = ['a', 'b', 'c', 'd', 'e'];

var list = new CustomArray(a)
console.log('list:', list);
console.log('remove:', list.remove('c'));
console.log('list:', list);

// Some examples of effects on other methods
console.log(list.map(x => x) instanceof CustomArray); // false
console.log(list.filter(x => 1) instanceof CustomArray); // false
console.log(list.slice() instanceof CustomArray); // false
console.log(list.concat() instanceof CustomArray); // false
// Other methods, that do not return a new instance, are not affected:
console.log(list.reverse() instanceof CustomArray); // true
console.log(list.sort() instanceof CustomArray); // true

Sign up to request clarification or add additional context in comments.

Comments

1

I want to prevent splice method from initiating a new instance of CustomArray class and instead return normal array (an instance of Array object).

Then you need to create a different method with a different name. The semantics of splice are clearly and precisely defined; they form a contract for the Array type. Having your CustomArray violate that contract would mean it isn't an Array anymore, it's something array-like, and shouldn't extend Array.

Since your method is called remove, that's fine; if you want remove to return Array, not CustomArray, you just need to implement the logic yourself:

remove(element) {
  let index = this.indexOf(element);
  if (index > -1) {
    const newLength = this.length - 1;
    while (index < newLength) {
        this[index] = this[index + 1];
        ++index;
    }
    this.length = newLength;
    return [element];
  } 
  return [];
}

Alternately, of course, make CustomArray's constructor work correctly when called by the various Array.prototype methods. (The one you have in the question works just fine, other than logging something you don't expect with console.log.)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.