В Javascript, когда я выполняю глубокое копирование, как избежать цикла из-за свойства "this"?
У меня есть некоторый библиотечный код, который бесконечно зацикливается на мне.
Я не понимаю, как лучше всего выполнять обнаружение и предотвращение цикла в JavaScript. то есть, нет никакого программного способа проверить, произошел ли объект по "этой" ссылке, не так ли?
Вот код Спасибо!
setAttrs: function(config) {
var go = Kinetic.GlobalObject;
var that = this;
// set properties from config
if(config !== undefined) {
function setAttrs(obj, c) {
for(var key in c) {
var val = c[key];
/*
* if property is an object, then add an empty object
* to the node and then traverse
*/
if(go._isObject(val) && !go._isArray(val) && !go._isElement(val)) {
if(obj[key] === undefined) {
obj[key] = {};
}
setAttrs(obj[key], val); // <--- offending code;
// one of my "val"s is a "this" reference
// to an enclosing object
}
3 ответа
"Надежный и чистый" способ, которым я знаю, чтобы иметь дело с этой ситуацией, состоит в том, чтобы использовать коллекцию "посещенных" объектов, а затем реагировать - завершать, вставлять символическую ссылку и т. Д. - в зависимости от того, был ли текущий объект уже "посещен". " или нет.
Мистер Крокфорд использует этот подход в cycle.js и для коллекции использует массив. Выдержка:
// If the value is an object or array, look to see if we have already
// encountered it. If so, return a $ref/path object. This is a hard way,
// linear search that will get slower as the number of unique objects grows.
for (i = 0; i < objects.length; i += 1) {
if (objects[i] === value) {
return {$ref: paths[i]};
}
}
К сожалению, в JavaScript невозможно использовать примитивный подход "хэш" в JavaScript, поскольку в нем отсутствует Identity-Map. В то время как границы Array-collection являются O(n^2)
это не так плохо, как кажется
Это связано с тем, что если "посещенная" коллекция является всего лишь средством защиты, то значение n
это всего лишь глубина стека: важны только циклы, а копирование одного и того же объекта несколько раз - нет. То есть объекты в "посещенной" коллекции могут быть удалены при разворачивании стека.
В коде cycle.js "посещенная" коллекция не может быть удалена, поскольку она должна гарантировать, что всегда используется одно и то же символическое имя для данного объекта, что позволяет сериализации "поддерживать ссылки" при ее восстановлении. Однако даже в этом случае n
это только количество уникальных пройденных непримитивных значений.
Единственный другой метод, который я могу придумать, - это добавить "посещенное свойство" непосредственно к объектам, которые нужно просмотреть, что я считаю в целом нежелательной функцией. (Тем не менее, см. Комментарий Берги о том, что этот артефакт [относительно] легко очищается.)
Удачного кодирования.
Хорошо, меня интересует, как может выглядеть упомянутое "посещенное" свойство @pst, поэтому я написал следующее:
Object.copyCircular = function deepCircularCopy(o) {
const gdcc = "__getDeepCircularCopy__";
if (o !== Object(o))
return o; // primitive value
var set = gdcc in o,
cache = o[gdcc],
result;
if (set && typeof cache == "function")
return cache();
// else
o[gdcc] = function() { return result; }; // overwrite
if (o instanceof Array) {
result = [];
for (var i=0; i<o.length; i++) {
result[i] = deepCircularCopy(o[i]);
} else {
result = {};
for (var prop in o)
if (prop != gdcc)
result[prop] = deepCircularCopy(o[prop]);
else if (set)
result[prop] = deepCircularCopy(cache);
}
if (set)
o[gdcc] = cache; // reset
else
delete o[gdcc]; // unset again
return result;
};
Обратите внимание, это только пример. Он не поддерживает:
- непрозрачные объекты. Все с прототипом (кроме массивов) не будет клонировано, но скопировано в
new Object
! Это включает в себя функции! - кросс-глобальные объекты: он использует
instanceof Array
, - дескрипторы свойств, такие как сеттеры / геттеры, не записываемые и не перечисляемые свойства
Плюсы:
- он не использует большой массив, который нужно искать каждый раз, когда он встречает объект.
Недостатки:
- не работает на объектах, которые имеют
__getDeepCircularCopy__
метод, который не то, что он утверждает. Хотя методы (свойства с функцией в качестве значения) не поддерживаются в любом случае в этой облегченной версии.
Это решение будет работать на объектах с круговыми ссылками, копируя круговую структуру, не заканчиваясь в бесконечном цикле. Обратите внимание, что "круговой" здесь означает, что свойство ссылается на одного из своих "родителей" в "дереве":
[Object]_ [Object]_
/ |\ / |\
prop | prop |
\_____/ | |
\|/ |
[Object] |
\ |
prop |
\___/
Структура деревьев, имеющих общий лист, не будет скопирована, они станут двумя независимыми листами:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
Нет, если вы не хотите отслеживать каждую скопированную собственность.
Но если вы уверены, что каждая собственность null
, строку, число, массив или простой объект, вы можете поймать JSON.stringify
исключения, чтобы увидеть, есть ли обратные ссылки, например:
try {
JSON.stringify(obj);
// It's ok to make a deep copy of obj
} catch (e) {
// obj has back references and a deep copy would generate an infinite loop
// Or finite, i.e. until the stack space is full.
}
Это просто идея, и я не имею представления о спектаклях. Боюсь, это может быть довольно медленно на больших объектах.
Вот простой рекурсивный метод клонирования. Как и многие другие решения, большинство неосновных свойств будет иметь ссылку на исходный объект (например, функции).
Он обрабатывает бесконечные циклы, сохраняя карту ссылочных объектов, чтобы последующие ссылки могли использовать один и тот же клон.
const georgeCloney = (originalObject, __references__ = new Map()) => {
if(typeof originalObject !== "object" || originalObject === null) {
return originalObject;
}
// If an object has already been cloned then return a
// reference to that clone to avoid an infinite loop
if(__references__.has(originalObject) === true) {
return __references__.get(originalObject);
}
let clonedObject = originalObject instanceof Array ? [] : {};
__references__.set(originalObject, clonedObject);
for(let key in originalObject) {
if(originalObject.hasOwnProperty(key) === false) {
continue;
}
clonedObject[key] = georgeCloney(originalObject[key], __references__);
}
return clonedObject;
};
Пример использования...
let foo = {};
foo.foo = foo;
let bar = georgeCloney(foo);
bar.bar = "Jello World!";
// foo output
// {
// foo: {
// foo: {...}
// }
// }
//
// bar output
// {
// foo: {
// foo: {...},
// bar: "Jello World!"
// },
// bar: "Jello World!"
// }
Мне пришлось сделать это для интервью, и вот что я получил:
Object.defineProperty(Object.prototype, 'deepclone', { enumerable :false, configurable :true, value :function(){
let map /*for replacement*/ =new Map(), rep ={} ;map.set(this, rep)
let output = (function medclone(_, map){ let output ={..._}
for(let k in _){ let v =_[k]
let v2 ;if(!(v instanceof Object)) v2 =v ;else {
if(map.has(v)) v2 =map.get(v) ;else {
let rep ={}
map.set(v, rep), v2 =medclone(v, map)
Replace(rep, v2), map.set(v, v2)
}
}
output[k] =v2
} return output
})(this, map)
Replace(rep, output)
return output
/*[*/ function Replace(rep/*resentative*/, proper, branch =proper, seens =new Set()){
for(let k in branch){ let v =branch[k]
if(v ===rep) branch[k] =proper
else if(v instanceof Object &&!seens.has(v)) seens.add(v), Replace(rep, proper, v, seens)
}
}/*]*/
} })
// Code is freely available for use for academia/scrutiny. As for development, contact author.
Используется методология "рубить и чертить" вместо "планировать достаточно времени до кодирования", так что результат не может быть таким уж замечательным, но он работает. можно выполнить итерацию, чтобы удалить WET в точке входа рекурсии, например.