JSON.stringify()實現原理

JSON 的語法可以表示以下三種類型的值。

1、 簡單值:使用與 JavaScript  相同的語法,可以在JSON 中表示字符串、數值、布爾值和 null。但JSON 不支持 JavaScript 中的特殊值 undefined。

2、 對象:對象作爲一種複雜數據類型,表示的是一組無序的鍵值對兒。而每個鍵值對兒中的值可以是簡單值,也可以是複雜數據類型的值。

3、 數組:數組也是一種複雜數據類型,表示一組有序的值的列表,可以通過數值索引來訪問其中的值。數組的值也可以是任意類型——簡單值、對象或數組。

JSON 不支持變量、函數或對象實例,它就是一種表示結構化數據的格式,雖然與JavaScript 中表示數據的某些語法相同,但它並不侷限於JavaScript 的範疇。

let myObj = {
  undef: undefined,
  bool: false,
  fun: function(){},
  date: new Date(),
  arr: [1, 2],
  obj: {a: 1, b: 2},
  reg: /\d/,
  sym: Symbol(),
  nul: null,
  set: new Set(),
  map: new Map()
}
console.log(JSON.stringify(myObj))


// {"bool":false,"date":"2020-05-27T03:22:47.587Z","arr":[1,2],"obj":{"a":1,"b":2},"reg":{},"nul":null,"set":{},"map":{}}

這個例子使用 JSON.stringify() 把一個JavaScript 對象序列化爲一個JSON 字符串,現在,我們已經瞭解了 JSON.stringify() 方法的輸出以及它的工作方式,讓我們從序列化值開始實現它。

序列化值,首先,我們將從以下數據類型開始。

1、undefined

2、number

3、boolean

4、string

function stringify(value) {
  // 參數類型
  var type = typeof value;


  function getValues(value) {
    if (type === "undefined") {
      return undefined;
    }


    if (type === "number" || type === "boolean") {
      return "" + value + "";
    }


    if (type === "string") {
      return '"' + value + '"';
    }
  }


  return getValues(value);
}


console.log(stringify(1)); // "1"
console.log(stringify("abc")); // ""abc""
console.log(stringify(true)); // "true"


// 這裏是 undefined 而不是 "undefined"
console.log(stringify(undefined) === JSON.stringify(undefined)); // true

到目前爲止,上述功能是比較簡單。它所做的只是用引號把值引起來,但是 undefined 並不需要轉換爲字符串,而是直接返回 undefined 數據類型。

現在,我們將添加對更多數據類型的支持,例如

1、array

2、object

3、null

4、date

5、functions (methods)

爲了支持數組和對象,我們應該解析屬性之間的多層嵌套。我們必須遞歸地處理子元素並序列化值。

對於數組而言,它非常簡單,請使用一個開括號和一個閉括號對數組進行迭代,然後調用該stringify()函數,然後依次調用該getValues()函數並重復進行,直到所有值都考慮在內。

但是對於對象,我們需要使用對象字面量的左,右括號將值和屬性都進行序列化。

對於日期對象,還有一件有趣的事情,JSON.stringify()方法返回的值爲 ISO 8601 日期字符串(與在Date對象上調用toISOString()的結果完全一樣)。

function stringify(value) {
  var type = typeof value;


  function getValues(value) {
    if (type === "symbol" || type === "undefined" || type === "function") {
      return undefined;
    }


    if (type === "number" || type === "boolean") {
      return "" + value + "";
    }


    if (type === "string") {
      return '"' + value + '"';
    }
  }


  // 對於對象數據類型
  // 在javascript中,數組和對象都是對象
  if (type === "object") {


    // 檢查值是否爲null
    if (!value) {
      return "" + value + "";
    }


    // 檢查值是否爲日期對象
    if (value instanceof Date) {
      return '"' + new Date(value).toISOString() + '"'; // 返回ISO 8601日期字符串
    }


    // 檢查值是否爲Array
    if (value instanceof Array) {
      return "[" + value.map(stringify) + "]"; // 遞歸調用stringify函數


    } else {
      // 遞歸調用stringify函數
      return (
        "{" +
        Object.keys(value).map(
          key => {
            let result = stringify(value[key])
            if (result === undefined) {
              return undefined
            }
            return '"' + key + '"' + ":" + result
          }
        ).filter(item => item !== undefined) +
        "}"
      );
    }
  }


  return getValues(value);
}


console.log(stringify([1, 2, 3])); // "[1,2,3]"
console.log(stringify(new Date())); // 返回日期字符串
console.log(stringify({ a: 1 })); // "{"a":1}"

上面的函數現在適用於所有數據類型,並且輸出與 JSON.stringify() 方法相同。

實際上,JSON.stringify() 除了要序列化的 JavaScript 對象外,還可以接收另外兩個參數,這兩個參數用於指定以不同的方式序列化 JavaScript 對象。第一個參數是個過濾器,可以是一個數組,也可以是一個函數;第二個參數是一個選項,表示是否在 JSON 字符串中保留縮進。單獨或組合使用這兩個參數,可以更全面深入地控制 JSON 的序列化。(這裏實現的代碼暫時不支持這兩個參數)

與 JavaScript 不同,JSON 中對象的屬性名任何時候都必須加雙引號。手工編寫 JSON 時,忘了給對象屬性名加雙引號或者把雙引號寫成單引號都是常見的錯誤。

如果對象中有 undefined,那麼相應的屬性會被忽略。

現在你已經瞭解了 JSON 在格式化數據的時候轉換過程是怎樣的,因爲 JSON 對有些數據類型是不支持的,所以不建議大家使用 JSON 進行對象的深度克隆

JSON.parse(JSON.stringify(obj))

如果你需要用到深度克隆,可以參考下面的寫法:

function deepClone (obj, hash = new WeakMap()) {
  if (obj == null) return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (typeof obj === 'symbol') {
    let desc = obj.description
    return desc ? Symbol(desc) : Symbol()
  }
  if (typeof obj !== 'object') return obj;
  if (hash.has(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor;
  hash.set(obj, cloneObj);
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = obj[key];
    }
  }
  return cloneObj;
}

上面代碼使用了 WeakMap 數據結構來解決循環引用的問題,使用 JSON 拷貝循環引用的對象是會報錯的,我們先了解一下什麼是循環引用,也就是對象的某個屬性引用對象本身。

let obj = { a: 1 }
obj.b = obj

由於 WeakMap 只接受對象作爲鍵名,我們可把拷貝之前的對象作爲鍵名,拷貝之後的對象作爲鍵值,調用 WeakMap 的 get 方法讀取對象鍵名,如果存在說明這個對象發生了循環引用,然後直接返回鍵值就是拷貝之後的對象,而不用再次遞歸。

關於 WeakMap 知識你得先了解其他幾種數據結構:

Set

ES6 提供了新的數據結構 Set。它類似於數組,但是成員的值都是唯一的,沒有重複的值。

WeakSet

WeakSet 結構與 Set 類似,也是不重複的值的集合。但是,它與 Set 有兩個區別。首先,WeakSet 的成員只能是對象,而不能是其他類型的值。

Map

JavaScript 的對象(Object),本質上是鍵值對的集合,但是傳統上只能用字符串當作鍵。這給它的使用帶來了很大的限制。

爲了解決這個問題,ES6 提供了 Map 數據結構。它類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,對象 Object 結構提供了“字符串—值”的對應,Map 結構提供了“值—值”的對應。

WeakMap

WeakMap結構與Map結構類似,也是用於生成鍵值對的集合。WeakMap與Map的區別有兩點。

首先,WeakMap只接受對象作爲鍵名(null除外),不接受其他類型的值作爲鍵名。其次,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章