JSON.stringify 的那些小祕密

JSON.stringify 是咱們在平常開發中常常會用到的一個函數。例如:用來處理 POST 請求中的 body 參數,將 object 對象存儲到 localStorage 裏面,甚至能夠用來實現一個簡單的深拷貝。javascript

然而,就是這麼一個常見的 JS 官方函數,卻有着一些少有人知道的小祕密。今天,就讓咱們來一探究竟吧。html

Secret 1:美化輸出

這個可能有很多人都知道。JSON.stringify 接收一個參數來美化輸出結果,讓其更加可讀。java

默認狀況下,JSON.stringify 會把序列化結果輸出爲一行。但當提供了第三個參數 space 的時候,JSON.stringify 會爲每個鍵值單起一行,而且鍵名key前面附加上 space 前綴。web

space 能夠是數字或者字符串。json

const obj = { a: 1, b: { c: 2 } }

JSON.stringify(obj)
// "{"a":1,"b":{"c":2}}"

JSON.stringify(obj, null, 2)
// "{
// "a": 1,
// "b": {
// "c": 2
// }
// }"

JSON.stringify(obj, null, '**')
// "{
// **"a": 1,
// **"b": {
// ****"c": 2
// **}
// }"
複製代碼

Secret 2: 神奇的 replacer

相信必定有同窗很好奇上面第二個參數 null 是幹嗎的。莫要着急,且聽我細細講來。app

JSON.stringify 的第二個參數咱們稱之爲 replacer。能夠是列表或者函數。ide

若是 replacer 是列表

replacer是列表的時候,它的做用就像是白名單同樣,最終結果中只會包含 key 在列表中的 key: value函數

const user = {
    name: 'Hopsken',
    website: 'https://hopsken.com',
    password: 'SUPER_SECRET',
}

JSON.stringify(user, ['name', 'website'], 2)
// "{
// "name": "Hopsken",
// "website": "https://hopsken.com"
// }"
複製代碼

值得一提的是,最終結果中的鍵名排列會遵守列表中的前後順序。ui

JSON.stringify(user, ['website', 'name'], 2)
// "{
// "website": "https://hopsken.com",
// "name": "Hopsken"
// }"
    
// 你甚至能夠這樣
const config = { ... }
JSON.stringify(config, Object.keys(config).sort(), 2)
複製代碼

若是 replacer 是函數

replacer是函數的時候,JS 會在序列化是對每一個鍵值對調用這個函數,而且使用函數的返回值做爲 key 對應的 valuethis

const obj = { a: 1, b: { c: 2 } }

JSON.stringify(obj, (key, value) => {
    if (typeof value === 'number') {
        return value + 1;
    }
    return value;
}, 2)
// "{
// "a": 2,
// "b": {
// "c": 3
// }
// }"
複製代碼

若是返回值爲 undefined ,那麼最終結果中將忽略該值。(這是預期的行爲,由於 JSON 標準格式中並無 undefined 這個值)。

const user = {
    name: 'Hopsken',
    password: 'SUPER_SECRET',
}

JSON.stringify(user, (key, value) => {
    if (key.match(/password/i)) {
        return undefined;
    }
    return value;
}, 2)
// "{
// "name": "Hopsken"
// }"
複製代碼

Secret 3: 自行控制須要輸出啥

JSON.stringify 的第三個祕密!當 JSON.stringify() 在嘗試對一個對象進行序列化時,會先遍歷這個對象,檢查對象是否存在 toJSON() 這個屬性。若是有的話,JSON.stringify() 將會對這個函數返回的值進行序列化,而非原來的對象。

簡單來講,toJSON() 方法定義了什麼值將被序列化。

經過 toJSON() 這個方法,咱們能夠本身去控制 JSON.stringify() 的行爲。

舉個例子:

const movie = {
    title: '讓子彈飛',
    year: 2010,
    stars: new Set(['周潤發', '姜文', '葛優']),
    toJSON() {
        return {
            name: `${this.title} ${this.year}`,
            actors: [... this.stars]
        }
    }
}

JSON.stringify(movie, null, 2)
// "{
// "name": "讓子彈飛 2010",
// "actors": [
// "周潤發",
// "姜文",
// "葛優"
// ]
// }"
複製代碼

上面的例子,咱們就用了 toJSON 屬性來讓 JSON.stringify() 來支持序列化 Set 類型數據。

值得一提的是,toJSON 中的 this 指向的是當前層級的對象,做用域也只在當前層級對象上。

const user = {
    name: 'Hospken',
    wechat: {
        name: 'FEMinutes',
        toJSON() {
            return `WX: ${this.name}`;
        },
    }
}
JSON.stringify(user, null, 2)
// "{
// "name": "Hospken",
// "wechat": "WX: FEMinutes"
// }"
複製代碼

能夠看到,wechat.toJSON() 只做用在了 wechat 這個屬性上。

附加題: 對列表使用 JSON.stringify ?

那咱們知道,JS 裏面列表也是 object。那按理說 JSON.stringify 應該也能夠用在列表上。

首先,咱們先來試一下默認參數下的結果。

const arr = ["apple", "orange", "banana"]

JSON.stringify(arr)
// "["apple","orange","banana"]"
複製代碼

一切正常。

再來試一下 space 參數:

const arr = ["apple", "orange", "banana"]

JSON.stringify(arr, null, 2)
// "[
// "apple",
// "orange",
// "banana"
// ]"
複製代碼

Perfect!

replacer 呢?

const arr = ["apple", "orange", "banana"]

JSON.stringify(arr, (key, value) => {
	return `one ${value}`
}, 2)
// ""one apple,orange,banana""
複製代碼

涼涼,彷佛不行啊。。。看上去 replacer 函數只執行了一次,而不是預期中的三次。可是,原本 JSON.stringify() 也不是這麼用的,出現預期之外的結果也是正常的。

可是不要緊,我們還有 toJSON() 啊!讓咱們試一下:

const arr = ["apple", "orange", "banana"]
arr.toJSON = function() {
	return this.slice().map(val => `one ${val}`)
}
JSON.stringify(arr, null, 2)
// "[
// "apple",
// "orange",
// "banana"
// ]"
複製代碼

這樣我們就經過 toJSON() 實現了 replacer 的做用。豈不美哉。

尾聲

那麼今天的探祕就到這裏啦。篇幅限制,關於 JSON.stringify() 對邊界狀況的處理(如循環引用)我們就不詳細展開講啦。

感謝各位的觀看~我們下期再見~

參考資料

JSON.stringify() - MDN

ECMAScript 2015 (6th Edition, ECMA-262)

The 80/20 Guide to JSON.stringify in JavaScript

The secret power of JSON stringify

相關文章
相關標籤/搜索