如何判断对象是否存在循环引用

89 阅读1分钟

前言

循环引用存在的几种形式:

1. 对象的属性引用对象本身

直接引用最外层对象

const obj = {
  a: 1
};
obj.b = obj;

引用对象的部分属性

let obj = { 
  name: 'hi', 
  child: {} 
} 
obj.child.obj = obj.child

2. 对象之间相互引用

const obj1 = {
  a: 1
}
const obj2 = {
  a: obj1,
  b: 1
}
obj1.b = obj2

判断是否存在循环引用

源码实现

function hasCircularReference(obj) {
  // 创建一个 Set,用于存储已经访问过的对象
  const stackSet = new Set();

  const detect = (obj) => {

    if (stackSet.has(obj)) {
      return true;
    }
    stackSet.add(obj);
    for (const key in obj) {
      if (typeof obj[key] === 'object' && obj[key] !== null) {
        if (detect(obj[key])) {
          return true;
        }
      }
    }
    // 平级检测完成之后,将当前对象删除,防止误判 
    // 例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用
    // let tempObj = { name: '00' }
    // let obj4 = { obj1: tempObj, obj2: tempObj }   
    
    stackSet.delete(obj);
    return false;
  }
  return detect(obj)
}

测试

// 1. 对象之间相互引用

let obj1 = { name: '00' }
let obj2 = { name: '11' }
// 对象1的属性引用了对象2
obj1.obj = obj2
// 对象2的属性引用了对象1
obj2.obj = obj1

console.log(hasCircularReference(obj1)) // true
console.log(hasCircularReference(obj2)) // true

// 2. 对象的属性引用了对象本身

let obj = { name: '00' }
// 对象的属性引用了对象本身
obj.child = obj

console.log(hasCircularReference(obj)) // true

// 3. 对象的属性引用部分属性

let obj3 = {
  name: '00',
  child: {}
}

obj3.child.obj = obj3.child

console.log(hasCircularReference(obj3)) // true

// 4. 对象的属性指向同一引用
let tempObj = {
  name: '00'
}
let obj4 = {
  obj1: tempObj,
  obj2: tempObj
}

console.log(hasCircularReference(obj4)) // false

// 5. 其他数据类型

console.log(hasCircularReference(1)) // false
console.log(hasCircularReference('00')) // false
console.log(hasCircularReference(false)) // false
console.log(hasCircularReference(null)) // false
console.log(hasCircularReference(undefined)) // false
console.log(hasCircularReference([])) // false
console.log(hasCircularReference(Symbol('00'))) // false