Javascript的JSON.stringify()知多少?
1寫在前面
前幾天看到前端胖頭魚的一篇文章《就因為JSON.stringify,我的年終獎差點打水漂了》,講的就是JSON.stringify在工程開發中的應用,線上用戶不能提交表單。因為字段中經過JSON.stringify后的字符串對象缺少value key,導致后端parse之后無法正確讀取value值,進而報接口系統異常,用戶無法進行下一步動作。本篇文章就將詳細談談JSON.stringify,并將帶著你進行自己手寫一個JSON.stringify,站在全局考察自己對于各種數據類型理解的深度,和各種極端的邊界情況的處理能力。
2JSON.stringify()
JSON.stringify是日常開發中經常用到的JSON對象中的一個方法,用于將一個 JavaScript 對象或值轉換為 JSON 字符串,如果指定了一個 replacer 函數,則可以選擇性地替換值,或者指定的 replacer 是數組,則可選擇性地僅包含數組指定的屬性。
簡而言之,就是用于將對象轉換成JSON字符串。
- JSON.stringify(value[, replacer [, space]])
- value:必填參數,需要序列化的JSON對象。
- replacer:可選參數。
- 函數類型:則在序列化過程中,被序列化的值的每個屬性都會經過該函數的轉換和處理;
- 數組類型:則只有包含在這個數組中的屬性名才會被序列化到最終的 JSON 字符串中;
- null或未提供:則對象所有的屬性都會被序列化。
- space:可選參數,用來控制字符串之間的間距。
- 指定縮進用的空白字符串,用于美化輸出(pretty-print);
- 數字類型,它代表有多少的空格;上限為10。小于1,意味著沒有空格;
- 字符串類型,當字符串長度超過10個字母,取其前10個字母,該字符串將被作為空格;
- null或未提供,將沒有空格。
注意:
- 循環引用的對象(對象之間相互引用,形成無限循環)執行此方法,會拋出錯誤。
- 布爾值、數字、字符串的包裝對象在序列化過程中會自動轉換成對應的原始值。
- undefined、任意的函數以及symbol值,在序列化過程中會被忽略(出現在非數組對象的屬性值中時)或者被轉換成 null(出現在數組中時)。函數、undefined被單獨轉換時,會返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined)。這就是為什么對象中有這些類型的屬性,不能使用JSON.parse(JSON.stringify())來進行深拷貝。
- Date 日期調用了 toJSON() 將其轉換為了 string 字符串(同Date.toISOString()),因此會被當做字符串處理。
- NaN 和 Infinity 格式的數值及 null 都會被當做 null。
- 其他類型的對象,包括 Map/Set/WeakMap/WeakSet,僅會序列化可枚舉的屬性。
- const user = {name:"yichuan",age:18,university:"SCU"};
- //1.序列化對象
- console.log(JSON.stringify(user));//'{"name":"yichuan","age":18,"university":"SCU"}'
- //2.序列化基礎數據類型
- console.log(JSON.stringify("平"));//"平"
- console.log(JSON.stringify(18));//"18"
- console.log(JSON.stringify(true));//"true"
- console.log(JSON.stringify(null));//"null"
- console.log(JSON.stringify(undefined));//undefined
- //3.使用replacer函數
- console.log(JSON.stringify(user,function(key,value){
- return typeof value === "number" ? 666 : "sixsixsix";
- }));//'{name:"sixsixsix",age:666,university:"sixsixsix"}'
- //4.指定數組
- console.log(JSON.stringify(user,["name"]));//'{"name":"yichuan"}'
- //5.指定字符串間的間距
- console.log(JSON.stringify(user,null,2));
- /*
- {
- "name": "yichuan",
- "age": 18,
- "university": "SCU"
- }
- */
- //6.指定字符串的間距”*“
- console.log(JSON.stringify(user,null,"*****"));
- /*
- {
- *****"name": "yichuan",
- *****"age": 18,
- *****"university": "SCU"
- }
- */
整理歸納:
JSON.stringify | 輸入 | 輸出 |
---|---|---|
基礎數據類型 | string | string |
number | 字符串類型的字符串 | |
boolean | "true"/"false" | |
undefined | undefined | |
null | "null" | |
NaN和Infinity | "null" | |
symbol | undefined | |
BigInt | 報錯 | |
引用數據類型 | function | undefined |
Array數組中出現了function、undefined、symbol | string/"null" |
| | | regExp | "{}" | | | Date | Date的toJSON()字符串 | | | 普通object |
- 如果有toJSON()方法,那么序列化toJSON()的返回值
- 如果屬性值中出現了function、undefined、symbol則忽略
- 所有以symbol為屬性鍵的屬性都會被完全忽略掉 |
3手撕JSON.stringify()
其實現場手撕代碼還是有點麻煩的,需要考慮到對各種類型的數據進行處理,考慮各種邊界情況。
- function stringify(data){
- const type = typeof data;
- //可能為基礎數據類型的處理
- if(type !== "object"){
- //判斷是否為NaN或Infinity或者null
- if(Number.isNaN(data)||data===Infinity){
- return "null";
- }
- //判斷可能為function、undefined、symbol類型
- if(type === "function" || type === "undefined" || type === "symbol" ){
- return undefined
- }
- //判斷字符串或數值的處理
- if(type === "string" || type === "number"){
- return `"${data}"`
- }
- if (typeof data === 'bigint') {
- throw new TypeError('Do not know how to serialize a BigInt')
- }
- if (isCyclic(data)) {
- throw new TypeError('Converting circular structure to JSON')
- }
- return String(data);
- }else{
- //null
- if(type==="null"){
- return "null"
- }else if(data.toJSON && typeof data.toJSON === "function"){
- //遞歸
- return stringify(data.toJSON);
- }else if(Array.isArray(data)){
- const arr = [];
- //對于數組類型有很多種情況
- data.forEach((item,index)=>{
- //判斷可能為function、undefined、symbol類型
- if(type === "function" || type === "undefined" || type === "symbol" ){
- arr[index] = "null"
- }else{
- arr[index] = stringify(item);
- }
- });
- result = `"${arr}"`;
- return arr.result.replace(/'/g,"");
- }else{
- //普通對象
- const arr = [];
- //遍歷對象
- Object.keys(data).forEach((key)=>{
- //key如果是symbol類型,忽略
- if(data[key]!=="symbol"){
- if(typeof data[key]!=="undefined" && typeof data[key]!=="function" && typeof data[key]!=="symbol"){
- arr.push(`"${key}":stringify(data[key])`);
- }
- }
- })
- return `{${arr}}`.replace(/'/g, '"')
- }
- }
- }
4參考文章
《MDN:JSON.stringify()》
《就因為JSON.stringify,我的年終獎差點打水漂了》
5寫在最后
我們平時開發中將JSON.stringify應用最多的可能就是淺層的對象進行深拷貝,也就是進行序列化處理。但是當我們進行手撕代碼的時候,需要考慮各種邊界情況,這對于我們來說就比較麻煩,作為面試也是對數據類型的全面考察。