手写 AJAX
目录
- 没有 AJAX 的年代,怎么发请求
- AJAX 是什么
- XMLHttpRequest 的实例属性
- XMLHttpRequest 的实例方法
- 手写 AJAX
- (1)原生 ajax
- (2)封装 jQuery.ajax
- 总结
没有 AJAX 的年代,怎么发请求
- 用
form
可以发请求,但是会刷新页面或新开页面
- 用
a
可以发 get 请求,但是也会刷新页面或新开页面
- 用
img
可以发 get 请求,但是只能以图片的形式展示
- 用
link
可以发 get 请求,但是只能以 CSS、favicon 的形式展示
- 用
script
可以发 get 请求,但是只能以脚本的形式运行
AJAX 是什么
AJAX(Asynchronous JavaScript and XML),指的是通过 JavaScript 的异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
后来,AJAX 这个词就成为 JavaScript 脚本发起 HTTP 通信的代名词,也就是说,只要用脚本发起通信,就可以叫做 AJAX 通信。
AJAX 的步骤
- 创建 XMLHttpRequest 实例
- 发出 HTTP 请求
- 服务器返回 XML 格式的字符串
- JS 解析 XML,并更新局部页面
不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。
XMLHttpRequest 对象是 AJAX 的主要接口,用于浏览器与服务器之间的通信。尽管名字里面有 XML 和 HTTP,它实际上可以使用多种协议(比如 file 或 ftp),发送任何格式的数据(包括字符串和二进制)。
注意:AJAX 只能向同源网址(协议、域名、端口都相同)发出 HTTP 请求,如果发出跨域请求,就会报错。
XMLHttpRequest 的实例属性
XMLHttpRequest.readyState
XMLHttpRequest.readyState 属性返回一个 XMLHttpRequest 代理当前所处的状态。
值 | 状态 | 描述 |
---|---|---|
0 | UNSENT | 代理被创建,但尚未调用 open() 方法 |
1 | OPENED | open() 方法已经被调用 |
2 | HEADERS_RECEIVED | send() 方法已经被调用,并且头部和状态已经可获得 |
3 | LOADING | 下载中; responseText 属性已经包含部分数据 |
4 | DONE | 下载操作已完成 |
XMLHttpRequest.status
XMLHttpRequest.status 属性返回一个整数,表示服务器回应的 HTTP 状态码。
XMLHttpRequest.onreadystatechange
XMLHttpRequest.onreadystatechange 属性指向一个监听函数。readystatechange 事件发生时(实例的 readyState 属性变化),就会执行这个属性。
XMLHttpRequest.responseText
XMLHttpRequest.responseText 属性返回从服务器接收到的字符串,该属性为只读。只有 HTTP 请求完成接收以后,该属性才会包含完整的数据。
XMLHttpRequest 的实例方法
XMLHttpRequest.open()
XMLHttpRequest.open() 方法用于指定 HTTP 请求的参数,或者说初始化 XMLHttpRequest 实例对象。
它一共可以接受五个参数:
- method:表示 HTTP 动词方法,比如GET、POST、PUT、DELETE、HEAD等
- url: 表示请求发送目标 URL
- async(可选): 布尔值,表示请求是否为异步,默认为 true。如果设为 false,则 send() 方法只有等到收到服务器返回了结果,才会进行下一步操作。由于同步 AJAX 请求会造成浏览器失去响应,许多浏览器已经禁止在主线程使用,只允许 Worker 里面使用。所以,这个参数轻易不应该设为false。
- user(可选):表示用于认证的用户名,默认为空字符串
- password(可选):表示用于认证的密码,默认为空字符串
注意:再次使用 open()
,等同于调用 abort()
。
XMLHttpRequest.send()
XMLHttpRequest.send()方法用于实际发出 HTTP 请求。
它的参数是可选的:
- 如果不带参数,就表示 HTTP 请求只有一个 URL,没有数据体,典型例子就是 GET 请求
- 如果带有参数,就表示除了头信息,还带有包含具体数据的信息体,典型例子就是 POST 请求
GET 请求:
var xhr = new XMLHttpRequest();
xhr.open('GET',
'http://www.example.com/?id=' + encodeURIComponent(id),
true
);
xhr.send(null);
POST 请求:
var xhr = new XMLHttpRequest();
var data = 'email='
+ encodeURIComponent(email)
+ '&password='
+ encodeURIComponent(password);
xhr.open('POST', 'http://www.example.com', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(data);
XMLHttpRequest.setRequestHeader()
XMLHttpRequest.setRequestHeader()方法用于设置浏览器发送的 HTTP 请求的头信息。
该方法必须在open()之后、send()之前调用。
该方法接受两个参数。第一个参数是字符串,表示头信息的字段名,第二个参数是字段值。
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
xhr.send(JSON.stringify(data));
XMLHttpRequest.getResponseHeader()
XMLHttpRequest.getResponseHeader()方法返回 HTTP 头信息指定字段的值。
XMLHttpRequest.getAllResponseHeaders()
XMLHttpRequest.getAllResponseHeaders()方法返回一个字符串,表示服务器发来的所有 HTTP 头信息。
格式为字符串,每个头信息之间使用 CRLF 分隔(回车+换行),如果没有收到服务器回应,该属性为 null。如果发生网络错误,该属性为空字符串。
手写 AJAX
简而言之,AJAX 就是在浏览器通过 XMLHttpRequest 对象, 构造(set)HTTP 请求和获取(get)HTTP 响应的技术。
那么 AJAX 具体如何实现?
- JS 设置(set)任意请求 header
- 请求内容第一部分
request.open('get', '/xxx')
- 请求内容第二部分
request.setRequestHeader('content-type','x-www-form-urlencoded')
- 请求内容第四部分
request.send('a=1&b=2')
- 请求内容第一部分
- JS 获取(get)任意响应 header
- 响应内容第一部分
request.status / request.statusText
- 响应内容第二部分
request.getResponseHeader() / request.getAllResponseHeaders()
- 响应内容第四部分
request.responseText
- 响应内容第一部分
(1)原生 ajax
了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。
version 1.0:
myButton.addEventListener('click', function(){
ajax()
})
function ajax() {
let request = new XMLHttpRequest()
request.open('get', 'https://www.google.com')
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status <300) {
let string = request.responseText
let object = JSON.parse(string)
}
}
}
request.send()
}
在 1.0 版本中,并没有设置请求报头,因为是 GET 请求,所以 send()
中也没有设置请求主体。并且,其内容是写死的,我们应该从变量来获取更好。
(2)封装 jQuery.ajax
需求:实现一个满足这些 API 的 jQuery.ajax(url, method, body, success, fail)。
在之前的文章中,我实现了一个简易版 jQuery,这里就在它的基础上再添加函数 ajax。
version 2.0:
myButton.addEventListener("click", (e) => {
$.ajax(
'/xxx',
'post',
'a=1&b=2',
(responseText) => { console.log('success') },
(request) => { console.log('fail') }
)
})
window.jQuery = function(nodeOrSelector) {
let nodes = {}
return nodes
}
window.jQuery.ajax = function(url, method, body, success, fail) {
let request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status <300) {
success.call(undefined, request.responseText)
} else if (request.status >= 400) {
fail.call(undefined, request)
}
}
}
request.send(body)
}
window.$ = window.jQuery
version 2.0 版本实现了 jQuery 版本的以变量传入的方式实现API的 ajax,但是封装得并不好,容易忘记每个参数是什么,并且诸如 GET 请求并不需要参数 body,因此这个位置应该填入 undefined 或者 null,这样代码就很丑。
因此,我们需要传入一个有结构的参数来包含上述功能。
version 3.0:
myButton.addEventListener("click", (e) => {
$.ajax({
url: '/xxx',
method: 'post',
body: 'a=1&b=2',
success: (responseText) => {
console.log('success')
console.log(responseText)
},
fail: (request) => {
console.log('fail')
console.log(request.status)
}
})
})
window.jQuery = function (nodeOrSelector) {
let nodes = {}
return nodes
}
window.jQuery.ajax = function (options) {
let url = options.url
let method = options.method
let body = options.body
let success = options.success
let fail = options.fail
let request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300) {
success.call(undefined, request.responseText)
} else if (request.status >= 400) {
fail.call(undefined, request)
}
}
}
request.send(body)
}
window.$ = window.jQuery
继续优化上面的代码,带入ES6中数组的解构赋值。
// ES 5
let url = options.url
let method = options.method
let body = options.body
let success = options.success
let fail = options.fail
// ES 6
let {url, method, body, success, fail} = options
并且,由于 options 参数只使用了一次,那么可以直接省略掉。得到:
version 4.0:
myButton.addEventListener("click", (e) => {
$.ajax({
url: '/xxx',
method: 'post',
body: 'a=1&b=2',
success: (responseText) => {
console.log('success')
console.log(responseText)
},
fail: (request) => {
console.log('fail')
console.log(request.status)
}
})
})
window.jQuery = function (nodeOrSelector) {
let nodes = {}
return nodes
}
window.jQuery.ajax = function ({ url, method, body, success, fail }) {
let request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300) {
success.call(undefined, request.responseText)
} else if (request.status >= 400) {
fail.call(undefined, request)
}
}
}
request.send(body)
}
window.$ = window.jQuery
总结
AJAX 非常重要,基本上,有了 AJAX 之后,前端才被称之为前端,在这之前的程序员,基本可以被称为页面仔。因此,深入理解 AJAX 的手动实现,如何设置和获取 request 和 response,完成 HTTP 请求,是学习的重点,也是面试常考的内容。
这里只提到了比较浅显的 HTTP 相关知识,在后面补看《HTTP权威指南》后,会对 AJAX 有更深入的理解。