admin管理员组

文章数量:1334887

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

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?

Share Improve this question edited Dec 3, 2018 at 17:48 Peter Seliger 13.4k3 gold badges30 silver badges44 bronze badges asked Dec 3, 2018 at 15:40 Soul ReaverSoul Reaver 2,0924 gold badges38 silver badges53 bronze badges 6
  • 7 I'd imagine it's calling the constructor again because the .filter() method creates a new array. – Tyler Roper 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(). – Shilly 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. – Soul Reaver 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. – Soul Reaver 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. – Peter Seliger Commented Dec 3, 2018 at 15:49
 |  Show 1 more ment

2 Answers 2

Reset to default 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; }

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

本文标签: