在 Javascript 中,当执行深度复制时,由于属性是“this”,我如何避免循环?
Posted
技术标签:
【中文标题】在 Javascript 中,当执行深度复制时,由于属性是“this”,我如何避免循环?【英文标题】:In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "this"? 【发布时间】:2012-05-30 12:01:49 【问题描述】:我有一些库代码在我身上无休止地循环。
我不清楚如何最好地在 javascript 中执行循环检测和避免。 即没有检查对象是否来自“this”引用的编程方式,是吗?
这是代码。 谢谢!
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
【问题讨论】:
跟踪“已访问”对象的集合,不要重新访问该集合中的对象。数组可以工作,但没有良好的界限......并且任意对象不会成为对象中的好键。 我想到了这个……但似乎没有更好的词,重量级?即没有更好的解决方案? 我认为在遍历对象图时这是一种非常常见的方法。当然,在这种情况下它更尴尬,因为 JavaScript 中没有身份映射。另一个(讨厌的)替代方法是使用“访问的属性”修改遍历的对象。 这就是克罗克福德先生在cycle.js中的做法@ 【参考方案1】:我所知道的处理这种情况的“可靠和干净”的方法是使用“已访问”对象的集合,然后根据当前对象是否已经做出反应——终止、插入符号引用等是否被“访问”过。
先生。 Crockford 在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 中为此使用原始的“哈希”方法,因为它缺少 Identity-Map。虽然 Array-collection 的边界是 O(n^2)
,但 并没有听起来那么糟糕:
这是因为,如果“已访问”集合只是一个守卫,那么 n
的值就是堆栈的深度:只有循环很重要,而多次复制同一个对象则不重要。也就是说,“已访问”集合中的对象可以在堆栈展开时进行修剪。
在 cycle.js 代码中,“已访问”集合不能被修剪,因为它必须确保始终使用给定对象的相同符号名称,这允许序列化在恢复时“保持引用”。然而,即使在这种情况下,n
只是遍历的唯一非原始值的数量。
我能想到的唯一其他方法需要直接向正在遍历的对象添加“已访问属性”,我认为这是一个通常不受欢迎的功能。 (但是,请参阅 Bergi 的评论,关于这个神器[相对] 很容易清理。)
编码愉快。
【讨论】:
我不认为“已访问”属性是个坏主意 - 如果您之后将其删除... @Bergi 够公平的。不过,我仍然觉得在“复制”操作中修改对象很脏。 是的,我也是。此外,具有递归深度优先算法的“已访问”属性方法不适用于任何交叉引用结构,它只检测圆圈。看我的回答:) 很好的答案...我对“只有循环很重要,而多次复制同一个对象不重要”有点困惑。每个原始对象只有一个克隆对象不是最好的吗?即,如果原始对象中有多个对单个对象的引用,则所有这些引用仍将指向结果中的单个对象。这可能是用户所期望的,并且使用更少的内存,不是吗?【参考方案2】:除非您想跟踪复制的每个属性,否则不会。
但是如果你确定每个属性要么是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.
这只是一个想法,我对表演一无所知。我担心它在大型物体上可能会很慢。
【讨论】:
【参考方案3】:好的,我对@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
。
属性描述符,例如 setter/getter、不可写和不可枚举的属性
好东西:
它不使用每次遇到对象时都需要搜索的大数组。缺点:
不适用于具有__getDeepCircularCopy__
方法的对象,该方法不符合它的要求。尽管在这个轻量级版本中无论如何都不支持方法(以函数为值的属性)。
此解决方案适用于具有循环引用的对象,复制循环结构,不会以无限循环结束。请注意,“循环”在这里表示属性引用“树”中的其中一个“父级”:
[Object]_ [Object]_
/ |\ / |\
prop | prop |
\_____/ | |
\|/ |
[Object] |
\ |
prop |
\___/
共享一个叶子的树的结构不会被复制,它们会成为两个独立的叶子:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
【讨论】:
漂亮的图表和解释。但是,我认为如果属性包含“符号引用名称”(例如在 cycle.js 中使用),也可以连接到查找以维护关系(文本序列化需要符号查找)。或者,存储的属性(如果存在)可以评估为 新 [第一个] 克隆的 对象,并在检测时返回它...【参考方案4】:这是一个简单的递归克隆方法。与许多其他解决方案一样,大多数非基本属性将与原始对象(例如函数)共享一个引用。
它通过保存引用对象的映射来处理无限循环,以便后续引用可以共享同一个克隆。
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!"
//
【讨论】:
【参考方案5】:我必须这样做才能面试,这就是我得到的:
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。
【讨论】:
以上是关于在 Javascript 中,当执行深度复制时,由于属性是“this”,我如何避免循环?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不替换 ES6/Javascript 中的整个属性的情况下深度复制对象 [重复]
检查 JavaScript 中是不是存在深度嵌套对象属性的最简单方法是啥? [复制]
深入探讨JavaScript如何实现深度复制(deep clone)
当用户单击使用 JavaScript 的按钮时,如何关闭当前浏览器选项卡? [复制]