其實有不少有用的東西,當時學習了,也記住了,可是時間久了就是記不住,因此致使在平常開發中老是想不起來原來這個東西能夠這麼用,而去選擇了更加複雜和麻煩的方式。其實咱們平常學習的知識就是拿來用的,即便你今天把知識點背下來了,沒有去思考這個知識點咱們能夠用來幹嗎,不須要幾天就會慢慢地忘掉。因此今天咱們來了解一下在平常學習時你遺漏掉或者忘掉或者沒有思考過的你不知道的 JSON.stringify()
的威力。javascript
原文戳我java
JSON.stringify()
首先咱們在開發的過程中遇到這樣一個處理數據的需求git
const todayILearn = { _id: 1, content: '今天學習 JSON.stringify(),我很開心!', created_at: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中國標準時間)', updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中國標準時間)' } 複製代碼
咱們須要將上面這個對象處理成下面這個對象github
const todayILearn = { id: 1, content: '今天學習 JSON.stringify(),我很開心!', createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中國標準時間)', updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中國標準時間)' } 複製代碼
也就是在不改變屬性的值的前提下,將對象屬性修改一下。 把_id
改爲 id
,把 updated_at
改爲 updatedAt
,把 created_at
改爲 createdAt
。咱們如今經過這個小小的需求來見識一下 JSON.stringify()
的強大吧。面試
首先要解決這個問題咱們有不少種解決方式,咱們先提供兩種不優雅的解決方案:json
// 多一個變量存儲 const todayILearnTemp = {}; for (const [key, value] of Object.entries(todayILearn)) { if (key === "_id") todayILearnTemp["id"] = value; else if (key === "created_at") todayILearnTemp["createdAt"] = value; else if (key === "updatedAt") todayILearnTemp["updatedAt"] = value; else todayILearnTemp[key] = value; } console.log(todayILearnTemp); // 結果: // { id: 1, // content: '今天學習 JSON.stringify(),我很開心!', // createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中國標準時間)', // updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中國標準時間)' // } 複製代碼
方案一徹底沒有問題,能夠實現。可是多聲明瞭一個變量又加上一層循環而且還有不少的 if
else
語句,怎麼都顯得不太優雅。數組
delete
屬性和增長屬性// 極致的暴力美學 todayILearn.id = todayILearn._id; todayILearn.createdAt = todayILearn.created_at; todayILearn.updatedAt = todayILearn.updated_at; delete todayILearn._id; delete todayILearn.created_at; delete todayILearn.updated_at; console.log(todayILearn); // 太暴力😢 //{ // content: '今天學習 JSON.stringify(),我很開心!', // id: 1, // createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中國標準時間)', // updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中國標準時間)' //} 複製代碼
直接 delete 暴力解決太粗魯了,並且有一個缺點,屬性的順序變了。markdown
replace
美學典範const mapObj = { _id: "id", created_at: "createdAt", updated_at: "updatedAt" }; JSON.parse( JSON.stringify(todayILearn).replace( /_id|created_at|updated_at/gi, matched => mapObj[matched]) ) // { // id: 1, // content: '今天學習 JSON.stringify(),我很開心!', // createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中國標準時間)', // updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中國標準時間)' // } 複製代碼
瞬間感受很是優雅和舒服,有木有!函數
接下來,正片開始,咱們今天將系統的學習或者說是複習一遍 JSON.stringify
的基礎知識,讓咱們在平常開發中更加的遊刃有餘。oop
JSON.stringify()
九大特性JSON.stringify()
第一大特性對於 undefined
、任意的函數以及 symbol
三個特殊的值分別做爲對象屬性的值、數組元素、單獨的值時 JSON.stringify()
將返回不一樣的結果。
首先,咱們來複習一下知識點,看一道很是簡單的面試題目:請問下面代碼會輸出什麼?
const data = { a: "aaa", b: undefined, c: Symbol("dd"), fn: function() { return true; } }; JSON.stringify(data); // 輸出:? // "{"a":"aaa"}" 複製代碼
很簡單這道題目面試官主要考察的知識點是:
undefined
、任意的函數以及 symbol
做爲對象屬性值時 JSON.stringify()
將跳過(忽略)對它們進行序列化面試官追問:假設 undefined
、任意的函數以及 symbol
值做爲數組元素會是怎樣呢?
JSON.stringify(["aaa", undefined, function aa() { return true }, Symbol('dd')]) // 輸出:? // "["aaa",null,null,null]" 複製代碼
知識點是:
undefined
、任意的函數以及 symbol
做爲數組元素值時,JSON.stringify()
會將它們序列化爲 null
咱們稍微再動下腦筋,若是單獨序列化這些值會是什麼樣的結果呢?
JSON.stringify(function a (){console.log('a')}) // undefined JSON.stringify(undefined) // undefined JSON.stringify(Symbol('dd')) // undefined 複製代碼
單獨轉換的結果就是:
undefined
、任意的函數以及 symbol
被 JSON.stringify()
做爲單獨的值進行序列化時都會返回 undefined
JSON.stringify()
第一大特性總結undefined
、任意的函數以及 symbol
做爲對象屬性值時 JSON.stringify()
對跳過(忽略)它們進行序列化
undefined
、任意的函數以及 symbol
做爲數組元素值時,JSON.stringify()
將會將它們序列化爲 null
undefined
、任意的函數以及 symbol
被 JSON.stringify()
做爲單獨的值進行序列化時,都會返回 undefined
JSON.stringify()
第二大特性也是在使用過程當中必需要很是注意的一個點:
const data = { a: "aaa", b: undefined, c: Symbol("dd"), fn: function() { return true; }, d: "ddd" }; JSON.stringify(data); // 輸出:? // "{"a":"aaa","d":"ddd"}" JSON.stringify(["aaa", undefined, function aa() { return true }, Symbol('dd'),"eee"]) // 輸出:? // "["aaa",null,null,null,"eee"]" 複製代碼
正如咱們在第一特性所說,JSON.stringify()
序列化時會忽略一些特殊的值,因此不能保證序列化後的字符串仍是以特定的順序出現(數組除外)。
JSON.stringify()
第三大特性toJSON()
函數,該函數返回什麼值,序列化結果就是什麼值,而且忽略其餘屬性的值。JSON.stringify({ say: "hello JSON.stringify", toJSON: function() { return "today i learn"; } }) // "today i learn" 複製代碼
JSON.stringify()
第四大特性JSON.stringify()
將會正常序列化 Date
的值。JSON.stringify({ now: new Date() }); // "{"now":"2019-12-08T07:42:11.973Z"}" 複製代碼
實際上 Date
對象本身部署了 toJSON()
方法(同Date.toISOString()),所以 Date
對象會被當作字符串處理。
JSON.stringify()
第五大特性NaN
和 Infinity
格式的數值及 null
都會被當作 null
。直接上代碼:
JSON.stringify(NaN) // "null" JSON.stringify(null) // "null" JSON.stringify(Infinity) // "null" 複製代碼
JSON.stringify()
第六大特性關於基本類型的序列化:
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // "[1,"false",false]" 複製代碼
JSON.stringify()
第七大特性關於對象屬性的是否可枚舉:
// 不可枚舉的屬性默認會被忽略: JSON.stringify( Object.create( null, { x: { value: 'json', enumerable: false }, y: { value: 'stringify', enumerable: true } } ) ); // "{"y":"stringify"}" 複製代碼
JSON.stringify()
第八大特性咱們都知道實現深拷貝最簡單粗暴的方式就是序列化:JSON.parse(JSON.stringify())
,這個方式實現深拷貝會由於序列化的諸多特性從而致使諸多的坑點:好比如今咱們要說的循環引用問題。
// 對包含循環引用的對象(對象之間相互引用,造成無限循環)執行此方法,會拋出錯誤。 const obj = { name: "loopObj" }; const loopObj = { obj }; // 對象之間造成循環引用,造成閉環 obj.loopObj = loopObj; // 封裝一個深拷貝的函數 function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } // 執行深拷貝,拋出錯誤 deepClone(obj) /** VM44:9 Uncaught TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'loopObj' -> object with constructor 'Object' --- property 'obj' closes the circle at JSON.stringify (<anonymous>) at deepClone (<anonymous>:9:26) at <anonymous>:11:13 */ 複製代碼
這也就是爲何用序列化去實現深拷貝時,遇到循環引用的對象會拋出錯誤的緣由。
JSON.stringify()
第九大特性最後,關於 symbol
屬性還有一點要說的就是:
symbol
爲屬性鍵的屬性都會被徹底忽略掉,即使 replacer
參數中強制指定包含了它們。JSON.stringify({ [Symbol.for("json")]: "stringify" }, function(k, v) { if (typeof k === "symbol") { return v; } }) // undefined 複製代碼
replacer
是 JSON.stringify()
的第二個參數,咱們比較少用到,因此不少時候咱們會忘記 JSON.stringify()
第二個、第三個參數,場景很少,可是用的好的話會很是方便,關於 JSON.stringify()
第九大特性的例子中對 replacer
參數不明白的同窗先別急,其實很簡單,咱們立刻就會在下面的學習中弄懂。
replacer
replacer
參數有兩種形式,能夠是一個函數或者一個數組。做爲函數時,它有兩個參數,鍵(key)和值(value),函數相似就是數組方法 map
、filter
等方法的回調函數,對每個屬性值都會執行一次該函數。若是 replacer
是一個數組,數組的值表明將被序列化成 JSON 字符串的屬性名。
replacer
做爲函數時第二個參數replacer
很是強大, replacer
做爲函數時,咱們能夠打破九大特性的大多數特性,咱們直接來看代碼吧。
const data = { a: "aaa", b: undefined, c: Symbol("dd"), fn: function() { return true; } }; // 不用 replacer 參數時 JSON.stringify(data); // "{"a":"aaa"}" // 使用 replacer 參數做爲函數時 JSON.stringify(data, (key, value) => { switch (true) { case typeof value === "undefined": return "undefined"; case typeof value === "symbol": return value.toString(); case typeof value === "function": return value.toString(); default: break; } return value; }) // "{"a":"aaa","b":"undefined","c":"Symbol(dd)","fn":"function() {\n return true;\n }"}" 複製代碼
雖然使用 toString() 方法有點耍流氓的意思可是不得不說第二個參數很強大。
replacer
函數的第一個參數須要注意的是,replacer 被傳入的函數時,第一個參數不是對象的第一個鍵值對,而是空字符串做爲 key 值,value 值是整個對象的鍵值對:
const data = { a: 2, b: 3, c: 4, d: 5 }; JSON.stringify(data, (key, value) => { console.log(value); return value; }) // 第一個被傳入 replacer 函數的是 {"":{a: 2, b: 3, c: 4, d: 5}} // {a: 2, b: 3, c: 4, d: 5} // 2 // 3 // 4 // 5 複製代碼
map
函數咱們還能夠用它來手寫實現一個對象的相似 map 的函數。
// 實現一個 map 函數 const data = { a: 2, b: 3, c: 4, d: 5 }; const objMap = (obj, fn) => { if (typeof fn !== "function") { throw new TypeError(`${fn} is not a function !`); } return JSON.parse(JSON.stringify(obj, fn)); }; objMap(data, (key, value) => { if (value % 2 === 0) { return value / 2; } return value; }); // {a: 1, b: 3, c: 2, d: 5} 複製代碼
replacer
做爲數組時replacer
做爲數組時,結果很是簡單,數組的值就表明了將被序列化成 JSON 字符串的屬性名。
const jsonObj = { name: "JSON.stringify", params: "obj,replacer,space" }; // 只保留 params 屬性的值 JSON.stringify(jsonObj, ["params"]); // "{"params":"obj,replacer,space"}" 複製代碼
space
space
參數用來控制結果字符串裏面的間距。首先看一個例子就是到這東西究竟是幹啥用的:
const tiedan = { name: "彈鐵蛋同窗", describe: "今天在學 JSON.stringify()", emotion: "like shit" }; JSON.stringify(tiedan, null, "🐷"); // 接下來是輸出結果 // "{ // 🐷"name": "彈鐵蛋同窗", // 🐷"describe": "今天在學 JSON.stringify()", // 🐷"emotion": "like shit" // }" JSON.stringify(tiedan, null, 2); // "{ // "name": "彈鐵蛋同窗", // "describe": "今天在學 JSON.stringify()", // "emotion": "like shit" // }" 複製代碼
上面代碼一眼就能看出第三個參數的做用了,花裏胡哨的,其實這個參數仍是比較雞肋的,除了好看沒啥特別的用處。咱們用 \t
、 \n
等縮進能讓輸出更加格式化,更適於觀看。
若是是一個數字, 則在字符串化時每一級別會比上一級別縮進多這個數字值的空格(最多10個空格);
若是是一個字符串,則每一級別會比上一級別多縮進該字符串(或該字符串的前10個字符)。
JSON.stringify()
九大特性:1、對於 undefined
、任意的函數以及 symbol
三個特殊的值分別做爲對象屬性的值、數組元素、單獨的值時的不一樣返回結果。
undefined
、任意的函數以及 symbol
做爲對象屬性值時 JSON.stringify()
跳過(忽略)對它們進行序列化
undefined
、任意的函數以及 symbol
做爲數組元素值時,JSON.stringify()
將會將它們序列化爲 null
undefined
、任意的函數以及 symbol
被 JSON.stringify()
做爲單獨的值進行序列化時都會返回 undefined
2、非數組對象的屬性不能保證以特定的順序出如今序列化後的字符串中。
3、轉換值若是有 toJSON()
函數,該函數返回什麼值,序列化結果就是什麼值,而且忽略其餘屬性的值。
4、JSON.stringify()
將會正常序列化 Date
的值。
5、NaN
和 Infinity
格式的數值及 null
都會被當作 null
。
6、布爾值、數字、字符串的包裝對象在序列化過程當中會自動轉換成對應的原始值。
7、其餘類型的對象,包括 Map/Set/WeakMap/WeakSet,僅會序列化可枚舉的屬性。
8、對包含循環引用的對象(對象之間相互引用,造成無限循環)執行此方法,會拋出錯誤。
9、全部以 symbol
爲屬性鍵的屬性都會被徹底忽略掉,即使 replacer
參數中強制指定包含了它們。
JSON.stringify()
第二個參數和第三個參數map
、filter
等方法的回調函數,對每個屬性值都會執行一次該函數(期間咱們還簡單實現過一個 map
函數)。replacer
是一個數組,數組的值表明將被序列化成 JSON 字符串的屬性名。若是是一個數字, 則在字符串化時每一級別會比上一級別縮進多這個數字值的空格(最多10個空格);
若是是一個字符串,則每一級別會比上一級別多縮進該字符串(或該字符串的前10個字符)。
第一個例子的方案三,有小夥伴提示說這個方案會有風險,確實是這樣的(可能會把對象的值給替換掉)。你們慎用吧,大部分狀況下這樣使用是 ok 的。小夥伴們提供的第四種方案仍是很不錯的:
const todayILearn = { _id: 1, content: '今天學習 JSON.stringify(),我很開心!', created_at: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中國標準時間)', updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中國標準時間)' } const mapObj = { _id: "id", created_at: "createdAt", updated_at: "updatedAt" }; Object.fromEntries(Object.entries(todayILearn).map(([k, v]) => [mapObj[k]||k, v])) // { // id: 1, // content: '今天學習 JSON.stringify(),我很開心!', // createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中國標準時間)', // updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中國標準時間)' // } 複製代碼