[TOC]javascript
最近在看《你所不知道的javascript》[中卷]一書,第一部分是類型和語法。本文是基於這部分的產物。在強制類型轉換->抽象值操做-> toString 部分,其中對工具函數 JSON.stringify(..) 將 JSON 對象序列化爲字符串部分介紹進行了詳細的介紹,而本身以前對 JSON.stringify(..) 認識也比較淺。html
JSON.stringify() 不管是在面試仍是工做中(對象的深拷貝、json 字符串序列化)都是重點,老是能看到它的身影。因此針對這個知識點記錄整理一下。java
參考MDNgit
JSON.stringify(value[, replacer [, space]])
將要序列化成 一個JSON 字符串的值。github
這是第一個參數,應該都不陌生,最經常使用的也是這個。其餘兩個基本用不到。面試
通常傳入一個對象。可是不只僅如此,還能夠傳入其餘值哦。json
能夠三種類型的值:數組
通常狀況下,咱們都不傳,按第3種方式處理。安全
指定縮進用的空白字符串,用於美化輸出。性能優化
能夠指定三種類型的值:
通常狀況下,咱們都不傳,按第3種方式處理。
一個表示給定值的 json 字符串。
JSON 字符串化並不是嚴格意義上的強制類型轉換,但其中涉及 ToString 的相關規則:
// 1.07 連續乘以七個 1000 var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000; // 七個1000一共21位數字 a.toString(); // "1.07e21"
內部屬性 [[Class]] 的值,如 "[object Object]"。
若是對象有本身的 toString() 方法,字符串化時就會調用該方法並使用其返回值。
將對象強制類型轉換爲 string 是經過 ToPrimitive 抽象操做來完成的。
補充:
[[Class]]:全部 typeof 返回值爲 "object" 的對象(如數組)都包含一個內部屬性 [[Class]](能夠把它看做一個內部的分類,而非傳統的面向對象意義上的類)。這個屬性沒法直接訪問,通常經過 Object.prototype.toString(..) 來查看。
Object.prototype.toString.call( [1,2,3] ); // "[object Array]" Object.prototype.toString.call( /regex-literal/i ); // "[object RegExp]" 上例中,數組的內部 [[Cl
ToPrimitive:爲了將值轉換爲相應的基本類型值,抽象操做 ToPrimitive 會首先(經過內部操做 DefaultValue)檢查該值是否有 valueOf() 方法。
若是有而且返回基本類型值,就使用該值進行強制類型轉換。若是沒有就使用 toString()的返回值(若是存在)來進行強制類型轉換。
若是 valueOf() 和 toString() 均不返回基本類型值,會產生 TypeError 錯誤。
對大多數簡單值來講,JSON 字符串化和 toString() 的效果基本相同,只不過序列化的結果老是字符串:
JSON.stringify( 42 ); // "42" JSON.stringify( "42" ); // ""42"" (含有雙引號的字符串) JSON.stringify( null ); // "null" JSON.stringify( true ); // "true"
undefined、任意的函數以及 symbol 值,在序列化過程當中
JSON.stringify({}); // '{}' JSON.stringify(true); // 'true' JSON.stringify("foo"); // '"foo"' JSON.stringify([1, "false", false]); // '[1,"false",false]' JSON.stringify({ x: 5 }); // '{"x":5}' JSON.stringify({x: 5, y: 6}); // "{"x":5,"y":6}" JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // '[1,"false",false]' JSON.stringify({x: undefined, y: Object, z: Symbol("")}); // '{}' JSON.stringify([undefined, Object, Symbol("")]); // '[null,null,null]' JSON.stringify({[Symbol("foo")]: "foo"}); // '{}' JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]); // '{}' JSON.stringify( {[Symbol.for("foo")]: "foo"}, function (k, v) { if (typeof k === "symbol"){ return "a symbol"; } } ); // undefined // 不可枚舉的屬性默認會被忽略: JSON.stringify( Object.create( null, { x: { value: 'x', enumerable: false }, y: { value: 'y', enumerable: true } } ) ); // "{"y":"y"}" // 序列化,而後反序列化後丟失 constructor function Animation (name) { this.name = name; } var dog = new Animation('小白'); console.log(dog.constructor); // ƒ Animation (name) { this.name = name; } var obj = JSON.parse(JSON.stringify(dog)); console.log(obj.constructor); // ƒ Object() { [native code] }
全部安全的 JSON 值均可以使用 JSON.stringify(..) 字符串化。安全的 JSON 值是指可以呈現爲有效 JSON 格式的值。
不安全的 JSON 值:undefined、function、symbol(ES6+)和包含循環引用的對象都不符合 JSON 結構標準,支持 JSON 的語言沒法處理它們。例如:
JSON.stringify( undefined ); // undefined JSON.stringify( function(){} ); // undefined JSON.stringify( [1,undefined,function(){},4] ); // "[1,null,null,4]" JSON.stringify( { a:2, b:function(){} } ); // "{"a":2}"
若是對象中定義了 toJSON() 方法,JSON 字符串化時會首先調用該方法,而後用它的返回值來進行序列化。
若是要對含有非法 JSON 值的對象作字符串化,或者對象中的某些值沒法被序列化時,就須要定義 toJSON() 方法來返回一個安全的 JSON 值。例如:
var o = { }; var a = { b: 42, c: o, d: function(){} }; // 在a中建立一個循環引用 o.e = a; // 循環引用在這裏會產生錯誤 // JSON.stringify( a ); // 自定義的JSON序列化 a.toJSON = function() { // 序列化僅包含b return { b: this.b }; }; JSON.stringify( a ); // "{"b":42}"
toJSON() 應該「返回一個可以被字符串化的安全的 JSON 值」,而不是「返回一個 JSON 字符串」。
可選參數 replacer,能夠是數組或者函數,用來指定對象序列化過程當中哪些屬性應該被處理,哪些應該被排除,和 toJSON() 很像。
若是 replacer 是一個數組,那麼它必須是一個字符串數組,其中包含序列化要處理的對象
的屬性名稱,除此以外其餘的屬性則被忽略。
做爲函數,它有兩個參數,鍵(key)值(value)都會被序列化。
注意: 不能用replacer方法,從數組中移除值(values),如若返回undefined或者一個函數,將會被null取代。
因此若是要忽略某個鍵就返回 undefined,不然返回指定的值。舉例:
var a = { b: 42, c: "42", d: [1,2,3] }; JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}" JSON.stringify( a, function(k,v){ if (k !== "c") return v; } ); // "{"b":42,"d":[1,2,3]}"
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7}; function replacer(key, value) { if (typeof value === "string") { return undefined; } return value; } // 函數 var jsonString = JSON.stringify(foo, replacer); // {"week":45,"month":7} // 數組 JSON.stringify(foo, ['week', 'month']); // '{"week":45,"month":7}', 只保留「week」和「month」屬性值。
方式3:利用一些工具(好比JSON-js),主要處理 循環引用問題。可參考:
JSON-js是老外寫的一個對JSON處理的小工具,其中的decycle和retrocycle是專門用來破除/恢復這種循環結構的。基本用法以下:
let a={name:'aaa',link:''} let b={name:'bbb',link:''} a.link=b; b.link=a; /*decycle*/ JSON.stringify(JSON.decycle(a)); /*結果*/ "{"name":"aaa","link":{"name":"bbb","link":{"$ref":"$"}}}"
能夠看到,破解循環後確實沒有報錯,可是出現了$ref:'$'這樣的代碼,這種標誌表示識別除了循環引用,其中$ref爲固定的,右邊的'$...'表示它循環引用的部分,單個$爲頂層對象。
JSON.string 還有一個可選參數 space,用來指定輸出的縮進格式。
var a = { b: 42, c: "42", d: [1,2,3] }; // 數字 JSON.stringify( a, null, 3 ); /* "{ "b": 42, "c": "42", "d": [ 1, 2, 3 ] }" */ // 字符串 JSON.stringify( a, null, "-----" ); /* "{ -----"b": 42, -----"c": "42", -----"d": [ ----------1, ----------2, ----------3 -----] }" */
語法:
JSON.parse(text[, reviver])
參數:
返回值:Object類型, 對應給定JSON文本的對象/值
reviver 參數和 JSON.stringify 的第二個參數 replacer,原理差很少。具體爲:
舉例
JSON.parse('{"p": 5}', function (k, v) { if(k === '') return v; // 若是到了最頂層,則直接返回屬性值, return v * 2; // 不然將屬性值變爲原來的 2 倍。 }); // { p: 10 } JSON.parse('{"1": 1, "2": 2,"3": {"4": 4, "5": {"6": 6}}}', function (k, v) { console.log(k); // 輸出當前的屬性名,從而得知遍歷順序是從內向外的, // 最後一個屬性名會是個空字符串。 return v; // 返回原始屬性值,至關於沒有傳遞 reviver 參數。 }); // 1 2 4 6 5 3 ''
注意:不容許用逗號做爲結尾
// both will throw a SyntaxError JSON.parse("[1, 2, 3, 4, ]"); JSON.parse('{"foo" : 1, }');
var myJson = { parse: function (jsonStr) { return (new Function('return ' + jsonStr))(); }, stringify: function (jsonObj) { var result = '', curVal; if (jsonObj === null) { return String(jsonObj); } switch (typeof jsonObj) { case 'number': case 'boolean': return String(jsonObj); case 'string': return '"' + jsonObj + '"'; case 'undefined': case 'function': return undefined; } switch (Object.prototype.toString.call(jsonObj)) { case '[object Array]': result += '['; for (var i = 0, len = jsonObj.length; i < len; i++) { curVal = JSON.stringify(jsonObj[i]); result += (curVal === undefined ? null : curVal) + ","; } if (result !== '[') { result = result.slice(0, -1); } result += ']'; return result; case '[object Date]': return '"' + (jsonObj.toJSON ? jsonObj.toJSON() : jsonObj.toString()) + '"'; case '[object RegExp]': return "{}"; case '[object Object]': result += '{'; for (i in jsonObj) { if (jsonObj.hasOwnProperty(i)) { curVal = JSON.stringify(jsonObj[i]); if (curVal !== undefined) { result += '"' + i + '":' + curVal + ','; } } } if (result !== '{') { result = result.slice(0, -1); } result += '}'; return result; case '[object String]': return '"' + jsonObj.toString() + '"'; case '[object Number]': case '[object Boolean]': return jsonObj.toString(); } } };
說明:JSON.parse() 在這裏是利用 new Function() 擁有字符串參數特性,即能動態編譯 js 代碼的能力。可參考神奇的eval()與new Function()
JSON.parse() 其餘方式實現:
利用 eval() 實現,儘可能避免在沒必要要的狀況下使用。 eval() '惡名昭彰',擁有執行代碼的能力(可能被惡意使用,帶來安全問題),除此以外,不能利用預編譯的優點進行性能優化,會比較慢。
var json = eval('(' + jsonStr + ')');
還有其餘方式,好比遞歸,可參考:JSON.parse 三種實現方式
言盡於此,固然,不止於此(你懂得)。歡迎你們來補充~