Javascript中经典继承和原型继承的混淆

3

// 原型继承

var AnswerPrototype = {
    constructor: function(value){
        this._val = value;
    },
    get: function(){
        return this._val;   
    }
}

var lifeAnswer = Object.create(AnswerPrototype);
lifeAnswer.constructor(100);
alert(lifeAnswer.get());

var desertAnswer = Object.create(AnswerPrototype);
desertAnswer.constructor(200);
alert(desertAnswer.get());

var firmAnswerProtoype = Object.create(AnswerPrototype);
firmAnswerProtoype.get = function(){
     return AnswerPrototype.get.call(this);   
}

var luckyAnswer = Object.create(firmAnswerProtoype);
luckyAnswer.constructor(1);
alert(luckyAnswer.get());

var magicAnswer = Object.create(firmAnswerProtoype);
magicAnswer.constructor(2);
alert(magicAnswer.get());

// 经典继承

function Answer(value){
    this._val = value;
}

Answer.prototype.get = function(){
     return this._val;   
}

var lifeAnswer = new Answer(100);
alert(lifeAnswer.get());

var desertAnswer = new Answer(200);
alert(desertAnswer.get());

function firmAnswer(value){
     return Answer.call(this,value);   
}

firmAnswer.prototype = Object.create(Answer);
firmAnswer.prototype.constructor = firmAnswer;

firmAnswer.prototype.get = function(){
    return Answer.prototype.get.call(this);   
}

var luckyAnswer = new firmAnswer(20);
alert(luckyAnswer.get())

有人能告诉我第二个是经典的,第一个是原型的吗?我在观看 http://www.objectplayground.com/ ,两种情况下我们都使用了 Object.create()prototype 对象,让我感到非常困惑。

3个回答

2
首先,需要明确的是,Javascript 只有原型继承。你可以模拟“经典”的继承方式。两者的区别在于实例化对象时使用的语法不同——“经典”继承使用更为熟悉的构造函数和“new”运算符,这掩盖了 Javascript 对象创建和继承属性的内在原型特性。
让我们稍微分解一下这两种情况。
var AnswerPrototype = {
   constructor: function(value){
      this._val = value;
   },
   get: function(){
      return this._val;   
   }
}

这里的原型是明确创建的,同时还有一个“构造函数”(或更准确地说,初始化函数)。
var lifeAnswer = Object.create(AnswerPrototype);
lifeAnswer.constructor(100);
alert(lifeAnswer.get());

现在,在这里创建对象稍显笨拙。首先需要调用Object.create(),然后需要调用初始化函数。但实际上不必如此 - 下面是一个不同的样例原型,它可以一次完成两个步骤:
var AnswerPrototype = {
   create: function(value){
      var obj = Object.create(AnswerPrototype);
      obj._val = value;
      return obj;
   },
   get: function(){
      return this._val;   
   }
}
var lifeAnswer = AnswerPrototype.create(100);

模拟“经典”的继承依赖于让开发人员使用更熟悉的构造函数和“new”运算符来创建对象,但由于它只是掩盖了Javascript原型继承的本质,因此本质上仍然是相同的东西。

function Answer(value){
    this._val = value;
}

Answer.prototype.get = function(){
     return this._val;   
}

这跟原型继承并没有太大区别,只是答案与隐式创建的原型对象分离(可以通过Answer.prototype属性访问该对象)

var lifeAnswer = new Answer(100);

实际上,对于var lifeAnswer = Object.create(Answer.prototype); // 在新创建的对象上调用Answer以初始化一些值,例如使用Answer.call(lifeAnswer,100);,它是关于IT技术的。

firmAnswer.prototype = Object.create(Answer);
firmAnswer.prototype.constructor = firmAnswer;

firmAnswer.prototype只是隐式创建的原型,与第一种情况中的firmAnswerPrototype非常相似。

回答你具体的问题,这里使用Object.create()的原因是因为通过new进行继承很快变得非常笨重。你需要像这样做:

firmAnswer.prototype = new Answer();

现在你需要将Answer变成一个空的构造函数(function Answer() {}),或者明确区分调用它时没有参数(用于继承),和调用它时有参数(用于实例化"Answer"对象)。


1
你把它们搞反了。第二个是原型式的,因为它使用对象的prototype属性进行继承。这个块。
firmAnswer.prototype = Object.create(Answer);
firmAnswer.prototype.constructor = firmAnswer;

firmAnswer.prototype.get = function(){
    return Answer.prototype.get.call(this);   
}

正在将 Answer 的实例赋值给 firmAnswer 的原型。然而,这是烂代码,因为它实际上没有利用继承,而是重新声明了 get 函数。

我建议完全避免使用这段代码。阅读 Crockford 的 The Good Parts 可以很好地区分这两种类型。

编辑:为了解释一下(不涉及该代码),以下是基本区别。

Javascript 中的“经典”继承(我看到的用法)是在对象属性中重写。你有一个带有 foobar 方法的对象。然后你使用一个库(如 jQuery 或 Prototype)调用一个 extend 方法。该方法接受两个参数,一个基础对象和一个子对象。它获取基础对象的所有属性,插入子对象的属性(可能覆盖),然后返回一个具有两种属性混合的新对象。还有一些细节,但这就是要点。它只是在没有原型的情况下操作对象属性。

原型继承使用了Javascript内置的原型。原型是一系列对象(本质上)。假设你有一个从A继承的B对象。要创建一个从B继承的C类,我们创建一个名为C的函数,然后将B分配给原型。然后,当从C请求属性时,Javascript将执行以下操作:
检查属性是否存在于C实例上。如果没有:
检查属性是否在B的原型上。如果没有:
检查属性是否在A的原型上。如果没有:
抛出错误。
很抱歉有点模糊。我正在尝试简化它。Javascript中的原型有很多内容。我建议阅读一些Crockford的材料。

你所描述的“经典继承”通常被称为寄生式继承,或者在使用extend时称为mixin模式。如果你崇拜Crockford,我建议你自己阅读他的JavaScript中的经典继承 - Bergi
我并不崇拜Crockford,他只是很懂行。而且我只读过《JavaScript语言精粹》,没有看过他的网站内容,所以我并没有完全理解。 - GJK

1
第二个是经典的,第一个是原型的,它们都使用原型继承,在JavaScript中没有类。第二个被称为“经典”(有时称为“伪类”),因为它使用类模式,并在使用new运算符创建对象时模仿传统类语法(如Java中所知)。唯一的区别是从构造函数到原型对象的.prototype链接,以及只使用一个命令的更短实例化语法,而不是两个,背后发生的事情在两种方法中是相同的。正如我们可以在您的示例中看到的那样,两个“类”之间的继承也有所不同。在两种方法中,子原型对象都继承自父原型对象,并且在两种方法中,您可以通过显式在子实例上调用函数来调用父(“超级”)方法。然而,在“经典”模式中,您需要为子声明一个新的构造函数(以引用它),而在您的显式原型方法中,子类继承父类的.constructor 方法

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