Why is Object.setPrototypeOf discouraged/inefficient when constructing Javascript class hierarchies?

Viewed 885

I read an article about keeping data in class hierarchies private here. The way I do it is different. I use factory functions with Object.setPrototypeOf(obj, prototype).

Why is my way of doing it not considered good practice?


Here's my way of doing it:

I don't want public variables, so I create my dog object with a factory function:

const makeDog = (name) => {
    return { bark: () => { console.log(name) } }
}
const myDog = makeDog("sniffles")
myDog.bark() // "sniffles"

All animals can eat and I want my dog to inherit from Animal:

const makeAnimal = () => {
    let numTimesEat = 0
    return { 
        eat: (food) => {
          numTimesEat += 1 
          console.log( "I eat " + food.toString() )
        },
        get numTimesEat() { return numTimesEat }
    }
}
const myAnimal = makeAnimal()

myDog will delegate to myAnimal for eating:

Object.setPrototypeOf(myDog, myAnimal)

And now I can do:

myDog.eat("shoe") // "I eat shoe"
console.log( myDog.numTimesEat ) // 1
myDog.bark() // "sniffles"

Note that myDog.numTimesEat is supposed to refer to the number of times myDog ate.


p.s. I know you can do it with classes:

class Animal {
  constructor() {
    this.numTimesEat = 0;
  }

  eat(food) {
    this.numTimesEat += 1;
    console.log( "I eat " + food.toString() );
  }
}

class Dog extends Animal {
  constructor(myName) {
    super();
    this.name = myName;
  }
  bark() {
    console.log( this.name );
  }
}

const dog2 = new Dog("sniffles");
dog2.eat("shoe"); // "I eat shoe"
console.log( dog2.numTimesEat ); // 1
console.log( dog2.name ); // "sniffles"
dog2.bark(); // "sniffles"

But the class keyword seems to end up producing public variables on my object. If I try to do techniques like these it kind of looks ugly (I guess the underscore syntax is okay looking but it's not really private).


Solution:

If we create 10 dogs all with the same Animal as its prototype, the "let numTimesEat" is shared. If a dog eats one time you don't want numTimesEat to be 10.

So in addition to setting the prototype 10 times (a slow operation performed repeatedly), you also have to create 10 animals for those 10 dogs to delegate to.


Update: Instead you can put everything on the newly created object

const Dog = function(name) {
    let that = Animal()
    that.bark = () => { console.log(name) }
    return that
}

const Animal = function() {
    let numTimesEat = 0
    return { 
        eat: (food) => {
          numTimesEat += 1 
          console.log( "I eat " + food.toString() )
        },
        get numTimesEat() { return numTimesEat }
    }
}

const lab = new Dog("sniffles")
lab.bark() // sniffles
lab.eat("food") // I eat food
lab.numTimesEat // 1

This is MUCH cleaner than trying to do OOP in Javascript.

3 Answers
Related