Ajax
没出现前,JavaScript
在大多数开发者眼里应该还是个玩具语言,毕竟在那个刀耕火种的年代,压根就没有前端开发师这个角色,不是个全栈你都不好意思称呼自己是个程序猿,所以在那个年代出现的大牛也比较多。Ajax
和 Node
发布之后,随着时代的发展,人们对页面的 UI 设计和交互体验也越来看重,前端所需要的承担的职责也越来越多,导致前后端的界限也越来越明显,进而衍生出了前端工程师这个职位!当然前端生态现在也是欣欣向荣,例如服务端、可视化、3D动画、小程序、APP等都可以通过 JavaScript
来实现。说了这么多,其实还是想突出 Ajax
的重要性,毕竟在前端历史上有着承上启下的作用。
说起 Ajax
,感觉里程碑式的 Jquery
真的必须留下姓名,我在两年前写过一篇为什么github放弃jQuery,但是我们不得不承认 Jquey
在前端历史上的地位。历史的车轮纵然是滚滚向前,但是照亮行业的星辰还是值得每一个人肃然起敬。煽情完毕,进入主题:
Ajax
的作用不过多赘述了,主要是连接前后端通信和数据传输用的,像我们在 jQuery
中写的 $.ajax
,vue
中使用的 axios
,还有基于传统语法优化后的 fetch,其实都是 ajax
的一种封装应用。
原生 AJAX 请求的五个步骤:
- 创建一个XMLHttpRequest异步对象
- 设置请求方式和请求地址
- 接着,用send发送请求
- 监听状态变化
- 最后,接收返回的数据
结合上面的五个步骤,我们先来手写一下代码:
const xhr = new XMLHttpRequest()
xhr.open('GET', './data/test.json', true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText))
} else {
console.log('其它情况...')
}
}
}
xhr.send()
代码结合上述步骤看,应该很清晰,我这里就罗列一下你可能会觉得奇怪的地方:
-
xhr.open('GET', './data/test.json', true)
第三个参数true
代表啥?
第三个参数为
true
,则表示JavaScript
异步执行,不等待后台返回直接向下执行。而为false
的时候,表示同步执行,等待返回后再执行下一步。
-
xhr.readyState === 4
是什么鬼,为啥不是等于其他值偏偏等于 4 呢?
0:(未初始化),还没有调用
send()
方法。
1:(载入) 已调用send()
方法,正在发送请求。
2:(载入完成)send()
方法执行完成,已经接收到全部响应内容。
3:(交互) 正在解析响应内容。
4:(完成) 响应内容解析完成,可以在客户端调用。
-
xhr.status == 200
又代表啥呢,有没有其它可选值?
2xx:一般代表请求成功并正确返回后端传给我们的值,例如:200
3xx:需要重定向,浏览器直接跳转,例如:301、302、304
4xx:客户端请求错误,例如:404、403
5xx:服务端错误
跨域
了解跨域之前,我们先认识一下 同源策略 这个概念:
- ajax 请求时,浏览器要求当前网页和
server
必须同源(保证安全) - 同源的定义:协议、域名、端口,三者必须完全一致
举个栗子:
前端:http://a.com:8080/
server: https://b.com/api/xxx
上述两个链接,协议不同(一个 http
协议,一个 https
协议),端口不同(一个 8080
端口,一个没写端口号继承默认的 80
端口),域名也不同(一个 a.com
,另一个 b.com
)。
但是加载图片或者 css/js
可无视同源策略。即我们做的网站可以直接使用淘宝的图片,或者直接引用淘宝线上的 css
样式。向我们使用 cdn
基本就是这个道理。
认识跨域:
1、如果我们的
ajax
请求不满足同源策略规定的任意一项,即可被认为存在跨域请求行为。
2、所有的跨域,都必须经server
端允许和配合 。
3、未经server
端允许就实现跨域,说明浏览器有漏洞,危险信号。
认识 JSONP
jsonp
应该算是以前处理跨域的一种方法,并且存在很多限制(比如说只能处理 get
请求的跨域)。其实现在我们用的 vue
这些框架来写的话跨域的配置基本都是使用代理形式,也比较简单易懂,这里就不展开。jsonp
的实现原理其实也很简单,就是利用我们前面说的 css/js
可无视同源策略,干巴巴的文字肯定不如代码来的痛快:
a.html => 'http://192.168.0.105:8000/ajax.html' // a.html的url
<script src="http://192.168.0.105:8080/c.js"></script> // a.html 里面写的内容
c.js => 'http://192.168.0.105:8080/c.js' // c.js 的url
alert(111) // c.js 里面写的内容
如果我们要在 a.html
里面用 ajax 请求 c.js 里面返回的数据,受制于同源策略两者之间访问肯定是存在跨域行为的(它们俩端口号不一样),所以导致它们俩无法直接通信。但是如果我们通过 server
端的帮助,将要返回的数据放入到一个新的脚本文件中,然后把脚本文件的地址返回给我们,我们在将这个脚本地址手动添加到 html
页面上,那么我们就能直接获取到这个文件里面的数据了。例如上述代码,a.html
可以直接读取 'c.js' 中的内容并正确弹出 111
。
我们先来看一段跨域的代码及报错提示:
// 请求代码就用我们最初手写的 (文件路径:http://192.168.0.105:8000/ajax.html)
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://192.168.0.105:8080/d.json', true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText))
} else {
console.log('其它情况...')
}
}
}
xhr.send()
// d.json(文件路径:http://192.168.0.105:8080/d.json)
{"name": "zhangsan"}
具体的报错提示一般如下:
其实就是两者端口号不同,所以导致跨域,接下来我们用我们刚刚说的 jsonp
的原理实现跨域正常请求(这里我们将后端模拟返回的数据放到 c.js 中):
// a.html
<script>
function abc(data) {
console.log(data.message) // zhangsan
}
</script>
<script src="http://192.168.0.105:8080/c.js"></script>
// c.js
<script>
abc({
message: 'zhangsan'
})
</script>
看懂了吗?后端处理要返回的 json
数据,然后放入到 abc
函数中,前端就可以通过 abc
函数直接拿到后端给我们返回的对象。这其实就是 JSONP
的简单实现模式,或者说是 JSONP
的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将 JSON
数据形式作为参数传递,完成回调。将 JSON
数据填充进回调函数。
当然,一般情况下来说 <script src="http://192.168.0.105:8080/c.js"></script>
这句话肯定是动态添加的,因为这个 src
的地址我们提前是不知道的,只有后端告诉我们了,我们在将它动态添加到页面上,所以一般向下面的代码这样写:
<script>
function abc(data) {
console.log(data.message); // zhangsan
}
//添加<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://192.168.0.105:8080/c.js");
}
</script>
上面的例子是最简单的JSONP的实现模型,不过它还算不上一个真正的JSONP服务。我们来看一下真正的JSONP服务是怎么样的,比如Google的ajax搜索接口:http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=?&callback=,看这个请求,你会发现后面有一个 ?q=
这个问号是表示你要搜索的内容,最重要的是第二个 callback=?
这个是正如其名表示回调函数的名称,也就是将你自己在客户端定义的回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调。
<script>
window.onload = function () {
// 这里的 q 可以为我们查询的关键字,而 callback 为我们前端约定给后端返回的回调函数方法名
addScriptTag("http://192.168.0.105:8080/c.js?q=aaa&callback=abc");
}
</script>
当然,我们也可以直接使用 jquery
,因为它里面已经帮我们做了 jsonp
请求的代码封装,在这里我们在将上面的代码用 jquery
在重新实现一遍:
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
$.ajax({
url: "http://192.168.0.105:8080/c.js",
type: "GET",
dataType: "jsonp", //指定服务器返回的数据类型
jsonpCallback: 'abc', // 这里服务端返回的回调函数方法名
success: function (data) {
console.log(data) // {message: "zhangsan"}
}
});
</script>
其实看上面的代码我们就知道,jsonp
限制不少,根本不好用,前端用的不舒服,后端更是觉得麻烦,所以我们大致了解实现的过程就行了,因为现在基本不会采用 jsonp
的方式来解决跨域。像现在比较流行的前端框架都有自己处理跨域的成熟方法,也可以通过服务器端配置来做跨域设置,说实话无论哪一种真的都完爆 jsonp
。
了解 cors,服务器设置 http header 实现跨域请求
// 第二个参数填写允许跨域的域名称,* 表示所有域名都可以,不建议直接用 *
response.setHeader('Access-Control-Allow-Origin', 'http://192.168.0.105:8080 || *')
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With')
response.setHeader('Access-Control-Allow-Methods', 'PUT,GET,POST,DELETE')
// 接收跨域的 cookie
response.setHeader('Access-Control-Allow-Credentials', 'true')
这种主要还是后端进行配置,一次配置再无跨域烦恼,我们毕竟是前端工程师,简单了解就好了。
结语
其实应该用 promise
手写一个简易的 ajax
来做总结的,但是 promise
和 async/await
我都写过类似的文章了,这里就不在赘述了!因为像现在非常流行的 axios
基本也都是 promise
封装 XMLHttpRequest
对象进而实现的!!!对 Promise 感兴趣但是了解又不深入的可以直接看我写的 Promise基础详解。如果文中有不对的地方或者理解有误的地方欢迎大家提出并指正。每一天都要相对前一天进步一点,加油!!!