We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在用SON.parse、JSON.stringify做对象深拷贝时,发现会丢失对象中某些类型的值,所以我们来一探究竟,看看在转换过程中做了什么处理。
先看下MDN是怎么介绍 JSON.stringify 的
JSON.stringify(value[, replacer [, space]])
将要序列化成 一个JSON 字符串的值。
这是第一个参数,应该都不陌生,最常用的也是这个。其他两个基本用不到。
一般传入一个对象。但是不仅仅如此,还可以传入其他值哦。
可以三种类型的值:
一般情况下,我们都不传,按第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格式规则:
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 -----] }" */
参考MDN 语法:
JSON.parse(text[, reviver])
参数:
text:要被解析成JavaScript值的字符串。
reviver(可选):转换器, 如果传入该参数(函数),可以用来修改解析生成的原始值,调用时机在parse函数返回之前。
返回值:Object类型, 对应给定JSON文本的对象/值
reviver 参数和 JSON.stringify 的第二个参数 replacer,原理差不多。具体为:
解析值本身以及它所包含的所有属性,会按照一定的顺序(从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身)分别的去调用 reviver 函数,在调用过程中,当前属性所属的对象会作为 this 值,当前属性名和属性值会分别作为第一个和第二个参数传入 reviver 中。
如果 reviver 返回 undefined,则当前属性会从所属对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值。
当遍历到最顶层的值时,传入 reviver 函数的参数会是空字符串 ""(因为此时已经没有真正的属性)和当前的解析值(有可能已经被修改过了),当前的 this 值会是 {"": 修改过的解析值},在编写 reviver 函数时,要注意到这个特例。
函数的遍历顺序依照:从最内层开始,按照层级顺序,依次向外遍历
举例
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 三种实现方式
大部分内容来自:https://www.cnblogs.com/EnSnail/p/11183071.html 和 MDN
The text was updated successfully, but these errors were encountered:
No branches or pull requests
在用SON.parse、JSON.stringify做对象深拷贝时,发现会丢失对象中某些类型的值,所以我们来一探究竟,看看在转换过程中做了什么处理。
先看下MDN是怎么介绍 JSON.stringify 的
参数
将要序列化成 一个JSON 字符串的值。
这是第一个参数,应该都不陌生,最常用的也是这个。其他两个基本用不到。
一般传入一个对象。但是不仅仅如此,还可以传入其他值哦。
可以三种类型的值:
一般情况下,我们都不传,按第3种方式处理。
指定缩进用的空白字符串,用于美化输出。
可以指定三种类型的值:
一般情况下,我们都不传,按第3种方式处理。
返回值
一个表示给定值的 json 字符串。
深入理解
ToString 规则
JSON 字符串化并非严格意义上的强制类型转换,但其中涉及 ToString 的相关规则:
如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。
将对象强制类型转换为 string 是通过 ToPrimitive 抽象操作来完成的。
补充:
[[Class]]:所有 typeof 返回值为 "object" 的对象(如数组)都包含一个内部属性 [[Class]](可以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,一般通过 Object.prototype.toString(..) 来查看。
ToPrimitive:为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有 valueOf() 方法。
如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString()的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
对大多数简单值来说,JSON 字符串化和 toString() 的效果基本相同,只不过序列化的结果总是字符串:
JSON.stringify() 将值转换为相应的JSON格式规则:
安全的 json 值
所有安全的 JSON 值都可以使用 JSON.stringify(..) 字符串化。安全的 JSON 值是指能够呈现为有效 JSON 格式的值。
不安全的 JSON 值:undefined、function、symbol(ES6+)和包含循环引用的对象都不符合 JSON 结构标准,支持 JSON 的语言无法处理它们。例如:
将不安全的 json 转换成安全的 json
如果对象中定义了 toJSON() 方法,JSON 字符串化时会首先调用该方法,然后用它的返回值来进行序列化。
如果要对含有非法 JSON 值的对象做字符串化,或者对象中的某些值无法被序列化时,就需要定义 toJSON() 方法来返回一个安全的 JSON 值。例如:
toJSON() 应该“返回一个能够被字符串化的安全的 JSON 值”,而不是“返回一个 JSON 字符串”。
可选参数 replacer,可以是数组或者函数,用来指定对象序列化过程中哪些属性应该被处理,哪些应该被排除,和 toJSON() 很像。
如果 replacer 是一个数组,那么它必须是一个字符串数组,其中包含序列化要处理的对象
的属性名称,除此之外其他的属性则被忽略。
作为函数,它有两个参数,键(key)值(value)都会被序列化。
注意: 不能用replacer方法,从数组中移除值(values),如若返回undefined或者一个函数,将会被null取代。
所以如果要忽略某个键就返回 undefined,否则返回指定的值。举例:
JSON-js解决对象循环引用问题
decycle和retrocycle实现
JSON-js是老外写的一个对JSON处理的小工具,其中的decycle和retrocycle是专门用来破除/恢复这种循环结构的。基本用法如下:
可以看到,破解循环后确实没有报错,但是出现了$ref:'$'这样的代码,这种标志表示识别除了循环引用,其中$ref为固定的,右边的'$...'表示它循环引用的部分,单个$为顶层对象。
美化序列化后的字符串
JSON.string 还有一个可选参数 space,用来指定输出的缩进格式。
反序列化:JSON.parse(..)
参考MDN
语法:
参数:
text:要被解析成JavaScript值的字符串。
reviver(可选):转换器, 如果传入该参数(函数),可以用来修改解析生成的原始值,调用时机在parse函数返回之前。
返回值:Object类型, 对应给定JSON文本的对象/值
reviver 参数和 JSON.stringify 的第二个参数 replacer,原理差不多。具体为:
解析值本身以及它所包含的所有属性,会按照一定的顺序(从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身)分别的去调用 reviver 函数,在调用过程中,当前属性所属的对象会作为 this 值,当前属性名和属性值会分别作为第一个和第二个参数传入 reviver 中。
如果 reviver 返回 undefined,则当前属性会从所属对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值。
当遍历到最顶层的值时,传入 reviver 函数的参数会是空字符串 ""(因为此时已经没有真正的属性)和当前的解析值(有可能已经被修改过了),当前的 this 值会是 {"": 修改过的解析值},在编写 reviver 函数时,要注意到这个特例。
函数的遍历顺序依照:从最内层开始,按照层级顺序,依次向外遍历
举例
注意:不允许用逗号作为结尾
原生 js 实现
说明:JSON.parse() 在这里是利用 new Function() 拥有字符串参数特性,即能动态编译 js 代码的能力。可参考神奇的eval()与new Function()
JSON.parse() 其他方式实现:
利用 eval() 实现,尽量避免在不必要的情况下使用。 eval() '恶名昭彰',拥有执行代码的能力(可能被恶意使用,带来安全问题),除此之外,不能利用预编译的优势进行性能优化,会比较慢。
还有其他方式,比如递归,可参考:JSON.parse 三种实现方式
大部分内容来自:https://www.cnblogs.com/EnSnail/p/11183071.html 和 MDN
The text was updated successfully, but these errors were encountered: