Найти тему
ZDG

Наследование в Javascript: Динамические свойства

Предыдущая часть:

Обратите внимание, что наследование – это в целом не про свойства, а про методы объекта.

Так как набор свойств у объектов одного класса одинаковый, но значения им можно задавать разные, это значит, что у каждого объекта есть копия этих свойств. То есть у объекта 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, который ссылается на внешнюю функцию.

Задача наследования, однако, состоит в том, чтобы у объектов не было этих личных методов. А при попытке использовать метод чтобы он искался где-то у "родителя". Пока такого не происходит, потому что для этого нужно ввести само понятие "родитель".

Тут мы подходим к финальному боссу. Это наследование через прототип. которому будет посвящён следующий выпуск.

Читайте дальше: