最近在看《你所不知道的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"
若是对象有本身的 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"
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”属性值。
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 三种实现方式
言尽于此,固然,不止于此(你懂得)。欢迎你们来补充~