前言
其實有很多有用的東西刨沦,當時學習了倍权,也記住了,但是時間久了就是記不住囱修,所以導致在日常開發(fā)中總是想不起來原來這個東西可以這么用赎瑰,而去選擇了更加復雜和麻煩的方式。其實我們?nèi)粘W習的知識就是拿來用的破镰,即使你今天把知識點背下來了餐曼,沒有去思考這個知識點我們可以用來干嘛,不需要幾天就會慢慢地忘掉鲜漩。所以今天我們來了解一下在日常學習時你遺漏掉或者忘掉或者沒有思考過的你不知道的 JSON.stringify()
的威力源譬。
通過需求學習JSON.stringify()
首先我們在開發(fā)的過程當中遇到這樣一個處理數(shù)據(jù)的需求
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 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
厚脉。我們現(xiàn)在通過這個小小的需求來見識一下 JSON.stringify()
的強大吧。
首先要解決這個問題我們有很多的解決方式胶惰,我們先提供兩種不優(yōu)雅的解決方案:
- 方案一:一次遍歷+多聲明一個變量
// 多一個變量存儲
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);
// 結(jié)果:
// { 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 (中國標準時間)'
// }
方案一完全沒有問題可以實現(xiàn)孵滞。但是多聲明了一個變量又加上一層循環(huán)并且還有很多的 if
else
語句中捆,怎么都顯得不太優(yōu)雅。
- 方案二:暴力
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 暴力解決太粗魯了,而且有一個缺點匿级,屬性的順序變了蟋滴。
- 方案三:序列化+
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 (中國標準時間)'
// }
瞬間感覺非常優(yōu)雅和舒服津函,有木有!如果你這樣寫孤页,你導師給你 review 代碼的時候尔苦,你導師肯定會夸贊你的??。
接下來行施,正片開始允坚,我們今天將系統(tǒng)的學習或者說是復習一遍 JSON.stringify
的基礎(chǔ)知識,讓我們在日常開發(fā)中更加的游刃有余悲龟。
溫故知新之非常簡單的 JSON.stringify()
九大特性
JSON.stringify()
第一大特性
對于 undefined
屋讶、任意的函數(shù)以及 symbol
三個特殊的值分別作為對象屬性的值、數(shù)組元素须教、單獨的值時 JSON.stringify()
將返回不同的結(jié)果皿渗。
首先,我們來復習一下知識點轻腺,看一道非常簡單的面試題目:請問下面代碼會輸出什么乐疆?
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
JSON.stringify(data); // 輸出:?
// "{"a":"aaa"}"
很簡單這道題目面試官主要考察的知識點是:
-
undefined
贬养、任意的函數(shù)以及symbol
作為對象屬性值時JSON.stringify()
跳過(忽略)對它們進行序列化
面試官追問:假設(shè) undefined
挤土、任意的函數(shù)以及 symbol
值作為數(shù)組元素會是怎樣呢?
JSON.stringify(["aaa", undefined, function aa() {
return true
}, Symbol('dd')]) // 輸出:误算?
// "["aaa",null,null,null]"
知識點是:
-
undefined
仰美、任意的函數(shù)以及symbol
作為數(shù)組元素值時迷殿,JSON.stringify()
將會將它們序列化為null
我們再發(fā)揮動下腦筋,如果單獨序列化這些值會是什么樣的結(jié)果呢咖杂?
JSON.stringify(function a (){console.log('a')})
// undefined
JSON.stringify(undefined)
// undefined
JSON.stringify(Symbol('dd'))
// undefined
單獨轉(zhuǎn)換的結(jié)果就是:
-
undefined
庆寺、任意的函數(shù)以及symbol
被JSON.stringify()
作為單獨的值進行序列化時都會返回undefined
JSON.stringify()
第一大特性總結(jié)
undefined
、任意的函數(shù)以及symbol
作為對象屬性值時JSON.stringify()
對跳過(忽略)它們進行序列化undefined
诉字、任意的函數(shù)以及symbol
作為數(shù)組元素值時懦尝,JSON.stringify()
將會將它們序列化為null
undefined
、任意的函數(shù)以及symbol
被JSON.stringify()
作為單獨的值進行序列化時壤圃,都會返回undefined
JSON.stringify()
第二大特性
也是在使用過程中必須要非常注意的一個點:
- 非數(shù)組對象的屬性不能保證以特定的順序出現(xiàn)在序列化后的字符串中陵霉。
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()
序列化時會忽略一些特殊的值,所以不能保證序列化后的字符串還是以特定的順序出現(xiàn)(數(shù)組除外)墨叛。
JSON.stringify()` 第三大特性
- 轉(zhuǎn)換值如果有
toJSON()
函數(shù)止毕,該函數(shù)返回什么值,序列化結(jié)果就是什么值漠趁,并且忽略其他屬性的值扁凛。
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
格式的數(shù)值及null
都會被當做null
。
直接上代碼:
JSON.stringify(NaN)
// "null"
JSON.stringify(null)
// "null"
JSON.stringify(Infinity)
// "null"
JSON.stringify()
第六大特性
關(guān)于基本類型的序列化:
- 布爾值甥绿、數(shù)字字币、字符串的包裝對象在序列化過程中會自動轉(zhuǎn)換成對應的原始值。
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// "[1,"false",false]"
JSON.stringify()
第七大特性
關(guān)于對象屬性的是否可枚舉:
- 其他類型的對象共缕,包括 Map/Set/WeakMap/WeakSet洗出,僅會序列化可枚舉的屬性。
// 不可枚舉的屬性默認會被忽略:
JSON.stringify(
Object.create(
null,
{
x: { value: 'json', enumerable: false },
y: { value: 'stringify', enumerable: true }
}
)
);
// "{"y","stringify"}"
JSON.stringify()
第八大特性
我們都知道實現(xiàn)深拷貝最簡單粗暴的方式就是序列化:JSON.parse(JSON.stringify())
图谷,這個方式實現(xiàn)深拷貝會因為序列化的諸多特性導致諸多的坑點:比如現(xiàn)在我們要說的循環(huán)引用問題翩活。
// 對包含循環(huán)引用的對象(對象之間相互引用,形成無限循環(huán))執(zhí)行此方法便贵,會拋出錯誤菠镇。
const obj = {
name: "loopObj"
};
const loopObj = {
obj
};
// 對象之間形成循環(huán)引用,形成閉環(huán)
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
*/
- 對包含循環(huán)引用的對象(對象之間相互引用承璃,形成無限循環(huán))執(zhí)行此方法利耍,會拋出錯誤。
這也就是為什么用序列化去實現(xiàn)深拷貝時,遇到循環(huán)引用的對象會拋出錯誤的原因隘梨。
JSON.stringify()
第九大特性
最后程癌,關(guān)于 symbol
屬性還有一點要說的就是:
- 所有以
symbol
為屬性鍵的屬性都會被完全忽略掉,即便replacer
參數(shù)中強制指定包含了它們出嘹。
JSON.stringify({ [Symbol.for("json")]: "stringify" }, function(k, v) {
if (typeof k === "symbol") {
return v;
}
})
// undefined
關(guān)于 replacer
是什么呢席楚,它是 JSON.stringify()
的第二個參數(shù),我們比較少的會用到税稼,所以很多時候我們會忘記 JSON.stringify()
第二個、第三個參數(shù)垮斯,場景不多郎仆,但是用的好的話會非常的方便,關(guān)于 JSON.stringify()
第九大特性的例子中對 replacer
參數(shù)不明白的同學先別急兜蠕,其實很簡單扰肌,我們馬上就會在下面的學習中弄懂。
枕典席文之 JSON.stringify()
第二個參數(shù)和第三個參數(shù)
強大的第二個參數(shù) replacer
replacer
參數(shù)有兩種形式熊杨,可以是一個函數(shù)或者一個數(shù)組曙旭。作為函數(shù)時,它有兩個參數(shù)晶府,鍵(key)和值(value)桂躏,函數(shù)類似就是數(shù)組方法 map
、filter
等方法的回調(diào)函數(shù)川陆,對每一個屬性值都會執(zhí)行一次該函數(shù)剂习。如果 replacer
是一個數(shù)組,數(shù)組的值代表將被序列化成 JSON 字符串的屬性名较沪。
replacer
作為函數(shù)時
可以打破九大特性的大多數(shù)特性
第二個參數(shù)replacer
非常強大鳞绕, replacer
作為函數(shù)時,我們可以打破九大特性的大多數(shù)特性尸曼,我們直接來看代碼吧们何。
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
// 不用 replacer 參數(shù)時
JSON.stringify(data);
// "{"a":"aaa"}"
// 使用 replacer 參數(shù)作為函數(shù)時
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() 方法有點耍流氓的意思但是不得不說第二個參數(shù)很強大。
傳入 replacer
函數(shù)的第一個參數(shù)
需要注意的是控轿,replacer 被傳入的函數(shù)時冤竹,第一個參數(shù)不是對象的第一個鍵值對镀迂,而是空字符串作為 key 值废岂,value 值是整個對象的鍵值對:
const data = {
a: 2,
b: 3,
c: 4,
d: 5
};
JSON.stringify(data, (key, value) => {
console.log(value);
return value;
})
// 第一個被傳入 replacer 函數(shù)的是 {"":{a: 2, b: 3, c: 4, d: 5}}
// {a: 2, b: 3, c: 4, d: 5}
// 2
// 3
// 4
// 5
實現(xiàn) map
函數(shù)
我們還可以用它來手寫實現(xiàn)一個對象的類似 map 的函數(shù)。
// 實現(xiàn)一個 map 函數(shù)
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
作為數(shù)組時
replacer
作為數(shù)組時红且,結(jié)果非常簡單躲株,數(shù)組的值就代表了將被序列化成 JSON 字符串的屬性名片部。
const jsonObj = {
name: "JSON.stringify",
params: "obj,replacer,space"
};
// 只保留 params 屬性的值
JSON.stringify(jsonObj, ["params"]);
// "{"params":"obj,replacer,space"}"
有意思卻沒啥用的第三個參數(shù) space
space
參數(shù)用來控制結(jié)果字符串里面的間距。首先看一個例子就是到這東西到底是干啥用的:
const tiedan = {
name: "彈鐵蛋同學",
describe: "今天在學 JSON.stringify()",
emotion: "like shit"
};
JSON.stringify(tiedan, null, "??");
// 接下來是輸出結(jié)果
// "{
// ??"name": "彈鐵蛋同學",
// ??"describe": "今天在學 JSON.stringify()",
// ??"emotion": "like shit"
// }"
JSON.stringify(tiedan, null, 2);
// "{
// "name": "彈鐵蛋同學",
// "describe": "今天在學 JSON.stringify()",
// "emotion": "like shit"
// }"
上面代碼一眼就能看出第三個參數(shù)的作用了,花里胡哨的档悠,其實這個參數(shù)還是比較雞肋的廊鸥,除了好看沒啥特別的用處。我們用 \t
辖所、 \n
等縮進能讓輸出更加格式化惰说,更適于觀看。
如果是一個數(shù)字, 則在字符串化時每一級別會比上一級別縮進多這個數(shù)字值的空格(最多10個空格)缘回;
如果是一個字符串吆视,則每一級別會比上一級別多縮進該字符串(或該字符串的前10個字符)。
總結(jié)
JSON.stringify()
九大特性:
一酥宴、對于 undefined
啦吧、任意的函數(shù)以及 symbol
三個特殊的值分別作為對象屬性的值、數(shù)組元素拙寡、單獨的值時的不同返回結(jié)果授滓。
undefined
、任意的函數(shù)以及symbol
作為對象屬性值時JSON.stringify()
對跳過(忽略)它們進行序列化undefined
肆糕、任意的函數(shù)以及symbol
作為數(shù)組元素值時般堆,JSON.stringify()
將會將它們序列化為null
undefined
、任意的函數(shù)以及symbol
被JSON.stringify()
作為單獨的值進行序列化時都會返回undefined
二诚啃、非數(shù)組對象的屬性不能保證以特定的順序出現(xiàn)在序列化后的字符串中淮摔。
三、轉(zhuǎn)換值如果有 toJSON()
函數(shù)绍申,該函數(shù)返回什么值噩咪,序列化結(jié)果就是什么值,并且忽略其他屬性的值极阅。
四胃碾、JSON.stringify()
將會正常序列化 Date
的值。
五筋搏、NaN
和 Infinity
格式的數(shù)值及 null
都會被當做 null
仆百。
六、布爾值奔脐、數(shù)字俄周、字符串的包裝對象在序列化過程中會自動轉(zhuǎn)換成對應的原始值。
七髓迎、其他類型的對象峦朗,包括 Map/Set/WeakMap/WeakSet,僅會序列化可枚舉的屬性排龄。
八波势、對包含循環(huán)引用的對象(對象之間相互引用,形成無限循環(huán))執(zhí)行此方法,會拋出錯誤尺铣。
九拴曲、所有以 symbol
為屬性鍵的屬性都會被完全忽略掉,即便 replacer
參數(shù)中強制指定包含了它們凛忿。
JSON.stringify()
第二個參數(shù)和第三個參數(shù)
強大的第二個參數(shù):
- 作為函數(shù)時澈灼,它有兩個參數(shù),鍵(key)和值(value)店溢,函數(shù)類似就是數(shù)組方法
map
叁熔、filter
等方法的回調(diào)函數(shù),對每一個屬性值都會執(zhí)行一次該函數(shù)(期間我們還簡單實現(xiàn)過一個map
函數(shù))床牧。 - 如果
replacer
是一個數(shù)組者疤,數(shù)組的值代表將被序列化成 JSON 字符串的屬性名。
華麗的第三個參數(shù):
如果是一個數(shù)字, 則在字符串化時每一級別會比上一級別縮進多這個數(shù)字值的空格(最多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 (中國標準時間)'
// }