你不知道的 JSON.stringify() 的威力

前言

其實有不少有用的東西,當時學習了,也記住了,可是時間久了就是記不住,因此致使在平常開發中老是想不起來原來這個東西能夠這麼用,而去選擇了更加複雜和麻煩的方式。其實咱們平常學習的知識就是拿來用的,即便你今天把知識點背下來了,沒有去思考這個知識點咱們能夠用來幹嗎,不須要幾天就會慢慢地忘掉。因此今天咱們來了解一下在平常學習時你遺漏掉或者忘掉或者沒有思考過的你不知道的 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、任意的函數以及 symbolJSON.stringify() 做爲單獨的值進行序列化時都會返回 undefined

JSON.stringify() 第一大特性總結

  • undefined、任意的函數以及 symbol 做爲對象屬性值時 JSON.stringify() 對跳過(忽略)它們進行序列化

  • undefined、任意的函數以及 symbol 做爲數組元素值時,JSON.stringify() 將會將它們序列化爲 null

  • undefined、任意的函數以及 symbolJSON.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() 第五大特性

  • NaNInfinity 格式的數值及 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() 第七大特性

關於對象屬性的是否可枚舉:

  • 其餘類型的對象,包括 Map/Set/WeakMap/WeakSet,僅會序列化可枚舉的屬性。
// 不可枚舉的屬性默認會被忽略:
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
複製代碼

replacerJSON.stringify() 的第二個參數,咱們比較少用到,因此不少時候咱們會忘記 JSON.stringify() 第二個、第三個參數,場景很少,可是用的好的話會很是方便,關於 JSON.stringify() 第九大特性的例子中對 replacer 參數不明白的同窗先別急,其實很簡單,咱們立刻就會在下面的學習中弄懂。

第二個參數和第三個參數

強大的第二個參數 replacer

replacer 參數有兩種形式,能夠是一個函數或者一個數組。做爲函數時,它有兩個參數,鍵(key)和值(value),函數相似就是數組方法 mapfilter 等方法的回調函數,對每個屬性值都會執行一次該函數。若是 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、任意的函數以及 symbolJSON.stringify() 做爲單獨的值進行序列化時都會返回 undefined

2、非數組對象的屬性不能保證以特定的順序出如今序列化後的字符串中。

3、轉換值若是有 toJSON() 函數,該函數返回什麼值,序列化結果就是什麼值,而且忽略其餘屬性的值。

4、JSON.stringify() 將會正常序列化 Date 的值。

5、NaNInfinity 格式的數值及 null 都會被當作 null

6、布爾值、數字、字符串的包裝對象在序列化過程當中會自動轉換成對應的原始值。

7、其餘類型的對象,包括 Map/Set/WeakMap/WeakSet,僅會序列化可枚舉的屬性。

8、對包含循環引用的對象(對象之間相互引用,造成無限循環)執行此方法,會拋出錯誤。

9、全部以 symbol 爲屬性鍵的屬性都會被徹底忽略掉,即使 replacer 參數中強制指定包含了它們。

JSON.stringify() 第二個參數和第三個參數

強大的第二個參數:

  • 做爲函數時,它有兩個參數,鍵(key)和值(value),函數相似就是數組方法 mapfilter 等方法的回調函數,對每個屬性值都會執行一次該函數(期間咱們還簡單實現過一個 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 (中國標準時間)' 
// }
複製代碼

推薦閱讀

面試官連環追問:數組拍平(扁平化) flat 方法實現

相關文章
相關標籤/搜索