Предыдущая часть:
Обратите внимание, что наследование – это в целом не про свойства, а про методы объекта.
Так как набор свойств у объектов одного класса одинаковый, но значения им можно задавать разные, это значит, что у каждого объекта есть копия этих свойств. То есть у объекта obj есть своя, отдельная память со свойствами x, y, z, и у объекта obj2 также есть своя, отдельная память со свойствами x, y, z.
Конструктор выполняет реальную работу по выделению памяти под свойства в каждом объекте. А что насчёт методов?
Когда мы создавали объект вот так:
var obj = { x: 1, y: 2, z: 3, test: function() { return true; } };
То там, в объекте, создавался новый метод. То есть под него тоже выделялась память, генерировался и размещался где-то код.
Объявив второй объект таким же способом:
var obj2 = { x: 1, y: 2, z: 3, test: function() { return true; } };
Мы опять создадим новый метод. То есть получим в двух разных объектах два отдельных, но одинаковых метода.
Данный код:
console.log(obj.test === obj2.test);
выведет false, то есть методы test() у двух объектов – не идентичны.
Теперь вспомним, как работает конструктор. Он берёт пустой объект и создаёт в нём новые свойства. А что, если мы возьмём на себя роль конструктора и добавим в уже созданный объект новое свойство? Например,
obj.hello = 'hello';
И вдруг свойство создалось, и теперь объект содержит свойства x, y, z, hello и test. А может быть, можно добавить и новый метод?
obj.test2 = function() { return false; }
И это тоже работает. Оказывается, объект в JavaScript никогда не имеет зафиксированного состояния. После того, как над ним поработал конструктор, мы можем самостоятельно добавить сколько угодно новых свойств и методов. А также наоборот, удалить их:
delete obj.z;
То есть, добавляя и удаляя свойства, мы можем сделать объект совершенно другим по сравнению с тем, каким он был создан.
Если вы всё ещё не видите приоткрывающийся ад, то продолжим. Добавим свойство в метод:
obj.test.x = 10;
Да-да, у объекта есть свойство test, которое является методом, и в этот метод мы добавили новое свойство.
Вспомним то, что говорилось ранее про код и данные. Код это неизменная структура, а данные это фарш. Это больше не работает. Здесь ломаются все стереотипы. Данные это объект, но и код это тоже объект. Всё – объект.
Метод test является объектом. А раз он объект, значит мы можем добавлять в него новые свойства и методы – да, добавлять метод в метод!
Теперь вернемся к вопросу наследования.
Классическая схема с объявлением классов подразумевает, что это объявление фиксирует структуру класса. Класс не может изменить свои методы во время выполнения. Поэтому, наследуясь от какого-то класса, объект получает признак, что он порождён именно этим классом. И далее, если мы вызываем какой-то метод, объекту не нужно иметь этот метод у себя.
Виртуальная машина или транслятор просто смотрит – этот объект порождён классом A, вызывается метод test(), но этого метода нет в объекте, значит нужно просто обратиться к объявлению класса A, найти там метод test и вызвать его.
Естественно, что все объекты, сконструированные по образцу класса A, будут иметь доступ к единственному методу test, который расположен в классе A и работает одинаково для всех.
Но в JavaScript нет классов. Запись:
class A {}
будто бы объявляет класс, но это не класс! Это всего лишь скрытые от нас манипуляции над объектами. А как мы видели выше, манипуляции над объектами ничем не ограничены. То есть классы с фиксированной структурой в JavaScript изначально невозможны!
Попробуем организовать минимальное наследование теми средствами, которые есть в JS:
function testMethod() { return true };
var obj1 = { test: testMethod };
var obj2 = { test: testMethod };
console.log(obj1.test === obj2.test);
На этот раз мы не писали код метода test прямо в объявлении объекта, а написали его заранее, в виде функции testMethod(). А в объектах присвоили свойству test эту функцию. Мы можем это сделать, потому что функция – тоже объект.
Теперь при сравнении методов obj1.test и obj2.test мы получим true – они ссылаются на один и тот же экземпляр функции.
Можно сказать, что мы частично отнаследовали obj1 и obj2 от функции testMethod.
Хотя код метода test теперь не нужно повторять в каждом объекте, тем не менее надо повторять само свойство test. То есть каждый объект по-прежнему имеет свой личный метод test, который ссылается на внешнюю функцию.
Задача наследования, однако, состоит в том, чтобы у объектов не было этих личных методов. А при попытке использовать метод чтобы он искался где-то у "родителя". Пока такого не происходит, потому что для этого нужно ввести само понятие "родитель".
Тут мы подходим к финальному боссу. Это наследование через прототип. которому будет посвящён следующий выпуск.
Читайте дальше: