Московский физико-технический институт
Опубликован: 23.12.2005 | Доступ: свободный | Студентов: 2868 / 252 | Оценка: 4.61 / 4.44 | Длительность: 27:18:00
ISBN: 978-5-9556-0051-2
Лекция 6:

Классы

Оператор new и копирование объектов

Вариант с нерекурсивным копированием

Для того, чтобы обеспечить функциональность, похожую на функциональность настоящего оператора new , нам понадобится вспомогательная функция копирования объектов ; сама же функция, эмулирующая оператор new , должна помимо копирования прототипа еще и вызывать конструктор. Так что возьмем код из предыдущего параграфа, но после определения класса lift напишем следующее:

_global.newFunc = function (constr, args){
  var tmp = {}; // Или var tmp = new Object();
  
  	  // Копируем прототип:
  copyOneLevelObj(tmp, constr.prototype);
	// Выделяем подмассив с аргументами, которые надо передать в 
	// конструктор (все, кроме нулевого -  это сам конструктор) 
	// и вызываем его:
  constr.apply(tmp, arguments.slice(1));
  return tmp;
}
_global.copyOneLevelObj = function (toObj, fromObj){
	for (var fieldName in fromObj){ 
		toObj[fieldName] = fromObj[fieldName];	
	}
}
  // Создаем 2 объекта
l1 = newFunc(lift, 2, 5);
l2 = newFunc(lift, 0, 3);
  // Тестируем 
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);

Тестировочные операторы оставляем такими же, как раньше, и убеждаемся, что на выходе получается то же самое:

l1:
-----------
Закрываем двери.
Этаж 2
Этаж 3
Этаж 4
Открываем двери.
-----------
l2:
-----------
Закрываем двери.
Этаж 1
Этаж 2
Этаж 3
Открываем двери.
-----------

А это значит, что не только создались наши объекты со всеми нужными методами, но и конструкторы отработали правильно.

(В данном примере был использован вызов метода одного объекта в качестве метода другого при помощи функции apply. Если вы подзабыли детали этого способа - просмотрите еще раз в лекции о функциях конец параграфа про объект arguments. Там же описывается работа функций apply и call. А более подробные примеры их применения вы найдете в лекции о наследовании - в параграфе про вызов функции с явным указанием базового класса.)

Недостатки нерекурсивного варианта. Как скопировать объект

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

Надо заметить, что, совершенствуя функцию newFunc в этом направлении, мы удаляемся от оригинала. Здесь уже пора приоткрыть краешек тайны и сказать о том, что настоящий оператор new вовсе не копирует объекты. Точнее, он делает возможным использование в дальнейшем так называемого механизма copy-on-write, то есть " копирование при записи". Что же касается размножения "вложенных" объектов, то эта задача возлагается на конструктор - и программист сам проследит за созданием новых объектов. Тем не менее, раз уж мы взялись за задачу размножения объектов, давайте доведем ее до конца. Хотя метод создания дочерних объектов в конструкторе наиболее универсален, он все же требует создания соответствующего кода в каждом конкретном конструкторе. В некоторых случаях может оказаться удобным использовать готовую функцию копирования.

Рекурсивный вариант функции копирования

Вот код рекурсивной функции copyObj. Мы проверим его работу в отдельном файле.

_global.copyObj = function(toObj, fromObj){
	for (var fieldName in fromObj){
		var type = typeof(fromObj[fieldName]);
			// Ссылки на объекты требуют копирования этих объектов					 
		if (type == "object"){ 
			// Параметризованные функции и клипы игнорируем
			toObj[fieldName] = {};
			copyObj(toObj[fieldName], fromObj[fieldName]);	
		}else{
			toObj[fieldName] = fromObj[fieldName];	
		}
	}
}
obj1 = {dataObj: {dataStr: "Some data"}, x: 10};
obj2 = {y: 5};
copyObj(obj2, obj1);
obj1.dataObj.dataStr = "Another data";

Этот пример ничего не выводит в консоль, но, нажав Ctrl+Alt+V ( List Variables ), мы получим:

Global Variables:
 Variable _global.copyObj = [function 'copyObj']
Level #0:
Variable _level0.$version = "WIN 6,0,21,0"
Variable _level0.obj1 = [object #2, class 'Object'] {
  x:10,
  dataObj:[object #3, class 'Object'] {
   dataStr:"Another data"
  }
 }
Variable _level0.obj2 = [object #4, class 'Object'] {
  y:5,
  dataObj:[object #5, class 'Object'] {
   dataStr:"Some data"
  },
  x:10
 }

Все поля объекта obj1 скопировались в obj2, включая поля вложенного подобъекта; более того, после изменения значения поля подобъекта, на который есть ссылка в obj1, значение поля аналогичного подобъекта в obj2 не изменилось. Отсюда мы можем сделать вывод, что рекурсивная функция копирования действительно работает. Так что теперь в нашей функции newFunc можно заменить вызов copyOneLevelObj на вызов copyObj (приводить результаты проверки нового варианта newFunc в примере с лифтами мы не будем, поскольку лифты не содержат пока ссылок на другие объекты и результат работы примера остается тем же самым).