28.07.2019, 20:28
|
Интересующийся
|
|
Регистрация: 25.04.2019
Сообщений: 19
|
|
Глубокое копирование в 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 таже логика но всё более прозрачно.
Но каким образом, при этом, копировать значения самой функции - что-то мне совсем не понятно
|
|
28.07.2019, 21:56
|
|
Тлен
|
|
Регистрация: 02.01.2010
Сообщений: 6,577
|
|
Функция в js тоже объект.
alert((function(){}) instanceof Object)
Так что по ссылке. Всё кроме примитивов - по ссылке.
А клонировать фунцию ни в каком разумном случае не требуется. Если требуется - значит что-то вы делаете не так.
__________________
29375, 35
Последний раз редактировалось Aetae, 28.07.2019 в 22:02.
|
|
29.07.2019, 01:35
|
|
Профессор
|
|
Регистрация: 25.10.2016
Сообщений: 1,012
|
|
Да, в общем случае функцию склонировать невозможно, да и не нужно. Просто передают ссылку на неё, как примитивное значение. Конечно, это объект, но только с методами из прототипа, а что-то своё в этот объект добавлять не принято.
Передать по ссылке можно и регулярку (объект RegExp), если она без флага g. А если с этим флагом, то создать новый регекс с тем же паттерном и скопировать поле lastIndex.
Любые иммутабельные объекты можно передавать по ссылке - промисы, блобы и т.д.
Клонировать надо {}, [], специальные объекты вроде Date, Map, Set, бинарные массивы. В общем, всё, что мутабельное.
|
|
29.07.2019, 17:43
|
Интересующийся
|
|
Регистрация: 25.04.2019
Сообщений: 19
|
|
var sum = function(a, b)
{
return a + b;
}
sum.a = 'Masha';
sum.b = 'Phedya';
Ну, то есть, даже вот такую простейшую функцию со своими свойствами(да, по-видимому и без них) - не склонировать? Она будет существовать в единственном экземпляре, до тех пор, пока на неё ссылается хотя бы одна ссылка? Вообще, как-то странно, прочитать её можно, выполнить - тоже, а сделать точную копию, то есть прочитать и записать в другую "область памяти" - нельзя...
PS: навскидку, сделать какие-нибудь финты ушами - проверить, на функцию ли ссылается наша переменная, если да, вызвать какой-нибудь хитрый .toString и сохранить результат строке, а потом из неё соорудить новую функцию через new Function, возможен такой ход мысли или может ещё какой?...
Последний раз редактировалось Launder, 29.07.2019 в 18:03.
|
|
29.07.2019, 18:38
|
|
Профессор
|
|
Регистрация: 20.12.2009
Сообщений: 1,714
|
|
Можно всё скопировать кроме внутренних полей!
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!)
А зачем вам именно копия функции?
Последний раз редактировалось Malleys, 29.07.2019 в 18:45.
|
|
29.07.2019, 18:47
|
|
Профессор
|
|
Регистрация: 25.10.2016
Сообщений: 1,012
|
|
Сообщение от 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 надо свою функцию использовать, я так уж для примера написал
Последний раз редактировалось Alexandroppolus, 29.07.2019 в 18:52.
|
|
30.07.2019, 00:11
|
|
Тлен
|
|
Регистрация: 02.01.2010
Сообщений: 6,577
|
|
Сообщение от Launder
|
Вообще, как-то странно, прочитать её можно, выполнить - тоже, а сделать точную копию, то есть прочитать и записать в другую "область памяти" - нельзя...
|
Проблема, как уже заметили выше, в том, что функция тянет за собой контекст: замыкания и пр., так что получить абсолютно независимый клон не получится, для этого надо весь поток склонировать. Иначе же сайдэффекты всё равно будут, хоть тресни. Потому и не имеет это смысла.
__________________
29375, 35
|
|
05.08.2019, 18:53
|
Интересующийся
|
|
Регистрация: 25.04.2019
Сообщений: 19
|
|
Спасибо большое ответившим, идея более-менее ясна, буду учить синтаксис ES6, чтоб понять Ваши примеры!
|
|
03.10.2019, 18:50
|
Интересующийся
|
|
Регистрация: 25.04.2019
Сообщений: 19
|
|
Кстати, можно сделать копию, вот таким незамысловатым образом(ходил вокруг да около, а в голову, чего-то не пришло).
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), по-видимому, потребуется копирование отдельно методов, и отдельно тела функции, и их дальнейшего соединения в что-то общее и работающее - вопрос всё-таки требующий отдельного рассмотрения.
|
|
|
|