強(qiáng)大的JSON.stringify如何使用
前言
JSON.stringify 作為日常開發(fā)中經(jīng)常使用的方法,你真的能靈活運(yùn)用它嗎?
學(xué)習(xí)本文之前,小包想讓大家?guī)е鴰讉€(gè)問題,一起來深入學(xué)習(xí) stringify 。
stringify函數(shù)有幾個(gè)參數(shù),每個(gè)參數(shù)分別有啥用啊?stringify序列化準(zhǔn)則有哪些啊?- 函數(shù)序列化中會(huì)如何處理?
null、undefined、NaN等特殊的值又會(huì)如何處理?ES6后增加的Symbol類型、BigInt序列化過程中會(huì)有特別處理嗎?
stringify為什么不適合做深拷貝?- 你能想到那些
stringify的妙用?
整個(gè)文章的脈絡(luò)跟下面思維導(dǎo)圖一致,大家可以先留一下印象。

三參數(shù)
在日常編程中,我們經(jīng)常 JSON.stringify 方法將某個(gè)對(duì)象轉(zhuǎn)換成 JSON 字符串形式。
const stu = {
name: 'zcxiaobao',
age: 18
}
// {"name":"zcxiaobao","age":18}
console.log(JSON.stringify(stu));但 stringify 真的就這么簡單嗎?我們先來看一下 MDN 中對(duì) stringify 的定義。
MDN 中指出: JSON.stringify() 方法將一個(gè) JavaScript 對(duì)象或值轉(zhuǎn)換為 JSON 字符串,如果指定了一個(gè) replacer 函數(shù),則可以選擇性地替換值,或者指定的 replacer 是數(shù)組,則可選擇性地僅包含數(shù)組指定的屬性。
看完定義,小包就一驚,stringfy 不止一個(gè)參數(shù)嗎?當(dāng)然了,stringify 有三個(gè)參數(shù)。
咱們來看一下 stringify 語法和參數(shù)介紹:
JSON.stringify(value[, replacer [, space]])
value: 將要序列后成 JSON 字符串的值。replacer(可選)如果該參數(shù)是一個(gè)函數(shù),則在序列化過程中,被序列化的值的每個(gè)屬性都會(huì)經(jīng)過該函數(shù)的轉(zhuǎn)換和處理;
如果該參數(shù)是一個(gè)數(shù)組,則只有包含在這個(gè)數(shù)組中的屬性名才會(huì)被序列化到最終的
JSON字符串中如果該參數(shù)為
null或者未提供,則對(duì)象所有的屬性都會(huì)被序列化。space(可選): 指定縮進(jìn)用的空白字符串,用于美化輸出如果參數(shù)是個(gè)數(shù)字,它代表有多少的空格。上限為10。
該值若小于1,則意味著沒有空格
如果該參數(shù)為字符串(當(dāng)字符串長度超過10個(gè)字母,取其前10個(gè)字母),該字符串將被作為空格
如果該參數(shù)沒有提供(或者為 null),將沒有空格
replacer
我們來嘗試一下 replacer 的使用。
replacer作為函數(shù)
replacer 作為函數(shù),它有兩個(gè)參數(shù),鍵(key) 和 值(value),并且兩個(gè)參數(shù)都會(huì)被序列化。
在開始時(shí),replacer 函數(shù)會(huì)被傳入一個(gè)空字符串作為 key 值,代表著要被 stringify 的這個(gè)對(duì)象。理解這點(diǎn)很重要,replacer 函數(shù)并非是上來就把對(duì)象解析成鍵值對(duì)形式,而是先傳入了待序列化對(duì)象。隨后每個(gè)對(duì)象或數(shù)組上的屬性會(huì)被依次傳入。 如果函數(shù)返回值為undefined或者函數(shù)時(shí),該屬性值會(huì)被過濾掉,其余按照返回規(guī)則。
// repalcer 接受兩個(gè)參數(shù) key value
// key value 分別為對(duì)象的每個(gè)鍵值對(duì)
// 因此我們可以根據(jù)鍵或者值的類型進(jìn)行簡單篩選
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}
// function 可自己測試
function replacerFunc(key, value) {
if (typeof value === "string") {
return () => {};
}
return value;
}
const foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
const jsonString = JSON.stringify(foo, replacer);
JSON 序列化結(jié)果為 {"week":45,"month":7}
但如果序列化的是數(shù)組,若 replacer 函數(shù)返回 undefined 或者函數(shù),當(dāng)前值不會(huì)被忽略,而將會(huì)被 null 取代。
const list = [1, '22', 3] const jsonString = JSON.stringify(list, replacer)
JSON 序列化的結(jié)果為 '[1,null,3]'
replacer作為數(shù)組
作為數(shù)組比較好理解,過濾數(shù)組中出現(xiàn)的鍵值。
const foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
const jsonString = JSON.stringify(foo, ['week', 'month']);
JSON 序列化結(jié)果為 {"week":45,"month":7}, 只保留 week 和 month 屬性值。
九特性
特性一: undefined、函數(shù)、Symbol值
- 出現(xiàn)在非數(shù)組對(duì)象屬性值中:
undefined、任意函數(shù)、Symbol值在序列化過程中將會(huì)被忽略 - 出現(xiàn)在數(shù)組中:
undefined、任意函數(shù)、Symbol值會(huì)被轉(zhuǎn)化為 null - 單獨(dú)轉(zhuǎn)換時(shí): 會(huì)返回 undefined
// 1. 對(duì)象屬性值中存在這三種值會(huì)被忽略
const obj = {
name: 'zc',
age: 18,
// 函數(shù)會(huì)被忽略
sayHello() {
console.log('hello world')
},
// undefined會(huì)被忽略
wife: undefined,
// Symbol值會(huì)被忽略
id: Symbol(111),
// [Symbol('zc')]: 'zc',
}
// 輸出結(jié)果: {"name":"zc","age":18}
console.log(JSON.stringify(obj));
// 2. 數(shù)組中這三種值會(huì)被轉(zhuǎn)化為 null
const list = [
'zc',
18,
// 函數(shù)轉(zhuǎn)化為 null
function sayHello() {
console.log('hello world')
},
// undefined 轉(zhuǎn)換為 null
undefined,
// Symbol 轉(zhuǎn)換為 null
Symbol(111)
]
// ["zc",18,null,null,null]
console.log(JSON.stringify(list))
// 3. 這三種值單獨(dú)轉(zhuǎn)化將會(huì)返回 undefined
console.log(JSON.stringify(undefined)) // undefined
console.log(JSON.stringify(Symbol(111))) // undefined
console.log(JSON.stringify(function sayHello() {
console.log('hello world')
})) // undefined
特性二: toJSON() 方法
轉(zhuǎn)換值如果有 toJSON() 方法,toJSON() 方法返回什么值,序列化結(jié)果就返回什么值,其余值會(huì)被忽略。
const obj = {
name: 'zc',
toJSON(){
return 'return toJSON'
}
}
// return toJSON
console.log(JSON.stringify(obj));
特性三: 布爾值、數(shù)字、字符串的包裝對(duì)象
布爾值、數(shù)字、字符串的包裝對(duì)象在序列化過程中會(huì)自動(dòng)轉(zhuǎn)換成對(duì)應(yīng)的原始值
JSON.stringify([new Number(1), new String("zcxiaobao"), new Boolean(true)]);
// [1,"zcxiaobao",true]
特性四: NaN Infinity null
特性四主要針對(duì) JavaScript 里面的特殊值,例如 Number 類型里的 NaN 和 Infinity 及 null 。此三種數(shù)值序列化過程中都會(huì)被當(dāng)做 null 。
// [null,null,null,null,null]
JSON.stringify([null, NaN, -NaN, Infinity, -Infinity])
// 特性三講過布爾值、數(shù)字、字符串的包裝對(duì)象在序列化過程中會(huì)自動(dòng)轉(zhuǎn)換成對(duì)應(yīng)的原始值
// 隱式類型轉(zhuǎn)換就會(huì)調(diào)用包裝類,因此會(huì)先調(diào)用 Number => NaN
// 之后再轉(zhuǎn)化為 null
// 1/0 => Infinity => null
JSON.stringify([Number('123a'), +'123a', 1/0])
特性五: Date對(duì)象
Date 對(duì)象上部署了 toJSON 方法(同 Date.toISOString())將其轉(zhuǎn)換為字符串,因此 JSON.stringify() 將會(huì)序列化 Date 的值為時(shí)間格式字符串。
// "2022-03-06T08:24:56.138Z" JSON.stringify(new Date())
特性六: Symbol
特性一提到,Symbol 類型當(dāng)作值來使用時(shí),對(duì)象、數(shù)組、單獨(dú)使用分別會(huì)被忽略、轉(zhuǎn)換為 null 、轉(zhuǎn)化為 undefined。
同樣的,所有以 Symbol 為屬性鍵的屬性都會(huì)被完全忽略掉,即便 replacer 參數(shù)中強(qiáng)制指定包含了它們。
const obj = {
name: 'zcxiaobao',
age: 18,
[Symbol('lyl')]: 'unique'
}
function replacer(key, value) {
if (typeof key === 'symbol') {
return value;
}
}
// undefined
JSON.stringify(obj, replacer);
通過上面案例,我們可以看出,雖然我們通過 replacer 強(qiáng)行指定了返回 Symbol 類型值,但最終還是會(huì)被忽略掉。
特性七: BigInt
JSON.stringify 規(guī)定: 嘗試去轉(zhuǎn)換 BigInt 類型的值會(huì)拋出 TypeError
const bigNumber = BigInt(1) // Uncaught TypeError: Do not know how to serialize a BigInt console.log(JSON.stringify(bigNumber))
特性八: 循環(huán)引用
特性八指出: 對(duì)包含循環(huán)引用的對(duì)象(對(duì)象之間相互引用,形成無限循環(huán))執(zhí)行此方法,會(huì)拋出錯(cuò)誤
日常開發(fā)中深拷貝最簡單暴力的方式就是使用 JSON.parse(JSON.stringify(obj)),但此方法下的深拷貝存在巨坑,關(guān)鍵問題就在于 stringify 無法處理循環(huán)引用問題。
const obj = {
name: 'zcxiaobao',
age: 18,
}
const loopObj = {
obj
}
// 形成循環(huán)引用
obj.loopObj = loopObj;
JSON.stringify(obj)
/* 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 <anonymous>:10:6
*/
特性九: 可枚舉屬性
對(duì)于對(duì)象(包括 Map/Set/WeakMap/WeakSet)的序列化,除了上文講到的一些情況,stringify 也明確規(guī)定,僅會(huì)序列化可枚舉的屬性
// 不可枚舉的屬性默認(rèn)會(huì)被忽略
// {"age":18}
JSON.stringify(
Object.create(
null,
{
name: { value: 'zcxiaobao', enumerable: false },
age: { value: 18, enumerable: true }
}
)
);
六妙用
localStorage
localStorage 對(duì)象用于長久保存整個(gè)網(wǎng)站的數(shù)據(jù),保存的數(shù)據(jù)沒有過期時(shí)間,直到手動(dòng)去刪除。通常我們以對(duì)象形式進(jìn)行存儲(chǔ)。
- 單純調(diào)用
localStorage對(duì)象方法
const obj = {
name: 'zcxiaobao',
age: 18
}
// 單純調(diào)用 localStorage.setItem()
localStorage.setItem('zc', obj);
// 最終返回結(jié)果是 [object Object]
// 可見單純調(diào)用localStorage是失敗的
console.log(localStorage.getItem('zc'))
localStorage配合JSON.stringify方法
localStorage.setItem('zc', JSON.stringify(obj));
// 最終返回結(jié)果是 {name: 'zcxiaobao', age: 18}
console.log(JSON.parse(localStorage.getItem('zc')))
屬性過濾
來假設(shè)這樣一個(gè)場景,后端返回了一個(gè)很長的對(duì)象,對(duì)象里面屬性很多,而我們只需要其中幾個(gè)屬性,并且這幾個(gè)屬性我們要存儲(chǔ)到 localStorage 中。
- 方案一: 解構(gòu)賦值+
stringify
// 我們只需要 a,e,f 屬性
const obj = {
a:1, b:2, c:3, d:4, e:5, f:6, g:7
}
// 解構(gòu)賦值
const {a,e,f} = obj;
// 存儲(chǔ)到localStorage
localStorage.setItem('zc', JSON.stringify({a,e,f}))
// {"a":1,"e":5,"f":6}
console.log(localStorage.getItem('zc'))
- 使用
stringify的replacer參數(shù)
// 借助 replacer 作為數(shù)組形式進(jìn)行過濾
localStorage.setItem('zc', JSON.stringify(obj, ['a','e','f']))
// {"a":1,"e":5,"f":6}
console.log(localStorage.getItem('zc'))
當(dāng) replacer 是數(shù)組時(shí),可以簡單的過濾出我們所需的屬性,是一個(gè)不錯(cuò)的小技巧。
三思而后行之深拷貝
使用 JSON.parse(JSON.stringify) 是實(shí)現(xiàn)對(duì)象的深拷貝最簡單暴力的方法之一。但也正如標(biāo)題所言,使用該種方法的深拷貝要深思熟慮。
- 循環(huán)引用問題,
stringify會(huì)報(bào)錯(cuò) - 函數(shù)、
undefined、Symbol會(huì)被忽略 NaN、Infinity和-Infinity會(huì)被序列化成null- ...
因此在使用 JSON.parse(JSON.stringify) 做深拷貝時(shí),一定要深思熟慮。如果沒有上述隱患,JSON.parse(JSON.stringify) 是一個(gè)可行的深拷貝方案。
對(duì)象的 map 函數(shù)
在使用數(shù)組進(jìn)行編程時(shí),我們會(huì)經(jīng)常使用到 map 函數(shù)。有了 replacer 參數(shù)后,我們就可以借助此參數(shù),實(shí)現(xiàn)對(duì)象的 map 函數(shù)。
const ObjectMap = (obj, fn) => {
if (typeof fn !== "function") {
throw new TypeError(`${fn} is not a function !`);
}
// 先調(diào)用 JSON.stringify(obj, replacer) 實(shí)現(xiàn) map 功能
// 然后調(diào)用 JSON.parse 重新轉(zhuǎn)化成對(duì)象
return JSON.parse(JSON.stringify(obj, fn));
};
// 例如下面給 obj 對(duì)象的屬性值乘以2
const obj = {
a: 1,
b: 2,
c: 3
}
console.log(ObjectMap(obj, (key, val) => {
if (typeof value === "number") {
return value * 2;
}
return value;
}))
很多同學(xué)有可能會(huì)很奇怪,為什么里面還需要多加一部判斷,直接 return value * 2 不可嗎?
上文講過,replacer 函數(shù)首先傳入的是待序列化對(duì)象,對(duì)象 * 2 => NaN => toJSON(NaN) => undefined => 被忽略,就沒有后續(xù)的鍵值對(duì)解析了。
刪除對(duì)象屬性
借助 replacer 函數(shù),我們還可以刪除對(duì)象的某些屬性。
const obj = {
name: 'zcxiaobao',
age: 18
}
// {"age":18}
JSON.stringify(obj, (key, val) => {
// 返回值為 undefined時(shí),該屬性會(huì)被忽略
if (key === 'name') {
return undefined;
}
return val;
})
對(duì)象判斷
JSON.stringify 可以將對(duì)象序列化為字符串,因此我們可以借助字符串的方法來實(shí)現(xiàn)簡單的對(duì)象相等判斷。
//判斷數(shù)組是否包含某對(duì)象
const names = [
{name:'zcxiaobao'},
{name:'txtx'},
{name:'mymy'},
];
const zcxiaobao = {name:'zcxiaobao'};
// true
JSON.stringify(names).includes(JSON.stringify(zcxiaobao))
// 判斷對(duì)象是否相等
const d1 = {type: 'div'}
const d2 = {type: 'div'}
// true
JSON.stringify(d1) === JSON.stringify(d2);
數(shù)組對(duì)象去重
借助上面的思想,我們還能實(shí)現(xiàn)簡單的數(shù)組對(duì)象去重。
但由于 JSON.stringify 序列化 {x:1, y:1} 和 {y:1, x:1} 結(jié)果不同,因此在開始之前我們需要處理一下數(shù)組中的對(duì)象。
- 方法一: 將數(shù)組中的每個(gè)對(duì)象的鍵按字典序排列
arr.forEach(item => {
const newItem = {};
Object.keys(item) // 獲取對(duì)象鍵值
.sort() // 鍵值排序
.map(key => { // 生成新對(duì)象
newItem[key] = item[key];
})
// 使用 newItem 進(jìn)行去重操作
})
但方法一有些繁瑣,JSON.stringify 提供了 replacer 數(shù)組格式參數(shù),可以過濾數(shù)組。
- 方法二: 借助
replacer數(shù)組格式
function unique(arr) {
const keySet = new Set();
const uniqueObj = {}
// 提取所有的鍵
arr.forEach(item => {
Object.keys(item).forEach(key => keySet.add(key))
})
const replacer = [...keySet];
arr.forEach(item => {
// 所有的對(duì)象按照規(guī)定鍵值 replacer 過濾
unique[JSON.stringify(item, replacer)] = item;
})
return Object.keys(unique).map(u => JSON.parse(u))
}
// 測試一下
unique([{}, {},
{x:1},
{x:1},
{a:1},
{x:1,a:1},
{x:1,a:1},
{x:1,a:1,b:1}
])
// 返回結(jié)果
[{},{"x":1},{"a":1},{"x":1,"a":1},{"x":1,"a":1,"b":1}]
參考鏈接
到此這篇關(guān)于強(qiáng)大的JSON.stringify如何使用的文章就介紹到這了,更多相關(guān)JSON.stringify 使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JSON.stringify 語法實(shí)例講解
- JSON.parse()和JSON.stringify()使用介紹
- 談?wù)凧SON對(duì)象和字符串之間的相互轉(zhuǎn)換JSON.stringify(obj)和JSON.parse(string)
- 解決JSON.stringify()自動(dòng)將中文轉(zhuǎn)譯成unicode的問題
- 深入淺析JSON.parse()、JSON.stringify()和eval()的作用詳解
- 你可能不知道的JSON.stringify()詳解
- js JSON.stringify()基礎(chǔ)詳解
- JS實(shí)現(xiàn)JSON.stringify的實(shí)例代碼講解
- JSON.stringify()方法講解
相關(guān)文章
JavaScript數(shù)據(jù)結(jié)構(gòu)之二叉樹的計(jì)數(shù)算法示例
這篇文章主要介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)之二叉樹的計(jì)數(shù)算法,結(jié)合具體實(shí)例形式分析了javascript二叉樹記錄更新次數(shù)的原理與操作技巧,需要的朋友可以參考下2017-04-04
ECMAScript中var let const常見問題及區(qū)別詳解
這篇文章主要為大家介紹了ECMAScript中var let const常見問題及區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
webpack5搭建一個(gè)簡易的react腳手架項(xiàng)目實(shí)踐
本文文章主要介紹了webpack5搭建一個(gè)簡易的react腳手架項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
JavaScript原生開發(fā)視頻播放器的實(shí)現(xiàn)代碼
這篇文章我們將一起探索一份自定義的視頻播放器實(shí)現(xiàn)代碼,甚至還可以實(shí)現(xiàn)有彈幕功能,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2023-06-06
PHP配置文件php.ini中打開錯(cuò)誤報(bào)告的設(shè)置方法
這篇文章主要介紹了PHP配置文件php.ini中打開錯(cuò)誤報(bào)告的設(shè)置方法,需要的朋友可以參考下2015-01-01
JS實(shí)現(xiàn)利用兩個(gè)隊(duì)列表示一個(gè)棧的方法
這篇文章主要介紹了JS實(shí)現(xiàn)利用兩個(gè)隊(duì)列表示一個(gè)棧的方法,簡單分析了使用兩個(gè)隊(duì)列表示一個(gè)棧的原理,并結(jié)合具體實(shí)例分析了javascript相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
用js查找法實(shí)現(xiàn)當(dāng)前欄目的高亮顯示的代碼
本文給大家介紹了使用js查找法實(shí)現(xiàn)當(dāng)前欄目的高亮顯示的代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2007-11-11

