目录
- referer、prototype、array、json笔记整理
- referer
- Referrer-policy
- 如何设置referer
- 绕过图片防盗链
- 1、利用https网站盗链http资源网站,refer不会发送
- 2、设置meta
- 3、设置referrerpolicy="no-referrer"
- 4、利用iframe伪造请求referer
- 5、客户端在请求时修改header头部
- 1、利用XMLHttpRequest
- 2、利用fetch
- prototype
- 对象的继承
- 原型对象概述
- 构造函数的缺点
- prototype 属性的作用
- 原型链
- constructor 属性
- instanceof 运算符
- 构造函数的继承
- 多重继承
- array
- Array 对象
- 构造函数
- 实例方法
- valueOf(),toString()
- push(),pop()
- shift(),unshift()
- join()
- slice()
- sort()
- map()
- filter()
- 总结
- 那些方法会改变原数组
- 那些方法不会改变原数组
- json
- JSON 对象
- JSON 格式
- JSON 对象
- JSON.stringify()
- 第二个参数
- 参数对象的 toJSON() 方法
- JSON.parse()
- 总结
- 1、JSON.parse()可以**将一个字符串转为 JSON值 **
- 2、JSON.stringify()可以**将一个值转为 JSON 字符串**
referer、prototype、array、json笔记整理
referer
Referrer-policy
Referrer-policy作用就是为了控制请求头中referer的内容
包含以下值:
- no-referrer : 整个referee首部会被移除,访问来源信息不随着请求一起发送。
- no-referrer-when-downgrade : 在没有指定任何策略的情况下用户代理的默认行为。在同等安全级别的情况下,引用页面的地址会被发送(HTTPS->HTTPS),但是在降级的情况下不会被发送 (HTTPS->HTTP).
- origin: 在任何情况下,仅发送文件的源作为引用地址。例如 https://example.com/page.html 会将 https://example.com/ 作为引用地址。
- origin-when-cross-origin: 对于同源的请求,会发送完整的URL作为引用地址,但是对于非同源请求仅发送文件的源。
- same-origin: 对于同源的请求会发送引用地址,但是对于非同源请求则不发送引用地址信息。
- strict-origin: 在同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS),但是在降级的情况下不会发送 (HTTPS->HTTP)。
- strict-origin-when-cross-origin: 对于同源的请求,会发送完整的URL作为引用地址;在同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS);在降级的情况下不发送此首部 (HTTPS->HTTP)。
- unsafe-url: 无论是同源请求还是非同源请求,都发送完整的 URL(移除参数信息之后)作为引用地址。(最不安全了):
如何设置referer
- 在HTML里设置meta
<meta name="referrer" content="origin">
在<a>、<area>、<img>、<iframe>、<script> 或者 <link> 元素上的 referrerpolicy 属性为其设置独立的请求策略。
<script src='/javascripts/test.js'
referrerpolicy="no-referrer"></script>
绕过图片防盗链
1、利用https网站盗链http资源网站,refer不会发送
2、设置meta
3、设置referrerpolicy=“no-referrer”
4、利用iframe伪造请求referer
#index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- <meta name="referer" content="no-referrer" > -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img src="https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg" >
<!-- <iframe src="./2.html" width="20%" height="200" frameborder="1" sandbox="allow-scripts allow-same-origin allow-modals "></iframe>-->
</body>
<script src="./1.js" ></script>
</html>
#js
function showImg(src, wrapper ) {
let url = new URL(src);
let frameid = 'frameimg' + Math.random();
window.img = `<img id="tmpImg" width=400 src="${url}" alt="图片加载失败,请稍后再试"/> `;
// 构造一个iframe
iframe = document.createElement('iframe')
iframe.id = frameid
iframe.src = "javascript:parent.img;" // 通过内联的javascript,设置iframe的src
// 校正iframe的尺寸,完整展示图片
iframe.onload = function () {
var img = iframe.contentDocument.getElementById("tmpImg")
if (img) {
iframe.height = img.height + 'px'
iframe.width = img.width + 'px'
}
}
iframe.width = 10
iframe.height = 10
iframe.scrolling = "no"
iframe.frameBorder = "0"
wrapper.appendChild(iframe)
}
showImg('https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg', document.querySelector('#container'))
5、客户端在请求时修改header头部
1、利用XMLHttpRequest
// 通过ajax下载图片
function loadImage(uri) {
return new Promise(resolve => {
let xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.onload = function() {
resolve(xhr.response);
};
xhr.open("GET", uri, true);
// 通过setRequestHeader设置header不会生效
// 会提示 Refused to set unsafe header "Referer"
xhr.setRequestHeader("Referer", "");
xhr.send();
});
}
// 将下载下来的二进制大对象数据转换成base64,然后展示在页面上
function handleBlob(blob) {
let reader = new FileReader();
reader.onload = function(evt) {
let img = document.createElement('img');
img.src = evt.target.result;
document.getElementById('container').appendChild(img)
};
reader.readAsDataURL(blob);
}
const imgSrc = "https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg";
loadImage(imgSrc).then(blob => {
handleBlob(blob);
});
上述代码运行时会发现控制台提示错误:
Refused to set unsafe header “Referer”
可以看见setRequestHeader设置referer响应头是无效的,这是由于浏览器为了安全起见,无法手动设置部分保留字段,不幸的是Referer恰好就是保留字段之一
2、利用fetch
// 将下载下来的二进制大对象数据转换成base64,然后展示在页面上
function handleBlob(blob) {
let reader = new FileReader();
reader.onload = function(evt) {
let img = document.createElement('img');
img.src = evt.target.result;
document.getElementById('container').appendChild(img)
};
reader.readAsDataURL(blob);
}
const imgSrc = "https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg";
function fetchImage(url) {
return fetch(url, {
headers: {
// "Referer": "", // 这里设置无效
},
method: "GET",
referrer: "", // 将referer置空
// referrerPolicy: 'no-referrer',
}).then(response => response.blob());
}
fetchImage(imgSrc).then(blob => {
handleBlob(blob);
});
prototype
对象的继承
面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现,本章介绍 JavaScript 的原型链继承。
ES6 引入了 class 语法,基于 class 的继承不在这个教程介绍,请参阅《ES6 标准入门》一书的相关章节。
原型对象概述
构造函数的缺点
JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。
function Person (name){
this.name = name;
}
Person.prototype.color = 'white';
var Aferican_a = new Person('person_a');
var Aferican_b = new Person('person_b');
var American_a = new Person('person_c');
American_a.color ='black';
console.info(Aferican_a.color ===American_a.color);
console.info(Aferican_a.color ===Aferican_b.color);
上面代码中,person
函数是一个构造函数,函数内部定义了name
属性和color
属性,所有实例对象都会生成这两个属性,即这两个属性会定义在实例对象上面。
通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
function Person (name){
this.name = name;
}
Person.prototype.color = 'white';
var Aferican_a = new Person('person_a');
var Aferican_b = new Person('person_b');
var American_a = new Person('person_c');
American_a.color ='black';
Person.prototype.speak = function () {
console.info('Speak English');
}
console.info(Aferican_a.color ===American_a.color);
console.info(Aferican_a.color ===Aferican_b.color);
Aferican_a.speak();
上面代码中,ta们都具有speak
方法。由于speak
方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个speak
方法。这既没有必要,又浪费系统资源,因为所有speak
方法都是同样的行为,完全应该共享。
这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。
prototype 属性的作用
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
下面,先看怎么为对象指定原型。JavaScript 规定,每个函数都有一个prototype
属性,指向一个对象。
function f() {}
typeof f.prototype // "object"
上面代码中,函数f
默认具有prototype
属性,指向一个对象。
对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
上面代码中,构造函数Animal
的prototype
属性,就是实例对象cat1
和cat2
的原型对象。原型对象上添加一个color
属性,结果,实例对象都共享了该属性。
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"
上面代码中,原型对象的color
属性的值变为yellow
,两个实例对象的color
属性立刻跟着变了。这是因为实例对象其实没有color
属性,都是读取原型对象的color
属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
cat1.color = 'black';
cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';
上面代码中,实例对象cat1
的color
属性改为black
,就使得它不再去原型对象读取color
属性,后者的值依然为yellow
。
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
Animal.prototype.walk = function () {
console.log(this.name + ' is walking');
};
上面代码中,Animal.prototype
对象上面定义了一个walk
方法,这个方法将可以在所有Animal
实例对象上面调用。
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。也就是说,所有对象都继承了Object.prototype
的属性。这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。
那么,Object.prototype
对象有没有它的原型呢?回答是Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
。
Object.getPrototypeOf(Object.prototype)
// null
上面代码表示,Object.prototype
对象的原型是null
,由于null
没有任何属性,所以原型链到此为止。Object.getPrototypeOf
方法返回参数对象的原型
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype
还是找不到,则返回undefined
。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
注意,一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
举例来说,hasownProperty 有没有属于自己的属性
constructor 属性
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。
function A() {}
A.prototype.constructor === A // true
由于constructor
属性定义在prototype
对象上面,意味着可以被所有实例对象继承。
function A() {}
var a = new A();
a.constructor === A // true
a.constructor === A.prototype.constructor // true
a.hasOwnProperty('constructor') // false
上面代码中,p
是构造函数P
的实例对象,但是p
自身没有constructor
属性,该属性其实是读取原型链上面的P.prototype.constructor
属性。
constructor
属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
instanceof 运算符
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
var v = new Vehicle();
v instanceof Vehicle // true
上面代码中,对象v
是构造函数Vehicle
的实例,所以返回true
。
instanceof
运算符的左边是实例对象,右边是构造函数。它会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上。
var d = new Date();
d instanceof Date // true
d instanceof Object // true
上面代码中,d
同时是Date
和Object
的实例,因此对这两个构造函数都返回true
。
由于任意对象(除了null
)都是Object
的实例,所以instanceof
运算符可以判断一个值是否为非null
的对象。
构造函数的继承
举例来说,下面是一个Shape
构造函数。
function Shape() {
this.x = 0;
this.y = 0;
}
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
例如:我们需要让Rectangle
构造函数继承Shape
。
// 第一步,子类继承父类的实例
function Rectangle() {
Shape.call(this); // 调用父类构造函数
}
// 另一种写法
function Rectangle() {
this.base = Shape;
this.base();
}
// 第二步,子类继承父类的原型
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
Rectangle.prototype继承了 Object.create(Shape.prototype)的属性;
Rectangle.prototype.constructor 继承了Rectangle的属性。
多重继承
JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。
function M1() {
this.hello = 'hello';
}
function M2() {
this.world = 'world';
}
function S() {
M1.call(this);
M2.call(this);
}
// 继承 M1
S.prototype = Object.create(M1.prototype);
// 继承链上加入 M2
Object.assign(S.prototype, M2.prototype);
// 指定构造函数
S.prototype.constructor = S;
var s = new S();
s.hello // 'hello'
s.world // 'world'
上面代码中,子类S
同时继承了父类M1
和M2
。这种模式又称为 Mixin(混入)。
array
Array 对象
构造函数
Array
是 JavaScript 的原生对象,同时也是一个构造函数,可以用它生成新的数组。
var arr = new Array(2);
arr.length // 2
arr // [ empty x 2 ]
上面代码中,Array()
构造函数的参数2
,表示生成一个两个成员的数组,每个位置都是空值。
如果没有使用new
关键字,运行结果也是一样的。
var arr = Array(2);
// 等同于
var arr = new Array(2);
考虑到语义性,以及与其他构造函数用法保持一致,建议总是加上new
。
Array()
构造函数有一个很大的缺陷,不同的参数个数会导致不一致的行为。
// 无参数时,返回一个空数组
new Array() // []
// 单个正整数参数,表示返回的新数组的长度
new Array(1) // [ empty ]
new Array(2) // [ empty x 2 ]
// 非正整数的数值作为参数,会报错
new Array(3.2) // RangeError: Invalid array length
new Array(-3) // RangeError: Invalid array length
// 单个非数值(比如字符串、布尔值、对象等)作为参数,
// 则该参数是返回的新数组的成员
new Array('abc') // ['abc']
new Array([1]) // [Array[1]]
// 多参数时,所有参数都是返回的新数组的成员
new Array(1, 2) // [1, 2]
new Array('a', 'b', 'c') // ['a', 'b', 'c']
可以看到,Array()
作为构造函数,行为很不一致。因此,不建议使用它生成新数组,直接使用数组字面量是更好的做法。
// bad
var arr = new Array(1, 2);
// good
var arr = [1, 2];
注意,如果参数是一个正整数,返回数组的成员都是空位。虽然读取的时候返回undefined
,但实际上该位置没有任何值。虽然这时可以读取到length
属性,但是取不到键名。
var a = new Array(3);
var b = [undefined, undefined, undefined];
a.length // 3
b.length // 3
a[0] // undefined
b[0] // undefined
0 in a // false
0 in b // true
上面代码中,a
是Array()
生成的一个长度为3的空数组,b
是一个三个成员都是undefined
的数组,这两个数组是不一样的。读取键值的时候,a
和b
都返回undefined
,但是a
的键名(成员的序号)都是空的,b
的键名是有值的
实例方法
valueOf(),toString()
valueOf
方法表示对该对象求值。不同对象的valueOf
方法不尽一致,数组的valueOf
方法返回数组本身。
var arr = [1, 2, 3];
arr.valueOf() // [1, 2, 3]
toString
方法也是对象的通用方法,数组的toString
方法返回数组的字符串形式。
var arr = [1, 2, 3];
arr.toString() // "1,2,3"
var arr = [1, 2, 3, [4, 5, 6]];
arr.toString() // "1,2,3,4,5,6"
push(),pop()
push`方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。
var arr = [];
arr.push(1) // 1
arr.push('a') // 2
arr.push(true, {}) // 4
arr // [1, 'a', true, {}]
上面代码使用push
方法,往数组中添加了四个成员。
pop
方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组。
var arr = ['a', 'b', 'c'];
arr.pop() // 'c'
arr // ['a', 'b']
对空数组使用pop
方法,不会报错,而是返回undefined
。
[].pop() // undefined
push
和pop
结合使用,就构成了“后进先出”的栈结构(stack)。
var arr = [];
arr.push(1, 2);
arr.push(3);
arr.pop();
arr // [1, 2]
上面代码中,3
是最后进入数组的,但是最早离开数组。
shift(),unshift()
shift()
方法用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组。
var a = ['a', 'b', 'c'];
a.shift() // 'a'
a // ['b', 'c']
删除空数组会返回undefined
var a = ['a', 'b', 'c'];
a.shift()
shift()
方法可以遍历并清空一个数组。
var list = [1, 2, 3, 4];
var item;
while (item = list.shift()) {
console.log(item);
}
list // []
上面代码通过list.shift()
方法每次取出一个元素,从而遍历数组。它的前提是数组元素不能是0
或任何布尔值等于false
的元素,因此这样的遍历不是很可靠。
push()
和shift()
结合使用,就构成了“先进先出”的队列结构(queue)。
unshift()
方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。
var a = ['a', 'b', 'c'];
a.unshift('x'); // 4
a // ['x', 'a', 'b', 'c']
unshift()
方法可以接受多个参数,这些参数都会添加到目标数组头部。
var arr = [ 'c', 'd' ];
arr.unshift('a', 'b') // 4
arr // [ 'a', 'b', 'c', 'd' ]
join()
join()
方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。
var a = [1, 2, 3, 4];
a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"
如果数组成员是undefined
或null
或空位,会被转成空字符串。
[undefined, null].join('#')
// '#'
['a',, 'b'].join('-')
// 'a--b'
slice()
slice()
方法相当于切片
它的第一个参数为起始位置(从0开始,会包括在返回的新数组之中),第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员。
var a = ['a', 'b', 'c'];
a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]
a.slice() // ["a", "b", "c"]
上面代码中,最后一个例子slice()
没有参数,实际上等于返回一个原数组的拷贝。
果slice()
方法的参数是负数,则表示倒数计算的位置。
var a = ['a', 'b', 'c'];
a.slice(-2) // ["b", "c"]
a.slice(-2, -1) // ["b"]
上面代码中,-2
表示倒数计算的第二个位置,-1
表示倒数计算的第一个位置。
如果第一个参数大于等于数组长度,或者第二个参数小于第一个参数,则返回空数组。
var a = ['a', 'b', 'c'];
a.slice(4) // []
a.slice(2, 1) // []
slice()
方法的一个重要应用,是将类似数组的对象转为真正的数组。
Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
// ['a', 'b']
Array.prototype.slice.call(document.querySelectorAll("div"));
上面代码的参数都不是数组,但是通过call
方法,在它们上面调用slice()
方法,就可以把它们转为真正的数组。
sort()
sort
方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。
['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']
[4, 3, 2, 1].sort()
// [1, 2, 3, 4]
[11, 101].sort()
// [101, 11]
[10111, 1101, 111].sort()
// [10111, 1101, 111]
上面代码的最后两个例子,需要特殊注意。sort()
方法不是按照大小排序,而是按照字典顺序。也就是说,数值会被先转成字符串,再按照字典顺序进行比较,所以101
排在11
的前面。
map()
map()
方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
var numbers = [1, 2, 3];
numbers.map(function (n) {
return n + 1;
});
// [2, 3, 4]
numbers
// [1, 2, 3]
上面代码中,numbers
数组的所有成员依次执行参数函数,运行结果组成一个新数组返回,原数组没有变化。
filter()
filter()
方法用于过滤数组成员,满足条件的成员组成一个新数组返回。
它的参数是一个函数,所有数组成员依次执行该函数,返回结果为true
的成员组成一个新数组返回。该方法不会改变原数组。
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
})
// [4, 5]
上面代码将大于3
的数组成员,作为一个新数组返回。
var arr = [0, 1, 'a', false];
arr.filter(Boolean)
// [1, "a"]
上面代码中,filter()
方法返回数组arr
里面所有布尔值为true
的成员。
filter()
方法的参数函数可以接受三个参数:当前成员,当前位置和整个数组。
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0;
});
// [1, 3, 5]
上面代码返回偶数位置的成员组成的新数组。
总结
那些方法会改变原数组
1、push()
2、pop()
3、shift()
4、unshift()
5、sort()
那些方法不会改变原数组
1、map()
2、filter()
json
JSON 对象
JSON 格式
JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001年由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。
相比 XML 格式,JSON 格式有两个显著的优点:
1、书写简单,一目了然;
2、符合 JavaScript 原生语法,可以由解释引擎直接处理,不用另外添加解析代码。所以,JSON 迅速被接受,已经成为各大网站交换数据的标准格式,并被写入标准。
每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。总之,只能是一个值,不能是两个或更多的值。
JSON 对值的类型和格式有严格的规定。
复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用
NaN
,Infinity
,-Infinity
和undefined
)。NAN 不是一个数字
Infinity 无穷大 1/0
-Infinity 无穷小 -1/0
undefined 未定义
字符串必须使用双引号表示,不能使用单引号。
对象的键名必须放在双引号里面。
数组或对象最后一个成员的后面,不能加逗号。
以下都是合法的 JSON。
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 }
{"names": ["张三", "李四"] }
[ { "name": "张三"}, {"name": "李四"} ]
以下都是不合法的 JSON。
{ name: "张三", 'age': 32 } // 属性名必须使用双引号
[32, 64, 128, 0xFFF] // 不能使用十六进制值
{ "name": "张三", "age": undefined } // 不能使用 undefined
{ "name": "张三",
"birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
"getName": function () {
return this.name;
}
} // 属性值不能使用函数和日期对象
注意,null
、空数组和空对象都是合法的 JSON 值。
JSON 对象
JSON
对象是 JavaScript 的原生对象,用来处理 JSON 格式数据。它有两个静态方法:JSON.stringify()
和JSON.parse()
。
JSON.stringify()
JSON.stringify()可以将一个值转为 JSON 字符串
JSON.stringify('abc') // ""abc""
JSON.stringify(1) // "1"
JSON.stringify(false) // "false"
JSON.stringify([]) // "[]"
JSON.stringify({}) // "{}"
JSON.stringify([1, "false", false])
// '[1,"false",false]'
JSON.stringify({ name: "张三" })
// '{"name":"张三"}'
上面代码将各种类型的值,转成 JSON 字符串。
注意,对于原始类型的字符串,转换结果会带双引号。
JSON.stringify('foo') === "foo" // false
JSON.stringify('foo') === "\"foo\"" // true
上面代码中,字符串foo
,被转成了"\"foo\""
。这是因为将来还原的时候,内层双引号可以让 JavaScript 引擎知道,这是一个字符串,而不是其他类型的值。
JSON.stringify(false) // "false"
JSON.stringify('false') // "\"false\""
上面代码中,如果不是内层的双引号,将来还原的时候,引擎就无法知道原始值是布尔值还是字符串。
JSON.stringify()`方法会忽略对象的不可遍历的属性。
var obj = {};
Object.defineProperties(obj, {
'foo': {
value: 1,
enumerable: true
},
'bar': {
value: 2,
enumerable: false
}
});
JSON.stringify(obj); // "{"foo":1}"
上面代码中,bar
是obj
对象的不可遍历属性,JSON.stringify
方法会忽略这个属性。
第二个参数
1、JSON.stringify()
方法还可以接受一个数组,作为第二个参数,指定参数对象的哪些属性需要转成字符串。
var obj = {
'prop1': 'value1',
'prop2': 'value2',
'prop3': 'value3'
};
var selectedProperties = ['prop1', 'prop2'];
JSON.stringify(obj, selectedProperties);
// "{"prop1":"value1","prop2":"value2"}"
上面代码中,JSON.stringify()
方法的第二个参数指定,只转prop1
和prop2
两个属性。
2、第二个参数还可以是一个函数,用来更改JSON.stringify()
的返回值。
function f(key, value) {
if (typeof value === "number") { //typeof 判断类型
value = 2 * value;
}
return value;
}
JSON.stringify({ a: 1, b: 2 }, f)
// '{"a": 2,"b": 4}'
上面代码中的f
函数,接受两个参数,分别是被转换的对象的键名和键值。如果键值是数值,就将它乘以2
,否则就原样返回。
参数对象的 toJSON() 方法
如果参数对象有自定义的toJSON()
方法,那么JSON.stringify()
会使用这个方法的返回值作为参数,而忽略原对象的其他属性。
下面是一个普通的对象。
var user = {
firstName: '三',
lastName: '张',
get fullName(){
return this.lastName + this.firstName;
}
};
JSON.stringify(user)
// "{"firstName":"三","lastName":"张","fullName":"张三"}"
现在,为这个对象加上toJSON()
方法。
var user = {
firstName: '三',
lastName: '张',
toJSON: function () {
return {
name: this.lastName + this.firstName
};
};
;
JSON.stringify(user);
// "{"name":"张三"}"
上面代码中,JSON.stringify()
发现参数对象有toJSON()
方法,就直接使用这个方法的返回值作为参数,而忽略原对象的其他参数。
Date对象就有一个自己的
toJSON()`方法。
var date = new Date('2015-01-01');
date.toJSON() // "2015-01-01T00:00:00.000Z"
JSON.stringify(date) // ""2015-01-01T00:00:00.000Z""
上面代码中,JSON.stringify()
发现处理的是Date
对象实例,就会调用这个实例对象的toJSON()
方法,将该方法的返回值作为参数。
toJSON()
方法的一个应用是,将正则对象自动转为字符串。因为JSON.stringify()
默认不能转换正则对象,但是设置了toJSON()
方法以后,就可以转换正则对象了。
JSON.parse()
JSON.parse()可以**将一个字符串转为 JSON值 **
JSON.parse('{}') // {}
JSON.parse('true') // true
JSON.parse('"foo"') // "foo"
JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
JSON.parse('null') // null
var o = JSON.parse('{"name": "张三"}');
o.name // 张三
如果传入的字符串不是有效的 JSON 格式,JSON.parse()
方法将报错。
JSON.parse("'String'") // illegal single quotes
// SyntaxError: Unexpected token ILLEGAL
上面代码中,双引号字符串中是一个单引号字符串,因为单引号字符串不符合 JSON 格式,所以报错。
为了处理解析错误,可以将JSON.parse()
方法放在try...catch
代码块中。
try {
JSON.parse("'String'");
} catch(e) {
console.log('parsing error');
}
JSON.parse()
方法可以接受一个处理函数,作为第二个参数,用法与JSON.stringify()
方法类似。
function f(key, value) {
if (key === 'a') {
return value + 10;
}
return value;
}
JSON.parse('{"a": 1, "b": 2}', f)
// {a: 11, b: 2}
上面代码中,JSON.parse()
的第二个参数是一个函数,如果键名是a
,该函数会将键值加上10。
总结
1、JSON.parse()可以**将一个字符串转为 JSON值 **
var JSON_parse = '{"name":"张三","age":"18","身高":"1.8"}';
var obj = JSON.parse(JSON_parse);
console.log(obj); //{name: '张三', age: '18', 身高: '1.8'}
2、JSON.stringify()可以将一个值转为 JSON 字符串
var JSON_stringify = '{name:李四, age:28 , 身高:1.8}';
var obj = JSON.stringify(JSON_stringify);
console.log(obj);