本文主要從 JS 中爲何會出現循環引用,垃圾回收策略中引用計數爲何有很大的問題,以及循環引用時的對象在使用 JSON.stringify 時爲何會報錯,怎樣解決這個問題簡單談談本身的一些理解。html
1. 什麼是循環引用
當對象 1 中的某個屬性指向對象 2,對象 2 中的某個屬性指向對象 1 就會出現循環引用,(固然不止這一種狀況,不過原理是同樣的)下面經過代碼和內存示意圖來講明一下。node
function circularReference() { let obj1 = { }; let obj2 = { b: obj1 }; obj1.a = obj2; }
上述代碼在內存中的示意圖git
從上圖能夠看出 obj1 中的 a 屬性引用 obj2,obj2 中的 b 屬性引用 obj1,這樣就構成了循環引用。github
2.JS 中引用計數垃圾回收策略的問題
先簡單講一下 JS 中引用垃圾回收策略大致是什麼樣的一個原理,當一個變量被賦予一個引用類型的值時,這個引用類型的值的引用計數加 1。就像是代碼中的 obj1 這個變量被賦予了 obj1 這個對象的地址,obj1 這個變量就指向了這個 obj1(右上)這個對象,obj1(右上)的引用計數就會加1.當變量 obj1的值再也不是 obj1(右上)這個對象的地址時,obj1(右上)這個對象的引用計數就會減1.當這個 obj1(右上)對象的引用計數變成 0 後,垃圾收集器就會將其回收,由於此時沒有變量指向你,也就沒辦法使用你了。數組
看似很合理的垃圾回收策略爲何會有問題呢?函數
就是上面講到的循環引用致使的,下面來分析一下。當 obj1 這個變量執行 obj1 這個對象時,obj1 這個對象的引用計數會加 1,此時引用計數值爲 1,接下來 obj2 的 b 屬性又指向了 obj1 這個對象,因此此時 obj1 這個對象的引用計數爲 2。同理 obj2 這個對象的引用計數也爲2.測試
當代碼執行完後,會將變量 obj1 和 obj2 賦值爲 null,可是此時 obj1 和 obj2 這兩個對象的引用計數都爲1,並不爲 0,因此並不會進行垃圾回收,可是這兩個對象已經沒有做用了,在函數外部也不可能使用到它們,因此這就形成了內存泄露。字體
在如今普遍採用的標記清除回收策略中就不會出現上面的問題,標記清除回收策略的大體流程是這樣的,最開始的時候將全部的變量加上標記,當執行 cycularReference 函數的時候會將函數內部的變量這些標記清除,在函數執行完後再加上標記。這些被清除標記又被加上標記的變量就被視爲將要刪除的變量,緣由是這些函數中的變量已經沒法被訪問到了。像上述代碼中的 obj1 和 obj2 這兩個變量在剛開始時有標記,進入函數後被清除標記,而後函數執行完後又被加上標記被視爲將要清除的變量,所以不會出現引用計數中出現的問題,由於標記清除並不會關心引用的次數是多少。ui
3. 循環引用的對象使用 JSON.stringify 爲何會報錯
JSON.stringify
用於將一個 JS 對象序列化爲一個 JSON 字符串,假設如今咱們要將 obj1 這個對象序列化爲 JSON 字符串,如今咱們先將 obj1 這個對象打印出來看一下。spa
function circularReference() { let obj1 = { }; let obj2 = { b: obj1 }; obj1.a = obj2; console.log(obj1); } circularReference();
結果以下所示:
obj1 這個對象和 obj2 會無限相互引用,JSON.tostringify 沒法將一個無限引用的對象序列化爲 JOSN 字符串。
下面是 MDN 的解釋:
JSON.stringify() 將值轉換爲相應的JSON格式:
- 轉換值若是有 toJSON() 方法,該方法定義什麼值將被序列化。
- 非數組對象的屬性不能保證以特定的順序出如今序列化後的字符串中。
- 布爾值、數字、字符串的包裝對象在序列化過程當中會自動轉換成對應的原始值。
undefined
、任意的函數以及 symbol 值,在序列化過程當中會被忽略(出如今非數組對象的屬性值中時)或者被轉換成null
(出如今數組中時)。函數、undefined 被單獨轉換時,會返回 undefined,如JSON.stringify(function(){})
orJSON.stringify(undefined)
.- 對包含循環引用的對象(對象之間相互引用,造成無限循環)執行此方法,會拋出錯誤。
- 全部以 symbol 爲屬性鍵的屬性都會被徹底忽略掉,即使
replacer
參數中強制指定包含了它們。- Date 日期調用了 toJSON() 將其轉換爲了 string 字符串(同Date.toISOString()),所以會被當作字符串處理。
- NaN 和 Infinity 格式的數值及 null 都會被當作 null。
- 其餘類型的對象,包括 Map/Set/WeakMap/WeakSet,僅會序列化可枚舉的屬性。
咱們能夠從加粗的字體中看到,對包含循環引用的對象執行 JSON.stringify,會拋出錯誤。
解決方法
一個天然的想法能不能消除循環引用,一個 JSON 擴展包 作到了這一點, 使用 JSON.decycle 能夠去除循環引用。爲了方便測試我直接在 JSON 擴展包的 Github 倉庫中下載了 cycle.js 這個函數,將下面這段代碼賦值到最下面,而後利用 node 運行進行測試,問題獲得解決,結果以下圖所示。
function circularReference() { let obj1 = { }; let obj2 = { b: obj1 }; obj1.a = obj2; let c = JSON.decycle(obj1); console.log(JSON.stringify(c)); } circularReference();
運行結果
完,若有不恰當之處,歡迎指正哦。
原文出處:https://www.cnblogs.com/zhangguicheng/p/12173538.html