當我們想把對象轉為JSON串的時候,肯定會想到JSON.stringify;還一種情況,當我們想實作一個深拷貝的時候,也會想到JSON.parse(JSON.stringify());但其實JSON.stringify序列化是有些問題的。先給大家舉個例子:
現在有個對象,通過JSON.stringify轉一下如下:
轉完之後我們發現,函數和值為undefined的屬性都丢失了,值為NaN的轉為了null ,這就是存在的問題,當我們想實作一個深拷貝的時候,如果對象中沒有函數或者undefined或者上面的問題不影響我們的使用,自然是可以通過這種方式的,又友善,誰不願意用呢。但如果上面的問題有影響到我們正常使用,或者我們就想保留上面的屬性,那就不能使用JSON這種方式,可以手寫一個深拷貝來實作,可以去看下我之前的一篇博文:《js深拷貝》
出現上面的原因是因為:
- 使用JSON.Stringify 轉換的資料中,如果包含 function,undefined,Symbol,這幾種類型,不可枚舉屬性,JSON.Stringify序列化後,這個鍵值對會消失。
- 轉換的資料中包含 NaN,Infinity 值(含-Infinity),JSON序列化後的結果會是null。
- 轉換的資料中包含Date對象,JSON.Stringify序列化之後,會變成字元串。
- 轉換的資料包含RegExp 引用類型序列化之後會變成空對象。
- 無法序列化不可枚舉屬性。
- 無法序列化對象的循環引用,(例如: obj[key] = obj)。
- 無法序列化對象的原型鍊。
JSON.stringify自然好用,但是也要避免上面的問題
針對上面的問題,我想看下能不能解決,然後自己試着寫了一下,當然也找了一些關于JSON的方法、參數等資料,重新寫了兩個方法(算是對stringify和parse方法進行了擴充吧) 如下:
// JSON.stringify對象序列化,解決undefined、函數和NaN 丢失問題
function JSONStringify(option) {
return JSON.stringify(option, (key, val) => {
// 處理函數丢失問題
if (typeof val === 'function') {
return `${val}`;
}
// 處理undefined丢失問題
if (typeof val === 'undefined') {
return 'undefined';
}
// 處理NaN轉為null的情況(注意: 這裡如果使用isNaN的話,那麼對象也會走進去)
if (val !== val) {
return `${val}`
}
return val;
}, 2)
}
// JSON.parse 反序列化
function JSONParse(jsonStr) {
const retain = ['function', 'undefined', 'NaN']
return JSON.parse(jsonStr, (key, val) => {
// eval 可能在eslint中報錯,需要加入下行注釋
// eslint-disable-next-line
if (typeof val === 'string' && retain.some(s => val.indexOf(s) >= 0)) {
return eval(`(function(){return ${val}})()`);
}
return val
})
}
然後使用這兩個方法再試一下
console.log(JSONStringify(o), 'stringify序列化處理----->>>')
console.log(JSONParse(JSONStringify(o)), 'parse解析序列化處理----->>>')
列印如下:
開始我以為萬事大吉了,但是仔細一看,序列化确實沒有問題的,全部保留了,但是解析的時候我的undefined還是沒了,undefined和函數、NaN一樣處理的,因為一個函數沒有傳回值的話,預設傳回的就是undefined,現在我們讓它傳回一個undefined,但是沒出來呢,也可能是JSON.parse确實轉不了undefined,目前還沒找到答案,先這樣吧,至少比之前好多了。
深拷貝的時候如果不考慮undefined,基本上直接可以用上面的兩個方法了,還是蠻友善的,當然手寫一個深拷貝的工具方法也友善,看自己喜好吧。
注意:上面兩個方法互相結合、互相使用
最後再補充一下,上面有用到eval函數,這是原生js一個函數,可以直接使用,可以參考一位博友的文章:淺談JS中的 eval函數