Ответ 1
Нет. Нет. Этого не будет. Вы делаете наследование в JavaScript все неправильно. Ваш код дает мне мигрень.
Создание псевдоклассического шаблона наследования в JavaScript
Если вам нужно что-то похожее на классы в JavaScript, тогда есть много библиотек, которые предоставляют вам это. Например, используя augment, вы можете перестроить свой код следующим образом:
var augment = require("augment");
var ABC = augment(Object, function () {
this.constructor = function (key, value) {
this.key = key;
this.value = value;
};
this.what = function () {
alert("what");
};
});
var XYZ = augment(ABC, function (base) {
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
Я не знаю о вас, но для меня это очень похоже на классическое наследование на С++ или Java. Если это решает вашу проблему, отлично! Если это не так, продолжайте чтение.
Изоморфизм прототипа класса
Во многом прототипы похожи на классы. На самом деле прототипы и классы настолько похожи, что мы можем использовать прототипы для моделирования классов. Сначала рассмотрим как работает прототипное наследование:
Вышеприведенное изображение было взято из следующего answer. Я предлагаю вам внимательно прочитать его. Диаграмма показывает нам:
- Каждый конструктор имеет свойство
prototype
, которое указывает на объект-прототип функции-конструктора. - Каждый прототип имеет свойство
constructor
, которое указывает на конструкторную функцию объекта-прототипа. - Мы создаем экземпляр из функции-конструктора. Однако экземпляр фактически наследует от
prototype
, а не конструктора.
Это очень полезная информация. Традиционно мы всегда создавали конструкторную функцию, а затем устанавливаем ее свойства prototype
. Однако эта информация показывает нам, что мы можем сначала создать объект-прототип, а затем определить вместо него свойство constructor
.
Например, традиционно мы можем написать:
function ABC(key, value) {
this.key = key;
this.value = value;
}
ABC.prototype.what = function() {
alert("what");
};
Однако используя наше новообретенное знание, мы можем написать то же самое, что:
var ABC = CLASS({
constructor: function (key, value) {
this.key = key;
this.value = value;
},
what: function () {
alert("what");
}
});
function CLASS(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
Как вы видите, инкапсуляция легко достигается в JavaScript. Все, что вам нужно сделать, это думать боком. Наследование, однако, является другой проблемой. Вам нужно сделать немного больше работы для достижения наследования.
Наследование и супер
Посмотрите, как augment
достигает наследования:
function augment(body) {
var base = typeof this === "function" ? this.prototype : this;
var prototype = Object.create(base);
body.apply(prototype, arrayFrom(arguments, 1).concat(base));
if (!ownPropertyOf(prototype, "constructor")) return prototype;
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
Обратите внимание, что последние три строки такие же, как у CLASS
из предыдущего раздела:
function CLASS(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
Это говорит нам, что, когда у нас есть объект-прототип, нам нужно только получить его свойство конструктора и вернуть его.
Первые три строки дополнения используются для:
- Получить прототип базового класса.
- Создайте прототип производного класса, используя
Object.create
. - Заполните прототип производного класса с указанными свойствами.
Это все, что есть для наследования в JavaScript. Если вы хотите создать свой собственный классический шаблон наследования, вы должны думать по тем же строкам.
Охватывание True Prototypal Inheritance
Каждый разработчик JavaScript, заслуживающий своей соли, скажет вам, что прототипное наследование лучше классического наследования, Тем не менее новички, которые исходят из языка с классическим наследованием, всегда пытаются реализовать классическое наследование поверх прототипального наследования, и обычно они терпят неудачу.
Они терпят неудачу не потому, что невозможно реализовать классическое наследование поверх прототипального наследования, а потому, что для реализации классического наследования поверх прототипного наследования вам нужно сначала понять, как истинное прототипное наследование работает.
Однако, как только вы поймете истинное прототипное наследование, вы никогда не захотите вернуться к классическому наследованию. Я тоже пробовал до реализовать классическое наследование сверху прототипное наследование как новичок. Теперь, когда я понимаю, как работает истинное прототипное наследование, я пишу код следующим образом:
function extend(self, body) {
var base = typeof self === "function" ? self.prototype : self;
var prototype = Object.create(base, {new: {value: create}});
return body.call(prototype, base), prototype;
function create() {
var self = Object.create(prototype);
return prototype.hasOwnProperty("constructor") &&
prototype.constructor.apply(self, arguments), self;
}
}
Вышеупомянутая функция extend
очень похожа на augment
, Однако вместо того, чтобы возвращать функцию-конструктор, он возвращает объект-прототип. Это на самом деле очень аккуратный трюк, который позволяет наследовать статические свойства. Вы можете создать класс, используя extend
следующим образом:
var Abc = extend(Object, function () {
this.constructor = function (key, value) {
this.value = 333 + Number(value);
this.key = key;
};
this.what = function () {
alert("what");
};
});
Наследование так же просто:
var Xyz = extend(Abc, function (base) {
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
Помните, однако, что extend
не возвращает функцию конструктора. Он возвращает объект-прототип. Это означает, что вы не можете использовать ключевое слово new
для создания экземпляра класса. Вместо этого вам нужно использовать new
как метод, следующим образом:
var x = Xyz.new("x", "123");
var y = Xyz.new("y", "456");
var it = Abc.new("it", "789");
На самом деле это хорошо. Ключевое слово new
считается вредоносным, и я настоятельно рекомендую вам прекратить использование. Например, это невозможно использовать apply
с new
ключевое слово. Однако можно использовать apply
с помощью метода new
следующим образом:
var it = Abc.new.apply(null, ["it", "789"]);
Так как Abc
и Xyz
не являются конструкторскими функциями, мы не можем использовать instanceof
для проверки того, является ли объект экземпляром Abc
или Xyz
. Однако это не проблема, потому что у JavaScript есть метод под названием isPrototypeOf
, который проверяет, является ли объект является прототипом другого объекта:
alert(x.key + ": " + x.value + "; isAbc: " + Abc.isPrototypeOf(x));
alert(y.key + ": " + y.value + "; isAbc: " + Abc.isPrototypeOf(y));
alert(it.key + ": " + it.value + "; isAbc: " + Abc.isPrototypeOf(it));
alert(it.key + ": " + it.value + "; isXyz: " + Xyz.isPrototypeOf(it));
Фактически isPrototypeOf
более мощный, чем instanceof
, потому что он позволяет нам проверять, распространяется ли один класс другого класса:
alert(Abc.isPrototypeOf(Xyz)); // true
Помимо этого незначительного изменения все остальное работает так же, как и раньше:
x.what();
y.that();
it.what();
it.that(); // will throw; it is not Xyz and does not have that method
Смотрите демонстрацию для себя: http://jsfiddle.net/Jee96/
Что еще предлагает истинное прототипное наследование? Одно из самых больших преимуществ истинного прототипного наследования заключается в том, что нет никакого различия между нормальными свойствами и статическими свойствами, позволяющими писать такой код:
var Xyz = extend(Abc, function (base) {
this.empty = this.new();
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
Обратите внимание, что мы можем создавать экземпляры класса из самого класса, вызывая this.new
. Если this.constructor
еще не определено, он возвращает новый неинициализированный экземпляр. В противном случае он возвращает новый инициализированный экземпляр.
Кроме того, поскольку Xyz
является объектом прототипа, мы можем напрямую обращаться к Xyz.empty
(т.е. empty
является статическим свойством Xyz
). Это также означает, что статические свойства автоматически унаследованы и ничем не отличаются от обычных свойств.
Наконец, поскольку класс доступен из определения класса как this
, вы можете создать вложенные классы, которые наследуют от класса, в который они вложены, используя extend
следующим образом:
var ClassA = extend(Object, function () {
var ClassB = extend(this, function () {
// class definition
});
// rest of the class definition
alert(this.isPrototypeOf(ClassB)); // true
});
Смотрите демонстрацию для себя: http://jsfiddle.net/Jee96/1/