Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Глубокое копирование в JavaScript. Функция + объект. (https://javascript.ru/forum/misc/78111-glubokoe-kopirovanie-v-javascript-funkciya-obekt.html)

Launder 28.07.2019 20:28

Глубокое копирование в JavaScript. Функция + объект.
 
Насколько я понимаю, функция копируется как примитив - по значению. А объект - по ссылке. Допустим у нас есть определённая функция и мы сохраняем её в переменную - мы получим ещё одну копию. Но если у этой функции есть свойства или методы, как быть тогда?
Как совместить эти способы?...
Ниже способ копирования среднестатистического объекта с возможными перекрёстными ссылками. Идея в том, что мы делаем полные копии всех объектов, на которые ссылается данный объект, но только один раз. И там где ссылка ссылается на оригинал, в копии мы ссылаемся на копию этого оригинала.
Удобнее всего это сделать с помощью Map:
function deepCopy(obj)
 	     {
			var newObj = new Object();
								 
			var map = new Map();
			map.set(obj, newObj);

			function engine(obj1, newObj1)
						 {
							for(var key in obj1)
								{
								         if( typeof( obj1[key] ) !== 'object' || obj1[key] == null)
									       {	
										      newObj1[key] = obj1[key];
									        }
								          else if(  !map.get( obj1[key] )   )
                                              	                                {
                                            			                     newObj1[key] = new Object();
                                            			                     map.set(obj1, newObj1);
                                            			                     engine( obj1[key], newObj1[key] );
                                                                                 }
									  else
                                               		                         {             
                                            			                     newObj1[key] = map.get(obj1[key]);                                                   
                                                                                 }                                        
                                                               }
                                                      }
                        engine(obj, newObj);
			return newObj;
                }


Можно это делать и без Map, делаешь массив из ссылок на объекты, а когда сталкивается с новым объектом при копировании, то, сначала просматриват свою коллекцию ссылок и если находит, то, там массив из двух элементов, первый элемент - старая ссылка, второй - новая. нашли старую - выдали новую. если кому интересно скину код, но он слегка объёмный, с Map таже логика но всё более прозрачно.
Но каким образом, при этом, копировать значения самой функции - что-то мне совсем не понятно :-?

Aetae 28.07.2019 21:56

Функция в js тоже объект.
alert((function(){}) instanceof Object)
Так что по ссылке. Всё кроме примитивов - по ссылке.
А клонировать фунцию ни в каком разумном случае не требуется. Если требуется - значит что-то вы делаете не так.

Alexandroppolus 29.07.2019 01:35

Да, в общем случае функцию склонировать невозможно, да и не нужно. Просто передают ссылку на неё, как примитивное значение. Конечно, это объект, но только с методами из прототипа, а что-то своё в этот объект добавлять не принято.

Передать по ссылке можно и регулярку (объект RegExp), если она без флага g. А если с этим флагом, то создать новый регекс с тем же паттерном и скопировать поле lastIndex.

Любые иммутабельные объекты можно передавать по ссылке - промисы, блобы и т.д.

Клонировать надо {}, [], специальные объекты вроде Date, Map, Set, бинарные массивы. В общем, всё, что мутабельное.

Launder 29.07.2019 17:43

var sum = function(a, b)
			   {
            return a + b;       
         }
sum.a = 'Masha';
sum.b = 'Phedya';


Ну, то есть, даже вот такую простейшую функцию со своими свойствами(да, по-видимому и без них) - не склонировать? Она будет существовать в единственном экземпляре, до тех пор, пока на неё ссылается хотя бы одна ссылка? Вообще, как-то странно, прочитать её можно, выполнить - тоже, а сделать точную копию, то есть прочитать и записать в другую "область памяти" - нельзя...
PS: навскидку, сделать какие-нибудь финты ушами - проверить, на функцию ли ссылается наша переменная, если да, вызвать какой-нибудь хитрый .toString и сохранить результат строке, а потом из неё соорудить новую функцию через new Function, возможен такой ход мысли или может ещё какой?...

Malleys 29.07.2019 18:38

Можно всё скопировать кроме внутренних полей!

var sum = function(a, b) {
	return a + b;
}
sum.a = 'Masha';
sum.b = 'Phedya';

function cloneFunction(fn) {
	return Object.assign({ __proto__: fn.constructor.prototype }, fn);
}

var sum2 = cloneFunction(sum);
console.log(sum2);


Но такую функцию вы не можете вызвать как функцию, поскольку у неё нет внутреннего поля [[Call]]. В JS нет публичного механизма для чтения и копирования внутренних полей (так называемые слоты), поэтому невозможно произвести ручное копирование некоторых объектов, которые были созданы при помощи встроенных конструкторов (например, функции, промисы и т. д.), если только класс не предоставляет метод для копирования объекта (как например метод cloneNode у класса Node!)

А зачем вам именно копия функции?

Alexandroppolus 29.07.2019 18:47

Цитата:

Сообщение от Launder
Ну, то есть, даже вот такую простейшую функцию со своими свойствами(да, по-видимому и без них) - не склонировать?

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

Можно сделать обертку, которая будет вызывать исходную функцию:
function cloneFunc(f) {
  if (!f) { return null; }
  var args = Array.from({length: f.length}).map((v, i) => 'a' + i).join(',');
  var newFunc = new Function('f', 'return function (' + args + ') { return f.call(this, ' + args + '); }')(f);
  return Object.assign(newFunc, f);
}

здесь костыль с "new Function" позволяет сохранить length исходной функции, так то можно и проще сделать.

----
если у нас глубокое копирование, то вместо Object.assign надо свою функцию использовать, я так уж для примера написал

Aetae 30.07.2019 00:11

Цитата:

Сообщение от Launder (Сообщение 510879)
Вообще, как-то странно, прочитать её можно, выполнить - тоже, а сделать точную копию, то есть прочитать и записать в другую "область памяти" - нельзя...

Проблема, как уже заметили выше, в том, что функция тянет за собой контекст: замыкания и пр., так что получить абсолютно независимый клон не получится, для этого надо весь поток склонировать. Иначе же сайдэффекты всё равно будут, хоть тресни. Потому и не имеет это смысла.

Launder 05.08.2019 18:53

Спасибо большое ответившим, идея более-менее ясна, буду учить синтаксис ES6, чтоб понять Ваши примеры!

Launder 03.10.2019 18:50

Кстати, можно сделать копию, вот таким незамысловатым образом(ходил вокруг да около, а в голову, чего-то не пришло).

function simple(a, b)
		{
    		    var A = a*a;
                    var B = b*b;
                    var C = A*B;
                    return C;
                }
    
var simple1 = String(simple); //превращаем в строку

simple2 = simple1.slice(0, 83) + '/' + simple1.slice(84); //меняем знак

changeSimple = new Function(simple2[16], simple2[19], simple2.slice(32, 104)); //копируем отдельно переменные, отдельно тело, и приводим её в боевую готовность

alert( simple(5, 2) );  //100
alert( changeSimple(5, 2) ); //6.25


То есть, мы скопировали функцию в строку, немного поменяли(чтоб было немного посложней и поинтересней) и вновь собрали работающий экземпляр.
Понятно, что это всё в первом приближении, что тут много допущений, о которых говорилось выше(например, замыкания, и не понятные мне "слоты", впрочем понятно, что есть некие внутренние поля, которыми движок, при компиляции, помечает какие-то характерные участки кода), что в одном случае, Function Declaration, в другом Function Expression, что добавим мы пару пробелов перед названием, и тело функции изменит свои "координаты" в строке(впрочем, не думаю, что найти тело и аргументы будет такой уж проблемой, у произвольной, и не слишком замысловатой функции), что если функция используется как объект (то есть в ней хранятся свойства и методы), то, хоть и не сложно идентифицировать функцию (с помощью Object.prototype.toString), по-видимому, потребуется копирование отдельно методов, и отдельно тела функции, и их дальнейшего соединения в что-то общее и работающее - вопрос всё-таки требующий отдельного рассмотрения.:)


Часовой пояс GMT +3, время: 04:37.