Опубликован: 23.12.2005 | Уровень: специалист | Доступ: платный | ВУЗ: Московский физико-технический институт
Лекция 6:

Классы

Механизм copy on write для содержимого прототипа

Мы разобрались, что означают и куда указывают ссылки, создаваемые в объектах оператором new . Но для чего они используются?

Недостатки прямого копирования

Прежде чем ответить на этот вопрос, давайте еще раз вспомним, в чем основные недостатки функции new , которую мы уже реализовали, и почему настоящая функция вряд ли может работать таким образом. Итак, мы уже говорили, что вариант с рекурсивным копированием очевидно избыточен - он тратит слишком много памяти. Как правило, изменяемых подобъектов в реальном объекте немного и каждый из них можно создать в конструкторе явным образом. Более того, нерекурсивное копирование также избыточно, поскольку мы создаем дублирующие ссылки на функции, из которых лишь немногие будут изменены (переопределены). Мы вынуждены производить копирование только потому, что не знаем, какие именно функции потребуется изменить впоследствии. Особенно сильно объекты раздувались бы при наследовании - каждый производный класс добавлял бы свои функции, и все приходилось бы копировать. Наконец, копирование большого объекта требует еще и значительных временных затрат. Но можно ли обойтись без копирования? Оказывается, можно.

Сущность copy on write

Поможет решить нашу проблему механизм, известный под названием copy on write, то есть копирование при записи. В данном случае мы можем трактовать его так: пока мы не пытаемся изменить объект, все значения его полей берутся из прототипа. То есть в объекте не заводится ни одной реальной ссылки. И только если мы захотим изменить значение какого-либо из полей - только тогда это поле действительно заводится в самом объекте. Конечно, это требует поддержки со стороны языка. Алгоритм работы извлечения значения поля мы можем представить себе таким образом: сначала ищем поле в самом объекте, если не находим - обращаемся к прототипу (а если и там нет - к его прототипу и т.д.). Все это мы можем проиллюстрировать следующим кодом:

a = {x: 10, y:20};
b = {x: 15, z:25};
c = {x: 21, t:31};
// Эмуляция настоящего __proto__
c.proto = b;  
b.proto = a;
_global.getFieldValByName = function(name){
	if (this[name] != undefined) return this[name];
	if (this.proto != undefined) 
		return getFieldValByName.call(this.proto, name);
		
	return undefined;
}
trace("b.x = " + getFieldValByName.call(b, "x"));
trace("c.y = " + getFieldValByName.call(c, "y"));
trace("----- На самом деле -----");
trace("b.x = " + b.x);
trace("c.y = " + c.y);
c.__proto__ = b;
b.__proto__ = a;
trace("----- После установки __proto__ -----");
trace("b.x = " + b.x);
trace("c.y = " + c.y);

Запустив этот код, обнаружим в консоли следующее:

b.x = 15
c.y = 20
----- На самом деле -----
b.x = 15
c.y =
----- После установки __proto__ -----
b.x = 15
c.y = 20

То есть наша эмуляция механизма работы __proto__ вполне соответствует реальному положению вещей. (Заметим только, что для аналогичного вызова функций нам надо было бы передать по цепочке ссылку на первоначальный объект и потом сделать apply ). Более того, мы видим, что безо всякого использования new , одной только установкой поля __proto__ мы устроили нечто вроде наследования! Впрочем, с подробным анализом открывающихся здесь возможностей мы подождем до следующей лекции.

Анатолий Федоров
Анатолий Федоров
Россия, Москва, Московский государственный университет им. М. В. Ломоносова, 1989