最近在看《你所不知道的javascript》[中卷]一书,第一部分是类型和语法。本文是基于这部分的产物。在强制类型转换->抽象值操作-> toString 部分,其中对工具函数 JSON.stringify(..) 将 JSON 对象序列化为字符串部分介绍进行了详细的介绍,而自己之前对 JSON.stringify(..) 认识也比较浅。
JSON.stringify() 不论是在面试还是工作中(对象的深拷贝、json 字符串序列化)都是重点,总是能看到它的身影。所以针对这个知识点记录整理一下。
参考MDN
JSON.stringify(value[, replacer [, space]])
将要序列化成 一个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 + ')');
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
什么是JSONP?先说说JSONP是怎么产生的?不管jQuery也好,ExtJs也罢,又或者是其他支持jsonp的框架,他们幕后所做的工作都是一样的,下面我来循序渐进的说明一下jsonp在客户端的实现
由于Sencha Touch 2这种开发模式的特性,基本决定了它原生的数据交互行为几乎只能通过AJAX来实现。当然了,通过调用强大的PhoneGap插件然后打包,你可以实现100%的Socket通讯和本地数据库功能
jsonp是利用浏览器请求script文件时不受同源策略的限制而实现的,伪造一个script标签,将请求数据的url赋值给script的src属性,并将该标签添加到html中,浏览器会自动发送请求,返回的一般时一段js代码,即函数的调用代码。
一个众所周知的问题,Ajax直接请求普通文件存在跨域无权限访问的问题;jsonp不是ajax的一个特例,哪怕jquery等巨头把jsonp封装进了ajax,也不能改变!
JSONP与JSON只有一字之差,我们在使用Jquery的Ajax调用的时候也是使用相同的方法来调用,两者的区别几乎只在于使用的dataType这个属性的不同。但是实际上JSON和JSONP是完全不同的两个东西,JSON是一个数据格式
因为不想让再引用新的第三方组件了,所以执念了一下,于是搜索到了下面的代码,调试了一下,发现确实能用,但是存在一个缺陷,就是如果存在连续多次的请求,都会回调到同一个函数上
Jsonp(JSON with Padding) 是 json 的一种\\\"使用模式\\\",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。为什么我们从不同的域(网站)访问数据需要一个特殊的技术( JSONP )呢?这是因为同源策略。
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现
事先定义一个用于获取跨域响应数据的回调函数,并通过没有同源策略限制的script标签发起一个请求(将回调函数的名称放到这个请求的query参数里),然后服务端返回这个回调函数的执行,并将需要响应的数据放到回调函数的参数里
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!