【问题标题】:Javascript prototypal inheritance - descendants override each otherJavascript原型继承 - 后代相互覆盖
【发布时间】:2013-01-11 21:59:58
【问题描述】:

我正在创建两个对象(继承),它们都继承自 Base。 第二个对象的属性赋值会覆盖第一个对象中的值。

有什么想法吗?如何进行适当的继承,以便基类将包含其继承后代的公共成员,但后代可以分配自己的值而不会相互干扰。

var testParams1 = { title: "john" };
var testParams2 = { title: "mike" };

Function.prototype.inheritsFrom = function (baseClass)
{
    this.prototype = new baseClass;
    this.prototype.constructor = this;
    this.prototype.base = baseClass.prototype;
    return this;
};

function Base() {
    this.viewModel = {};
    this.viewModel.title = (function ()
    {
        var savedVal = "";
        return function (newVal)
        {
            if (typeof (newVal) === "undefined")
            {
                return savedVal;
            }
            savedVal = newVal;
        };
    })();
}
function Inherits(params) {
    this.viewModel.title(params.title);
}

Inherits.inheritsFrom(Base);

///// preparing code:
var testObj1 = new Inherits(testParams1);
var testObj2 = new Inherits(testParams2);

///// testing code:
equals(testObj1.viewModel.title(), testParams1.title, "first instance ok"); // returns "john"
equals(testObj2.viewModel.title(), testParams2.title, "second instance ok");  // also returns "john"

【问题讨论】:

  • Javascript 的继承模型略有不同。如果你想学习 Javascript 的方式,我强烈推荐 Douglas Crockford 的 Javascript the Good parts 书:amazon.com/JavaScript-Good-Parts-Douglas-Crockford/dp/… 在那里你甚至可以找到与你的想法相似的函数。
  • 当您的“inheritsFrom”调用new baseClass 时,在基类构造函数中this 将引用该新对象实例。因此,“viewModel”属性在原型对象上,而不是在每个实例上。
  • 您有什么特殊要求不能通过简单的原型继承来完成?我在您的代码中没有看到任何内容。如果您的问题与视图模型有关,那么视图模型应该是它自己的“类”,并且您应该使用组合将对象连接在一起。我的意思是你的代码看起来很复杂。

标签: javascript


【解决方案1】:

问题

  • Base 类构造函数只被调用一次。
    this.prototype = new baseClass;
  • 特权方法将跨实例引用相同的 this 对象,因为这些方法是在构造函数中创建的(仅调用一次)。

基于意见的问题

  • 如果您打算与不属于您的 JavaScript 一起工作,则应尽量避免修改本机原型(即Function.prototype)。

解决方案

  • 为每个创建的新实例调用构造函数和父构造函数。
  • 继承父原型链(不是父类的实例)。
  • 在原型链中将创建的实例数与调用构造函数的次数保持 1:1 的比例。

Max 问题的最终解决方案

请特别注意inherits 函数和ParentClass.call(this, title); 行。 要查找的构造函数是 ParentClassChildClass

/**
 * Allows a child constructor to safely inherit the parent constructors prototype chain.
 * @type {Function}
 * @param {!Function} childConstructor
 * @param {!Function} parentConstructor
 */
function inherits(childConstructor, parentConstructor){
    var TempConstructor = function(){};
    TempConstructor.prototype = parentConstructor.prototype; // Inherit parent prototype chain

    childConstructor.prototype = new TempConstructor(); // Create buffer object to prevent assignments directly to parent prototype reference.
    childConstructor.prototype.constructor = childConstructor; // Reset the constructor property back the the child constructor
};

//////////////////////////////////////
/** @constructor */
function ParentClass(title) {
    this.setTitle(title);

    var randId_ = Math.random();
    /** @return {number} */
    this.getPrivlegedRandId = function()
        {return randId_;};
};

/** @return {string} */
ParentClass.prototype.getTitle = function()
    {return this.title_;};

/** @param {string} value */
ParentClass.prototype.setTitle = function(value)
    {this.title_ = value;};

//////////////////////////////////////    
/**
 * @constructor
 * @param {string} title
 * @param {string} name 
 */
ChildClass = function (name, title) {
    ParentClass.call(this, title); // Call the parent class constructor with the required arguments

    this.setName(name);
}
inherits(ChildClass, ParentClass); // Inherit the parent class prototype chain.

/** @return {string} */
ChildClass.prototype.getName = function()
    {return this.name_;};

 /** @param {string} value */
ChildClass.prototype.setName = function(value)
    {this.name_ = value;};

钻进兔子洞

对于那些好奇为什么它有效而不是简单地记住 inherits 函数的人。

如何使用原型链解析属性

当在实例级别找不到属性时,JavaScript 将尝试通过搜索实例构造函数原型链来解决丢失的属性。如果在第一个原型对象中没有找到该属性,它将搜索父原型对象,依此类推,直到Object.prototype。如果在Object.prototype 中找不到,则会抛出错误。

从子构造函数调用父构造函数:尝试#1

// Bad
var ChildConstructor = function(arg1, arg2, arg3){
    var that = new ParentConstructor(this, arg1, arg2, arg3);
    that.getArg1 = function(){return arg1};
    return that;
}

使用new ChildConstructor 创建的任何变量都将返回ParentConstructor 的实例。 ChildConstructor.prototype 将不会被使用。

从子构造函数调用父构造函数:尝试#2

// Good
var ChildConstructor = function(arg1, arg2, arg3){
    ParentConstructor.call(this, arg1, arg2, arg3);
}

现在构造函数和父构造函数被适当地调用。但是,只有在构造函数中定义的方法才会存在。不会使用父原型上的属性,因为它们尚未链接到子构造函数原型。

继承父原型:尝试#1

// Bad
ChildConstructor.prototype = new ParentConstructor();

根据是否使用ParentConstructor.call(this),父构造函数将被调用一次或多次调用。

继承父原型尝试#2

// Bad
ChildConstructor.prototype = ParentConstructor.prototype;

虽然这在技术上可行,但对ChildConstructor.prototype 的任何分配也将分配给ParentConstructor.prototype,因为对象是通过引用而不是通过复制传递的。

继承父原型尝试#3

// Almost there
var TempConstructor = function(){};
TempConstructor.prototype = ParentConstructor.prototype;
ChildConstructor.prototype = new TempConstructor();

这允许您将属性分配给ChildConstructor.prototype,因为它是一个临时匿名函数的实例。 在TempConstructor 的实例上找不到的属性将检查它的原型链以查找该属性,因此您已成功继承父原型。唯一的问题是ChildConstructor.prototype.constructor 现在指向TempConstructor

继承父原型尝试#4

// Good
var TempConstructor = function(){};
TempConstructor.prototype = ParentConstructor.prototype;
ChildConstructor.prototype = new TempConstructor();
ChildConstructor.prototype.constructor = ChildConstructor;

齐心协力

var ParentConstructor = function(){
};


var ChildConstructor = function(){
    ParentConstructor.call(this)
};

var TempConstructor = function(){};
TempConstructor.prototype = ParentConstructor.prototype;
ChildConstructor.prototype = new TempConstructor();
ChildConstructor.prototype.constructor = ChildConstructor;

您已成功从父类继承!看看能不能做得更好。

继承函数

function inherits(childConstructor, parentConstructor){
    var TempConstructor = function(){};
    TempConstructor.prototype = parentConstructor.prototype; // Inherit parent prototype chain

    childConstructor.prototype = new TempConstructor(); // Create buffer object to prevent assignments directly to parent prototype reference.
    childConstructor.prototype.constructor = childConstructor; // Reset the constructor property back the the child constructor (currently set to TempConstructor )
};


var ParentConstructor = function(){
};


var ChildConstructor = function(){
    ParentConstructor.call(this)
};
inherits(ChildConstructor, ParentConstructor);

【讨论】:

  • 不要理解我的错误,答案很好但是..哦。我的。上帝。这么多代码实现简单的继承模式,丑的像sin..
  • 好点@stawek,有点长。为简洁起见,我已从最后一个示例中删除了断言测试。仍然留在公众/有特权来说明这一点。如果您有任何其他建议,请告诉我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-01
  • 1970-01-01
  • 2010-09-28
  • 2018-02-21
  • 1970-01-01
相关资源
最近更新 更多