就我目前4年(實習了1年,965了1年,996了2年,算3年感受少了,說是4年老司機也不爲過吧。)的工做經驗來看,JSON.stringify通常有如下用途:javascript
但其實除了上面兩種經常使用的用法以外,JSON.stringify還有許多更增強大的特性,值得系統性學習。
關鍵是這些特性多是你開發過程當中常常用到的,只是你不知道而已。前端
也許你以爲這是一篇枯燥的長篇大論的講用法的博文,那麼我就說幾個有趣的場景吧:vue
若是這幾個問題看了以後是一臉懵逼,那麼必定要細細閱讀一下這篇博文,答案就在文中。聰明的你必定會找到答案的。java
工做中經常使用JSON.stringify()ios
序列化:服務端存儲重依賴前端的數據, localStorage/sessionStorage存儲的數據(例如fabric.js的canvas模板數據,vue-amap的svg路徑信息等等git
初識JSON.stringify()github
JSON.stringify(value, [ ,replacer[ ,space]]) 語法數據庫
參數canvas
異常axios
JSON.stringify()描述
replacer 參數
space 參數
JSON.stringify()序列化循環引用時的問題
const obj = { foo: 'hi', bar: { name: 'bar', age: 3 }, baz: ['hello','javascript'] } const arr = ['hi', { name: 'bar', age: 3 }, ['hello','javascript']]; const deepCopy = JSON.parse(JSON.stringify(obj/arr))
深拷貝以後,deepCopy會生成一個內存獨立的obj或者arr。
也就是說obj/arr與deepCopy存儲在不一樣的堆內存,修改obj/arr不會影響deepCopy,修改deepCopy也不會影響obj或者arr。
如果對於深淺拷貝不理解,建議先找資料系統性學習一下。
服務端存儲重依賴前端的數據:例如fabric.js的canvas模板數據,vue-amap的svg路徑信息等等。
localStorage/sessionStorage存儲的數據: LocalStorage/SessionStorage The keys and the values are always strings。
例如Canvas,SVG信息,服務端作持久化。
const complexFabricJSCanvasTemplate = { ... }; const complexVueAMapSVGObject = { ... }; const JSONStr = JSON.stringify(complexFabricJSCanvasTemplate/complexVueAMapSVGObject); axios.post('/create', { name: 'fabric.js', // "vue-amap" data: JSONStr, }) .then((res)=>{ console.log(res); }) .catch((err)=>{ console.log(err); });
const testObj = {foo: 1, bar: 'hi', baz: { name: 'frankkai', age: 25 }} localStorage.setItem('testObj', JSON.stringify(testObj ));
若不轉化,也不會報錯,會致使存儲失效:localStorage.getItem('testObj');// "[object Object]"
從服務端接口查詢到存儲好的Canvas或SVG數據,作解析後傳入到fabric.js,vue-amap等進行繪製。
return new Promise((resolve)=>{ axios.get('/retrive', { name: 'fabric.js', // "vue-amap" }) .then((res)=>{ const complexFabricJSCanvasTemplate = JSON.parse(res.result); const complexVueAMapSVGObject = JSON.parse(res.result); resolve(complexFabricJSCanvasTemplate/complexVueAMapSVGObject); }) .catch((err)=>{ console.log(err); }); })
console.log(JSON.stringify({ x: 5, y: 6 })); // expected output: "{"x":5,"y":6}" console.log(JSON.stringify([new Number(3), new String('false'), new Boolean(false)])); // expected output: "[3,"false",false]" console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] })); // expected output: "{"x":[10,null,null,null]}" console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5))); // expected output: ""2006-01-02T15:04:05.000Z""
var foo = {foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7}; // replacer爲數組 JSON.stringify(foo, ['week', 'month']); // '{"week":45,"month":7}', 只返回week"和"month" // replacer爲函數 function replacer(key, value) { if (typeof value === 'string') return undefined; return value; } JSON.stringify(foo, replacer); // '{"week":45,"month":7}',返回callback不爲undefined的。
首行縮進兩個空格。
JSON.stringify({ a: 2 }, null, ' '); // JSON.stringify({a:2}, null, 2)
=>
"{ "a": 2 }"
JSON.stringify(value, [ ,replacer[ ,space]]);
轉化爲JSON string的值。
過濾數據。
函數:replacer能夠是函數,返回undefined時不輸出數據,非undefined的數據被輸出。
字符串數組:replacer能夠是數組,通常是string,也能夠是number。指定輸出JSON的屬性白名單。['week', 'month']
。
null或不寫:replacer爲null或者不寫時,全部屬性都被輸出。null的話通常用於不過濾數據僅設置space的狀況。
疑惑:replacer的數組中是數字?[1,2,3,4,5] ?
const arr = { 999: 'hi', foo: 'js', bar:' java' }; JSON.stringify(arr, [999, 'foo']); // 打印出"{"999":"hi","foo":"js"}"。 這裏的999是number類型。
加強可讀性的縮進空格或填充字符串。
JSON string。
序列化自引用的對象時會報這個錯。高德地圖的segments就是自引用對象。如何序列化自引用的對象可見下文。
包含突破Number存儲上線的BitInt類型的obj,不能被序列化。
JSON.stringify({foo: 1n}) // TypeError 「BigInt value can't be serialized in JSON」
若是想更好的使用JSON.stringify(),下面這些使用須知必定要掌握。
JSON.stringify({ foo: Symbol('foo') });// "{}"
JSON.stringify({ [Symbol('foo')]: 'foo' });// '{}' JSON.stringify({ [Symbol.for('foo')]: 'foo' }, [Symbol.for('foo')]);// '{}'
var foo = [['foo',1]];var mp = new Map(foo);JSON.stringify(mp); // "{}"
let a = ['foo', 'bar']; a['baz'] = 'quux'; // a: [ 0: 'foo', 1: 'bar', baz: 'quux' ]; JSON.stringify(a); // '["foo","bar"]'
JSON.stringify([new Float32Array([1]), new Float64Array([1])]);// '[{"0":1},{"0":1}]'
究極使用示例:
JSON.stringify({}); // '{}' JSON.stringify(true); // 'true' JSON.stringify('foo'); // '"foo"' JSON.stringify([1, 'false', false]); // '[1,"false",false]' JSON.stringify([NaN, null, Infinity]); // '[null,null,null]' JSON.stringify({ x: 5 }); // '{"x":5}' JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)) // '"2006-01-02T15:04:05.000Z"' JSON.stringify({ x: 5, y: 6 }); // '{"x":5,"y":6}' JSON.stringify([new Number(3), new String('false'), new Boolean(false)]); // '[3,"false",false]' // String-keyed array elements are not enumerable and make no sense in JSON let a = ['foo', 'bar']; a['baz'] = 'quux'; // a: [ 0: 'foo', 1: 'bar', baz: 'quux' ] JSON.stringify(a); // '["foo","bar"]' JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] }); // '{"x":[10,null,null,null]}' // Standard data structures JSON.stringify([new Set([1]), new Map([[1, 2]]), new WeakSet([{a: 1}]), new WeakMap([[{a: 1}, 2]])]); // '[{},{},{},{}]' // TypedArray JSON.stringify([new Int8Array([1]), new Int16Array([1]), new Int32Array([1])]); // '[{"0":1},{"0":1},{"0":1}]' JSON.stringify([new Uint8Array([1]), new Uint8ClampedArray([1]), new Uint16Array([1]), new Uint32Array([1])]); // '[{"0":1},{"0":1},{"0":1},{"0":1}]' JSON.stringify([new Float32Array([1]), new Float64Array([1])]); // '[{"0":1},{"0":1}]' // toJSON() JSON.stringify({ x: 5, y: 6, toJSON(){ return this.x + this.y; } }); // '11' // Symbols: JSON.stringify({ x: undefined, y: Object, z: Symbol('') }); // '{}' 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 // Non-enumerable properties: JSON.stringify( Object.create(null, { x: { value: 'x', enumerable: false }, y: { value: 'y', enumerable: true } }) ); // '{"y":"y"}' // BigInt values throw JSON.stringify({x: 2n}); // TypeError: BigInt value can't be serialized in JSON
replacer能夠是function,也能夠是array。
function replacer(key="", value) { return value; }
做爲function,有兩個參數key 和 value。
默認狀況下replacer函數的key爲「」,對於對象中的每一個值,都會call一次這個函數。
`function replacer(key,value){ return value; } JSON.stringify({foo: false,bar:123},replacer);`
返回值有如下幾種狀況:
return 123; // "123"
return "foo"; // "foo"
return true; // "true"
return null; //"null"
return value;"{"foo":false,"bar":123}"
JSON.stringify({foo: false,bar:undefined},replacer);"{"foo":false}"。
使用強大的undefined是須要注意:
JSON.stringify()
的功勞。 v = JSON.stringify(v)
時,會將undefined類型的值自動過濾掉。
// axios源碼 buildURL.js 37~56行: utils.forEach(params, function serialize(val, key) { if (utils.isArray(val)) { key = key + '[]'; } else { val = [val]; } utils.forEach(val, function parseValue(v) { if (utils.isDate(v)) { v = v.toISOString(); } else if (utils.isObject(v)) { v = JSON.stringify(v); // 注意這裏 } parts.push(encode(key) + '=' + encode(v)); }); });
function replacer(key,value){ if(typeof value ==='boolean'){return undefined} return value; } JSON.stringify([1, 'false', false],replacer); // "[1,"false",null]"
function replacer(key, value) { // 返回undefined過濾屬性 if (typeof value === 'string') { return undefined; } return value; } var foo = {foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7}; JSON.stringify(foo, replacer); // '{"week":45,"month":7}'
JSON.stringify(foo, ['week', 'month']); // '{"week":45,"month":7}',
JSON.stringify({ a: 2 }, null, ' '); // '{ // "a": 2 // JSON.stringify({ uno: 1, dos: 2 }, null, '\t'); // returns the string: // '{ // "uno": 1, // "dos": 2 // }'
若是要被字符串化的對象具備一個名爲toJSON的屬性,其值是一個函數,則該toJSON()方法將自定義JSON字符串化行爲:代替被序列化的對象,該toJSON()方法返回的值將被序列化,而不是被序列化的對象。JSON.stringify()調用toJSON一個參數:
var obj = { data: 'data', toJSON (key) { return key; } }; JSON.stringify(obj); // '"""" JSON.stringify({ obj }) // '{"obj":"'obj'"}' JSON.stringify([ obj ]) // '["'0'"]'
TypeError: Converting circular structure to JSON
const circularReference = {}; circularReference.myself = circularReference; // Serializing circular references throws "TypeError: cyclic object value" JSON.stringify(circularReference);
路徑規劃plans的路徑SVG信息segments對象。
需求是這樣的,前端須要將路線的信息傳遞給後端,其中包括經緯度數組和SVG數組。
用JSON.stringify()序列化經緯度數組是ok的,可是序列化的SVG數組是自引用的,會報錯。(是後來才知道這個SVG數組能夠不往服務端存的,不過當時序列化報錯是真的懵逼了)
使用Douglas Crockford的cycle.js
它在全局JSON對象上新增了2個方法:
myself:obj
替換爲myself:{$ref: "$"}
)使用示例:
var circularReference = {}; circularReference.myself = circularReference; JSON.decycle(circularReference); // { "$ref": "$" }
不相等。
var a = JSON.stringify({ foo: "bar", baz: "quux" }) //'{"foo":"bar","baz":"quux"}' var b = JSON.stringify({ baz: "quux", foo: "bar" }) //'{"baz":"quux","foo":"bar"}' console.log(a === b) // false
localStorage只能存儲string類型的數據,所以須要使用JSON.stringify()將對象序列化爲JSON string。
var session = { 'screens': [], 'state': true }; session.screens.push({ 'name': 'screenA', 'width': 450, 'height': 250 }); session.screens.push({ 'name': 'screenB', 'width': 650, 'height': 350 }); session.screens.push({ 'name': 'screenC', 'width': 750, 'height': 120 }); localStorage.setItem('session', JSON.stringify(session)); var restoredSession = JSON.parse(localStorage.getItem('session')); console.log(restoredSession);
值爲undefined?
replacer對它作過濾了?
toJSON方法中作過濾了?
屬性的enumerable值爲false?
Symbol?
Map?Set?WeakMap?WeakSet?
找緣由吧。
v = JSON.stringify(v); // v = {foo: undefined} =>"{}"
const testObj = {foo: 1, bar: 'hi', baz: { name: 'frankkai', age: 25 }} JSON.stringify(testObj, null, 4);
=>
"{ "foo": 1, "bar": "hi", "baz": { "name": "frankkai", "age": 25 } }"
這是由於自引用以後,會限制死循環。
引擎應該是作了特殊的處理,發現這種無限循環時自動拋出異常。
這種對象不能序列化了嗎?可使用cycle.js的decycle(序列化。
var circularReference = {}; circularReference.myself = circularReference; JSON.decycle(circularReference); // { "$ref": "$" }
參考資料:
期待和你們交流,共同進步,歡迎你們加入我建立的與前端開發密切相關的技術討論小組:
- SegmentFault技術圈:ES新規範語法糖
- SegmentFault專欄:趁你還年輕,作個優秀的前端工程師
- 知乎專欄:趁你還年輕,作個優秀的前端工程師
- Github博客: 趁你還年輕233的我的博客
- 前端開發QQ羣:660634678
- 微信公衆號: 生活在瀏覽器裏的咱們 / excellent_developers
努力成爲優秀前端工程師!