我读了一篇关于将类层次结构中的数据保持为私有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
答案 0 :(得分:3)
简单。
如果我们在Javascript中没有原型或this
,我们仍然可以愉快地进行OOP和继承,就像你描述的那样:
甚至没有隐藏状态:
现在有人可能会说'嘿,我的对象有2个状态变量和10个方法,每当我想要一个新的对象实例时,我需要制作10个方法副本并不浪费吗?'
然后我们可以像“是的,让我们实际上共享相同类型的所有对象之间的功能”。但这有两个问题:
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。