为什么在构造Javascript类层次结构时,Object.setPrototypeOf不鼓励/效率低下?

时间:2017-07-15 17:27:02

标签: javascript oop prototype

我读了一篇关于将类层次结构中的数据保持为私有here的文章。我这样做的方式是不同的。我使用Object.setPrototypeOf(obj, prototype)的工厂函数。

为什么我这样做的方式不被视为良好做法?

这是我的方式:

我不想要公共变量,因此我使用工厂函数创建我的dog对象:

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

所有动物都可以吃,我希望我的狗继承动物:

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

myDog会委托myAnimal进食:

Object.setPrototypeOf(myDog, myAnimal)

现在我可以做到:

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

请注意,myDog.numTimesEat应该是指myDog吃的次数。

P.S。我知道你可以用课程来做:

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"

但是class关键字似乎最终会在我的对象上生成公共变量。如果我尝试做these之类的技术,那看起来很丑陋(我想下划线语法看起来还不错,但它并不是真的私有)。

解决方案:

如果我们使用与原型相同的Animal创建10只狗,则共享“let numTimesEat”。如果一只狗吃了一次你不想让numTimesEat为10。

因此,除了将原型设置10次(反复执行慢速操作)之外,您还必须为10只狗创建10只动物。

更新:相反,您可以将所有内容放在新创建的对象上

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

这比trying to do OOP in Javascript更清洁。

3 个答案:

答案 0 :(得分:3)

简单。

如果我们在Javascript中没有原型或this,我们仍然可以愉快地进行OOP和继承,就像你描述的那样:

  • 每个对象都包含其方法
  • 方法在闭包中具有共享状态

甚至没有隐藏状态:

  • 每个对象都包含其方法和状态
  • 方法在闭包中引用了对象

现在有人可能会说'嘿,我的对象有2个状态变量和10个方法,每当我想要一个新的对象实例时,我需要制作10个方法副本并不浪费吗?'

然后我们可以像“是的,让我们实际上共享相同类型的所有对象之间的功能”。但这有两个问题:

  • 我们把它们放在哪里? (好吧,让我们创建一个名为“prototype”的对象,让每个实例都有一个对这个原型的隐藏引用)
  • 该方法如何知道调用哪个对象?我们不能再在闭包中了...(让我们将this作为隐藏参数传递给每个方法)

...因此我们达到了标准JS OOP的当前状态。在某个地方,我们牺牲了在方法闭包中保持私有状态的可能性(因为每个对象只有1个方法)。

所以你想要真的不去那里,并留在方形一,每个对象都有自己的方法副本。那很好,你可以拥有私人状态!但是为什么你现在甚至需要原型呢?它们无法满足其原始目的。一个对象bark()与另一个bark()完全无关,它们有不同的闭包,无法共享。对于任何继承的方法都是一样的。

在这种情况下,由于您已经复制了所有方法,因此您也可以将它们保存在对象本身中。将(不同的)原型添加到每个对象并将超类方法放在那里并没有给你任何东西,它只是为方法调用增加了一层间接。

答案 1 :(得分:1)

您正在创建两个实例,然后将其设置为另一个实例的原型:

var myAnimal=makeAnimal(),myDog=makeDog();
Object.setPrototypeOf(myDog, myAnimal);

因此,我们基本上不需要这种简单的继承:

myDog -> Dog.prototype -> Animal.prototype
myDog2 ->
myDog3 ->

                   Animal ->
                  Animal2 ->

你这样做:

myDog -> myAnimal
myDog1 -> myAnimal1
myDog2 -> myAnimal2
Animal
Animal2

因此,不是两个原型保存所有函数和轻量级实例只保存数据,而是 2n (每只狗一只动物)实例,它们包含绑定函数引用和数据。  这在构造许多元素时真的没有效率,并且在工厂中分配函数也不是,所以可能会坚持类继承,因为它解决了这两个问题。或者如果你想使用setPrototype,则使用它一次(然后它的缓慢性没有太大影响):

var Animalproto = {
 birthday(){...}
}

var Dogproto={
 bark(){ ... }
}

Object.setPrototypeOf(Dogproto,Animalproto);

function makeAnimal(){
  return Object.create(Animalproto);
}

function makeDog(){
  return Object.create(Dogproto);
}

答案 2 :(得分:1)

我认为常规继承比切换原型更快,并且支持更多(setPrototypeOf是es6功能)。如果你使用它,你也可以把事情弄得一团糟(想想创建html元素并将它原型转换成别的东西)。

在您的情况下,您可以使用更好的模式而不是setPrototypeOf。相反,您可以在makeDog和makeAnimal工厂函数中实际向现有对象添加属性,而不是创建新对象。您可以使用Object.assign(可填充多个)来执行此操作:

const makeDog = (name, animal = {}) => {
    return  Object.assign(animal, { bark: () => { console.log(name) } })
}
const myAnimal = makeAnimal()
const myDog = makeDog("sniffles", myAnimal)

如果您想要高效(使用常规继承)并且仍保留私有变量,您仍然可以执行此操作!我写了一篇关于它的blog post,这里有一个gist,你可以用它来创建私人会员。最后,这里有关于继承的OOP模式是好还是坏的good article