В 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 в точке входа рекурсии, например.

Другие вопросы по тегам