JavaScript 类继承导致无限循环

3

我有以下内容:

function Person() {
  console.log('person');
}

function Player() {
  this.personConstructor();
}

Player.prototype = Person.prototype;
Player.prototype.constructor = Player;
Player.prototype.personConstructor = Person.prototype.constructor;

new Player();

意图是从 Person 继承到 Player,然后让这个新的子类调用父类的原始构造函数。但是,这会导致无限循环。我做错了什么,为什么会发生循环?


3
Player.prototype = Person.prototype; 应该改为 Player.prototype = Object.create(Person.prototype);,意为创建一个继承自 Person.prototype 的新对象作为 Player.prototype。注意不要改变原来的意思,并保证语言通俗易懂。 - cookie monster
1
这是因为当你将 Player.prototype 设为指向 Person.prototype引用,然后将 Player 赋值给 Player.prototype.constructor 时,你实际上将其赋值给了 Person.prototype.constructor。所以 personConstructor 实际上是一个指向 Player 而不是 Person 的引用,从而创建了无限调用循环。 - cookie monster
2个回答

4
这里的问题在于这一行代码:
Player.prototype = Person.prototype;

你想让Player的原型继承Person的原型,但它们之间不相等。目前,你的代码使PlayerPerson的原型引用相等,所以对Player.prototype的任何更改也会影响到Person.prototype(实际上使它们无法区分)。
你需要的是:
Player.prototype = Object.create(Person.prototype);
Object.create 可以实例化一个新对象,并继承给定原型的属性,而不需要像常规的 new Person() 调用一样调用构造函数。这允许您获取一个继承了 Person 原型的新对象,然后针对 Player 的具体需求进行修改。

编辑: 正如 Siddarth 在评论中所建议的那样,更好的解决方案是通过 属性描述符 设置 constructor 属性:

Player.prototype = Object.create(Person.prototype, {
    constructor: { value: Player }
});

通过这种方式,新创建的原型将具有不可配置、不可枚举和不可写的constructor属性。这可以防止您通过赋值(例如Player.prototype.constructor = Foo)意外更改它,并且它不会显示在Object.keysfor..in循环中。通常,这应该没有太大关系,但这是一个好习惯。


我会创建 Object.create(Person.prototype, { constructor: { value: Player } })。 - Siddharth
另外,在子类的构造函数中不要忘记调用父类的构造函数。 - Siddharth
1
@Siddharth 哦,没错,你可以将一堆属性描述符传递给Object.create。我个人更喜欢使用经典的赋值方式或者像_.extend这样的方法来向原型添加属性。不过属性描述符可能更好,因为你的解决方案可以防止原型的constructor在以后(意外地)被改变。感谢你的提示! - Mattias Buelens

0
function Person(args) {
  console.log('person');
  args=args||{};
  this.name=args.name||"no name";
}

function Player(args) {
//  this.personConstructor();
// I prefer 
  Person.call(this,args);
}

Player.prototype = Object.create(Person.prototype);
Player.prototype.constructor = Player;

console.log(new Player());
console.log(new Player({name:"Jon"}));

可能更好的做法是使用以下方式重新使用父类构造函数

Person.call(this,args);

因为如果你使用像this.parent()...这样的东西,当你继承3层深时,你会得到一个无限循环。

更多关于原型和构造函数的信息在这里


1
this.personConstructor() 运行良好,如果您要传入相同的 this,则不需要使用 .call。但是,Person.call(this,args) 确实是一种更通用的解决方案,它不需要额外的 Player.prototype.personConstructor 定义。 - Mattias Buelens
@MattiasBuelens 正确,我改变了答案。 - HMR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接