我已经看到了很多这样的东西,并且正在寻找基本JavaScript继承的适当解决方案:
function Food(){} // Food constructor (class)
function Bread(){} // Bread constructor (class)
var basicFood = new Food(); // Food classes will inherit from this basicFood instance.
Bread.prototype = basicFood; // Bread now inherits from Food.
var bread = new Bread(); // We create some bread!
bread.constructor == Food; // Though, now we feel very uneasy about how
// the constructor is wrong,
Bread.prototype.constructor = Bread; // So we explicitly set the prototype's constructor
bread = new Bread(); // and when we create our new bread,
bread.constructor == Bread; // we feel much better as the constructor appears correct.
// The issue? Suppose we have another food item,
// like in a real inheritance situation:
function Sushi(){}; // We might be
Sushi.prototype = basicFood; // tempted to do
Sushi.prototype.constructor = Sushi; // the same thing
var sushi = new Sushi(); // again
sushi.constructor == Sushi; // Before we realize
bread.constructor == Sushi; // that we've ruined our bread.
basicFood.constructor != Food; // More importantly, we've really ruined all our basicFood,
// because while it's a prototype,
// it's also an object in its own right,
// and deserves an accurate constructor property.
constructor
到底应该是谁?
constructor
和instanceof
的结果有关系吗?
我发现自己在想,什么是正确的?我知道许多人会选择为每个食品类(面包、寿司等)提供一个新的食品实例,而不是给它们所有的basicFood实例。我想要这个更优化的解决方案(而不是创建不需要的实例)。
给予我们食物、面包、寿司和basicFood的:
function Food(){}
function Bread(){}
function Sushi(){}
var basicFood = new Food();
我想我可以创建一个实例化助手,,它将在新实例上定义一个不可枚举、不可写、不可配置的属性‘’:
Bread.prototype = basicFood; // We still simply inherit from basicFood
Sushi.prototype = basicFood;
// But we use this helper function when we make instances
function reconstructify(target, Constructor){
return Object.defineProperty(target, 'constructor', {
enumerable: false,
configurable: false,
writable: false,
value: Constructor
});
}
var bread = reconstructify(new Bread(), Bread); // Like so
var sushi = reconstructify(new Sushi(), Sushi);
在测试中,我意识到 instanceof
的行为并不像我想象的那样:
// True expressions for testing -- all good
basicFood.constructor == Food;
bread.constructor == Bread;
sushi.constructor == Sushi;
basicFood instanceof Food; // good also
bread instanceof Food;
sushi instanceof Food;
sushi instanceof Bread; // uh oh, not so good that this is true
bread instanceof Sushi; // why is this?
更深入地研究它,我似乎根本无法让 instanceof
按照我假设的方式工作:
function Food(){}
function Bread(){}
function Sushi(){}
var basicFood = new Food();
Bread.prototype = basicFood;
Sushi.prototype = basicFood;
var bread = new Bread();
var sushi = new Sushi();
sushi instanceof Bread; // why true?
bread instanceof Sushi; // why true?
我希望bread
和sushi
都是食物的实例--而不是彼此。
如何在保持constructor
instanceof
属性和constructor
运算符的预期行为的同时实现继承?
发布于 2014-03-11 11:44:35
让我们稍微检查一下你的代码。
function Food(){}
function Bread(){}
function Sushi(){}
var basicFood = new Food();
Bread.prototype = basicFood;
Sushi.prototype = basicFood;
注意:当您将同一对象设置为两个对象的原型时,一个原型中的扩充也将反映在另一个原型中。例如,
Bread.prototype = basicFood;
Sushi.prototype = basicFood;
Bread.prototype.testFunction = function() {
return true;
}
console.log(Sushi.prototype.testFunction()); // true
让我们回到你的问题上来。
var bread = reconstructify(new Bread(), Bread);
var sushi = reconstructify(new Sushi(), Sushi);
console.log(sushi instanceof Bread); // Why true?
console.log(bread instanceof Sushi); // Why true?
运算符instanceof测试对象的原型链中是否具有构造函数的原型属性。
所以当我们做像这样的事情时
object1 instanceof object2
JavaScript将尝试找出object2
的原型是否在object1
的原型链中。
在这种情况下,只有当Bread.prototype
在sushi
的原型链中时,它才会返回true
。我们知道sushi
是由Sushi
构建的。因此,它将获取Sushi
的原型并检查它是否等于Bread
的原型,因为它们都指向相同的basicFood
对象,该对象返回true
。bread instanceof Sushi
也是如此。
因此,正确的继承方式应该是,如下所示
function Food() {}
function Bread() {}
function Sushi() {}
Bread.prototype = Object.create(Food.prototype);
Bread.prototype.constructor = Bread;
Sushi.prototype = Object.create(Food.prototype);
Sushi.prototype.constructor = Sushi;
var bread = new Bread();
var sushi = new Sushi();
console.log(sushi instanceof Bread); // false
console.log(bread instanceof Sushi); // false
console.log(sushi.constructor); // [Function: Sushi]
console.log(bread.constructor); // [Function: Bread]
console.log(sushi instanceof Food); // true
console.log(bread instanceof Food); // true
console.log(sushi instanceof Sushi); // true
console.log(bread instanceof Bread); // true
发布于 2014-03-11 11:50:00
您的逻辑中唯一的问题是将相同的对象basicFood
设置为Bread.prototype
和Sushi.prototype
。试着这样做:
Bread.prototype = new Food();
Bread.prototype.constructor = Bread;
Sushi.prototype = new Food();
Sushi.prototype.constructor = Sushi;
现在,instanceof
、bread
和sushi
将为Food
,但它们的构造函数将分别为Bread
和Sushi
;
发布于 2014-03-11 14:35:53
这是我个人的解决方案,我从@thefourtheye,@FelixKling,@SeanKinsey,甚至@helly0d的滑稽动作中开发出来的。
最简单的解决方案:
/** Food Class -- You can bite all foods **/
function Food(){ this.bites = 0 };
Food.prototype.bite = function(){ console.log("Yum!"); return this.bites += 1 };
/** All Foods inherit from basicFood **/
var basicFood = new Food();
/** Bread inherits from basicFood, and can go stale **/
function Bread(){
Food.apply(this); // running food's constructor (defines bites)
this.stale = false;
};
Bread.prototype = Object.create( basicFood );
Bread.prototype.constructor = Bread; // just conventional
Bread.prototype.goStale = function(){ return this.stale = true };
/** Sushi inherits from basicFood, and can be cooked **/
function Sushi(){
Food.apply(this);
this.raw = true;
};
Sushi.prototype = Object.create( basicFood );
Sushi.prototype.constructor = Sushi;
Sushi.prototype.cook = function(){ return this.raw = false };
高级方法:
这样做更好,因为它使constructor
原型属性成为不可枚举的属性。
/** My handy-dandy extend().to() function **/
function extend(source){
return {to:function(Constructor){
Constructor.prototype = Object.create(source);
Object.defineProperty(Constructor.prototype, 'constructor', {
enumerable: false,
configurable: false,
writable: false,
value: Constructor
});
return Constructor;
}}
};
function Food(){ this.bites = 0 };
Food.prototype.bite = function(){ console.log("Yum!"); return this.bites += 1 };
var basicFood = new Food();
var Bread = extend(basicFood).to(function Bread(){
Food.apply(this);
this.stale = false;
});
Bread.prototype.goStale = function(){ return this.stale = true };
var Sushi = extend(basicFood).to(function Sushi(){
Food.apply(this);
this.raw = true;
});
Sushi.prototype.cook = function(){ return this.raw = false };
以上两种方法产生相同的测试结果:
var food = new Food();
var bread = new Bread();
var sushi = new Sushi();
console.log( food instanceof Food ); // true
console.log( food instanceof Bread ); // false
console.log( food instanceof Sushi ); // false
console.log( bread instanceof Food ); // true
console.log( bread instanceof Bread ); // true
console.log( bread instanceof Sushi ); // false
console.log( sushi instanceof Food ); // true
console.log( sushi instanceof Bread ); // false
console.log( sushi instanceof Sushi ); // true
console.log( food.constructor ); // Food
console.log( bread.constructor ); // Bread
console.log( sushi.constructor ); // Sushi
我要特别感谢@FelixKling,他的经验帮助我在这个主题之外的聊天中磨练了我的理解--也要感谢@thefourtheye,他是第一个向我展示正确方法的人--还有@SeanKinsey,他强调了能够在孩子的上下文中运行父构造函数的好处。
我社区维基了这个答案--如果你在这个答案的代码中发现任何可疑的东西,请让我知道或编辑你自己:)
https://stackoverflow.com/questions/22315909
复制相似问题