学习链接
如何优雅地嗅探”对象“是否存在”环“?
阿里笔试题:链式调用、对象比较以及对象是否存在循环引用
cycle.js
判断循环引用
function checkLoop(object) {
const objSet = new WeakSet();
let loop = false;
function check(value) {
// 确保 value 是非null普通对象或数组 且未发现循环
// 否则直接返回
if (!value || typeof value !== 'object' || loop) {
return;
}
// 判断是否已经用到过该对象
if (objSet.has(value)) {
loop = true; // 存在循环
return;
}
// 未用到过 则添加进记录中去
objSet.add(value);
// value 为普通对象 或 数组
Object.keys(value).forEach(key => check(value[key]));
// 同级判断完后删除 防止误判 例4
objSet.delete(value);
return;
}
check(object);
return loop;
};
// 1. 对象之间相互引用
let obj1 = {};
let obj2 = {};
obj1.obj = obj2
obj2.obj = obj1
console.log(checkLoop(obj1)) // true
console.log(checkLoop(obj2)) // true
// 2. 对象的属性引用了对象本身
let obj = { father: 'father' }
obj.child = obj
console.log(checkLoop(obj)) // true
// 3. 对象的属性引用部分属性
let obj3 = {
father: 'father',
child: {}
}
obj3.child.obj = obj3.child
console.log(checkLoop(obj3)) // true
// 4. 对象的属性指向同一引用
let tempObj = {
name: '名字'
}
let obj4 = {
child1: tempObj,
child2: tempObj
}
console.log(checkLoop(obj4)) // false
// 5. 其他数据类型
console.log(checkLoop(1)) // false
console.log(checkLoop('str')) // false
console.log(checkLoop(false)) // false
console.log(checkLoop(null)) // false
console.log(checkLoop(undefined)) // false
console.log(checkLoop([])) // false
console.log(checkLoop(Symbol())) // false
补充
// 处理
// var a = [];
// a[0] = a;
// return JSON.stringify(JSON.decycle(a));
// produces the string '[{"$ref":"$"}]'.
if (typeof JSON.decycle !== "function") {
JSON.decycle = function decycle(object, replacer) {
const objMap = new WeakMap(); // 对象到路径的映射
return (function derez(value, path) {
// derez函数 通过递归 实现深拷贝
let oldPath; // 旧路径
let newValue; // 新对象或数组
// 调用自定义替换值函数 replacer
if (replacer !== undefined) {
value = replacer(value);
}
// 确保 value 是普通对象或数组, 既非null 又非内置对象
// 否则直接返回
if (
typeof value !== 'object'
|| value === null
|| value instanceof Boolean
|| value instanceof Number
|| value instanceof String
|| value instanceof RegExp
|| value instanceof Date
) {
return value;
}
// 此时 value 是一个普通对象或者数组
// 判断是否已经用到过该对象
// 如果用到了 返回 {"$ref": PATH}
oldPath = objMap.get(value);
if (oldPath !== undefined) {
return { $ref: oldPath };
}
// if (objMap.has(value)) {
// return { $ref: objMap.get(value) };
// }
// 未用到过 则添加进记录中去
objMap.set(value, path);
// value 为数组
if (Array.isArray(value)) {
newValue = [];
// 递归处理
value.forEach((element, i) => {
newValue[i] = derez(element, path + "[" + i + "]");
});
} else {
// value 为普通对象
newValue = {};
Object.keys(value).forEach(key => {
newValue[key] = derez(
value[key],
path + "[" + JSON.stringify(key) + "]"
);
});
}
return newValue;
}(object, "$"));
};
}
// 还原
// var s = '[{"$ref":"$"}]';
// return JSON.retrocycle(JSON.parse(s));
// produces an array containing a single element which is the array itself.
if (typeof JSON.retrocycle !== "function") {
JSON.retrocycle = function retrocycle($) {
const px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/;
function rez(value) {
if (!value || typeof value !== 'object') {
return;
}
if (Array.isArray(value)) {
value.forEach((element, i) => {
if (typeof element !== 'object' || element === null) {
return ;
}
const path = element.$ref;
if (typeof path === 'string' && px.test(path)) {
value[i] = eval(path);
return;
}
rez(element);
});
return;
}
Object.keys(value).forEach(key => {
const element = value[key];
if (typeof element !== 'object' || element === null) {
return;
}
const path = element.$ref;
if (typeof path === 'string' && px.test(path)) {
value[key] = eval(path);
return;
}
rez(element);
});
}
rez($);
return $;
}
}
// 处理
// const a = [];
// a[0] = a;
const sameObj = {
x: 1,
y: 2
}
const a = {
b: sameObj,
c: sameObj,
d: {
e: 5,
f: {
}
}
};
a.d.f.g = a.d;
const s = JSON.stringify(JSON.decycle(a))
console.log('JSON.stringify(JSON.decycle(a)): ', JSON.stringify(JSON.decycle(a)));
// 还原
console.log('JSON.parse(s): ', JSON.parse(s));
console.log('JSON.retrocycle(JSON.parse(s)): ', JSON.retrocycle(JSON.parse(s)));