XMLHttpRequest
JSON
AJAX
CORS
四个名词来开会
如何发请求
在前端的世界里也逛荡了不少日子了,目前已经get到大约5种发起请求的方式,主流的、非主流的。
何种方式 | 请求方法 | |
---|---|---|
最常见的form 表单 | 默认GET ,多用POST ,只此两种 | 会刷新页面或者新开页面 |
a 标签 | GET 请求 | 也会刷新页面或者新开页面 |
img 的src 属性 | GET | 只能以图片的形式展现 |
link 标签 | GET | 只能以CSS 、favicon 的形式展现 |
script 标签 | GET | 只能以脚本的形式运行 |
可是
- 我们可能想用
GET
POST
PUT
DELETE
方法 - 不想刷新整个页面,想用一种更易于理解的方式来响应
AJAX出现
浏览器和服务器交互模式 V1.0
在AJAX
未出现之前,浏览器想从服务器获得资源,注意是获取资源,会经过如下一个过程
- 浏览器发起请求->服务器接到请求响应给你HTML文档->浏览器收到资源,刷新页面,加载获得的的HTML。简略的过程
我称这种交互方式是 V1.0,此时还是以获取资源为导向。后来随着时代的发展,人们日益增长的文化需求成为了社会的主要矛盾……有一天,小明看了一篇报道,他只是想在下面评论一下,发表对实事的亲切问候,问候完了,唉,你给我刷新页面干啥,我只是想评论一下啊。
大概那是网民们第一次对 良好的用户体验 提出了要求。后来的苹果爸爸,把大家惯坏了,天天嚷着 "你这产品用户体验太差了"……
彼时,微软还是对web做出了很大的贡献的。
交互模式2.0
大约1999年,微软发布IE 5.0
版本,它允许JavaScript脚本向服务器发起HTTP请求。不过很遗憾,当时也没有火起来,直到2004年Gmail发布和2005年Google Map发布,才引起广泛重视。2005年,一个叫Jesse James Garrett的人提出了一个新术语----AJAX
,它是一系列技术的组合体,全称是 Asynchronous JavaScript + XML
(异步的JS和XML)可以阻止页面整体刷新,只是动态响应用户的操作,快速显示到局部,用户就可以很愉快的继续上网了。
可以看出IE当时还是很猛的,随着IE 6.0 市场份额进一步扩大,IE已经把火狐整的半死不活,放眼整个浏览器市场,微软是当之无愧的王者,后来微软就把浏览器团队解散了……不得不说这是一波神操作,能与之媲美的操作大概只有残血我能反杀
塔下我能秀他
了。微软强行为后续各家浏览器的发展提供了优秀的工程师,尤其是08、09年出生的谷歌浏览器,再看如今的IE……
既然AJAX
是一系列的技术的组合体,接下来认识一下其中的几位主角
XMLHttpRequest
XMLHttpRequest
对象是用来在浏览器和服务器之间传输数据的。
古代的操作的是:
- 浏览器构造
XMLHttpRequest
实例化对象 - 用这个对象发起请求
- 服务器响应一个
XML
格式的字符串,是字符串,是字符串,是字符串,也就是说响应的第四部分是字符串。 - JS解析符合XML格式的字符串,更新局部页面。
什么是,可扩展标记语言。
以上是最初的用法,用的是XML
,前端代码片段如下
let request = new XMLHttpRequest() //实例化XMLHttpRequest对象 request.onreadystatechange = () => { if (request.readyState === 4) { console.log('请求和响应都完毕了') if (request.status >= 200 && request.status <= 300) { console.log('说明请求成功了') console.log(request.responseText) let parser = new DOMParser() let xmlDoc = parser.parseFromString(request.responseText, "text/xml") //用parser解析request.responseText let c = xmlDoc.getElementsByTagName('body')[0].textContent console.log(c) } else if (request.status >= 400) { console.log('说明请求失败了') } } } request.open('GET', '/xxx') //配置request request.send()
服务器端的对应代码片段如下
... response.statusCode = 200 response.setHeader('Content-Type', 'text/xml;charset=utf-8') response.write(``) response.end() ... 木木 少少 你好哇 好久不见啊
本地模拟的话,一定要记得开俩不同的端口
例如:node server.js 8001node server.js 8002
XMLHttpRequest实例的
正如上面的前端代码片段写的一样,主要用到了open()
send()
方法, onreadystatechange
readyState
属性。
-
request.open(method, URL, async)方法。
- 一般用三个参数,第一个参数是请求的方法,可以用
GET POST DELETE PUT
等等,URL是用访问的路径,async是是否使用同步,默认true,开启异步,不需要做修改即可,所以实际中只写前两个参数 - 如果非要写false,开启同步,,而且如果值为false,则send()方法,直到接受到了服务器的返回数据
- 一般用三个参数,第一个参数是请求的方法,可以用
-
request.send()方法。
- 发送请求. 如果该请求是异步模式(默认),该方法会立刻返回. 相反,如果请求是同步模式,则直到请求的响应完全接受以后,该方法才会返回
-
readyState
属性。-
描述请求的五个状态。
- 0 === 常量
UNSENT
(未打开) open()方法未调用 - 1 ===
OPENED
(未发送) 只是open()方法调用了 - 2 ===
HEADERS_RECEIVED (已获取响应头)
send()方法调用了,响应头和响应状态已经返回了 - 3 ===
LOADING (正在下载响应体)
响应体下载中,responseText
已经获取了部分数据 - 4 ===
DONE (请求完成)
整个响应过程完毕了。 这个值是实际中用到的。 - 只要不等于4,就表示请求还在进行中。
- 0 === 常量
-
responseText
属性是此次响应的文本内容。-
onreadystatechange
属性。readyState
属性的值发生改变,就会触发readyStateChange
事件。- 我们可以通过
onReadyStateChange
属性,指定这个事件的回调函数,对不同状态进行不同处理。尤其是当状态变为4的时候,表示通信成功,这时回调函数就可以处理服务器传送回来的数据。即前面的代码片段的处理方式。
- 其他的方法、属性、事件详见、
习惯用javaScript
的前端是不想和XML
打交道的,应该用一种符合js
风格的数据格式语言。
JSON
后来一个美国程序员发明了JSON
,解决了上面的问题,这货还写了一本蝴蝶书,还发明了一个 ----JSLint。
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于 , 的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。
以上是的简介,可以看出它是一门全新的语言,。
JSON
很简单,数据类型和JS有点不同的地方。
JavaScript | JSON |
---|---|
string | "string" 必须写双引号 |
number | number |
object | {"object": "name"} 必须双引号 |
undefined | 没有 |
null | null |
boolean | 直接写true false |
array | array |
function | 没有 |
variable |
- 浏览器的全局对象
window
上有JSON
对象,直接使用window.JSON.parse(string)
let string = request.responseTextlet json = window.JSON.parse(string) //string 要符合JSON的格式
以上是JSON解析部分的代码。
此时服务器端代码是
response.statusCode = 200response.setHeader('Content-Type', 'text/json;charset=utf-8')response.write(` { "note" : { "to" : "木木", "from" : "少少", "heading" : "你好哇", "content" : "好久不见啊" } }`)
- 我们浏览器有同源政策,不是同协议 同域名 同端口 的网页无法相互访问。
4.AJAX
恰好是同源政策的拥趸
CORS
-
如果
AJAX
向非同源的地址发起请求,会报错。- 这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200,也就是说即使你看到了200的正确码,也没有用
- 但是form表单无视同源政策,可以发起跨域请求。
上述请求响应都没有问题
然而对于AJAX
就不行 ...request.open('GET', 'http://www.baidu.com')...
- 这是为什么呢,因为
原页面用 form 提交到另一个域名之后,原页面的脚本无法获取新页面中的内容,所以浏览器认为这是安全的。 而 AJAX 是可以读取响应内容的,因此浏览器不能允许你这样做。如果你细心的话你会发现,其实请求已经发送出去了,你只是拿不到响应而已。 所以浏览器这个策略的本质是,一个域名的 JS ,在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止你向另一个域名发送请求。作者:方应杭
链接:来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
那么如何让AJAX
跨域发起请求呢。
CORS
-
CORS
目前是W3C的标准,它允许浏览器跨域发起XMLHttpRequest
请求,而且可以发起多种请求,不像JSONP
只能发起GET
请求,全称是"跨域/源资源共享"(Cross-origin resource sharing)。- 如果想要发起跨域请求 例如: :8001 要想访问 :8002,可以做如下处理
request.open('GET', 'http://wushao.com:8001/xxx') //配置request
- 服务器端的代码需要做如下处理
response.setHeader('Access-Control-Allow-Origin', 'http://shaolin.com:8002')
一定要注意是谁去访问谁,8001去访问8002,那么8001的前端代码要告诉8002的后端代码,咱们是一家人,你和浏览器说说别让它禁我了。
AJAX一些其他知识
既然可以发请求,那么请求头的四部分如何获得的,响应的四部分又是如何获得呢
获得请求和响应头
- 获得请求头的方法
request.open('GET', 'http://shaolin.com:8002/xxx')// 请求的第一部分request.setRequestHeader('Content-Type', 'x-www-form-urlencoded')//请求的第二部分request.setRequestHeader('wushao', '18') //请求的第二部分request.send('我要设置请求的第四部分') //请求的第四部分request.send('name=wushao&password=wushao') //请求的第四部分
对应的典型的http请求四部分
GET /xxx HTTP/1.1HOST: http://shaolin.com:8002Content-Type: x-www-form-urlencodedwushao: 18name=wushao&password=wushao
- 获得响应的方法
request.status //响应的第一部分 200request.statusText //响应的第一部分 OKrequest.getAllResponseHeaders //响应的第二部分,这个方法好啊,全部的响应头request.getResponseHeader('Content-Type') //响应的第二部分具体的request.responseText //响应的第四部分
对应的典型的http响应的四部分
HTTP/1.1 200 OKContent-Type: text/json;charset=utf-8{ "note" : { "to" : "木木", "from" : "少少", "heading" : "你好哇", "content" : "好久不见啊" } }
回顾一下各个status对应的意思
100200 === OK,请求成功301 === 被请求的资源已永久移动到新位置302 === 请求临时重定向,要求客户端执行临时重定向304 === 和上次请求一样,未改变403 === 服务器已经理解请求,但是拒绝访问404 === 请求失败,服务器上没有这个资源502 === 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。503 === Service Unavailable 由于临时的服务器维护或者过载,服务器当前无法处理请求。
练习一下JQuery封装AJAX
- 初级的jq封装
这是一个很简陋的效果,首先我还是把jq假设的很简单,就是一个window的属性,请轻喷……
window.jQuery = function (nodeOrSelector) { let nodes = {} nodes.addClass = function () {} nodes.html = function () {} return nodes}window.jQuery.ajax = function (options) { let url = options.url let method = options.method let headers = options.headers let body = options.body let successFn = options.successFn let failFn = options.failFn let request = new XMLHttpRequest() //实例化XMLHttpRequest对象 request.open(method, url) for (let key in headers) { let value = headers[key] request.setRequestHeader(key, value) } request.onreadystatechange = () => { if (request.readyState === 4) { if (request.status >= 200 && request.status <= 300) { successFn.call(undefined, request.responseText) } else if (request.status >= 400) { failFn.call(undefined, request) } } } request.send(body)}
以上就是jq对ajax的简陋的封装,ajax()方法接受一个对象作为参数,这个对象有很多键。这些键就是http请求的头的各个部分,以及一个成功函数和一个失败函数。
myButton.addEventListener('click', (e) => { window.jQuery.ajax ({ url: '/xxx', method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded', 'wushao': '18' }, body: 'a=1&b=6', successFn: (x) => { ... }, failFn: (x) => { ... } }) })
以上就是简化后的使用方法,给button绑定事件的时候,函数体直接就是ajax()
- 目前你会发现options这个对象傻傻的,因为总有一些用户不希望只传一个参数。所以我们稍微改造一下。
let url if (arguments.length === 1) { url = options.url } else if (arguments.length === 2) { url = arguments[0] options = arguments[1] } let method = options.method let headers = options.headers let body = options.body let successFn = options.successFn let failFn = options.failFn
加了一点,判断ajax()的参数个数。
- 一千个人有一千零一个成功或失败函数的写法,所以为了维护世界和平,大家约定俗成了一套理论 Promise then( )
//Promise这个对象呢,大概长这个样子,真实面目我是没见过//简单的写一下promisewindow.Promise = function (fn) {//...一些其他代码return { then: function () {} }}
Promise这个构造函数呢,又会返回一个函数,这个返回的函数一个then属性,value又是一个函数。处处都体现着函数是第一公民的地位!!!
那我们可以利用这个强大的Promise对象搞一些事情了。//第一步的代码改造成这样,第一步用到了ES6的解构赋值法window.jQuery.ajax = function ({url, method, body, headers}) { return new Promise(function (resolve, reject) { let request = new XMLHttpRequest() request.open(method, url) for(let key in headers) { let value = headers[key] request.setRequestHeader(key, value) } request.onreadystatechange = () => { if (request.readyState === 4) { if (request.status >= 200 && request.status <= 300) { resolve.call(undefined, request.responseText) } else if (request.status >= 400) { reject.call(undefined, request) } } } request.send(body) })}
关于解构赋值:ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)
详见//经过上面这么一折腾,可以很简单的使用了myButton.addEventListener('click', (e) => { let promise = window.jQuery.ajax({ url: '/xxx', method: 'get', headers: { 'content-type': 'application/x-www-form-urlencoded', 'wushao': '18' } }) promise.then( (responseText) => { console.log(responseText) }, (request) => { console.log(request) } ) })
注意then可以传入两个函数,第一个函数表示成功了执行这个,第二个函数表示失败了执行这个,而且可以进行链式调用,一直点下去。
- 所以实际上jq的写法大多是这么写的
myButton.addEventListener('click', (e) => { $.ajax({ url: '/xxx', type: 'GET', }).then( (responseText) => { console.log(responseText) return responseText }, (request) => { console.log('error') return '已经处理' } ).then( (responseText) => { console.log(responseText) }, (request) => { console.log(error2) } )})
链式调用的意思就是:成功函数成功了,就执行第二个then的第一个函数;成功函数失败了,就执行第二个then的第二个函数。
完整代码详见