Extending arrays with classes for custom collections

Extending arrays with classes for custom collections
0

#1

While trying to think of ways to quickly build an iterable I came across this bit of code that creates a constructor that inherits from Array

class MovieCollection extends Array {
  constructor(name, ...items) {
    super(...items);
    this.name = name;
  }
  add(movie) {
    this.push(movie);
  }
  topRated(limit = 10) {
    return this.sort((a, b) => (a.stars > b.stars ? -1 : 1)).slice(0, limit);
  }
}

const movies = new MovieCollection('Fav Movies',
  { name: 'Star Wars', stars: 10 },
  { name: 'Aliens', stars: 8 },
  { name: 'Spaceballs', stars: 9 },
  { name: 'Source Code', stars: 8 }
);

movies.add({ name: 'Titanic', stars: 5 });

It is cool that I get all of the methods that I love like map, filter, reduce, plus I can add my own properties and methods and it is iterable so that I can do this:

for (let movie of movies) {
  console.log(movie);
}
console.log(movies);

The problem I’m having is trying to implement my own solution without es6 classes in a pattern that will pass those arguments to the super. In the code above I see that the super is passed all of movies in a clever way. But when I create my solution I can’t think of any way to pass the movies to the super ‘Array’- heck, I didn’t even call the super in the subclass.

function MovieCollection(name) {
	for (var i = 1; i < arguments.length; i++) {
		this.push(arguments[i]);
	}
	this.name = name;
//i didn't Array.call(this) since I think I didn't need to

}
MovieCollection.prototype = Object.create(Array.prototype);
MovieCollection.prototype.add = function(movie) {
	this.push(movie)
}

var movies = new MovieCollection('Fav Movies',
  { name: 'Star Wars', stars: 10 },
  { name: 'Aliens', stars: 8 },
  { name: 'Spaceballs', stars: 9 },
  { name: 'Source Code', stars: 8 }
);

movies.add({ name: 'Titanic', stars: 5 });
//to show it is still iterable
for (var movie of movies) {
  console.log(movie);
}
console.log(movies);

So how is it that in the class pattern they passed arguments to super as if super actually is called with the arguments:

super(...items);

I changed it to the code below and it works just fine

  class MovieCollection extends Array {
    constructor(name, ...items) {
//didn't pass the stuff to super and works fine but I get an error if I don't call super
      super();
      [...items].forEach( (item) => this.push(item))
      this.name = name;
    }
    add(movie) {
      this.push(movie);
    }
    topRated(limit = 10) {
      return this.sort((a, b) => (a.stars > b.stars ? -1 : 1)).slice(0, limit);
    }
  }

  const movies = new MovieCollection('Fav Movies',
    { name: 'Movie', stars: 10 },
    { name: 'Star Trek', stars: 1 },
    { name: 'Virgin', stars: 7 },
    { name: 'King of the Road', stars: 8 }
  );

  movies.add({ name: 'Titanic', stars: 5 });
for (var movie of movies) {
  console.log(movie);
}
console.log(movies);

Why do you have to call super if the es5 pattern didn’t have to and does Array really get called with the arguments?
thanks in advance for any advice/help


#2

Can’t extend builtins unless you use ES6 classes, end of :man_shrugging:


#3

but why since I keep hearing the phrase that classes is just syntactical sugar. Also, builtins are like array right? is extending array the same as just setting the prototype of array to a new object and adding property and methods?


#4

They primarily are but that doesn’t mean there aren’t low level things that allow them to work. Can’t extend builtins prior to ES6 classes and extends :man_shrugging:

Nope, it is not. It will work a little bit, then break; arrays have special rules regarding length that cannot be easily be copied.


#5

Just meant to add to this: what you’re trying to do seems reasonable, but in fact doesn’t really give you many (if any) benefits. You need to convert data to your new type, and be extremely careful that what comes out of the methods is of the same type. This means you need to reimplement most of the array methods you want — you can’t just borrow the array ones because they tend to return new arrays, not new movie lists. It’s an interesting thing to play around with, but it’s not going to be particularly practical.