Javascript oop, instanceof и базовый класс

Я проектирую некоторую иерархию классов в JavaScript. Пока это работает, но я не вижу, как определить, является ли объект "экземпляром" родительского класса. Пример:

function BaseObject(name){
    this.name = name;

    this.sayWhoAmI = function(){
        console.log(this.name + ' is a Derivation1 : ' + (this instanceof Derivation1));
        console.log(this.name + ' is a Derivation2 : ' + (this instanceof Derivation2));
        console.log(this.name + ' is a BaseObject : ' + (this instanceof BaseObject));
    };
}
function Derivation1(){
    BaseObject.apply(this, ['first derivation']);
}
function Derivation2(){
    BaseObject.apply(this, ['second derivation']);
}

var first = new Derivation1();
var second = new Derivation2();

first.sayWhoAmI(); регистрирует это:

first derivation is a Derivation1 : true
first derivation is a Derivation2 : false
first derivation is a BaseObject : false

while second.sayWhoAmI(); регистрирует это:

second derivation is a Derivation1 : false
second derivation is a Derivation2 : true
second derivation is a BaseObject : false

Мне кажется, что оба объекта first и second должны сказать, что они являются экземплярами BaseObject.

Я понимаю, что JavaScript не может быть сделан для этого, но мне интересно, есть ли способ достичь этого.

Ответ 1

Только вызов Base.apply(...) не устанавливает наследование. Все, что .apply делает, устанавливает this в первый аргумент, ничего больше. Важно вызвать родительский конструктор, но этого недостаточно.

Что вам нужно сделать, так это правильно настроить цепочку прототипов. То есть вам нужно установить Derivation1.prototype на то, что наследуется от Base.prototype.

Поскольку каждый экземпляр функции-конструктора наследуется от прототипа функций конструктора, вы увидите код, например

Derivation1.prototype = new Base();

Это плохая идея, и вы уже можете понять, почему: Base ожидает аргументов для настройки специальных свойств экземпляра (name в этом случае). Но мы не заботимся об этих свойствах, так как мы инициализируем их позже в дочернем конструкторе с Base.apply(this, ...).

Итак, все, что нам нужно - это объект, наследующий от Base.prototype, и, к счастью, ECMASCript 5 определяет функцию, которая может сделать это для нас (polyfill):

 Derivation1.prototype = Object.create(Base.prototype);

Это создает новый объект, наследующий от Base.prototype. Теперь, поскольку вы заменили оригинальный прототип новым объектом, вы должны установить свойство constructor так, чтобы оно правильно указывало на Derivation1:

Derivation1.prototype.constructor = Derivation1;

Ниже приведен полный пример. Также посмотрите эту скрипку и этот отличный ответ T.J. Crowder, который объясняет в основном те же проблемы, но, возможно, лучше.


Пример:

function BaseObject(name){
    this.name = name;
}

// move properties shared by all instances to the prototype!
BaseObject.prototype.sayWhoAmI = function() {
    console.log(this.name + ' is a Derivation1 : ' + (this instanceof Derivation1));
    console.log(this.name + ' is a Derivation2 : ' + (this instanceof Derivation2));
    console.log(this.name + ' is a BaseObject : ' + (this instanceof BaseObject));
};

function Derivation1(){
    BaseObject.apply(this, ['first derivation']);
}

Derivation1.prototype = Object.create(BaseObject.prototype);
Derivation1.prototype.constructor = Derivation1;

// some useless method of the child "class"
Derivation1.prototype.someOtherMethod = function() {
    return 42;
};

var first = new Derivation1();
first.sayWhoAmI();