学习链接
如何优雅地嗅探”对象“是否存在”环“?
阿里笔试题:链式调用、对象比较以及对象是否存在循环引用
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 = obj2obj2.obj = obj1console.log(checkLoop(obj1)) // trueconsole.log(checkLoop(obj2)) // true// 2. 对象的属性引用了对象本身let obj = { father: 'father' }obj.child = objconsole.log(checkLoop(obj)) // true// 3. 对象的属性引用部分属性let obj3 = { father: 'father', child: {}}obj3.child.obj = obj3.childconsole.log(checkLoop(obj3)) // true// 4. 对象的属性指向同一引用let tempObj = { name: '名字'}let obj4 = { child1: tempObj, child2: tempObj}console.log(checkLoop(obj4)) // false// 5. 其他数据类型console.log(checkLoop(1)) // falseconsole.log(checkLoop('str')) // falseconsole.log(checkLoop(false)) // falseconsole.log(checkLoop(null)) // falseconsole.log(checkLoop(undefined)) // falseconsole.log(checkLoop([])) // falseconsole.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)));