你不知道的 JSON.stringify?。?!
JSON.stringify是我們經(jīng)常用到的的一個(gè)方法,它主要作用是將 JavaScript 值和對(duì)象轉(zhuǎn)換為字符串。如:
- JSON.stringify({ foo: "bar" });
- // => '{"foo":"bar"}'
- JSON.stringify(123);
- // => '123'
但是JS 的許多地方都有問(wèn)題,這個(gè)函數(shù)也不例外。我們可能會(huì)想象一個(gè)叫做 "stringify "的函數(shù)總是返回一個(gè)字符串......但它并沒(méi)有!
例如,如果你嘗試 stringify undefined,它返回 undefined ,而不是一個(gè)字符串。
- JSON.stringify(undefined);
- // => undefined
接下來(lái),我將分兩部分講:
- 列舉 JSON.stringify 不返回字符串的情況
- 我們將如何避免這些陷阱
什么時(shí)候 JSON.stringify 不返回字符串?
undefined、任意的函數(shù)以及 symbol 值,在序列化過(guò)程中會(huì)被忽略(出現(xiàn)在非數(shù)組對(duì)象的屬性值中時(shí))或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時(shí))。函數(shù)、undefined 被單獨(dú)轉(zhuǎn)換時(shí),會(huì)返回 undefined。
對(duì)包含循環(huán)引用的對(duì)象(對(duì)象之間相互引用,形成無(wú)限循環(huán))執(zhí)行此方法,會(huì)拋出錯(cuò)誤
我認(rèn)為 JSON.stringify 能夠返回字符串以外的東西是挺驚訝的。但在6種情況下,它可以返回undefined:
試圖在頂層對(duì) undefined 進(jìn)行序列化,會(huì)返回 undefined。
- JSON.stringify(undefined);
- // => undefined
嘗試序列化函數(shù)也會(huì)返回 undefined。對(duì)于常規(guī)函數(shù)、箭頭函數(shù)、異步函數(shù)和生成器函數(shù)都是如此。
- JSON.stringify(function foo() {});
- // => undefined
- JSON.stringify(() => {});
- // => undefined
- function bar() {}
- bar.someProperty = 123;
- JSON.stringify(bar);
- // => undefined
嘗試序列化symbol 也會(huì)返回 undefined。
- JSON.stringify(Symbol("computers were a mistake"));
- // => undefined
在瀏覽器中,試圖序列化被廢棄的document.all 也會(huì)返回 undefined。
- // => undefined
這只影響到瀏覽器,因?yàn)閐ocument.all在其他環(huán)境中是不可用的,比如Node。
帶有 toJSON 函數(shù)的對(duì)象將被運(yùn)行,而不是試圖正常地序列化它們。但是如果 toJSON 返回上面的一個(gè)值,試圖在頂層序列化它將導(dǎo)致 JSON.stringify 返回undefined。
- JSON.stringify({ toJSON: () => undefined });
- // => undefined
- JSON.stringify({ ignored: true, toJSON: () => undefined });
- // => undefined
- JSON.stringify({ toJSON: () => Symbol("heya") });
- // => undefined
你可以傳遞第二個(gè)參數(shù),稱為 "replacer",它可以改變序列化的邏輯。如果這個(gè)函數(shù)為頂層返回上述值之一,JSON.stringify 將返回undefined。
- JSON.stringify({ ignored: true }, () => undefined);
- // => undefined
- JSON.stringify(["ignored"], () => Symbol("hello"));
- // => undefined
需要注意的是,其中的許多東西實(shí)際上只影響到頂層的序列化。例如,JSON.stringify({foo: undefined}),返回字符串"{}",這并不令人驚訝。
我還想提一下,TypeScript的類型定義在這里是不正確的。例如,下面的代碼類型的校驗(yàn)可以通過(guò):
- const result: string = JSON.stringify(undefined);
在第2部分中,我們將討論如何更新 TypeScript 的定義以確保其正確性。
JSON.stringify 也可能遇到問(wèn)題,導(dǎo)致它拋出一個(gè)錯(cuò)誤。在正常情況下,有四種情況會(huì)發(fā)生:
循環(huán)引用會(huì)導(dǎo)致拋出一個(gè)類型錯(cuò)誤。
- const b = { a };
- a.b = b;
- JSON.stringify(a);
- // => TypeError: cyclic object value
注意,這些錯(cuò)誤消息在不同瀏覽器可能提示是不樣的,例如,F(xiàn)irefox 的錯(cuò)誤信息與Chrome的不同。
BigInts不能用JSON.stringify 進(jìn)行序列化,這些也會(huì)導(dǎo)致一個(gè)TypeError。
- JSON.stringify(12345678987654321n);
- // => TypeError: BigInt value can't be serialized in JSON
- JSON.stringify({ foo: 456n });
- // => TypeError: BigInt value can't be serialized in JSON
帶有 toJSON 函數(shù)的對(duì)象將被運(yùn)行。如果這些函數(shù)拋出錯(cuò)誤,它將冒泡到調(diào)用者。
- const obj = {
- foo: "ignored",
- toJSON() {
- throw new Error("Oh no!");
- },
- };
- JSON.stringify(obj);
- // => Error: Oh no!
你可以傳遞第二個(gè)參數(shù),稱為 replacer。如果這個(gè)函數(shù)拋出一個(gè)錯(cuò)誤,它將冒泡。
- JSON.stringify({}, () => {
- throw new Error("Uh oh!");
- });
- // => Error: Uh oh!
現(xiàn)在我們已經(jīng)看到了 JSON.stringify 不返回字符串的情況,接下來(lái),我們來(lái)看看如何避免這些問(wèn)題。
如何避免這些問(wèn)題
沒(méi)有關(guān)于如何解決這些缺陷的通用方法,所以這里只介紹一些常見(jiàn)的情況。
處理循環(huán)引用
根據(jù)個(gè)人經(jīng)驗(yàn),JSON.stringify 在傳遞循環(huán)引用時(shí)最容易出錯(cuò)。如果這對(duì)你來(lái)說(shuō)是一個(gè)常見(jiàn)的問(wèn)題,我推薦 json-stringify-safe 包,它能很好地處理這種情況。
- const stringifySafe = require("json-stringify-safe");
- const a = {};
- const b = { a };
- a.b = b;
- JSON.stringify(a);
- // => TypeError: cyclic object value
- stringifySafe(a);
- // => '{"b":{"a":"[Circular ~]"}}'
封裝
你可能想用你自己的自定義函數(shù)來(lái)封裝 JSON.stringify。你可以決定你想要它做什么。錯(cuò)誤應(yīng)該冒出來(lái)嗎?如果 JSON.stringify 返回 undefined,應(yīng)該怎么做?
例如,Signal Desktop有一個(gè)名為 reallyJsonStringify 的函數(shù),它總是返回一個(gè)用于調(diào)試的字符串。就像這樣
- function reallyJsonStringify(value) {
- let result;
- try {
- result = JSON.stringify(value);
- } catch (_err) {
- // If there's any error, treat it like `undefined`.
- result = undefined;
- }
- if (typeof result === "string") {
- // It's a string, so we're good.
- return result;
- } else {
- // Convert it to a string.
- return Object.prototype.toString.call(value);
- }
- }
關(guān)于TypeScript類型的說(shuō)明
如果你已經(jīng)在用 TypeScript,可能會(huì)驚訝地發(fā)現(xiàn),TypeScript對(duì) JSON.stringify的官方定義在這里并不正確。它們實(shí)際上看起來(lái)像這樣:
- // Note: 這里面簡(jiǎn)化過(guò)
- interface JSON {
- // ...
- stringify(value: any): string;
- }
不幸的是,這是一個(gè)長(zhǎng)期存在的問(wèn)題,沒(méi)有一個(gè)完美的解決方案。
你可以嘗試修補(bǔ) JSON.stringify 的類型,但每個(gè)解決方案都有一定的缺點(diǎn)。我建議用自定義類型定義自己的包裝器并。例如,Signal Desktop的reallyJsonStringify 的模板:
- function reallyJsonStringify(value: unknown): string {
- // ...
總結(jié)
- JSON.stringify 有時(shí)會(huì)返回 undefined,而不是一個(gè)字符串
- JSON.stringify 有時(shí)會(huì)拋出一個(gè)錯(cuò)誤
- 我們可以通過(guò)用不同的方式包裝函數(shù)來(lái)解決這個(gè)問(wèn)題
希望這篇文章能讓你對(duì) JSON.stringify 有更全面的了解。
作者:BlackLivesMatter 譯者:前端小智
來(lái)源:devinduct 原文:https://evanhahn.com/when-stringify-doesnt-return-a-string