检测和修复 JavaScript 中的循环引用
Posted
技术标签:
【中文标题】检测和修复 JavaScript 中的循环引用【英文标题】:Detecting and fixing circular references in JavaScript 【发布时间】:2013-02-04 09:38:20 【问题描述】:鉴于我在大型 javascript 对象中有循环引用
我试试JSON.stringify(problematicObject)
浏览器抛出
“TypeError:将循环结构转换为 JSON”
(这是预期的)
那我想找这个循环引用的原因,最好用Chrome开发者工具?这可能吗?如何在大对象中查找和修复循环引用?
【问题讨论】:
你能发布problematicObject
是什么吗?
@jbabey - JSON.stringify(window);
是导致该错误的一种情况。
它是原生对象(例如,通过
创建的,或原生函数之一),还是宿主对象(例如,DOM 元素)?
【参考方案1】:
大多数其他答案仅显示如何检测对象树具有循环引用——它们没有告诉您如何修复这些循环引用(即将循环引用值替换为 undefined
)。
下面是我用undefined
替换所有循环引用的函数:
export const specialTypeHandlers_default = [
// Set and Map are included by default, since JSON.stringify tries (and fails) to serialize them by default
type: Set, keys: a=>a.keys(), get: (a, key)=>key, delete: (a, key)=>a.delete(key),
type: Map, keys: a=>a.keys(), get: (a, key)=>a.get(key), delete: (a, key)=>a.set(key, undefined),
];
export function RemoveCircularLinks(node, specialTypeHandlers = specialTypeHandlers_default, nodeStack_set = new Set())
nodeStack_set.add(node);
const specialHandler = specialTypeHandlers.find(a=>node instanceof a.type);
for (const key of specialHandler ? specialHandler.keys(node) : Object.keys(node))
const value = specialHandler ? specialHandler.get(node, key) : node[key];
// if the value is already part of visited-stack, delete the value (and don't tunnel into it)
if (nodeStack_set.has(value))
if (specialHandler) specialHandler.delete(node, key);
else node[key] = undefined;
// else, tunnel into it, looking for circular-links at deeper levels
else if (typeof value == "object" && value != null)
RemoveCircularLinks(value, specialTypeHandlers, nodeStack_set);
nodeStack_set.delete(node);
特别是与JSON.stringify
一起使用,只需在字符串化之前调用上面的函数(注意它确实改变了传入的对象):
const objTree = normalProp: true;
objTree.selfReference = objTree;
RemoveCircularLinks(objTree); // without this line, the JSON.stringify call errors
console.log(JSON.stringify(objTree));
【讨论】:
【参考方案2】:循环参考检测器
这是我的 CircularReferenceDetector 类,它输出循环引用值实际所在的所有属性堆栈信息,并显示罪魁祸首引用的位置。
这对于大型结构特别有用,因为通过键无法明显看出哪个值是伤害的来源。
它输出字符串化的循环引用值,但所有对自身的引用都替换为“[Circular object --- fix me]”。
用法:
CircularReferenceDetector.detectCircularReferences(value);
注意: 如果您不想使用任何日志记录或没有可用的记录器,请删除 Logger.* 语句。
技术说明: 递归函数遍历对象的所有属性并测试 JSON.stringify 是否成功。 如果它不成功(循环引用),那么它通过用一些常量字符串替换 value 本身来测试它是否成功。这意味着如果它成功使用这个替换器,这个值就是循环引用的值。如果不是,则递归遍历该对象的所有属性。
同时它还跟踪属性堆栈,为您提供罪魁祸首值所在的信息。
打字稿
import Logger from "../Logger";
export class CircularReferenceDetector
static detectCircularReferences(toBeStringifiedValue: any, serializationKeyStack: string[] = [])
Object.keys(toBeStringifiedValue).forEach(key =>
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try
JSON.stringify(value);
Logger.debug(`path "$Util.joinStrings(serializationKeyStack)" is ok`);
catch (error)
Logger.debug(`path "$Util.joinStrings(serializationKeyStack)" JSON.stringify results in error: $error`);
var isCircularValue:boolean;
var circularExcludingStringifyResult:string = "";
try
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
catch (error)
Logger.debug(`path "$Util.joinStrings(serializationKeyStack)" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
if (isCircularValue)
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "$Util.joinStrings(serializationKeyStackWithNewKey)" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n$circularExcludingStringifyResult\n`);
);
private static replaceRootStringifyReplacer(toBeStringifiedValue: any): any
var serializedObjectCounter = 0;
return function (key: any, value: any)
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value)
Logger.error(`object serialization with key $key has circular reference to being stringified object`);
return '[Circular object --- fix me]';
serializedObjectCounter++;
return value;
export class Util
static joinStrings(arr: string[], separator: string = ":")
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `$v1$separator$v2`);
从 TypeScript 编译的 JavaScript
"use strict";
const Logger_1 = require("../Logger");
class CircularReferenceDetector
static detectCircularReferences(toBeStringifiedValue, serializationKeyStack = [])
Object.keys(toBeStringifiedValue).forEach(key =>
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try
JSON.stringify(value);
Logger_1.Logger.debug(`path "$Util.joinStrings(serializationKeyStack)" is ok`);
catch (error)
Logger_1.Logger.debug(`path "$Util.joinStrings(serializationKeyStack)" JSON.stringify results in error: $error`);
var isCircularValue;
var circularExcludingStringifyResult = "";
try
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
catch (error)
Logger_1.Logger.debug(`path "$Util.joinStrings(serializationKeyStack)" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
if (isCircularValue)
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "$Util.joinStrings(serializationKeyStackWithNewKey)" of the given root object\n` +
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n$circularExcludingStringifyResult\n`);
);
static replaceRootStringifyReplacer(toBeStringifiedValue)
var serializedObjectCounter = 0;
return function (key, value)
if (serializedObjectCounter !== 0 && typeof (toBeStringifiedValue) === 'object' && toBeStringifiedValue === value)
Logger_1.Logger.error(`object serialization with key $key has circular reference to being stringified object`);
return '[Circular object --- fix me]';
serializedObjectCounter++;
return value;
;
exports.CircularReferenceDetector = CircularReferenceDetector;
class Util
static joinStrings(arr, separator = ":")
if (arr.length === 0)
return "";
return arr.reduce((v1, v2) => `$v1$separator$v2`);
exports.Util = Util;
【讨论】:
应该将它发布到 npm 这会导致无限循环,将数千个条目记录到控制台,其他答案之一也会导致无限循环。但是,另一个答案中 isCyclic 的原始实现并没有导致无限循环。 谢谢你的好例子,但是如果你不知道循环结构的对象在哪里呢?因为在我的情况下,我几个月都无法捕捉到它,我的应用程序有很多第三方库,其中许多使用 JSON.stringify。你有什么推荐的?【参考方案3】:您也可以使用符号 - 由于这种方法,您不必改变原始对象的属性,除了添加符号来标记访问的节点。
它更干净,应该比收集节点属性和与对象比较更快。如果您不想序列化大的嵌套值,它还具有可选的深度限制:
// Symbol used to mark already visited nodes - helps with circular dependencies
const visitedMark = Symbol('VISITED_MARK');
const MAX_CLEANUP_DEPTH = 10;
function removeCirculars(obj, depth = 0)
if (!obj)
return obj;
// Skip condition - either object is falsy, was visited or we go too deep
const shouldSkip = !obj || obj[visitedMark] || depth > MAX_CLEANUP_DEPTH;
// Copy object (we copy properties from it and mark visited nodes)
const originalObj = obj;
let result = ;
Object.keys(originalObj).forEach((entry) =>
const val = originalObj[entry];
if (!shouldSkip)
if (typeof val === 'object') // Value is an object - run object sanitizer
originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
const nextDepth = depth + 1;
result[entry] = removeCirculars(val, nextDepth);
else
result[entry] = val;
else
result = 'CIRCULAR';
);
return result;
这将导致对象的所有循环依赖项被剥离,并且不会比给定的MAX_CLEANUP_DEPTH
更深。
只要您不对对象执行任何元编程操作,使用符号是安全的 - 它们是透明的且不可枚举,因此 - 它们不会显示在对象的任何标准操作中。
此外,如果您需要对其执行任何其他操作,返回一个新的、已清理的对象具有不会改变原始对象的优点。
如果您不想标记CIRCULAR
,您可以稍微修改一下代码,从而在实际执行操作之前跳过对象(在循环内):
originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
const val = originalObj[entry];
// Skip condition - either object is falsy, was visited or we go too deep
const shouldSkip = val[visitedMark] || depth > MAX_SANITIZATION_DEPTH;
if (!shouldSkip)
if (typeof val === 'object') // Value is an object - run object sanitizer
const nextDepth = depth + 1;
result[entry] = removeCirculars(val, nextDepth);
else
result[entry] = val;
【讨论】:
我正在研究一种通用方法来深度克隆对象,你帮了很多忙,谢谢你 不错的答案,我有希望,但得到: TypeError: Cannot add property Symbol(VISITED_MARK), object is not extensible 。此处说明:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…【参考方案4】:以下是 MDN 在循环对象上使用 JSON.stringify()
时检测和修复循环引用的方法:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value:
像下面这样的循环结构
var circularReference = otherData: 123;
circularReference.myself = circularReference;
JSON.stringify()
将失败:
JSON.stringify(circularReference);
// TypeError: cyclic object value
要序列化循环引用,您可以使用支持它们的库(例如cycle.js)或自行实施解决方案,这需要通过可序列化值查找和替换(或删除)循环引用。
下面的 sn-p 说明了如何使用 the replacer parameter of
JSON.stringify()
查找和过滤(从而导致数据丢失)循环引用:
const getCircularReplacer = () =>
const seen = new WeakSet();
return (key, value) =>
if (typeof value === "object" && value !== null)
if (seen.has(value))
return;
seen.add(value);
return value;
;
;
JSON.stringify(circularReference, getCircularReplacer());
// "otherData":123
【讨论】:
这是最好的答案。每个人都想编写自己的本土代码。另一方面,MDN 是权威的。如果这段代码太具体(它应该放在 JSON.stringify 中),那么查看他们引用的代码 cycle.js,它是由知道他们在做什么的人编写的。【参考方案5】:只是为了把我的版本混在一起...下面是 @dkurzaj 's code 的混音(它本身是 @Aaron V 的混音,@user4976005 的混音,@Trey Mack 的混音,最后是 @Freddie Nfbnm的 [已删除?] 代码)加上 @darksinge 's WeakMap
idea。所以……我猜这个帖子是 Megamix :)
在我的版本中,可以选择将报告(而不是console.log
'ed 条目)作为对象数组返回。如果不需要报告,则在第一次看到循环引用(a'la @darksinge 的代码)时停止测试。
此外,hasOwnProperty
已被删除,因为 Object.keys
仅返回 hasOwnProperty
属性(请参阅:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)。
function isCyclic(x, bReturnReport)
var a_sKeys = [],
a_oStack = [],
wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
oReturnVal =
found: false,
report: []
;
//# Setup the recursive logic to locate any circular references while kicking off the initial call
(function doIsCyclic(oTarget, sKey)
var a_sTargetKeys, sCurrentKey, i;
//# If we've seen this oTarget before, flip our .found to true
if (wm_oSeenObjects.has(oTarget))
oReturnVal.found = true;
//# If we are to bReturnReport, add the entries into our .report
if (bReturnReport)
oReturnVal.report.push(
instance: oTarget,
source: a_sKeys.slice(0, a_oStack.indexOf(oTarget) + 1).join('.'),
duplicate: a_sKeys.join('.') + "." + sKey
);
//# Else if oTarget is an instanceof Object, determine the a_sTargetKeys and .set our oTarget into the wm_oSeenObjects
else if (oTarget instanceof Object)
a_sTargetKeys = Object.keys(oTarget);
wm_oSeenObjects.set(oTarget /*, undefined*/);
//# If we are to bReturnReport, .push the current level's/call's items onto our stacks
if (bReturnReport)
if (sKey) a_sKeys.push(sKey) ;
a_oStack.push(oTarget);
//# Traverse the a_sTargetKeys, pulling each into sCurrentKey as we go
//# NOTE: If you want all properties, even non-enumerables, see Object.getOwnPropertyNames() so there is no need to call .hasOwnProperty (per: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)
for (i = 0; i < a_sTargetKeys.length; i++)
sCurrentKey = a_sTargetKeys[i];
//# If we've already .found a circular reference and we're not bReturnReport, fall from the loop
if (oReturnVal.found && !bReturnReport)
break;
//# Else if the sCurrentKey is an instanceof Object, recurse to test
else if (oTarget[sCurrentKey] instanceof Object)
doIsCyclic(oTarget[sCurrentKey], sCurrentKey);
//# .delete our oTarget into the wm_oSeenObjects
wm_oSeenObjects.delete(oTarget);
//# If we are to bReturnReport, .pop the current level's/call's items off our stacks
if (bReturnReport)
if (sKey) a_sKeys.pop() ;
a_oStack.pop();
(x, '')); //# doIsCyclic
return (bReturnReport ? oReturnVal.report : oReturnVal.found);
【讨论】:
【参考方案6】:这里有很多答案,但我想我会将我的解决方案添加到组合中。它类似于@Trey Mack 的答案,但该解决方案需要 O(n^2)。此版本使用WeakMap
代替数组,将时间缩短到 O(n)。
function isCyclic(object)
const seenObjects = new WeakMap(); // use to keep track of which objects have been seen.
function detectCycle(obj)
// If 'obj' is an actual object (i.e., has the form of ''), check
// if it's been seen already.
if (Object.prototype.toString.call(obj) == '[object Object]')
if (seenObjects.has(obj))
return true;
// If 'obj' hasn't been seen, add it to 'seenObjects'.
// Since 'obj' is used as a key, the value of 'seenObjects[obj]'
// is irrelevent and can be set as literally anything you want. I
// just went with 'undefined'.
seenObjects.set(obj, undefined);
// Recurse through the object, looking for more circular references.
for (var key in obj)
if (detectCycle(obj[key]))
return true;
// If 'obj' is an array, check if any of it's elements are
// an object that has been seen already.
else if (Array.isArray(obj))
for (var i in obj)
if (detectCycle(obj[i]))
return true;
return false;
return detectCycle(object);
这就是它在行动中的样子。
> var foo = grault: ;
> detectCycle(foo);
false
> foo.grault = foo;
> detectCycle(foo);
true
> var bar = ;
> detectCycle(bar);
false
> bar.plugh = [];
> bar.plugh.push(bar);
> detectCycle(bar);
true
【讨论】:
【参考方案7】:@tmack 的答案绝对是我发现这个问题时所寻找的!
不幸的是,它会返回许多误报 - 如果在 JSON 中复制对象,它会返回 true,这与循环性不同。循环性意味着一个对象是它自己的孩子,例如
obj.key1.key2.[...].keyX === obj
我修改了原始答案,这对我有用:
function isCyclic(obj)
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key)
if (obj && typeof obj != 'object') return;
if (stackSet.has(obj)) // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) //dive on the object's children
if (Object.prototype.hasOwnProperty.call(obj, k)) detect(obj[k], k);
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
detect(obj, 'obj');
return detected;
这里有一些非常简单的测试:
var root =
var leaf = 'isleaf':true;
var cycle2 = l:leaf;
var cycle1 = c2: cycle2, l:leaf;
cycle2.c1 = cycle1
root.leaf = leaf
isCyclic(cycle1); // returns true, logs "CIRCULAR: obj.c2.c1 = obj"
isCyclic(cycle2); // returns true, logs "CIRCULAR: obj.c1.c2 = obj"
isCyclic(leaf); // returns false
isCyclic(root); // returns false
【讨论】:
添加记忆以使这个 O(N) 留给读者作为练习:) 我添加了这个答案的 TypeScript 版本作为新答案。见下文。if (typeof obj != 'object') return;
应该是 if (obj && typeof obj != 'object') return;
因为typeof null == "object"
。
@Willwsharp - 已经有一段时间了,但我相信这个想法是绝对有可能多次遇到同一个物体,即使在非圆形结构中也是如此。因此,在最坏的情况下,我在这里编写的代码会一遍又一遍地处理相同的对象,这使得这不必要地 O(N^2)。您可以使用 memoization 来存储每个对象的子对象并防止重新处理。
@AaronV 哦,太棒了!我在这方面做了更多工作,得出了同样的结论,很高兴听到我走在正确的道路上;谢谢!【参考方案8】:
这是由@Aaron V 和@user4976005 的答案混合而成的Node ES6 版本,它解决了调用hasOwnProperty 的问题:
const isCyclic = (obj =>
const keys = []
const stack = []
const stackSet = new Set()
let detected = false
const detect = ((object, key) =>
if (!(object instanceof Object))
return
if (stackSet.has(object)) // it's cyclic! Print the object and its locations.
const oldindex = stack.indexOf(object)
const l1 = `$keys.join('.').$key`
const l2 = keys.slice(0, oldindex + 1).join('.')
console.log(`CIRCULAR: $l1 = $l2 = $object`)
console.log(object)
detected = true
return
keys.push(key)
stack.push(object)
stackSet.add(object)
Object.keys(object).forEach(k => // dive on the object's children
if (k && Object.prototype.hasOwnProperty.call(object, k))
detect(object[k], k)
)
keys.pop()
stack.pop()
stackSet.delete(object)
)
detect(obj, 'obj')
return detected
)
【讨论】:
【参考方案9】:你也可以将JSON.stringify
与try/catch一起使用
function hasCircularDependency(obj)
try
JSON.stringify(obj);
catch(e)
return e.includes("Converting circular structure to JSON");
return false;
演示
function hasCircularDependency(obj)
try
JSON.stringify(obj);
catch (e)
return String(e).includes("Converting circular structure to JSON");
return false;
var a = b:c:d:"";
console.log(hasCircularDependency(a));
a.b.c.d = a;
console.log(hasCircularDependency(a));
【讨论】:
你是用问题本身来回答问题吗? OP 已经知道这是导致错误的循环引用,他在问题I want to find the **cause** of this circular reference
中说(强调我的)。所以你的回答根本不回答OP的问题。
@hudidit 我想将您的评论标记为有趣。此外,对于更多但同样不重要的信息,我无法将我自己的评论标记为任何内容。【参考方案10】:
我将 Freddie Nfbnm 的答案转换为 TypeScript:
export class JsonUtil
static isCyclic(json)
const keys = [];
const stack = [];
const stackSet = new Set();
let detected = false;
function detect(obj, key)
if (typeof obj !== 'object')
return;
if (stackSet.has(obj)) // it's cyclic! Print the object and its locations.
const oldIndex = stack.indexOf(obj);
const l1 = keys.join('.') + '.' + key;
const l2 = keys.slice(0, oldIndex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (const k in obj) // dive on the object's children
if (obj.hasOwnProperty(k))
detect(obj[k], k);
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
detect(json, 'obj');
return detected;
【讨论】:
【参考方案11】:这里是@Thomas's answer 适用于节点:
const logger = require("../logger")
// Or: const logger = debug: (...args) => console.log.call(console.log, args)
const joinStrings = (arr, separator) =>
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `$v1$separator$v2`);
exports.CircularReferenceDetector = class CircularReferenceDetector
detectCircularReferences(toBeStringifiedValue, serializationKeyStack = [])
Object.keys(toBeStringifiedValue).forEach(key =>
let value = toBeStringifiedValue[key];
let serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try
JSON.stringify(value);
logger.debug(`path "$joinStrings(serializationKeyStack)" is ok`);
catch (error)
logger.debug(`path "$joinStrings(serializationKeyStack)" JSON.stringify results in error: $error`);
let isCircularValue;
let circularExcludingStringifyResult = "";
try
circularExcludingStringifyResult = JSON.stringify(value, this.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
catch (error)
logger.debug(`path "$joinStrings(serializationKeyStack)" is not the circular source`);
this.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
if (isCircularValue)
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "$joinStrings(serializationKeyStackWithNewKey)" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n$circularExcludingStringifyResult\n`);
);
replaceRootStringifyReplacer(toBeStringifiedValue)
let serializedObjectCounter = 0;
return function (key, value)
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value)
logger.error(`object serialization with key $key has circular reference to being stringified object`);
return '[Circular object --- fix me]';
serializedObjectCounter++;
return value;
【讨论】:
【参考方案12】:这是对typeof obj != 'object'
条件下@Trey Mack 和@Freddie Nfbnm 答案的修复。相反,它应该测试obj
值是否不是对象的实例,以便在检查对象熟悉的值时也可以工作(例如,函数和符号(符号不是对象的实例,但仍然解决,顺便说一句。))。
由于我还不能在此 StackExchange 帐户中发表评论,因此我将其发布为答案。
PS.:请随时要求我删除此答案。
function isCyclic(obj)
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key)
if (!(obj instanceof Object)) return; // Now works with other
// kinds of object.
if (stackSet.has(obj)) // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) //dive on the object's children
if (obj.hasOwnProperty(k)) detect(obj[k], k);
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
detect(obj, 'obj');
return detected;
【讨论】:
【参考方案13】:来自http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html。添加了一行来检测循环在哪里。将其粘贴到 Chrome 开发工具中:
function isCyclic (obj)
var seenObjects = [];
function detect (obj)
if (obj && typeof obj === 'object')
if (seenObjects.indexOf(obj) !== -1)
return true;
seenObjects.push(obj);
for (var key in obj)
if (obj.hasOwnProperty(key) && detect(obj[key]))
console.log(obj, 'cycle at ' + key);
return true;
return false;
return detect(obj);
这是测试:
> a =
> b =
> a.b = b; b.a = a;
> isCyclic(a)
Object a: Object
"cycle at a"
Object b: Object
"cycle at b"
true
【讨论】:
不要忘记将根 JSON 对象本身添加到seenObjects
。应该是var seenObjects = [obj];
。
@AaditMShah,因为在输入对象上调用了detect
,这将导致它立即退出,声称从输入对象直接到自身存在一个循环。
这个误报为 null 并且不是递归的
一个引用被多次使用并不一定意味着它是循环的。 var x = ; JSON.stringify([x,x])
很好......而var x = ; x.x = x; JSON.stringify(x);
不是。
由于某种原因,当我使用此解决方案时,它给了我一个错误:obj.hasOwnProperty is not a function
。这是在浏览器加载的脚本中运行的。【参考方案14】:
尝试在 chrome/firefox 浏览器上使用 console.log()
以确定问题所在。
在使用 Firebug 插件的 Firefox 上,您可以逐行调试您的 javascript。
更新:
参考下面的循环引用问题示例并已处理:-
// JSON.stringify, avoid TypeError: Converting circular structure to JSON
// Demo: Circular reference
var o = ;
o.o = o;
var cache = [];
JSON.stringify(o, function(key, value)
if (typeof value === 'object' && value !== null)
if (cache.indexOf(value) !== -1)
// Circular reference found, discard key
alert("Circular reference found, discard key");
return;
alert("value = '" + value + "'");
// Store value in our collection
cache.push(value);
return value;
);
cache = null; // Enable garbage collection
var a = b:1;
var o = ;
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o);
var obj =
a: "foo",
b: obj
;
var replacement = "b":undefined;
alert("Result : " + JSON.stringify(obj,replacement));
参考示例LIVE DEMO
【讨论】:
您的意思是手动搜索参考资料?这与其说是一个好的解决方案,不如说是最后的手段。 “你可以逐行调试你的 javascript” - 内置JSON.stringify()
不是用 JavaScript 实现的,而是用原生代码实现的。你不能用 Firebug 调试它。
有很多库在 JavaScript 中为旧版浏览器提供 stringify() 函数。也许你可以使用其中之一?这可能会在它解析问题引用的确切点中断。
引用被多次使用并不一定意味着它是循环的。 var x = ; JSON.stringify([x,x])
很好......而var x = ; x.x = x; JSON.stringify(x);
不是。【参考方案15】:
我刚做了这个。它可能很脏,但无论如何都可以使用...:P
function dump(orig)
var inspectedObjects = [];
console.log('== DUMP ==');
(function _dump(o,t)
console.log(t+' Type '+(typeof o));
for(var i in o)
if(o[i] === orig)
console.log(t+' '+i+': [recursive]');
continue;
var ind = 1+inspectedObjects.indexOf(o[i]);
if(ind>0) console.log(t+' '+i+': [already inspected ('+ind+')]');
else
console.log(t+' '+i+': ('+inspectedObjects.push(o[i])+')');
_dump(o[i],t+'>>');
(orig,'>'));
然后
var a = [1,2,3], b = [a,4,5,6], c = 'x':a,'y':b;
a.push(c); dump(c);
说
== DUMP ==
> Type object
> x: (1)
>>> Type object
>>> 0: (2)
>>>>> Type number
>>> 1: (3)
>>>>> Type number
>>> 2: (4)
>>>>> Type number
>>> 3: [recursive]
> y: (5)
>>> Type object
>>> 0: [already inspected (1)]
>>> 1: (6)
>>>>> Type number
>>> 2: (7)
>>>>> Type number
>>> 3: (8)
>>>>> Type number
这说明 c.x[3] 等于 c,并且 c.x = c.y[0]。
或者,稍微修改一下这个函数就可以告诉你你需要什么......
function findRecursive(orig)
var inspectedObjects = [];
(function _find(o,s)
for(var i in o)
if(o[i] === orig)
console.log('Found: obj.'+s.join('.')+'.'+i);
return;
if(inspectedObjects.indexOf(o[i])>=0) continue;
else
inspectedObjects.push(o[i]);
s.push(i); _find(o[i],s); s.pop(i);
(orig,[]));
【讨论】:
inspectedObjects
数组应该包含原始 JSON 对象本身(即它应该是 var inspectedObjects = [orig];
)。
我故意分开检查原始对象(if(o[i] === orig)
)和其他对象(inspectedObjects.indexOf(o[i])
)。
引用被多次使用并不一定意味着它是循环的。 var x = ; JSON.stringify([x,x])
很好......而var x = ; x.x = x; JSON.stringify(x);
不是。以上是关于检测和修复 JavaScript 中的循环引用的主要内容,如果未能解决你的问题,请参考以下文章