译者按: 老司机们,你知道JSON.stringify还有第二个和第三个可选参数吗?它们是什么呢?json
为了保证可读性,本文采用意译而非直译。另外,本文版权归原做者全部,翻译仅用于学习。小程序
JSON已经逐渐替代XML被全世界的开发者普遍使用。本文深刻讲解JavaScript中使用JSON.stringify的一些细节问题。首先简单回顾一下JSON和JavaScript:微信小程序
let foo = { a: 2, b: function() {} }; JSON.stringify(foo); // "{ "a": 2 }"
JSON.stringify函数将一个JavaScript对象转换成文本化的JSON。不能被文本化的属性会被忽略。foo中属性b的值是函数定义,没有被转换而丢失。数组
若是一个对象的属性值经过某种间接的方式指回该对象自己,那么就是一个循环引用。好比:服务器
var bar = { a: { c: foo } }; var foo = { b: bar };
属性c指向本身,若是层层解析,将会进入一个无限循环。咱们尝试将其打印出来看看:微信
let fooStringified = JSON.stringify(foo); console.log(fooStringified); // {"b":{"a":{}}}
c的属性指向foo对象,foo对象中的b属性又指向bar对象而没法处理,整个被忽略而返回空对象。函数
以下定义(原文中的例子)是没法经过编译的:post
let foo = {b : foo};
错误信息:学习
ReferenceError: foo is not defined at repl:1:14
在函数式语言Haskell中,由于有Lazy Evaluation技术,可使用相似的定义方法。this
let foo = { b: undefined }; JSON.stringify(foo); // {} // Symbols foo.b = Symbol(); JSON.stringify(foo); // {}
在数组中,不可被stringify的元素用null填充。
let foo = [Symbol(), undefined, function() {}, 'works'] JSON.stringify(foo); // "[null,null,null,'works']"
这样能够保持数组自己的“形状”,也就是每个元素本来的索引。
由于JSON是一个通用的文本格式,和语言无关。设想若是将函数定义也stringify的话,如何判断是哪一种语言,而且经过合适的方式将其呈现出来将会变得特别复杂。特别是和语言相关的一些特性,好比JavaScript中的Symbol。
ECMASCript官方也特地强调了这一点:
It does not attempt to impose ECMAScript’s internal data representations on other programming languages. Instead, it shares a small subset of ECMAScript’s textual representations with all other programming languages.
一个绕过对象某些属性没法stringify的方法就是实现对象的toJSON方法来自定义被stringify的对象。由于几乎每个AJAX调用都会使用JSON.stringify,掌握该技巧将会对处理服务器交互有很大帮助。
和toString容许你将对象中的元素以字符串(string)的形式返回相似,toJSON提供了一种能够将对象中不能stringify的属性转换的方法,使得接下来调用的JSON.stringify能够将其转换成JSON格式。
function Person (first, last) { this.firstName = first; this.last = last; } Person.prototype.process = function () { return this.firstName + ' ' + this.lastName; }; let ade = new Person('Ade', 'P'); JSON.stringify(ade); // "{"firstName":"Ade","last":"P"}"
Person实例ade的process函数没有被stringify。假想若是服务器只想要ade的全称,而不是分别获取姓和名,咱们能够直接定义toJSON来达到目的:
Person.prototype.toJSON = function () { return { fullName: this.process(); }; }; let ade = new Person('Ade', 'P'); JSON.stringify(ade); // "{"fullName":"Ade P"}"
定义toJSON的优势是复用性和稳定性,你能够将ade配合任何库使用,传输的数据都将是你经过toJSON定义而返回的fullName。
// jQuery $.post('endpoint', ade); // Angular 2 this.httpService.post('endpoint', ade)
JSON.stringify完整的定义以下:
JSON.stringify(value, replacer?, space?)
replacer和space都是可选参数,接下来咱们来分别讲解。
replacer是一个过滤函数或则一个数组包含要被stringify的属性名。若是没有定义,默认全部属性都被stringify。
只有在数组中的属性被stringify:
let foo = { a : 1, b : "string", c : false }; JSON.stringify(foo, ['a', 'b']); //"{"a":1,"b":"string"}"
嵌套属性也一样会被过滤:
let bar = { a : 1, b : { c : 2 } }; JSON.stringify(bar, ['a', 'b']); //"{"a":1,"b":{}}" JSON.stringify(bar, ['a', 'b', 'c']); //"{"a":1,"b":{"c":2}}"
定义过滤数组有时候并不能知足需求,那么能够自定义过滤函数。
过滤函数以对象中的每个属性和值做为输入,返回值有如下几种状况:
let baz = { a : 1, b : { c : 2 } }; // 返回大于1的值 let replacer = function (key, value) { if(typeof === 'number') { return value > 1 ? value: undefined; } return value; }; JSON.stringify(baz, replacer); // "{"b":{"c":2}}"
经过改写上面的函数加入适当的输出,能够看到具体的执行步骤:
let obj = { a : 1, b : { c : 2 } }; let tracer = function (key, value){ console.log('Key: ', key); console.log('Value: ', value); return value; }; JSON.stringify(obj, tracer); // Key: // Value: Object {a: 1, b: Object} // Key: a // Value: 1 // Key: b // Value: Object {c: 2} // Key: c // Value: 2
你是否意识到调用默认的JSON.stringify返回的值只要一行,并且彻底没有空格?若是想要更加美观的打印出来,那么就须要使用space这个参数了。
我告诉你一个很是简单的方法:经过tab('t')来分割便可。
let space = { a : 1, b : { c : 2 } }; // 使用制表符 JSON.stringify(space, undefined, '\t'); // "{ // "a": 1, // "b": { // "c": 2 // } // }" JSON.stringify(space, undefined, ''); // {"a":1,"b":{"c":2}} // 自定义分隔符 JSON.stringify(space, undefined, 'a'); // "{ // a"a": 1, // a"b": { // aa"c": 2 // a} // }"
一道三颗星的思考题:为何打印结果的倒数第三行有两个a呢?
本文介绍了一些使用toJSON的技巧:
Fundebug专一于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了6亿+错误事件,获得了Google、360、金山软件等众多知名用户的承认。欢迎免费试用!
转载时请注明做者Fundebug以及本文地址:
https://blog.fundebug.com/2017/08/17/what-you-didnt-knowabout-json-stringify/