JSON那些事
JSON(JavaScript Object Notation)是一种轻量级的数据格式,采用完全独立于语言的文本格式,是理想的数据交换格式。同时,JSON是Javascript原生格式,这意味着在javascript中处理JSON数据不需要任何特殊的API或工具包,而且效率非常高。
JSON的结构如下:
“名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)
一个标准的json格式:
{"name":"jifeng","company":"taobao"}
总体而言,json是相对比较容易的理解和使用的,但同时存在很多的陷阱,如果不注意的话很容易掉进去,本文主要就是讲诉它容易让人犯错的地方。
一、json的的解析方法
json的解析方法共有两种:1. eval() ; 2.JSON.parse()。具体使用方法如下
var jsonstr = '{"name":"jifeng","company":"taobao"}';//eval functionvar evalJson = eval('(' + jsonstr + ')');// JSON.parse functionvar JSONParseJson = JSON.parse(jsonstr);
既然两者都能达到解析json的结果,但这两者有什么区别呢?我用以下的一个例子来进行说明:
1. 用eval方法:
var parse_json_by_eval = function(str){return eval('('+str+')');}var value = 1;var jsonstr = '{"name":"jifeng","company":"taobao","value":++value}';var json1 = parse_json_by_eval(jsonstr);console.log(json1);console.log('value: '+ value);
执行结果:
{ name: 'jifeng', company: 'taobao', value: 2 }value: 2
2. 用JSON.parse方法
var parse_json_by_JSON_parse = function(str){return JSON.parse(str);}value = 1;var jsonstr = '{"name":"jifeng","company":"taobao","value":++value}';var json2 = parse_json_by_JSON_parse(jsonstr);console.log(json2);console.log(value);
执行结果:
不能顺利执行,报错
前者能顺利执行,并修改了全局变量value的值,而后者报错了。从上例就可以明显地看出,eval在解析字符串时,会执行该字符串中的代码(这样的后果是相当恶劣的),如上例中,由于用eval解析一个json字符串而造成原先的value的值改变。《高性能Javascript》一书即指出:
警告:关于JSON和eval需要注意的是:在代码中使用eval是很危险的,特别是用它执行第三方的JSON数据(其中可能包含恶意代码)时,尽可能使用JSON.parse()方法解析字符串本身。该方法可以捕捉JSON中的语法错误,并允许你传入一个函数,用来过滤或转换解析结果。如果此方法以备Firfox 3.5 、IE8 及 Safari 4 原生支持。大多数javascript类库包含的JSON解析代码会直接调用原生版本,如果没有原生支持的话,会调用一个略微不那么强大的非原生版本来处理。
一、'\' 对JSON的影响
由于JSON对象是从字符串解析而来,因此在了解 '\' 对JSON影响之前,我们先来了解 '\' 对字符串的影响。这里特别要强调的是在javascript中 ' 和 " 在表示字符串时是等价,比如'a' = "a"
众所周知,由于String数据类型包含转义字符,比如 '\n'表示换行,'\b'表示空格等,因此用表示 he say “hello” 这个字符串时,需要用 '\' 这个特殊字符来表示,即"he say \"hello \"" 。在chrome的中console.log()即可明显的看出。
而JSON.parse()又是对字符串的真实含义进行解析,要表示 \ 必须要用"\\\\"表示,具体见下图:
在json中出现'\'时,必须万分小心,这的确是json中最容易出错的地方
在顺便提一个小插曲:当我知道在json解析时需要用"\\\\"时表示"\"时,你用JSON.parse('{"a":"a\\b"}'),竟然没有报错,难道我理解错了,细心的朋友应该看出来了,'\b'本身就是个转义字符,因此这里的第一个'\'是用来转义第二个'\' ,这样字符串就编程'a\b'('\b'是个转义字符),所以JSON.parse()还是可以顺利解析的。
对这个概念还是有点绕的,读者需要多想想,自己当时也是花了较长的时间来想这个问题。
补充资料:
线上解析json网站:http://json.parser.online.fr/
JSON那些事
JSON(JavaScript Object Notation)是一种轻量级的数据格式,采用完全独立于语言的文本格式,是理想的数据交换格式。同时,JSON是Javascript原生格式,这意味着在javascript中处理JSON数据不需要任何特殊的API或工具包,而且效率非常高。
JSON的结构如下:
“名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)
一个标准的json格式:
{"name":"jifeng","company":"taobao"}
总体而言,json是相对比较容易的理解和使用的,但同时存在很多的陷阱,如果不注意的话很容易掉进去,本文主要就是讲诉它容易让人犯错的地方。
一、json的的解析方法
json的解析方法共有两种:1. eval() ; 2.JSON.parse()。具体使用方法如下
var jsonstr = '{"name":"jifeng","company":"taobao"}';//eval functionvar evalJson = eval('(' + jsonstr + ')');// JSON.parse functionvar JSONParseJson = JSON.parse(jsonstr);
既然两者都能达到解析json的结果,但这两者有什么区别呢?我用以下的一个例子来进行说明:
1. 用eval方法:
var parse_json_by_eval = function(str){return eval('('+str+')');}var value = 1;var jsonstr = '{"name":"jifeng","company":"taobao","value":++value}';var json1 = parse_json_by_eval(jsonstr);console.log(json1);console.log('value: '+ value);
执行结果:
{ name: 'jifeng', company: 'taobao', value: 2 }value: 2
2. 用JSON.parse方法
var parse_json_by_JSON_parse = function(str){return JSON.parse(str);}value = 1;var jsonstr = '{"name":"jifeng","company":"taobao","value":++value}';var json2 = parse_json_by_JSON_parse(jsonstr);console.log(json2);console.log(value);
执行结果:
不能顺利执行,报错
前者能顺利执行,并修改了全局变量value的值,而后者报错了。从上例就可以明显地看出,eval在解析字符串时,会执行该字符串中的代码(这样的后果是相当恶劣的),如上例中,由于用eval解析一个json字符串而造成原先的value的值改变。《高性能Javascript》一书即指出:
警告:关于JSON和eval需要注意的是:在代码中使用eval是很危险的,特别是用它执行第三方的JSON数据(其中可能包含恶意代码)时,尽可能使用JSON.parse()方法解析字符串本身。该方法可以捕捉JSON中的语法错误,并允许你传入一个函数,用来过滤或转换解析结果。如果此方法以备Firfox 3.5 、IE8 及 Safari 4 原生支持。大多数javascript类库包含的JSON解析代码会直接调用原生版本,如果没有原生支持的话,会调用一个略微不那么强大的非原生版本来处理。
一、'\' 对JSON的影响
由于JSON对象是从字符串解析而来,因此在了解 '\' 对JSON影响之前,我们先来了解 '\' 对字符串的影响。这里特别要强调的是在javascript中 ' 和 " 在表示字符串时是等价,比如'a' = "a"
众所周知,由于String数据类型包含转义字符,比如 '\n'表示换行,'\b'表示空格等,因此用表示 he say “hello” 这个字符串时,需要用 '\' 这个特殊字符来表示,即"he say \"hello \"" 。在chrome的中console.log()即可明显的看出。
而JSON.parse()又是对字符串的真实含义进行解析,要表示 \ 必须要用"\\\\"表示,具体见下图:
在json中出现'\'时,必须万分小心,这的确是json中最容易出错的地方
在顺便提一个小插曲:当我知道在json解析时需要用"\\\\"时表示"\"时,你用JSON.parse('{"a":"a\\b"}'),竟然没有报错,难道我理解错了,细心的朋友应该看出来了,'\b'本身就是个转义字符,因此这里的第一个'\'是用来转义第二个'\' ,这样字符串就编程'a\b'('\b'是个转义字符),所以JSON.parse()还是可以顺利解析的。
对这个概念还是有点绕的,读者需要多想想,自己当时也是花了较长的时间来想这个问题。
补充资料:
线上解析json网站:http://json.parser.online.fr/
Python如何将unicode转换为中文
今天碰到一个很有意思的问题,需要将普通的 Unicode字符串转换为 Unicode编码的字符串,如下:将 \u9500\u552e 转化为 \u9500\u552e 也就是销售两个字。乍一看感觉挺简单的,用 re 库将前面的反斜杠去掉即可,但是在替换的过程中会抛出如下错误:Traceback (most recent call last): File "<pyshell#15>", line 1, in re.sub(r"(\)\u", r'', t)File "D:\Python36\lib\re.py", line 191, in subreturn _compile(pattern, flags).sub(repl, string, count)File "D:\Python36\lib\re.py", line 301, in _compilep = sre_compile.compile(pattern, flags)File "D:\Python36\lib\sre_compile.py", line 562, in compilep = sre_parse.parse(p, flags)File "D:\Python36\lib\sre_parse.py", line 855, in parsep = _parse_sub(source, pattern, flags & SRE_FLAG_VERBOSE, 0)File "D:\Python36\lib\sre_parse.py", line 416, in _parse_subnot nested and not items))File "D:\Python36\lib\sre_parse.py", line 765, in _parsep = _parse_sub(source, state, sub_verbose, nested + 1)File "D:\Python36\lib\sre_parse.py", line 416, in _parse_subnot nested and not items))File "D:\Python36\lib\sre_parse.py", line 502, in _parsecode = _escape(source, this, state)File "D:\Python36\lib\sre_parse.py", line 362, in _escaperaise source.error("incomplete escape %s" % escape, len(escape))sre_constants.error: incomplete escape \u at position 3大概意思就是去掉前面的反写杠之后剩下的 \u 不能组成完整的字符。到这里问题好像有点难以解决了,这时候我们会放弃吗?当然不会。于是我到谷歌上搜了一下,发现还真有人碰到过这个问题,解决方法也是十分的巧妙。竟然还可以使用 json 库的 loads 方法 ...解决方法如下:import json
s = '\\u9500\\u552e'
print(json.loads(f'"{s}"'))另外:python3 将字符串unicode转换为中文得到的文本打印出来是“\uxxxx”的字符串格式,在python3中使用text.decode('unicode_escape')会报错:‘str' object has no attribute 'decode'正确的姿势是:text.encode('utf-8').decode("unicode_escape")还有一个就是在爬取网站时,最终得到的是list内容,编码为unicode,想让其转换为汉字并输出。需要提取的为下图中unicode部分:保存为列表,然后使用for循环:text为获取的网页。pat = '"group": {"text": "(.*?)"'
text_list = re.compile(pat).findall(text)
for i in text_list:
print(i.encode('latin-1').decode('unicode_escape'))输出结果:
27、JSON 对象
JSON 格式JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001年由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。相比 XML 格式,JSON 格式有两个显著的优点:书写简单,一目了然;符合 JavaScript 原生语法,可以由解释引擎直接处理,不用另外添加解析代码。所以,JSON 迅速被接受,已经成为各大网站交换数据的标准格式,并被写入标准。每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。总之,只能是一个值,不能是两个或更多的值。JSON 对值的类型和格式有严格的规定。复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinity和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 格式,并且可以被JSON.parse()方法还原。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\""上面代码中,如果不是内层的双引号,将来还原的时候,引擎就无法知道原始值是布尔值还是字符串。如果对象的属性是undefined、函数或 XML 对象,该属性会被JSON.stringify()过滤。var obj = {
a: undefined,
b: function () {}
};
JSON.stringify(obj) // "{}"上面代码中,对象obj的a属性是undefined,而b属性是一个函数,结果都被JSON.stringify过滤。如果数组的成员是undefined、函数或 XML 对象,则这些值被转成null。var arr = [undefined, function () {}];
JSON.stringify(arr) // "[null,null]"上面代码中,数组arr的成员是undefined和函数,它们都被转成了null。正则对象会被转成空对象。JSON.stringify(/foo/) // "{}"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方法会忽略这个属性。第二个参数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两个属性。这个类似白名单的数组,只对对象的属性有效,对数组无效。JSON.stringify(['a', 'b'], ['0'])
// "["a","b"]"
JSON.stringify({0: 'a', 1: 'b'}, ['0'])
// "{"0":"a"}"上面代码中,第二个参数指定 JSON 格式只转0号属性,实际上对数组是无效的,只对对象有效。第二个参数还可以是一个函数,用来更改JSON.stringify()的返回值。function f(key, value) {
if (typeof value === "number") {
value = 2 * value;
}
return value;
}
JSON.stringify({ a: 1, b: 2 }, f)
// '{"a": 2,"b": 4}'上面代码中的f函数,接受两个参数,分别是被转换的对象的键名和键值。如果键值是数值,就将它乘以2,否则就原样返回。注意,这个处理函数是递归处理所有的键。var obj = {a: {b: 1}};
function f(key, value) {
console.log("["+ key +"]:" + value);
return value;
}
JSON.stringify(obj, f)
// []:[object Object]
// [a]:[object Object]
// [b]:1
// '{"a":{"b":1}}'上面代码中,对象obj一共会被f函数处理三次,输出的最后那行是JSON.stringify()的默认输出。第一次键名为空,键值是整个对象obj;第二次键名为a,键值是{b: 1};第三次键名为b,键值为1。递归处理中,每一次处理的对象,都是前一次返回的值。var obj = {a: 1};
function f(key, value) {
if (typeof value === 'object') {
return {b: 2};
}
return value * 2;
}
JSON.stringify(obj, f)
// "{"b": 4}"上面代码中,f函数修改了对象obj,接着JSON.stringify()方法就递归处理修改后的对象obj。如果处理函数返回undefined或没有返回值,则该属性会被忽略。function f(key, value) {
if (typeof(value) === "string") {
return undefined;
}
return value;
}
JSON.stringify({ a: "abc", b: 123 }, f)
// '{"b": 123}'上面代码中,a属性经过处理后,返回undefined,于是该属性被忽略了。第三个参数JSON.stringify()还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。默认返回的是单行字符串,对于大型的 JSON 对象,可读性非常差。第三个参数使得每个属性单独占据一行,并且将每个属性前面添加指定的前缀(不超过10个字符)。// 默认输出
JSON.stringify({ p1: 1, p2: 2 })
// JSON.stringify({ p1: 1, p2: 2 })
// 分行输出
JSON.stringify({ p1: 1, p2: 2 }, null, '\t')
// {
// "p1": 1,
// "p2": 2
// }上面例子中,第三个属性\t在每个属性前面添加一个制表符,然后分行显示。第三个属性如果是一个数字,则表示每个属性前面添加的空格(最多不超过10个)。JSON.stringify({ p1: 1, p2: 2 }, null, 2);
/*
"{
"p1": 1,
"p2": 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: '张',
get fullName(){
return this.lastName + this.firstName;
},
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()方法以后,就可以转换正则对象了。var obj = {
reg: /foo/
};
// 不设置 toJSON 方法时
JSON.stringify(obj) // "{"reg":{}}"
// 设置 toJSON 方法时
RegExp.prototype.toJSON = RegExp.prototype.toString;
JSON.stringify(/foo/) // ""/foo/""上面代码在正则对象的原型上面部署了toJSON()方法,将其指向toString()方法,因此转换成 JSON 格式时,正则对象就先调用toJSON()方法转为字符串,然后再被JSON.stringify()方法处理。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。参考链接MDN, Using native JSONMDN, JSON.parseDr. Axel Rauschmayer, JavaScript’s JSON APIJim Cowart, What You Might Not Know About JSON.stringify()Marco Rogers, What is JSON?
浅析ajax原理与用法
浅析ajax原理与用法
1 ajax原理
Ajax(Asynchronous JavaScript and XML (异步的JavaScript和XML)),是一种快速创建动态网页的技术,目的
是显示动态局部刷新.通过XMLHttpResponse对象来向服务器发起异步请求,从服务器获取数据.
(1) 异步的javascript: 使用javaScript语言及功能向服务器发起请求,当服务器处理完请求之后,自动执行
javaScript回调函数.(客户端可以不需要等到服务器响应才能运行)
(2) XML是一种标记语言,是ajax与后台交互传输数据格式之一
(3) ajax只能向同源网站发起Http请求(端口,协议,域名都相同),跨域请求会报错
利用Ajax可以实现:
1 注册时,输入用户名自动检测该用户是否存在
2 登录时提示密码用户名错误
3 页面局部进行更新数据
2 XMLHttpResponse基本属性
(1) onreadystatechange 每次状态改变所触发事件的时间处理程序
(2) responseText 从服务器响应返回字符串形式数据
(3) responseXML 从服务器响应返回以XML(DOM兼容文档)数据对象
(4) response 从服务器返回对象:如json对象
(5) timeout 表示多少毫秒后,如果请求仍然没有得到结果,就会自动终止
附: response类型由XMLHttpRequest.responseType属性的值决定
'': 字符串
'document': Document对象
'json': json对象
'text': 字符串
'blob': Blob对象,适合读取二进制文件
var ajax = new XMLHttpRequest();
ajax.open('GET', 'http://www.example.com/page.php', true);
var data = ajax.responseText;
data = JSON.parse(data); 将json字符串转json对象
var last=JSON.stringify(obj) 将json对象转字符串
3 ajax状态码
200, OK,访问正常
301, Moved Permanently,永久移动
302, Move temporarily,暂时移动
304, Not Modified,未修改
307, Temporary Redirect,暂时重定向
401, Unauthorized,未授权
403, Forbidden,禁止访问
404, Not Found,未发现指定网址
500, Internal Server Error,服务器发生错误
4 时间监听接口
onloadstart 请求发出
onprogress 正在发送和加载数据
onerror 请求失败
onload 请求成功完成
ontimeout 用户指定的时限到期,请求还未完成
onloadend 请求完成,不管成果或失败
5 ajax解析
第一步,创建ajax对象
高级浏览器,包括火狐、chrome、opera,ie7以上
var xhr = new XMLHttpRequest();
使用XMLHttpRequest对象的open创建请求
/*参数说明:
method:该参数用于指定HTTP的请求方法,一共有get、post、head、put、delete五种方法
,常用的方法为get和post。
URL:该参数用于指定HTTP请求的URL地址,可以是绝对URL,也可以是相对URL。
该文件可以是任何类型的文件,比如 .txt 和 .xml,或者服务器脚本文件,比如 .asp 和 .php(在传回响应之前,能够在服务器上执行任务)。
flag:该参数为可选参数,参数值为布尔型。该参数用于指定是否使用异步方式。
true表示异步方式、false表示同步方式,默认为true。
name:该参数为可选参数,用于输入用户名。如果服务器需要验证,则必须使用该参数。
password:该参数为可选参数,用于输入密码。
XMLHttpRequest.open(method,URL,flag,name,password);
//例
xhr.open('get','2.py?username=tom&pwd=123');
如果是get请求,参数需要附加到url里
第三步,发送请求
如果是get请求,send参数可以为空;
如果为post请求,参数必须在send参数中传递,参数格式和url中设置的一样
如果为post方式需要在send前,设置请求头信息:xmlHttp.setRequestHeader(“Content-Type”,”application/x-www-form-urlencoded”)
//get方式
xhr.send(null);
//post方式
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
xhr.send('username=tom&pwd=123');
第四步,监听Ajax状态变化
向服务器发送请求的目的是为了获得服务器端的数据,要获得服务器端返回的数据,需要监听XMLHttpRequest的状态,每当XMLHttpRequest状态发生改变时,会触发事件onreadystatechange,我们可以编写onreadystatechange事件处理函数,获取服务器返回的数据。
XMLHttpRequest对象的状态,可以通过属性readyState获取,公有以下5种状态:
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
当readyState值为4时,就可以获取返回数据了,但返回的数据是成功还是失败,需要根据http的状态码确定,XMLHttpRequest的status属性用于判断http的状态,200表示访问成功
xhr.onreadystatechange = function () {
if (4 == xhr.readyState) {
if (200 == xhr.status ) {
alert(xhr.responseText);
}
}
};
第五步,获取响应数据
可以通过XMLHttpRequest对象的responseText获取数据,现在已经不再使用responseXML获取数据了
客户端获取json数据
xhr.onreadystatechange = function () {
if (4 == this.readyState && 200 == this.status) {
//1.通过eval将json字符串转换为对象
// var obj = eval('('+xhr.responseText+')');
// console.log(obj);
//2 使用json.parse将json字符串转换为对象
obj = JSON.parse(this.responseText);
console.log(obj);
}
};
JSON字符串和JSON对象的转换
//json字符串 ==> json对象
var obj = JSON.parse(json字符串);
//或者
var obj = eval('(' + json字符串 + ')');
//json对象转json串
var str=obj.toJSONString();
//或
var str=JSON.stringify(obj);
6 原生javascript中ajax
{1} 同步ajax请求,get请求:
#定义一个触发函数fresh_code
function fresh_code() {
let xhr = new XMLHttpRequest()
xhr.open('get', '/api/v1/regist_code', false); false代表同步,true代表异步请求
xhr.send()
let regist_code = xhr.responseText
// 把注册码写到regist_code标签中
let regist_code_span = document.getElementById('regist_code')
regist_code_span.innerHTML = regist_code
}
{2} 异步ajax请求,get请求:
var xhr = new XMLHttpRequest();
// 指定通信过程中状态改变时的回调函数
xhr.onreadystatechange = function(){
// 通信成功时,状态值为4,状态码为200
if (xhr.readyState === 4 && xhr.status === 200) {
//xhr.response指接收到的responseText字符串
var data=JSON.parse(xhr.responseText);
console.log(data)
}
else {
console.error(xhr.statusText);
}
}
};
xhr.onerror = function (e) {
console.error(xhr.statusText);
};
// open方式用于指定HTTP动词、请求的网址、是否异步
xhr.open('GET', '/endpoint', true);
// 发送HTTP请求
xhr.send(null);
{3} 同步ajax请求,post请求
var name = encodeURI("北京西");
var data = "username=" + name + "&password=123";
//调用send()函数
send(data);
function send(arg) {
CreateXMLHttpRequest();
xmlhttp.onreadystatechange = callhandle;
//xmlhttp.open("GET","Default.aspx?goback=yes&arg=" + arg,true);
xmlhttp.open("POST", "http://172.16.5.25:9011/dynamicStation/loginByAdmin", true);
//用POST的时候一定要有这句
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;");
xmlhttp.send(arg);
}
function CreateXMLHttpRequest() {
if (window.ActiveXObject) {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}
}
7 jquery中ajax
$.ajax({
type:'get',
url:'/studentsinfo',
dataType:'json',
success:function(data,status){
console.log(data)
var d=data['data']
for(var i=0;i<d.length;i++){
document.write('<p>'+d[i][0]+'</p>')
}
}
})
也可以直接使用$get 或者 $post
python爬虫urllib模块详解
1.urllib模块简介
python2有urllib和urllib2两种模块,都用来实现网络请求的发送。python3将urllib和urllib2模块整合并命名为urllib模块。urllib模块有多个子模块,各有不同的功能:
①urllib.request模块:用于实现基本的http请求。②urllib.error模块:用于异常处理。如在发送网络请求时出现错误,用该模块捕捉并处理。③urllib.parse模块:用于解析。④urllib.robotparser:用于解析robots.txt文件,判断是否可以爬取网站信息。2.发送请求:urlopen()方法
urloprn() 方法由urllib.request模块提供,以实现基本的http请求,并接受服务器端返回的响应数据。 urlopen()语法: urllib.request.urlopen(url,data=None,[timeout,]*,cafile=None,capath=None,cadefault=False,context=None)
url:访问网站的完整url地址data:默认为None,表示请求方式为get请求;如果需要实现post请求,需要字典形式的数据作为参数。timeout:设置超时,单位为秒。cafile,capath:指定一组HTTPS请求信任的CA证书,cafile指包含单个证书的CA文件,capath指定证书文件的目录。cadefault:CA证书的默认值。context:描述SSL选项的 的实例。3. 发送GET请求
代码示例
import urllib.request # 导入request子模块
url = 'https://www.python.org/'
response = urllib.request.urlopen(url=url) # 发送网络请求
print('响应数据类型为:', type(response))
print('响应状态码为:', response.status)
print('响应状态码为:', response.getcode())
print('响应头所有信息为:', response.getheaders())
print('响应头指定信息为:', response.getheader('Accept-Ranges'))
# 读取HTML代码并进行utf-8解码
print('Python官网HTML代码如下:\n', response.read().decode('utf-8'))
输出结果如下(部分):
response是一个<class ‘http.client.HTTPResponse’>对象;响应状态码也称响应码,也称状态码,可以通过status属性查看,也可以通过getcode()方法查看。getheaders()用于查看响应头所有信息getheader()中传入参数,用于查看响应头的指定信息。关于请求头&响应头 当你使用http(https)协议请求一个网站的时候,你的浏览器会向对方的服务器发送一个http请求,这个请求同样包含三个部分请求方法 请求路径(URL) 请求协议版本 例:GET https://www.google.com.hk/ HTTP/1.1报文主体(POST/GET)参数当你向对方发送请求后,对方会回应你浏览器的请求,返回两个部分:响应头,Body Body就是返回给你的主体,比如说请求网站返回的html 响应头讯息里包含了服务器的响应讯息,如http版本,压缩方式,响应文件类型,文件编码等
4.发送post请求
即在上边基础上,在urlopen()函数中写入data参数。详情参考链接:python爬虫post访问案例-有道翻译。
post请求会携带一些form表单数据,这个需要复制过来以字典形式写入。 表单数据在网页上点击F12后,在Fetch/XHR一栏中的Payload中获取。 以爬取有道翻译,翻译“你好”为例,此时的表单数据如下:i: 你好 from: AUTO to: AUTO smartresult: dict client: fanyideskweb salt: 16417380413821 sign: 6545acd2d928b39eb5bead9349a2d4ff lts: 1641738041382 bv: fdac15c78f51b91dabd0a15d9a1b10f5 doctype: json version: 2.1 keyfrom: fanyi.web action: FY_BY_REALTlME
代码示例如下:
import urllib.request
import urllib.parse
import json
# 要进行输入内容
key = input("请输入内容:")
data = {
"i": key,
"from": "AUTO",
"to": "AUTO",
"smartresult": "dict",
"client": "fanyideskweb",
"salt": "16374132986488",
"sign": "dfd139af546a8cd63de0676f446ca2ee",
"lts": "1637413298648",
"bv": "03a6a27012b22bc3c7ecc76381772182",
"doctype": "json",
"version": "2.1",
"keyfrom": "fanyi.web",
"action": "FY_BY_REALTlME",
}
# 字节流,如果输入中文,需要处理的
data = urllib.parse.urlencode(data) # 转为十六进制形式
data = bytes(data, encoding='utf8') # 转为字节流
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'
}
# 目标url发请求
# {"errorCode":50} ,把_o
url = 'https://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'
# 构建请求对象
req = urllib.request.Request(url, data=data, headers=headers)
# 发请求,获取响应对象
res = urllib.request.urlopen(req)
# print(res.getcode()) # 得到响应码,200表示请求成功
html = res.read().decode('utf-8')
# print(type(html)) # <class 'str'>,得到的是json数据
# json数据转字典
dic = json.loads(html)
result = dic["translateResult"] # [[{'src': '你好', 'tgt': 'hello'}]]
print(result[0][0]['tgt'])
另一个简单的示例:
import urllib.request # 导入urllib.request模块
import urllib.parse # 导入urllib.parse模块
url = 'https://www.httpbin.org/post' # post请求测试地址
# 将表单数据转换为bytes类型,并设置编码方式为utf-8
print()
data = bytes(urllib.parse.urlencode({'hello': 'python'}), encoding='utf-8')
response = urllib.request.urlopen(url=url, data=data) # 发送网络请求
print(response.read().decode('utf-8')) # 读取HTML代码并进行
5. 设置网络超时
urlopen()的timeout参数用于设置请求超时,该参数以秒为单位,表示如果在请求时超出了设置的时间还没有得到响应时就会抛出异常。
import urllib.request
url = 'https://www.python.org/'
response = urllib.request.urlopen(url=url, timeout=0.1) # 设置超时时间为0.1秒
print(response.read().decode('utf-8'))
因为0.1秒设置的过快,结果因超时而产生异常,报错。 通常根据网络环境不同,设置一个合理的时间,如2秒,3秒。 对该网络超时异常进行捕捉并处理:
import urllib.request # 导入urllib.request模块
import urllib.error # 导入urllib.error模块
import socket # 导入socket模块
url = 'https://www.python.org/' # 请求地址
try:
# 发送网络请求,设置超时时间为0.1秒
response = urllib.request.urlopen(url=url, timeout=0.1)
print(response.read().decode('utf-8')) # 读取HTML代码并进行utf-8解码
except urllib.error.URLError as error: # 处理异常
if isinstance(error.reason, socket.timeout): # 判断异常是否为超时异常
print('当前任务已超时,即将执行下一任务!')
6. 复杂网络请求_urllib.request.Request()
urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None) 参数说明:
url:访问网站的完整url地址data:默认为None,表示请求方式为get请求;如果需要实现post请求,需要字典形式的数据作为参数。headers:设置请求头部信息,字典类型。origin_req_host:用于设置请求方的host名称或者IP。unverifiable:用于设置网页是否需要验证,默认值为False。method:用于设置请求方式,如GET,POST。7.设置请求头
7.1get请求示例
url = 'https://www.baidu.com'
# 定义请求头信息
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'}
# 创建Request对象
r = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(r) # 发送网络请求
print(response.read().decode('utf-8')) # 读取HTML代码并进行utf-8解码
7.2post请求示例
import urllib.request # 导入urllib.request模块
import urllib.parse # 导入urllib.parse模块
url = 'https://www.httpbin.org/post' # 请求地址
# 定义请求头信息
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'}
# 将表单数据转换为bytes类型,并设置编码方式为utf-8
data = bytes(urllib.parse.urlencode({'hello': 'python'}),encoding='utf-8')
# 创建Request对象
r = urllib.request.Request(url=url,data=data,headers=headers,method='POST')
response = urllib.request.urlopen(r) # 发送网络请求
print(response.read().decode('utf-8')) # 读取HTML代码并进行utf-8解码
创建出一个Request对象,然后直接调用urlopen()函数。 创建Request也被称为创建请求对象。
8. Cookies的获取与设置
Cookies是服务器向客户端返回响应数据时所留下的标记。当客户再次访问服务器时会携带这个标记。一般登录一个页面成功后,会在浏览器的cookie中保留一些信息,再次访问时服务器核对后即可确认当前用户登录过,此时可直接将登录后的数据返回。
import urllib.request
url = "https://www.csdn.net/"
opener = urllib.request.build_opener() # 获取opener对象
resp = opener.open(url)
print(resp.read().decode())
因为urlopen()方法不支持代理、cookie等其他的HTTP/GTTPS高级功能,所以这里不用urlopen()发送请求,而需要创建一个opener对象(这本来是urllib2中的方法)。示例如下。学习过程中这里作为了解即可,建议重点研究/使用requests库。
import urllib.request # 导入urllib.request模块
import http.cookiejar # 导入http.cookiejar子模块
# 登录后页面的请求地址
url = 'xxx'
cookie_file = 'cookie.txt' # cookie文件
cookie = http.cookiejar.LWPCookieJar() # 创建LWPCookieJar对象
# 读取cookie文件内容
cookie.load(cookie_file,ignore_expires=True,ignore_discard=True)
# 生成cookie处理器
handler = urllib.request.HTTPCookieProcessor(cookie)
# 创建opener对象
opener = urllib.request.build_opener(handler)
response = opener.open(url) # 发送网络请求
print(response.read().decode('utf-8')) # 打印登录后页面的html代码
9.设置代理IP
import urllib.request # 导入urllib.request模块
url= 'xxxxxxxxxxxxxxx'
# 创建代理IP
proxy_handler = urllib.request.ProxyHandler({
'https': 'xxxxxxxxxxxxxxxxx' # 写入代理IP
})
# 创建opener对象
opener = urllib.request.build_opener(proxy_handler)
response = opener.open(url,timeout=2)
print(response.read().decode('utf-8'))
10.异常处理
urllib模块中的urllib.error子模块包含了URLError与HTTPError两个比较重要的异常类。
10.1 URLError
URLError类提供了一个reason属性,可以通过这个属性了解错误的原因。示例如下:
import urllib.request # 导入urllib.request模块
import urllib.error # 导入urllib.error模块
try:
# 向不存在的网络地址发送请求
response = urllib.request.urlopen('https://www.python.org/1111111111.html')
except urllib.error.URLError as error: # 捕获异常信息
print(error.reason) # 打印异常原因
程序运行结果:
10.2HTTPError
HTTPError类是URLError的子类,主要用于处理HTTP请求所出现的一次。此类有以下三个属性。
code :返回HTTP状态码reason 返回错误原因headers 返回请求头import urllib.request # 导入urllib.request模块
import urllib.error # 导入urllib.error模块
try:
# 向不存在的网络地址发送请求
response = urllib.request.urlopen('https://www.python.org/1111111111.html')
print(response.status)
except urllib.error.HTTPError as error: # 捕获异常信息
print('状态码为:',error.code) # 打印状态码
print('异常信息为:',error.reason) # 打印异常原因
print('请求头信息如下:\n',error.headers) # 打印请求头
结果如下(部分):
10.3URLError&HTTPError双重异常捕捉
因为URLError是HTTPError的父类,所以在捕获异常的时候可以先找子类是否异常,父类的异常应当写到子类异常的后面,如果子类捕获不到,那么可以捕获父类的异常。 URLError产生的原因主要是
网络没有连接,
服务器连接失
找不到指定的服务器。当使用urlopen或 opener.open 不能处理的,服务器上都对应一个响应对象,其中包含一个数字(状态码),如果urlopen不能处理,urlopen会产生一个相应的HTTPError对应相应的状态码,HTTP状态码表示HTTP协议所返回的响应的状态码。
import urllib.request # 导入urllib.request模块
import urllib.error # 导入urllib.error模块
try:
response = urllib.request.urlopen('https://www.python.org/',timeout=0.1)
except urllib.error.HTTPError as error: # HTTPError捕获异常信息
print('状态码为:',error.code) # 打印状态码
print('HTTPError异常信息为:',error.reason) # 打印异常原因
print('请求头信息如下:\n',error.headers) # 打印请求头
except urllib.error.URLError as error: # URLError捕获异常信息
print('URLError异常信息为:',error.reason)
这里访问了一个真实存在的URL,输出结果为:
11.解析URL
urllin模块提供了parse子模块用来解析URL。
11.1 拆分URL
urlparse()方法 parse子模块提供了urlparse()方法,实现将URL分解成不同部分,语法格式如下: urllib.parse.urlparse(urlstring,scheme=’’,allow_fragment=True)
urlstring:需要拆分的URL,必选参数。scheme:可选参数,需要设置的默认协议,默认为空字符串,如果要拆分的URL中没有协议,可通过该参数设置一个默认协议。allow_fragment:可选参数,如果该参数设置为False,则表示忽略fragment这部分内容,默认为True。 示例:import urllib.parse #导入urllib.parse模块
parse_result = urllib.parse.urlparse('https://docs.python.org/3/library/urllib.parse.html')
print(type(parse_result)) # 打印类型
print(parse_result) # 打印拆分后的结果
程序运行结果:
用此方法,除了返回ParseResult对象以外,还可以直接获取ParseResult对象中的每个属性值:
print('scheme值为:', parse_result.scheme)
print('netloc值为:', parse_result.netloc)
print('path值为:', parse_result.path)
print('params值为:', parse_result.params)
print('query值为:', parse_result.query)
print('fragment值为:', parse_result.fragment)
urlsplit()方法 urlsplit()方法与urlparse()方法类似,都可以实现URL的拆分。只是urlsplit()方法不再单独拆分params这部分内容,而是将params合并到path中,所以返回结果只有5部分内容。且返回的数据类型为SplitResult。
import urllib.parse #导入urllib.parse模块
# 需要拆分的URL
url = 'https://docs.python.org/3/library/urllib.parse.html'
print(urllib.parse.urlsplit(url)) # 使用urlsplit()方法拆分URL
print(urllib.parse.urlparse(url)) # 使用urlparse()方法拆分URL
程序运行结果:
11.2 组合URL
urlunparse()方法 urlunparse()方法实现URL的组合 语法:urlunparse(parts) parts表示用于组合url的可迭代对象
import urllib.parse #导入urllib.parse模块
list_url = ['https', 'docs.python.org', '/3/library/urllib.parse.html', '', '', '']
tuple_url = ('https', 'docs.python.org', '/3/library/urllib.parse.html', '', '', '')
dict_url = {'scheme': 'https', 'netloc': 'docs.python.org', 'path': '/3/library/urllib.parse.html', 'params': '', 'query':'', 'fragment': ''}
print('组合列表类型的URL:', urllib.parse.urlunparse(list_url))
print('组合元组类型的URL:', urllib.parse.urlunparse(tuple_url))
print('组合字典类型的URL:', urllib.parse.urlunparse(dict_url.values()))
程序运行结果:
urlunsplit()方法 同样用于URL组合,只是参数中的元素必须是5个。
import urllib.parse #导入urllib.parse模块
list_url = ['https', 'docs.python.org', '/3/library/urllib.parse.html', '', '']
tuple_url = ('https', 'docs.python.org', '/3/library/urllib.parse.html', '', '')
dict_url = {'scheme': 'https', 'netloc': 'docs.python.org', 'path': '/3/library/urllib.parse.html', 'query': '', 'fragment': ''}
print('组合列表类型的URL:', urllib.parse.urlunsplit(list_url))
print('组合元组类型的URL:', urllib.parse.urlunsplit(tuple_url))
print('组合字典类型的URL:', urllib.parse.urlunsplit(dict_url.values()))
程序运行结果
11.3 连接URL
用**urljoin()**方法来实现URL的连接。 urllib.parse.urljoin(base,url,allow_fragments=True)
base 表示基础链接url 表示新的链接allow_fragments 为可选参数,默认为Ture,设为False则忽略fragment这部分内容。import urllib.parse #导入urllib.parse模块
base_url = 'https://docs.python.org' # 定义基础链接
# 第二参数不完整时
print(urllib.parse.urljoin(base_url,'3/library/urllib.parse.html'))
# 第二参数完整时,直接返回第二参数的链接
print(urllib.parse.urljoin(base_url,'https://docs.python.org/3/library/urllib.parse.html#url-parsing'))
程序运行结果:
11.4 URL的编码与解码
使用urlencode()方法编码请求参数,该方法接收的参数值为字典。 示例
import urllib.parse # 导入urllib.parse模块
base_url = 'http://httpbin.org/get?' # 定义基础链接
params = {'name': 'Jack', 'country': 'China', 'age': 18} # 定义字典类型的请求参数
url = base_url+urllib.parse.urlencode(params) # 连接请求地址
print('编码后的请求地址为:', url)
程序运行结果:
使用quote方法编码请求参数,该方法接收的参数值类型为字符串。 示例:
import urllib.parse #导入urllib.parse模块
base_url = 'http://httpbin.org/get?country=' # 定义基础链接
url = base_url + urllib.parse.quote('中国') # 字符串编码
print('编码后的请求地址为:', url)
程序运行结果:
使用unquote()方法解码请求参数,即逆向解码。 示例:
import urllib.parse #导入urllib.parse模块
u = urllib.parse.urlencode({'country': '中国'}) # 使用urlencode编码
q = urllib.parse.quote('country=中国') # 使用quote编码
print('urlencode编码后结果为:', u)
print('quote编码后结果为:', q)
print('对urlencode解码:', urllib.parse.unquote(u))
print('对quote解码:', urllib.parse.unquote(q))
程序运行结果:
11.5URL参数的转换
使用parse_qs()方法将参数转换为字典类型。
import urllib.parse #导入urllib.parse模块
# 定义一个请求地址
url = 'http://httpbin.org/get?name=Jack&country=%E4%B8%AD%E5%9B%BD&age=30'
q = urllib.parse.urlsplit(url).query # 获取需要的参数
q_dict = urllib.parse.parse_qs(q) # 将参数转换为字典类型的数据
print('数据类型为:', type(q_dict))
print('转换后的数据:', q_dict)
(其中query是前边拆分部分提到的拆分结果对象的一个属性)
程序运行结果:
使用parse_qsl()方法将参数转换为元组组成的列表
import urllib.parse # 导入urllib.parse模块
str_params = 'name=Jack&country=%E4%B8%AD%E5%9B%BD&age=30' # 字符串参数
list_params = urllib.parse.parse_qsl(str_params) # 将字符串参数转为元组所组成的列表
print('数据类型为:', type(list_params))
print('转换后的数据:', list_params)
程序运行结果:
一日一技:Scrapy 如何正确 Post 发送 JSON 数据
我们知道,HTTP请求的 POST 方式,提交上去的数据有很多种格式。例如JSON/form-data/x-www-form-urlencoded等等。我们在 Postman 的 POST 请求里面,可以看到这些数据格式,如下图所示:虽然同样都是 POST 方式,但是有些网站只能使用特定的格式才能正常返回数据。我们来看一个例子,现在向网址:http://exercise.kingname.info/ajax_1_postbackend POST 提交一个 JSON 字符串:{“name”:”xx”,”age”:24}可以正常得到返回:但如果提交的数据格式不是 JSON,而是form-data,那么就会报错,如下图所示:这也就是为什么在使用 requests 的时候,post 方法的第二个参数有data=和json=的区别,如下图所示:在使用 Scrapy 的时候,很多人都知道怎么提交 GET 请求,但却不太清楚怎么提交 POST 请求。如果你在网上搜索,那么,你会看到有两种回答:第一种回答,会建议你使用scrapy.FormRequest。但这个方法提交的数据是form-data格式,如果网站需要接收 JSON 格式的数据,那么提交就会失败。第二种回答,会建议你使用scrapy.Request(url, method='POST', body=json.dumps(xxx))。这种方式确实可以成功提交 JSON 数据,但写起来比较麻烦。但如果你看过 Scrapy 的官方文档Requests and Responses[1],你就会知道,实际上 Scrapy 原本就提供了一个专门用来POST 提交 JSON 数据的方式——JsonRequest。它的位置在scrapy.http.JsonRequest。并且使用起来跟 scrapy.Request一样简单:import scrapy
from scrapy.http import JsonRequest
class ExampleSpider(scrapy.Spider):
name = 'example'
allowed_domains = ['xxx.com']
# start_urls = ['http://xxx.com/']
def start_requests(self):
body = {
'name': 'kingname',
'age': 28
}
url = 'http://exercise.kingname.info/ajax_1_postbackend'
yield JsonRequest(url, data=body, callback=self.parse)
def parse(self, response, *args, **kwargs):
print(response.body.decode())运行效果如下图所示:JsonRequest本来就是scrapy.Request的一个子类,所以所有能在scrapy.Request使用的参数,都可以直接在JsonRequest中使用。同时,它额外支持两个参数,分别是data和dumps_kwargs。其中data参数的值就是一个可以被json.dumps序列化的对象,例如字典或者列表。而dumps_kwargs里面的参数,就是 json.dumps支持的那些参数,例如ensure_ascii=False、sort_keys=True等等。请关注微信公众号【未闻Code】获取更多精彩文章。
浏览器同源政策及其规避方法
浏览器安全的基石是"同源政策"(same-origin policy)。很多开发者都知道这一点,但了解得不全面。
本文详细介绍"同源政策"的各个方面,以及如何规避它。
一、概述
1.1 含义
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。
最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"。
协议相同
域名相同
端口相同
举例来说,http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。它的同源情况如下。
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
1.2 目的
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?
很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
1.3 限制范围
随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。下面,我将详细介绍,如何规避上面三种限制。
二、Cookie
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。
举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。
document.domain = 'example.com';
现在,A网页通过脚本设置一个 Cookie。
document.cookie = "test1=hello";
B网页就可以读到这个 Cookie。
var allCookie = document.cookie;
注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。
三、iframe
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。
比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
上面命令中,父窗口想获取子窗口的DOM,因为跨源导致报错。
反之亦然,子窗口获取主窗口的DOM也会报错。
window.parent.document.body
// 报错
如果两个窗口一级域名相同,只是二级域名不同,那么设置上一节介绍的document.domain属性,就可以规避同源政策,拿到DOM。
对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。
片段识别符(fragment identifier)
window.name
跨文档通信API(Cross-document messaging)
3.1 片段识别符
片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#fragment的#fragment。如果只是改变片段标识符,页面不会重新刷新。
父窗口可以把信息,写入子窗口的片段标识符。
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
子窗口通过监听hashchange事件得到通知。
window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ...
}
同样的,子窗口也可以改变父窗口的片段标识符。
parent.location.href= target + "#" + hash;
3.2 window.name
浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。
window.name = data;
接着,子窗口跳回一个与主窗口同域的网址。
location = 'http://parent.url.com/xxx.html';
然后,主窗口就可以读取子窗口的window.name了。
var data = document.getElementById('myFrame').contentWindow.name;
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
3.3 window.postMessage
上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。
这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。
举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。
子窗口向父窗口发送消息的写法类似。
window.opener.postMessage('Nice to see you', 'http://aaa.com');
父窗口和子窗口都可以通过message事件,监听对方的消息。
window.addEventListener('message', function(e) {
console.log(e.data); },false);
message事件的事件对象event,提供以下三个属性。
event.source:发送消息的窗口
event.origin: 消息发向的网址
event.data: 消息内容
下面的例子是,子窗口通过event.source属性引用父窗口,然后发送消息。
window.addEventListener('message', receiveMessage); function receiveMessage(event) {
event.source.postMessage('Nice to see you!', '*'); }
event.origin属性可以过滤不是发给本窗口的消息。
window.addEventListener('message', receiveMessage); function receiveMessage(event) { if (event.origin !== 'http://aaa.com') return; if (event.data === 'Hello World') {
event.source.postMessage('Hello', event.origin); } else {
console.log(event.data); } }
3.4 LocalStorage
通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。
下面是一个例子,主窗口写入iframe子窗口的localStorage。
window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') { return; } var payload = JSON.parse(e.data);
localStorage.setItem(payload.key, JSON.stringify(payload.data)); };
上面代码中,子窗口将父窗口发来的消息,写入自己的LocalStorage。
父窗口发送消息的代码如下。
var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' };
win.postMessage(JSON.stringify({key: 'storage', data: obj}), 'http://bbb.com');
加强版的子窗口接收消息的代码如下。
window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') return; var payload = JSON.parse(e.data);
switch (payload.method) {
case 'set':
localStorage.setItem(payload.key, JSON.stringify(payload.data)); break;
case 'get': var parent = window.parent; var data = localStorage.getItem(payload.key);
parent.postMessage(data, 'http://aaa.com'); break;
case 'remove':
localStorage.removeItem(payload.key); break; } };
加强版的父窗口发送消息代码如下。
var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' };
// 存入对象
win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://bbb.com');
// 读取对象
win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
window.onmessage = function(e) { if (e.origin != 'http://aaa.com') return; // "Jack"
console.log(JSON.parse(e.data).name); };
四、AJAX
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。
JSONP
WebSocket
CORS
4.1 JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
首先,网页动态插入<script>元素,由它向跨源网址发出请求。
function addScriptTag(src) { var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script); }
window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) {
console.log('Your public IP address is: ' + data.ip); };
上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
foo({ "ip": "8.8.8.8" });
由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
4.2 WebSocket
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
4.3 CORS
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。
下一篇文章,我会详细介绍,如何通过CORS完成跨源AJAX请求。
Django中Middleware中间件
1 Middleware中间件概述
django中间middleware实质就是一个类,django会根据自己的规则在合适的时机执行中
提前处理,此时中间件就上场了。
django在settings模块中,有一个MIDDLEWARE_CLASSES变量,其中每一个元素就是一个中间件。
在settings.py文件中:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.DataConvert',
'自定义中间件添加位置'
]
常见的middleware组件:
1. Sessions
2. Authentication
3. CSRF Protection
4. GZipping Content
2 Middleware处理功能
在Djano中,中间件承担作用 Resquest---->Middleware ---->View----->Response
比如如果想实现接入安全验证,middleware是比较好的选择,是连接request与view的桥梁
Django中支持的中间件可以实现如下方法:
方法名称 执行周期
process_request 接受到request之后,但在确定View之前
process_view 确定view之后,但真正执行view之前
process_response 执行view之后
process_exception view抛出异常之后
详解: 每个请求都是先通过中间件process_request函数,这个函数返回None或者
HttpResponse对象,返回前者继续处理其他中间件,返回HttpResponse,处理终止返回网页内
容.每个中间件都是按照顺序依次进入处理程序.
3 自定义中间件Middleware
我们网站放在服务器正式运行之后,DEBUG改为False,这样更加安全,但有时候发生错误不能
显示错误详情页面,普通用户看到的是友好的报错信息,管理员看到的是错误详情,以便于修复
BUG,为达到两者效果,利用middleware就能做到.下面我们看下我们定义的Middleware:
import sys
from django.views.debug import technical_500_response
from django.conf import settings
class UserBasedExceptionMiddleware(object):
def process_exception(self, request, exception):
if request.user.is_superuser or request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
return technical_500_response(request, *sys.exc_info())
在给大家展示一个有用的例子,我们都知道浏览器发送数据仅支持GET和POST两种请求方式,
那我们是怎么实现PUT,DELETE,OPTIONS请求的功能的呢?
下面我们将展示采用中间件将request中的请求信息进行转化的方法:
PUT/DELETE/OPTIONS方法的转换中间件:
import json
from django.utils.deprecation import MiddlewareMixin
from django.http.multipartparser import MultiPartParser
class MethodConvertMiddleware(MiddlewareMixin):
#创建请求处理函数
def process_request(self,request):
method = request.method
#判断请求请求头信息中content-type是否含有application/json
if 'application/json' in request.META['CONTENT_TYPE']:
#json格式转Python字典
data = json.loads(request.body.decode()
#判断请求请求头信息中content-type是否含有mutipart/form-data
elif 'mutipart/form-data' in request.META['CONTENT_TYPE']:
#解析器解析出data与文件
data,files = MultiPartParser(request.META,request,request.upload_handlers).parse()
else:
#这里不支持application/x-www-form-urlencoded,其余返回{}
data = {}
files = None
#前段讲请求方式放在HTTP_X_METHOD
if 'HTTP_X_METHOD' in request.META:
method = request.META['HTTP_X_METHOD'].upper()
#例如设定method = 'PUT'
setattr(request,'method',method)
if files:
#files存在 request.PUT_FILES = files 数据data也设置给PUT,这样就转换过来了
setattr(request,'{method}_FILES'.format(method=method),files)
setattr(request,method,data)
#中间件进入下一个处理程序
return None
注:1 中间件没有添加异常处理程序,可自行添加.
2 中间件只针对两种常见数据类型格式application/json 与 application/form-data.
强大的JSON.stringify,你会使用吗?
前言JSON.stringify 作为日常开发中经常使用的方法,你真的能灵活运用它吗?学习本文之前,小包想让大家带着几个问题,一起来深入学习 stringify 。stringify 函数有几个参数,每个参数分别有啥用啊?stringify 序列化准则有哪些啊?函数序列化中会如何处理?null、undefined、NaN 等特殊的值又会如何处理?ES6 后增加的 Symbol 类型、BigInt 序列化过程中会有特别处理吗?stringify 为什么不适合做深拷贝?你能想到那些 stringify 的妙用?整个文章的脉络跟下面思维导图一致,大家可以先留一下印象。三参数在日常编程中,我们经常 JSON.stringify 方法将某个对象转换成 JSON 字符串形式。const stu = {
name: 'zcxiaobao',
age: 18
}
// {"name":"zcxiaobao","age":18}
console.log(JSON.stringify(stu));
复制代码但 stringify 真的就这么简单吗?我们先来看一下 MDN 中对 stringify 的定义。MDN 中指出: JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。看完定义,小包就一惊,stringfy 不止一个参数吗?当然了,stringify 有三个参数。咱们来看一下 stringify 语法和参数介绍:JSON.stringify(value[, replacer [, space]])
复制代码value: 将要序列后成 JSON 字符串的值。replacer(可选)如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。space(可选): 指定缩进用的空白字符串,用于美化输出如果参数是个数字,它代表有多少的空格。上限为10。该值若小于1,则意味着没有空格如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格如果该参数没有提供(或者为 null),将没有空格replacer我们来尝试一下 replacer 的使用。replacer 作为函数replacer 作为函数,它有两个参数,键(key) 和 值(value),并且两个参数都会被序列化。在开始时,replacer 函数会被传入一个空字符串作为 key 值,代表着要被 stringify 的这个对象。理解这点很重要,replacer 函数并非是上来就把对象解析成键值对形式,而是先传入了待序列化对象。随后每个对象或数组上的属性会被依次传入。 如果函数返回值为undefined或者函数时,该属性值会被过滤掉,其余按照返回规则。// repalcer 接受两个参数 key value
// key value 分别为对象的每个键值对
// 因此我们可以根据键或者值的类型进行简单筛选
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}
// function 可自己测试
function replacerFunc(key, value) {
if (typeof value === "string") {
return () => {};
}
return value;
}
const foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
const jsonString = JSON.stringify(foo, replacer);
复制代码JSON 序列化结果为 {"week":45,"month":7}但如果序列化的是数组,若 replacer 函数返回 undefined 或者函数,当前值不会被忽略,而将会被 null 取代。const list = [1, '22', 3]
const jsonString = JSON.stringify(list, replacer)
复制代码JSON 序列化的结果为 '[1,null,3]'replacer 作为数组作为数组比较好理解,过滤数组中出现的键值。const foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
const jsonString = JSON.stringify(foo, ['week', 'month']);
复制代码JSON 序列化结果为 {"week":45,"month":7}, 只保留 week 和 month 属性值。九特性特性一: undefined、函数、Symbol值出现在非数组对象属性值中: undefined、任意函数、Symbol 值在序列化过程中将会被忽略出现在数组中: undefined、任意函数、Symbol值会被转化为 null单独转换时: 会返回 undefined// 1. 对象属性值中存在这三种值会被忽略
const obj = {
name: 'zc',
age: 18,
// 函数会被忽略
sayHello() {
console.log('hello world')
},
// undefined会被忽略
wife: undefined,
// Symbol值会被忽略
id: Symbol(111),
// [Symbol('zc')]: 'zc',
}
// 输出结果: {"name":"zc","age":18}
console.log(JSON.stringify(obj));
// 2. 数组中这三种值会被转化为 null
const list = [
'zc',
18,
// 函数转化为 null
function sayHello() {
console.log('hello world')
},
// undefined 转换为 null
undefined,
// Symbol 转换为 null
Symbol(111)
]
// ["zc",18,null,null,null]
console.log(JSON.stringify(list))
// 3. 这三种值单独转化将会返回 undefined
console.log(JSON.stringify(undefined)) // undefined
console.log(JSON.stringify(Symbol(111))) // undefined
console.log(JSON.stringify(function sayHello() {
console.log('hello world')
})) // undefined
复制代码特性二: toJSON() 方法转换值如果有 toJSON() 方法,toJSON() 方法返回什么值,序列化结果就返回什么值,其余值会被忽略。const obj = {
name: 'zc',
toJSON(){
return 'return toJSON'
}
}
// return toJSON
console.log(JSON.stringify(obj));
复制代码特性三: 布尔值、数字、字符串的包装对象布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值JSON.stringify([new Number(1), new String("zcxiaobao"), new Boolean(true)]);
// [1,"zcxiaobao",true]
复制代码特性四: NaN Infinity null特性四主要针对 JavaScript 里面的特殊值,例如 Number 类型里的 NaN 和 Infinity 及 null 。此三种数值序列化过程中都会被当做 null 。// [null,null,null,null,null]
JSON.stringify([null, NaN, -NaN, Infinity, -Infinity])
// 特性三讲过布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
// 隐式类型转换就会调用包装类,因此会先调用 Number => NaN
// 之后再转化为 null
// 1/0 => Infinity => null
JSON.stringify([Number('123a'), +'123a', 1/0])
复制代码特性五: Date对象Date 对象上部署了 toJSON 方法(同 Date.toISOString())将其转换为字符串,因此 JSON.stringify() 将会序列化 Date 的值为时间格式字符串。// "2022-03-06T08:24:56.138Z"
JSON.stringify(new Date())
复制代码特性六: Symbol特性一提到,Symbol 类型当作值来使用时,对象、数组、单独使用分别会被忽略、转换为 null 、转化为 undefined。同样的,所有以 Symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。const obj = {
name: 'zcxiaobao',
age: 18,
[Symbol('lyl')]: 'unique'
}
function replacer(key, value) {
if (typeof key === 'symbol') {
return value;
}
}
// undefined
JSON.stringify(obj, replacer);
复制代码通过上面案例,我们可以看出,虽然我们通过 replacer 强行指定了返回 Symbol 类型值,但最终还是会被忽略掉。特性七: BigIntJSON.stringify 规定: 尝试去转换 BigInt 类型的值会抛出 TypeErrorconst bigNumber = BigInt(1)
// Uncaught TypeError: Do not know how to serialize a BigInt
console.log(JSON.stringify(bigNumber))
复制代码特性八: 循环引用特性八指出: 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误日常开发中深拷贝最简单暴力的方式就是使用 JSON.parse(JSON.stringify(obj)),但此方法下的深拷贝存在巨坑,关键问题就在于 stringify 无法处理循环引用问题。const obj = {
name: 'zcxiaobao',
age: 18,
}
const loopObj = {
obj
}
// 形成循环引用
obj.loopObj = loopObj;
JSON.stringify(obj)
/* Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'loopObj' -> object with constructor 'Object'
--- property 'obj' closes the circle
at JSON.stringify (<anonymous>)
at <anonymous>:10:6
*/
复制代码特性九: 可枚举属性对于对象(包括 Map/Set/WeakMap/WeakSet)的序列化,除了上文讲到的一些情况,stringify 也明确规定,仅会序列化可枚举的属性// 不可枚举的属性默认会被忽略
// {"age":18}
JSON.stringify(
Object.create(
null,
{
name: { value: 'zcxiaobao', enumerable: false },
age: { value: 18, enumerable: true }
}
)
);
复制代码六妙用localStoragelocalStorage 对象用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。通常我们以对象形式进行存储。单纯调用 localStorage 对象方法const obj = {
name: 'zcxiaobao',
age: 18
}
// 单纯调用 localStorage.setItem()
localStorage.setItem('zc', obj);
// 最终返回结果是 [object Object]
// 可见单纯调用localStorage是失败的
console.log(localStorage.getItem('zc'))
复制代码localStorage 配合 JSON.stringify 方法localStorage.setItem('zc', JSON.stringify(obj));
// 最终返回结果是 {name: 'zcxiaobao', age: 18}
console.log(JSON.parse(localStorage.getItem('zc')))
复制代码属性过滤来假设这样一个场景,后端返回了一个很长的对象,对象里面属性很多,而我们只需要其中几个属性,并且这几个属性我们要存储到 localStorage 中。方案一: 解构赋值+ stringify// 我们只需要 a,e,f 属性
const obj = {
a:1, b:2, c:3, d:4, e:5, f:6, g:7
}
// 解构赋值
const {a,e,f} = obj;
// 存储到localStorage
localStorage.setItem('zc', JSON.stringify({a,e,f}))
// {"a":1,"e":5,"f":6}
console.log(localStorage.getItem('zc'))
复制代码使用 stringify 的 replacer 参数// 借助 replacer 作为数组形式进行过滤
localStorage.setItem('zc', JSON.stringify(obj, ['a','e','f']))
// {"a":1,"e":5,"f":6}
console.log(localStorage.getItem('zc'))
复制代码当 replacer 是数组时,可以简单的过滤出我们所需的属性,是一个不错的小技巧。三思而后行之深拷贝使用 JSON.parse(JSON.stringify) 是实现对象的深拷贝最简单暴力的方法之一。但也正如标题所言,使用该种方法的深拷贝要深思熟虑。循环引用问题,stringify 会报错函数、undefined、Symbol 会被忽略NaN、Infinity 和 -Infinity 会被序列化成 null...因此在使用 JSON.parse(JSON.stringify) 做深拷贝时,一定要深思熟虑。如果没有上述隐患,JSON.parse(JSON.stringify) 是一个可行的深拷贝方案。对象的 map 函数在使用数组进行编程时,我们会经常使用到 map 函数。有了 replacer 参数后,我们就可以借助此参数,实现对象的 map 函数。const ObjectMap = (obj, fn) => {
if (typeof fn !== "function") {
throw new TypeError(`${fn} is not a function !`);
}
// 先调用 JSON.stringify(obj, replacer) 实现 map 功能
// 然后调用 JSON.parse 重新转化成对象
return JSON.parse(JSON.stringify(obj, fn));
};
// 例如下面给 obj 对象的属性值乘以2
const obj = {
a: 1,
b: 2,
c: 3
}
console.log(ObjectMap(obj, (key, val) => {
if (typeof value === "number") {
return value * 2;
}
return value;
}))
复制代码很多同学有可能会很奇怪,为什么里面还需要多加一部判断,直接 return value * 2 不可吗?上文讲过,replacer 函数首先传入的是待序列化对象,对象 * 2 => NaN => toJSON(NaN) => undefined => 被忽略,就没有后续的键值对解析了。删除对象属性借助 replacer 函数,我们还可以删除对象的某些属性。const obj = {
name: 'zcxiaobao',
age: 18
}
// {"age":18}
JSON.stringify(obj, (key, val) => {
// 返回值为 undefined时,该属性会被忽略
if (key === 'name') {
return undefined;
}
return val;
})
复制代码对象判断JSON.stringify 可以将对象序列化为字符串,因此我们可以借助字符串的方法来实现简单的对象相等判断。//判断数组是否包含某对象
const names = [
{name:'zcxiaobao'},
{name:'txtx'},
{name:'mymy'},
];
const zcxiaobao = {name:'zcxiaobao'};
// true
JSON.stringify(names).includes(JSON.stringify(zcxiaobao))
// 判断对象是否相等
const d1 = {type: 'div'}
const d2 = {type: 'div'}
// true
JSON.stringify(d1) === JSON.stringify(d2);
复制代码数组对象去重借助上面的思想,我们还能实现简单的数组对象去重。但由于 JSON.stringify 序列化 {x:1, y:1} 和 {y:1, x:1} 结果不同,因此在开始之前我们需要处理一下数组中的对象。方法一: 将数组中的每个对象的键按字典序排列arr.forEach(item => {
const newItem = {};
Object.keys(item) // 获取对象键值
.sort() // 键值排序
.map(key => { // 生成新对象
newItem[key] = item[key];
})
// 使用 newItem 进行去重操作
})
复制代码但方法一有些繁琐,JSON.stringify 提供了 replacer 数组格式参数,可以过滤数组。方法二: 借助 replacer 数组格式function unique(arr) {
const keySet = new Set();
const uniqueObj = {}
// 提取所有的键
arr.forEach(item => {
Object.keys(item).forEach(key => keySet.add(key))
})
const replacer = [...keySet];
arr.forEach(item => {
// 所有的对象按照规定键值 replacer 过滤
unique[JSON.stringify(item, replacer)] = item;
})
return Object.keys(unique).map(u => JSON.parse(u))
}
// 测试一下
unique([{}, {},
{x:1},
{x:1},
{a:1},
{x:1,a:1},
{x:1,a:1},
{x:1,a:1,b:1}
])
// 返回结果
[{},{"x":1},{"a":1},{"x":1,"a":1},{"x":1,"a":1,"b":1}]
复制代码参考链接你不知道的 JSON.stringify() 的威力JSON.parse(JSON.stringify(obj))实现深拷贝的弊端