学习目标
本期开始我们学习Ajax。
AJAX
1. 概述
Web程序最初的目的就是将信息(数据)放到公共的服务器,让所有网络用户都可以通过浏览器访问。
在此之前,我们可以通过以下几种方式让浏览器发出对服务端的请求,获得服务端的数据:
地址栏输入地址,回车,刷新
特定元素的 href 或 src 属性
表单提交
这些方案都是我们无法通过或者很难通过代码的方式进行编程(对服务端发出请求并且接受服务端返回的响应), 如果我们可以通过 JavaScript 直接发送网络请求,那么 Web 的可能就会更多,随之能够实现的功能也会更多,至 少不再是“单机游戏”。
A JAX(Asynchronous JavaScript and XML),最早出现在 2005 年的 Google Suggest,是在浏览器端进行网络编 程(发送请求、接收响应)的技术方案,它使我们可以通过 JavaScript 直接获取服务端最新的内容而不必重新加载 页面。让 Web 更能接近桌面应用的用户体验。
说白了,A JAX 就是浏览器提供的一套 API(方法),可以通过 JavaScript 调用,从而实现通过代码控制请求与响应。实现 网络编程。
能力不够 API 凑。
2.快速上手
使用 A JAX 的过程可以类比平常我们访问网页过程
// 1. 创建一个 XMLHttpRequest 类型的对象 —— 相当于打开了一个浏览器
2 var xhr = new XMLHttpRequest()
3 // 2. 打开与一个网址之间的连接 —— 相当于在地址栏输入访问地址
4 xhr.open('GET', './time.php')
5 // 3. 通过连接发送一次请求 —— 相当于回车或者点击访问发送请求
6 xhr.send(null)
7 // 4. 指定 xhr 状态变化事件处理函数 —— 相当于处理网页呈现后的操作
8 xhr.onreadystatechange = function () {
9 // 通过 xhr 的 readyState 判断此次请求的响应是否接收完成
10 if (this.readyState === 4) {
11 // 通过 xhr 的 responseText 获取到响应的响应体
12 console.log(this)
13 }
14 }
练习:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AJAX 的快速上手(发送请求)</title>
</head>
<body>
<script>
// 涉及到 AJAX 操作的页面“不能”使用文件协议访问(文件的方式访问)
// AJAX 是一套 API 核心提供的类型:XMLHttpRequest
// 1. 安装浏览器(用户代理)
// xhr 就类似于浏览器的作用(发送请求接收响应)
var xhr = new XMLHttpRequest()
// 2. 打开浏览器 输入网址
xhr.open('GET', 'http://day-11.io/time.php')
// 3. 敲回车键 开始请求
xhr.send()
// 4. 等待响应
// 5. 看结果
</script>
</body>
</html>
快速上手(接受响应):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AJAX 的快速上手(接收响应)</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
// 如果需要补货第一个状态的变化,需要注意代码执行顺序的问题(不要出现来不及的情况)
// xhr.onreadystatechange = function () {
// // 这个事件并不是只在响应时触发,状态改变就触发
// // console.log(1)
// console.log(this.readyState)
// }
xhr.open('GET', './time.php')
xhr.send()
// 因为客户端永远不知道服务端何时才能返回我们需要的响应
// 所以 AJAX API 采用事件的机制(通知的感觉)
xhr.onreadystatechange = function () {
// 这个事件并不是只在响应时触发,XHR 状态改变就触发
// console.log(1)
if (this.readyState !== 4) return
// 获取响应的内容(响应体)
console.log(this.responseText)
}
// 因为响应需要时间,所以无法通过返回值的方式返回响应
// var response = xhr.send()
// console.log(response)
</script>
</body>
</html>
2.1. readyState
由于 readystatechange 事件是在 xhr 对象状态变化时触发(不单是在得到响应时),也就意味着这个事件会被触发多次,所以我们有必要了解每一个状态值代表的含义:
readyState | 状态描述 | 说明 |
---|---|---|
0 | UNSENT | 代理(XHR)被创建,但尚未调用 open() 方法 |
1 | OPENED | open() 方法已经被调用,建立了连接 |
2 | HEADERS_RECEIVED | send() 方法已经被调用,并且已经可以获取状态行和响应头。 |
3 | LOADING | 响应体下载中, responseText 属性可能已经包含部分数据。 |
4 | DONE | 响应体下载完成,可以直接使用 responseText 。 |
2.1.1. 时间轴
1 var xhr = new XMLHttpRequest()
2 console.log(xhr.readyState) 3 // => 0
4 // 初始化 请求代理对象
5
6 xhr.open('GET', 'time.php')
7 console.log(xhr.readyState) 8 // => 1
9 // open 方法已经调用,建立一个与服务端特定端口的连接
10
11 xhr.send() 12
13 xhr.addEventListener('readystatechange', function () {
14 switch (this.readyState) {
15 case 2:
16 // => 2
17 // 已经接受到了响应报文的响应头
18
19 // 可以拿到头
20 // console.log(this.getAllResponseHeaders())
21 console.log(this.getResponseHeader('server')) 22 // 但是还没有拿到体
23 console.log(this.responseText)
24 break
25
26 case 3:
27 // => 3
28 // 正在下载响应报文的响应体,有可能响应体为空,也有可能不完整
29
30 // 在这里处理响应体不保险(不可靠)
31 console.log(this.responseText)
32 break
33
34 case 4:
35 // => 4
36 // 一切 OK (整个响应报文已经完整下载下来了)
37
38 // 这里处理响应体
39 console.log(this.responseText)
40 break
41 }
42 })
通过理解每一个状态值的含义得出一个结论:一般我们都是在 readyState 值为 4 时,执行响应的后续逻辑。
1 xhr.onreadystatechange = function () {
2 if (this.readyState === 4) { 3 // 后续逻辑......
4 }
5 }
readyState状态变化:
readystate.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>readyState</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
console.log(xhr.readyState)
// => 0
// 初始化 请求代理对象
xhr.open('GET', 'time.php')
console.log(xhr.readyState)
// => 1
// open 方法已经调用,建立一个与服务端特定端口的连接
xhr.send()
xhr.addEventListener('readystatechange', function () {
switch (this.readyState) {
case 2:
// => 2
// 已经接受到了响应报文的响应头
// 可以拿到头
// console.log(this.getAllResponseHeaders())
console.log(this.getResponseHeader('server'))
// 但是还没有拿到体
console.log(this.responseText)
break
case 3:
// => 3
// 正在下载响应报文的响应体,有可能响应体为空,也有可能不完整
// 在这里处理响应体不保险(不可靠)
console.log(this.responseText)
break
case 4:
// => 4
// 一切 OK (整个响应报文已经完整下载下来了)
console.log(this.responseText)
break
}
})
</script>
</body>
</html>
2.2. 遵循 HTTP
本质上 XMLHttpRequest 就是 JavaScript 在 Web 平台中发送 HTTP 请求的手段,所以我们发送出去的请求任然是
HTTP 请求,同样符合 HTTP 约定的格式:
// 设置请求报文的请求行
xhr.open('GET', './time.php')
// 设置请求头
xhr.setRequestHeader('Accept', 'text/plain')
// 设置请求体
xhr.send(null)
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
// 获取响应状态码
console.log(this.status)
// 获取响应状态描述
console.log(this.statusText)
// 获取响应头信息 console.log(this.getResponseHeader('Content‐Type')) // 指定响应头 console.log(this.getAllResponseHeader()) // 全部响应头
// 获取响应体
console.log(this.responseText) // 文本形式
console.log(this.responseXML) // XML 形式,了解即可不用了
}
}
参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
3.具体用法
3.1. GET 请求
通常在一次 GET 请求过程中,参数传递都是通过 URL 地址中的 ? 参数传递。
var xhr = new XMLHttpRequest()
// GET 请求传递参数通常使用的是问号传参
// 这里可以在请求地址后面加上参数,从而传递数据到服务端
xhr.open('GET', './delete.php?id=1')
// 一般在 GET 请求时无需设置响应体,可以传 null 或者干脆不传
xhr.send(null)
xhr.onreadystatechange = function () { if (this.readyState === 4) {
console.log(this.responseText)
}
}
// 一般情况下 URL 传递的都是参数性质的数据,而 POST 一般都是业务数据
ajax-get.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AJAX发送GET请求并传递参数</title>
</head>
<body>
<ul id="list"></ul>
<script>
var listElement = document.getElementById('list')
// 发送请求获取列表数据呈现在页面
// =======================================
var xhr = new XMLHttpRequest()
xhr.open('GET', 'users.php')
xhr.send()
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
var data = JSON.parse(this.responseText)
// data => 数据
for (var i = 0; i < data.length; i++) {
var liElement = document.createElement('li')
liElement.innerHTML = data[i].name
liElement.id = data[i].id
listElement.appendChild(liElement)
liElement.addEventListener('click', function () {
// TODO: 通过AJAX操作获取服务端对应数据的信息
// 如何获取当前被点击元素对应的数据的ID
// console.log(this.id)
var xhr1 = new XMLHttpRequest()
xhr1.open('GET', 'users.php?id=' + this.id)
xhr1.send()
xhr1.onreadystatechange = function () {
if (this.readyState !== 4) return
var obj = JSON.parse(this.responseText)
alert(obj.age)
}
})
}
}
// 给每一个 li 注册点击事件
// 因为 li 后来动态创建,所以不能这样注册事件
// for (var i = 0; i < listElement.children.length; i++) {
// listElement.children[i].addEventListener('click', function () {
// console.log(111)
// })
// }
// var xhr = new XMLHttpRequest()
// // 这里任然是使用URL中的问号参数传递数据
// xhr.open('GET', 'users.php?id=2')
// xhr.send(null)
// xhr.onreadystatechange = function () {
// if (this.readyState !== 4) return
// console.log(this.responseText)
// }
</script>
</body>
</html>
user.php:
<?php
header('Content-Type: application/json');
/**
* 返回的响应就是一个 JSON 内容(返回的就是数据)
* 对于返回数据的地址一般我们称之为接口(形式上是 Web 形式)
*/
// `/users.php?id=1` => id 为 1 的用户信息
$data = array(
array(
'id' => 1,
'name' => '张三',
'age' => 18
),
array(
'id' => 2,
'name' => '李四',
'age' => 20
),
array(
'id' => 3,
'name' => '二傻子',
'age' => 18
),
array(
'id' => 4,
'name' => '三愣子',
'age' => 19
)
);
if (empty($_GET['id'])) {
// 没有 ID 获取全部
// 因为 HTTP 中约定报文的内容就是字符串,而我们需要传递给客户端的信息是一个有结构的数据
// 这种情况下我们一般采用 JSON 作为数据格式
$json = json_encode($data); // => [{"id":1,"name":"张三"},{...}]
echo $json;
} else {
// 传递了 ID 只获取一条
foreach ($data as $item) {
if ($item['id'] != $_GET['id']) continue;
$json = json_encode($item); // => [{"id":1,"name":"张三"},{...}]
echo $json;
}
}
3.2. POST 请求
POST 请求过程中,都是采用请求体承载需要提交的数据。
1 var xhr = new XMLHttpRequest()
2 // open 方法的第一个参数的作用就是设置请求的 method
3 xhr.open('POST', './add.php')
4 // 设置请求头中的 Content‐Type 为 application/x‐www‐form‐urlencoded 5 // 标识此次请求的请求体格式为 urlencoded 以便于服务端接收数据
6 xhr.setRequestHeader('Content‐Type', 'application/x‐www‐form‐urlencoded') 7 // 需要提交到服务端的数据可以通过 send 方法的参数传递
8 // 格式:key1=value1&key2=value2
9 xhr.send('key1=value1&key2=value2')
10 xhr.onreadystatechange = function () {
11 if (this.readyState === 4) {
12 console.log(this.responseText) 13 }
14 }
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AJAX发送POST请求</title>
<style>
#loading {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #555;
opacity: .5;
text-align: center;
line-height: 300px;
}
#loading::after {
content: '加载中...';
color : #fff;
}
</style>
</head>
<body>
<div id="loading"></div>
<table border="1">
<tr>
<td>用户名</td>
<td><input type="text" id="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" id="password"></td>
</tr>
<tr>
<td></td>
<td><button id="btn">登录</button></td>
</tr>
</table>
<script>
// 找一个合适的时机,做一件合适的事情
var btn = document.getElementById('btn')
// 1. 获取界面上的元素 value
var txtUsername = document.getElementById('username')
var txtPassword = document.getElementById('password')
var loading = document.getElementById('loading')
btn.onclick = function () {
loading.style.display = 'block'
var username = txtUsername.value
var password = txtPassword.value
// 2. 通过 XHR 发送一个 POST 请求
var xhr = new XMLHttpRequest()
xhr.open('POST', 'login.php')
// !!! 一定注意 如果请求体是 urlencoded 格式 必须设置这个请求头 !!!
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// xhr.send('username=' + username + '&password=' + password)
xhr.send(`username=${username}&password=${password}`)
// 3. 根据服务端的反馈 作出界面提示
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
console.log(this.responseText)
loading.style.display = 'none'
}
}
</script>
</body>
</html>
同步与异步
关于同步与异步的概念在生活中有很多常见的场景,举例说明。
同步:一个人在同一个时刻只能做一件事情,在执行一些耗时的操作(不需要看管)不去做别的事,只是等 待
异步:在执行一些耗时的操作(不需要看管)去做别的事,而不是等待
xhr.open() 方法第三个参数要求传入的是一个 bool 值,其作用就是设置此次请求是否采用异步方式执行,默认
为 true ,如果需要同步执行可以通过传递 false 实现:
console.log('before ajax')
var xhr = new XMLHttpRequest()
// 默认第三个参数为 true 意味着采用异步方式执行
xhr.open('GET', './time.php', true)
xhr.send(null)
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
// 这里的代码最后执行
console.log('request done')
}
}
console.log('after ajax')
如果采用同步方式执行,则代码会卡死在 xhr.send() 这一步:
console.log('before ajax')
var xhr = new XMLHttpRequest()
// 同步方式
xhr.open('GET', './time.php', false)
// 同步方式 执行需要 先注册事件再调用 send,否则 readystatechange 无法触发
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
// 这里的代码最后执行
console.log('request done')
}
}
xhr.send(null)
console.log('after ajax')
演示同步异步差异。
一定在发送请求 send() 之前注册 readystatechange (不管同步或者异步):
为了让这个事件可以更加可靠(一定触发),一定是先注册。
了解同步模式即可,切记不要使用同步模式。
至此,我们已经大致了解了 AJAX 的基本 API 。
练习:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
// console.time('abc')
// for (var i = 0; i < 100000000; i++) {}
// console.timeEnd('abc')
// console.log('begin request')
var xhrAsync = new XMLHttpRequest()
// open 方法的第三个参数是 async 可以传入一个布尔值,默认为 true
xhrAsync.open('GET', 'time.php', true)
console.time('async')
xhrAsync.send()
console.log(xhrAsync.responseText)
// console.log('end request')
console.timeEnd('async')
// 同步模式 ajax 操作会有等待的情况
// 区别在于 send 方法会不会出现等待情况
// console.log('begin request')
var xhrSync = new XMLHttpRequest()
// open 方法的第三个参数是 async 可以传入一个布尔值,默认为 true
xhrSync.open('GET', 'time.php', false)
console.time('sync')
xhrSync.send()
console.log(xhrSync.responseText)
// console.log('end request')
console.timeEnd('sync')
</script>
</body>
</html>
time.php:
<?php
// for ($i = 0; $i < 100000; $i++) {
// echo time();
// }
echo time();
同步模式注册事件时机问题
先来做一个练习:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>同步模式的AJAX</title>
</head>
<body>
<script>
var xhr=new XMLHttpRequest()
xhr.open('GET','time.php',false)
xhr.send()
xhr.onreadystatechange=function() {
console.log(this.readyState)
}
</script>
</body>
</html>
你会发现,之前open参数为true的时候会打印2、3、4,现在为false却完全不打印了。
原因:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>同步模式的AJAX</title>
</head>
<body>
<script>
var xhr=new XMLHttpRequest()
xhr.open('GET','time.php',false)
xhr.send()//等到请求响应的过程全部完成再继续
console.log(xhr.readyState)//这时候已经等于4了
xhr.onreadystatechange=function() {//这时候注册onreadystatechange已经没意义了,它在一个值变成另外一个值的时候才会触发。
console.log(this.readyState)
}
</script>
</body>
</html>
这就是注册时机太晚。所以要想onreadystatechange触发,必须在send之前。即:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>同步模式的AJAX</title>
</head>
<body>
<script>
var xhr=new XMLHttpRequest()
xhr.open('GET','time.php',false)
xhr.onreadystatechange=function() {
console.log(this.readyState)
}
xhr.send()//等到请求响应的过程全部完成再继续
console.log(xhr.readyState)
</script>
</body>
</html>
那为什么设置为true就能打印出4了呢?是因为true表示异步,send方法就是发一个信号,完成是需要一些时间的,此时注册事件同步进行,发送完,事件也执行完了,所以能打印出4.
3.4. 响应数据格式
提问:如果希望服务端返回一个复杂数据,该如何处理?
关心的问题就是服务端发出何种格式的数据,这种格式如何在客户端用 JavaScript 解析
3.4.1. XML
一种数据描述手段 老掉牙的东西,简单演示一下,不在这里浪费时间,基本现在的项目不用了。
淘汰的原因:数据冗余太多
xml.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AJAX 请求 XML 格式的数据</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'xml.php')
xhr.send()
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
// this.responseXML 专门用于获取服务端返回的 XML 数据,操作方式就是通过 DOM 的方式操作
// 但是需要服务端响应头中的 Content-Type 必须是 application/xml
console.log(this.responseXML.documentElement.children[0].innerHTML)
console.log(this.responseXML.documentElement.getElementsByTagName('name')[0])
}
</script>
</body>
</html>
xml.php:
<?php
header('Content-Type: application/xml');
?>
<?xml version="1.1" encoding="utf-8"?>
<person>
<name>张三</name>
<age>18</age>
<gender>男</gender>
</person>
3.4.2. JSON
也是一种数据描述手段,类似于 JavaScript 字面量方式
服务端采用 JSON 格式返回数据,客户端按照 JSON 格式解析数据。
不管是 JSON 也好,还是 XML,只是在 A JAX 请求过程中用到,并不代表它们之间有必然的联系,它们只是 数据协议罢了
3.5. 处理响应数据渲染
模板引擎:
artTemplate:https://aui.github.io/art-template/
模板引擎实际上就是一个 API,模板引擎有很多种,使用方式大同小异,目的为了可以更容易的将数据渲染到 HTML中。
3.6 兼容方案
XMLHttpRequest 在老版本浏览器(IE5/6)中有兼容问题,可以通过另外一种方式代替
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
3.7 关于进程和线程
进程与线程:
进程
进行中的程序
线程
线程就是拿着剧本(代码)去演戏的演员,专业上是CPU最小执行单元
总结
本期学习到此结束。