5

I have this example code:

class TestClass extends Array {
	constructor() {
		console.log( 'constructor' );
		
		let ar = [];
		ar.push( { id: 1, name: 'a' } );
		ar.push( { id: 2, name: 'b' } );
		ar.push( { id: 3, name: 'c' } );
		ar.push( { id: 4, name: 'd' } );
		
		// finalizing object
		super( ...ar );
	}
	
	Foo() {
		console.log( 'foo' );
		return this.filter( item => item.id > 2 );
	}
}

let t = new TestClass();
console.log( t.Foo() );

It is simpler version of what I've already written. My app worked up till now, but stopped the moment I needed to filter data in my extended array. I've found out, that the problem is that calling a filter function on an object of my class internally calls constructor. The above code shows that example. Is there any way to bypass this issue, because I can't call constructor again at this point. Also, I've found out (using this simple TestClass) that actual output is not what I would expect - I get an array of 4 items with id's 3, 4, 3, 4. Can anyone explain what's happening here?

6
  • 7
    I'd imagine it's calling the constructor again because the .filter() method creates a new array. Commented Dec 3, 2018 at 15:42
  • I would advice to use let ar as your array and not extend the base Array. And foo() can the return this.ar.filter(). Commented Dec 3, 2018 at 15:46
  • Yeah, just before I saw your answer it finally came to me. Instead of creating an Array, it creates another instance of my class. Commented Dec 3, 2018 at 15:46
  • @Shilly I was going to do that in my first version, but I wanted to try to play with it a bit more (as I don't use JS too much, wanted to learn a bit) and give objects of this class an "indexer" ( object[i] ), but failed miserably. So I went with extending Array instead of using it internally. Commented Dec 3, 2018 at 15:49
  • Symbol.species provides a way to return not another instance of the derived class but for .e.g. in this case an Array instance again. Commented Dec 3, 2018 at 15:49

2 Answers 2

7

Symbol.species provides a way to return not another instance of the derived class but for .e.g. in this case an Array instance again.

class TestClass extends Array {
  constructor() {
    console.log( 'constructor' );

    let ar = [];
    ar.push( { id: 1, name: 'a' } );
    ar.push( { id: 2, name: 'b' } );
    ar.push( { id: 3, name: 'c' } );
    ar.push( { id: 4, name: 'd' } );

    // finalizing object
    super( ...ar );
  }
  static get [Symbol.species]() { return Array; }

  Foo() {
    console.log( 'foo' );
    return this.filter( item => item.id > 2 );
  }
}

let t = new TestClass();
let a = t.Foo();

console.log('a : ', a);
console.log('(a instanceof Array) ? ', (a instanceof Array));
console.log('(a instanceof TestClass) ? ', (a instanceof TestClass));
.as-console-wrapper { max-height: 100%!important; top: 0; }

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

1 Comment

Thank you. This solves my problem. I don't know why I did not thought that it would create instance of my extended class instead of basic Array.
6

According to the spec,

Array.filter says

  1. Let A be ? ArraySpeciesCreate(O, 0).

(here, O is the original array and A the result)

and ArraySpeciesCreate says

  1. Let C be ? Get(originalArray, "constructor").

In layman's terms, X.filter creates a new object and applies X's constructor to it. That's why your constructor is called again.

In general, this design needs to be fixed. Your TestClass extends Array, now, if you replace Array with TestClass in the whole application, would it behave the same? Obviously not, which means that your TestClass violates a fundamental OOP principle, so called LSP, and should be redesigned (for example, by aggregating an array instead of extending it).

2 Comments

Thank you for your answer. Peter's answer solves my problem, but your's answer directly answers question from topic. I can agree that the way I am trying to write this class might not be very good, but I am not a pro developer and just trying to learn a bit of newer features of JavaScript. My initial intention was to have that array stored internally, but I failed to do other stuff I wanted in that class. I'll surely keep that in mind next time.
" I failed to do other stuff I wanted" - feel free to post a new question, we'll be glad to help.

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.