SOURCE

// 思路
// 递归时用parent数组记录遍历过的历史对象
// 与每层的对象属性对比 找是否存在循环引用

function fn(obj, parent) {
    let list = parent || [obj]
    for (let key in obj) {
        if (typeof obj[key] === 'object') {
            // 取出当前层obj中每个属性 与 parent中对比
            for (let val of list) {
                if (val === obj[key]) {
                    // 递归出口 引用地址相同 返回结果
                    return true
                }
            }
            // 没找到继续往下一层递归
            // 将当前层的obj[key] 与 历史parent合并成新的parent传入下一层
            let result = fn(obj[key], [...list, obj[key]])
            // 递归返回的结果为true 则 不继续进行遍历 而是返回结果
            if (result) return true
        }
    }
    // 遍历完仍然没有返回true 说明没有循环引用
    return false
}

// 验证
let a = 1
let b = { a }
let c = { b }
let o = { d: { a: 3 }, c }
o.c.b.aa = a
console.log(fn(o)) // false
console 命令行工具 X clear

                    
>
console