Наследование прототипа Javascript - потомки переопределяют друг друга

Я создаю два объекта (Inherits), оба наследуют от 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"

Ответы

Ответ 1

Проблема (s)

  • Конструктор класса Base вызывается один раз и только один раз.
    this.prototype = new baseClass;
  • Привилегированные методы будут ссылаться на один и тот же объект this через экземпляры, потому что эти методы создаются внутри конструктора (который вызывался только один раз).

Проблема (ы), основанная на мнении

  • Вы должны попытаться избежать модификации собственных прототипов (т.е. Function.prototype), если вы планируете работать вместе с JavaScript, который у вас нет.

Решение

  • Вызов конструктора и родительского конструктора для каждого созданного нового экземпляра.
  • Наследовать цепочку прототипов родителя (не экземпляр родительского класса).
  • Поддерживает соотношение количества экземпляров, созданных к числу вызовов конструктора (-ов) в цепочке прототипов, в соотношении 1:1.

Окончательное решение проблемы Max

Обратите особое внимание на функцию inherits и строку ParentClass.call(this, title);. Конструкторы для поиска - ParentClass и ChildClass

/**
 * 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);