聽說你的物件有個”環“?怎麼發現的呢?

前端胖頭魚發表於2021-10-29

1. 手寫62+方法學習JavaScript底層原理

判斷一個物件是否存在迴圈引用已收錄至 手寫各種原始碼實現,也可以直接點選isCyclic快速檢視,目前已有62+手寫實現,歡迎一起來學習喔。

image.png

2. 不得不說的迴圈引用

如下圖: 相信曾經你也到過類似的問題,迴圈引用。如果兩個物件相互傳遞引用或者物件的屬性引用其本身都有可能會造成迴圈引用。

WechatIMG183.jpeg

image.png

在舊的瀏覽器中迴圈引用是造成記憶體洩漏的一個原因,當然隨著垃圾收集演算法的改進,現在可以很好地處理迴圈引用,這不再是一個問題。

只需要3分鐘時間,本文會您一起學習

  1. 哪些情況可能會造成迴圈引用(重要)?
  2. 如何判斷物件是否存在迴圈引用(重要)?

3. 出現迴圈引用的幾種情況

常見的迴圈引用有兩種情況,物件之間相互引用物件的屬性引用物件本身

3.1 物件之間相互引用

let obj1 = { name: '前端胖頭魚1' }
let obj2 = { name: '前端胖頭魚2' }
// 物件1的屬性引用了物件2
obj1.obj = obj2
// 物件2的屬性引用了物件1
obj2.obj = obj1

image.png

3.2 物件的屬性引用物件本身

1. 直接引用最外層的物件

let obj = { name: '前端胖頭魚1' }
// 物件的屬性引用了物件本身
obj.child = obj

image.png
2. 引用物件的部分屬性


let obj = {
  name: '前端胖頭魚',
  child: {}
}

obj.child.obj = obj.child

image.png

4. 如何判斷物件是否存在迴圈引用?

根據出現迴圈引用可能有的幾種情況,我們可以試著寫出下列程式碼

4.1 原始碼實現

const isCyclic = (obj) => {
  // 使用Set資料型別來儲存已經檢測過的物件
  let stackSet = new Set()
  let detected = false

  const detect = (obj) => {
    // 不是物件型別的話,可以直接跳過
    if (obj && typeof obj != 'object') {
      return
    }
    // 當要檢查的物件已經存在於stackSet中時,表示存在迴圈引用
    if (stackSet.has(obj)) {
      return detected = true
    }
    // 將當前obj存如stackSet
    stackSet.add(obj)

    for (let key in obj) {
      // 對obj下的屬性進行挨個檢測
      if (obj.hasOwnProperty(key)) {
        detect(obj[key])
      }
    }
    // 平級檢測完成之後,將當前物件刪除,防止誤判
    /*
      例如:物件的屬性指向同一引用,如果不刪除的話,會被認為是迴圈引用
      let tempObj = {
        name: '前端胖頭魚'
      }
      let obj4 = {
        obj1: tempObj,
        obj2: tempObj
      }
    */
    stackSet.delete(obj)
  }

  detect(obj)

  return detected
}

4.2 測試一把

// 1. 物件之間相互引用

let obj1 = { name: '前端胖頭魚1' }
let obj2 = { name: '前端胖頭魚2' }
// 物件1的屬性引用了物件2
obj1.obj = obj2
// 物件2的屬性引用了物件1
obj2.obj = obj1

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

// 2. 物件的屬性引用了物件本身

let obj = { name: '前端胖頭魚1' }
// 物件的屬性引用了物件本身
obj.child = obj

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

// 3. 物件的屬性引用部分屬性

let obj3 = {
  name: '前端胖頭魚',
  child: {}
}

obj3.child.obj = obj3.child

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

// 4. 物件的屬性指向同一引用
let tempObj = {
  name: '前端胖頭魚'
}
let obj4 = {
  obj1: tempObj,
  obj2: tempObj
}

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

// 5. 其他資料型別

console.log(isCyclic(1)) // false
console.log(isCyclic('前端胖頭魚')) // false
console.log(isCyclic(false)) // false
console.log(isCyclic(null)) // false
console.log(isCyclic(undefined)) // false
console.log(isCyclic([])) // false
console.log(isCyclic(Symbol('前端胖頭魚'))) // false

5. 結尾

一個非常小的知識點,感謝大家閱讀。如果有興趣可以更進一步探索一些有意思的話題:

比如:

  1. 如何在JSON.stringify中輸出有迴圈引用的物件。
  2. JS的垃圾回收機制中是如何處理迴圈引用的等等。

相關文章