学习链接

如何优雅地嗅探”对象“是否存在”环“?

阿里笔试题:链式调用、对象比较以及对象是否存在循环引用

cycle.js

判断循环引用

  1. function checkLoop(object) {
  2. const objSet = new WeakSet();
  3. let loop = false;
  4. function check(value) {
  5. // 确保 value 是非null普通对象或数组 且未发现循环
  6. // 否则直接返回
  7. if (!value || typeof value !== 'object' || loop) {
  8. return;
  9. }
  10. // 判断是否已经用到过该对象
  11. if (objSet.has(value)) {
  12. loop = true; // 存在循环
  13. return;
  14. }
  15. // 未用到过 则添加进记录中去
  16. objSet.add(value);
  17. // value 为普通对象 或 数组
  18. Object.keys(value).forEach(key => check(value[key]));
  19. // 同级判断完后删除 防止误判 例4
  20. objSet.delete(value);
  21. return;
  22. }
  23. check(object);
  24. return loop;
  25. };
  1. // 1. 对象之间相互引用
  2. let obj1 = {};
  3. let obj2 = {};
  4. obj1.obj = obj2
  5. obj2.obj = obj1
  6. console.log(checkLoop(obj1)) // true
  7. console.log(checkLoop(obj2)) // true
  8. // 2. 对象的属性引用了对象本身
  9. let obj = { father: 'father' }
  10. obj.child = obj
  11. console.log(checkLoop(obj)) // true
  12. // 3. 对象的属性引用部分属性
  13. let obj3 = {
  14. father: 'father',
  15. child: {}
  16. }
  17. obj3.child.obj = obj3.child
  18. console.log(checkLoop(obj3)) // true
  19. // 4. 对象的属性指向同一引用
  20. let tempObj = {
  21. name: '名字'
  22. }
  23. let obj4 = {
  24. child1: tempObj,
  25. child2: tempObj
  26. }
  27. console.log(checkLoop(obj4)) // false
  28. // 5. 其他数据类型
  29. console.log(checkLoop(1)) // false
  30. console.log(checkLoop('str')) // false
  31. console.log(checkLoop(false)) // false
  32. console.log(checkLoop(null)) // false
  33. console.log(checkLoop(undefined)) // false
  34. console.log(checkLoop([])) // false
  35. console.log(checkLoop(Symbol())) // false

补充

  1. // 处理
  2. // var a = [];
  3. // a[0] = a;
  4. // return JSON.stringify(JSON.decycle(a));
  5. // produces the string '[{"$ref":"$"}]'.
  6. if (typeof JSON.decycle !== "function") {
  7. JSON.decycle = function decycle(object, replacer) {
  8. const objMap = new WeakMap(); // 对象到路径的映射
  9. return (function derez(value, path) {
  10. // derez函数 通过递归 实现深拷贝
  11. let oldPath; // 旧路径
  12. let newValue; // 新对象或数组
  13. // 调用自定义替换值函数 replacer
  14. if (replacer !== undefined) {
  15. value = replacer(value);
  16. }
  17. // 确保 value 是普通对象或数组, 既非null 又非内置对象
  18. // 否则直接返回
  19. if (
  20. typeof value !== 'object'
  21. || value === null
  22. || value instanceof Boolean
  23. || value instanceof Number
  24. || value instanceof String
  25. || value instanceof RegExp
  26. || value instanceof Date
  27. ) {
  28. return value;
  29. }
  30. // 此时 value 是一个普通对象或者数组
  31. // 判断是否已经用到过该对象
  32. // 如果用到了 返回 {"$ref": PATH}
  33. oldPath = objMap.get(value);
  34. if (oldPath !== undefined) {
  35. return { $ref: oldPath };
  36. }
  37. // if (objMap.has(value)) {
  38. // return { $ref: objMap.get(value) };
  39. // }
  40. // 未用到过 则添加进记录中去
  41. objMap.set(value, path);
  42. // value 为数组
  43. if (Array.isArray(value)) {
  44. newValue = [];
  45. // 递归处理
  46. value.forEach((element, i) => {
  47. newValue[i] = derez(element, path + "[" + i + "]");
  48. });
  49. } else {
  50. // value 为普通对象
  51. newValue = {};
  52. Object.keys(value).forEach(key => {
  53. newValue[key] = derez(
  54. value[key],
  55. path + "[" + JSON.stringify(key) + "]"
  56. );
  57. });
  58. }
  59. return newValue;
  60. }(object, "$"));
  61. };
  62. }
  63. // 还原
  64. // var s = '[{"$ref":"$"}]';
  65. // return JSON.retrocycle(JSON.parse(s));
  66. // produces an array containing a single element which is the array itself.
  67. if (typeof JSON.retrocycle !== "function") {
  68. JSON.retrocycle = function retrocycle($) {
  69. const px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/;
  70. function rez(value) {
  71. if (!value || typeof value !== 'object') {
  72. return;
  73. }
  74. if (Array.isArray(value)) {
  75. value.forEach((element, i) => {
  76. if (typeof element !== 'object' || element === null) {
  77. return ;
  78. }
  79. const path = element.$ref;
  80. if (typeof path === 'string' && px.test(path)) {
  81. value[i] = eval(path);
  82. return;
  83. }
  84. rez(element);
  85. });
  86. return;
  87. }
  88. Object.keys(value).forEach(key => {
  89. const element = value[key];
  90. if (typeof element !== 'object' || element === null) {
  91. return;
  92. }
  93. const path = element.$ref;
  94. if (typeof path === 'string' && px.test(path)) {
  95. value[key] = eval(path);
  96. return;
  97. }
  98. rez(element);
  99. });
  100. }
  101. rez($);
  102. return $;
  103. }
  104. }
  105. // 处理
  106. // const a = [];
  107. // a[0] = a;
  108. const sameObj = {
  109. x: 1,
  110. y: 2
  111. }
  112. const a = {
  113. b: sameObj,
  114. c: sameObj,
  115. d: {
  116. e: 5,
  117. f: {
  118. }
  119. }
  120. };
  121. a.d.f.g = a.d;
  122. const s = JSON.stringify(JSON.decycle(a))
  123. console.log('JSON.stringify(JSON.decycle(a)): ', JSON.stringify(JSON.decycle(a)));
  124. // 还原
  125. console.log('JSON.parse(s): ', JSON.parse(s));
  126. console.log('JSON.retrocycle(JSON.parse(s)): ', JSON.retrocycle(JSON.parse(s)));