笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
Ajax基础
Ajax概述
背景
可以简单地认为目前JS能力有限,因此在此之前web平台提供的API还全部停留在单机的阶段
这样就造成了我们无法实现某些功能,例如:
- 无法在实现用户登录功能时,当用户输入邮箱地址显示用户对应的头像
- 无法在实现用户注册功能时,当用户输入邮箱或者用户名就提示是否存在
- 无法在实现留言板功能时,实时看到最新的用户留言
数据是存在服务端的,之前的方法无法通过api来获取服务器的数据
已知发送请求方式
- 地址栏输入、回车、刷新
- 特定元素的href和src属性
- 表单提交
但这些方式我们无法通过或者很难通过代码的方式进行编程操作
需求
对服务端发出请求并且接受服务器端返回的响应
如果我们可以通过js直接发送网络请求,那么web就可以实现更多的功能,不再是单机的
Google Suggest
AJAX最早出现于2005年的Google Suggest
- 是一种正式的技术
- 在浏览器端进行网络编程(发送请求、接收响应)的技术方案
- 我们可以通过JS直接获取服务器端的最新内容而不需要重新加载页面
- 让web更接近桌面应用的体验
实现局部更新的效果
AJAX(Asynchronous Javascript And XML)
是浏览器提供的一套API,可通过JS的调用,实现通过代码控制相应和请求,实现通过js进行网络编程
XML:最早在客户端和服务端传递数据时所采用的数据格式
应用场景
- 按需获取数据
- 对用户进行校验
- 自动更新页面内容
- 提升用户体验,无刷新的体验
体验AJAX
使用jQuery中封装的方法
免费接口:jsonplaceholder.typicode.com/
// JQ中封装的AJAX方法
$.ajax({
// 地址
url: 'https://jsonplaceholder.typicode.com/posts',
// 请求方式
type: 'GET',
// 筛选数据(title为"doloribus ad provident suscipit at"的数据)
data: {
'title': "doloribus ad provident suscipit at"
},
// 数据格式
dataType: 'json',
// 返回数据,data为返回的数据,可以进行操作
success: function (data) {
console.log(data)
}
})
原生AJAX详解
工作中最常用的是封装好的AJAX库
发送AJAX请求步骤
- 创建XMLHttpRequest类型的对象
- 准备发送,打开一个网址之间的连接
- 执行发送动作
- 指定xhr状态变化事件处理函数
// 原生AJAX方法
// 创建XMLHttpRequest实例对象对象
const xhr = new XMLHttpRequest()
// 调用对象的open方法准备打开链接
// 参数:请求方法,链接地址
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts')
// 调用send方法发送请求
// 参数:GET方式参数为null
xhr.send(null)
// 监听对象的readystatechange事件
xhr.onreadystatechange = function () {
//一旦对象readyStat属性变为4表示数据获取成功
if (this.readyState === 4) {
// 打印获取到的数据,数据存在对象的responseText属性中
console.log(this.responseText)
}
}
XMLHttpRequest对象
AJAX中核心提供了一个XMLHttpRequest类,所有AJAX操作都要依靠这个类(属性和方法)
使用XMLHttpRequest类的时候我们先要创建一个实例对象
const xor = new XMLHttpRequest()
IE6兼容写法
xhr = new ActiveXObject('Microsoft.XMLHTTP')
// 创建XMLHttpRequest对象
// 创建变量
let xhr = null
// 进行浏览器能力判断,判断浏览器是否存在XMLHttpRequest
if (window.XMLHttpRequest) {
// 如果存在使用XMLHttpRequest类创建
xhr = new XMLHttpRequest()
} else {
// 不存在使用IE6方法创建
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
// 这是我写的三元表达式创建方法,在新浏览器可以实现,但没有测试IE6
// let xhr = window.XMLHttpRequest !== undefined ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
console.log(xhr)
open()方法
本质上XMLHttpRequest就是js在web平台发送HTTP请求的手段,所以同样符合HTTP约定的格式
语法:实例.open( 请求方式 ,链接地址 )
- 参数1:要使用的HTTP方法,例如:
GET获取数据、POST提交新数据、PUT更改、DELETE删除
等,大小写皆可 - 参数2:要向其发送的地址
两个参数都是字符串格式
xhr.open('get', 'https://jsonplaceholder.typicode.com/posts')
send()方法和请求头
发送HTTP请求
语法:实例.send( 数据 )
参数:在XHR请求重要发送的数据体,根据请求头中的类型进行传参数
如果是get方法,不需要传参,或者使用null即可
设置请求头setRequsetHeader()
此方法必须写在open和send方法之间调用
语法:实例.setRequsetHeader( header
数据类型,value具体数据类型 )
- 参数1:一般设置为
'Content-Type'
,服务器需要我们传送的数据类型 - 参数2:具体的数据类型,常用’
application/x-www-form-urlencoded
'和'application/json
'
// // 使用get方式,需要查询的内容直接写在网址后面,使用?连接(?id=1)
// xhr.open('get', 'https://jsonplaceholder.typicode.com/users?id=1')
// // get方法不需要设置请求头setRequsetHeader()
// // send方法也不需要传递参数
// xhr.send()
//使用post方法,把需要增加的内容写在send方法内部,而不直接写在open网址上
xhr.open('POST', 'https://jsonplaceholder.typicode.com/users')
// 并且需要设置请求头
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// send内部传递参数只用字符串并且每个参数名=参数值,使用&连接
xhr.send('name=cheng&age=19')
响应状态分析
创建onreadystatechange事件,监听readyState属性的变化
readyState属性返回一个值代表当前XMLHttoRequest当前的状态,而这个值在整个请求响应中对变化多次,而只有当这个值为4的时候,才是我们需要的,所以我们监听readyState变化,一旦值为4我们就获取返回的数据
readyState值 | 状态描述 | 说明 |
---|---|---|
0 | UNSENT | 实例被创建但尚未调用open方法 |
1 | OPENED | 调用open方法建立了连接 |
2 | HEADERS_RECEIVED | 调用send方法并且已经可以获状态行和响应头 |
3 | LOADING | 响应体下载中,responseText属性可能已经包含部分数据 |
4 | DONE | 响应体下载完成,可以直接使用responseText里面的数据 |
xhr.onreadystatechange = function () {
// 查看一下状态,这里只有2、3、4
console.log(this.readyState)
//一旦对象readyStat属性变为4表示数据获取成功
if (this.readyState === 4) {
// 打印获取到的数据,数据存在对象的responseText属性中
console.log(this.responseText)
}
}
同步和异步
- 同步:在同一时刻只能做一件事情,后面事情需要等待
- 异步:执行一个耗时的操作(不需要看管)去做别的事,不等待这件事完成
AJAX中设置同步和异步执行
- 在open()方法中传递第三个参数
- 值为布尔值,true(默认值)代表异步,flase代表同步
- 如果设置同步执行,则代码会卡死在send()这一步
不建议使用同步,甚至浏览器会提醒不建议,因为会卡在send这一步,等待全部数据加载成功了后面的代码才能执行,如果此时监听onreadystatechange事件写在send后面会导致执行失败
建议
- 先注册onreadystatechange事件,保证事件一定能触发(不论是同步还是异步)
- 切记不要使用同步
数据格式
我们真正关心的问题就是服务端发出何种格式的数据,这种格式如何在客户端用JavaScript解析。
xml格式
一种数据描述手段,非常古老,但现在不怎么使用了,因为数据冗余太多
json格式 - JavaScript Object Notation
js对象表示法,也是一种数据的描述手段,类似于js中的对象字面量
服务端采用json格式返回数据,客户端使用json格式解析
特点:
- json格式不需要存储在变量中,可以直接写在.json格式文件中,使用{}包裹全部数据
- 结束不需要写分号
- 属性名必须加引号的字符串,属性值可以是任意格式
// 创建一个对象
const obj = {
name: 'zs',
age: 19,
sex: true
},
// 创建一个json格式的字符串
str = '{"name":"ls","age":19,"sex":"flase"}'
// json格式和对象格式可以互相转换
// 使用ES5中新加的json对象的方法stringify转字符串
console.log(JSON.stringify(obj))
// 使用ES5中新加的json对象的方法parse把json转对象
console.log(JSON.parse(str))
注意事项:
- 不管是json还是XML,只不过是AJAX过程中用到的数据格式,和AJAX没有必然关系
- 不管是XML还是json,本质上都是把数据返回给客户端
- 服务器端应该根据相应内容格式设置一个合理的Content-Type
json server使用
需求:平时我们自己写一些数据,希望通过AJAX方式获取,就需要在本地搭建一个临时的服务器
json-server是一个node模块,运行Express服务器,可以指定一个json文件作为api数据源,可以使用它快速搭建一个服务器
步骤:
下载安装node.js
-
打开命令行(mac终端),输入(mac加
sudo
)npm install -g json-server
,等待完成即在本地全局安装了json-server -
新建db.json文件,在文件内书写json代码
//注意:json-server内部首层只支持数组或者对象类型,再往里可以书写其他类型 { "posts": [ { "id": 1, "title": "json-server", "author": "typicode" } ], "comments": [ { "id": 1, "body": "some comment", "postId": 1 } ], "profile": { "name": "typicode" } }
-
在VScode中点击终端 -- 新终端即可在VScode内新建终端(命令行)
-
在终端输入
json-server --watch db.json
回车,即可开启本地服务器,监听db.json文件 -
命令行下面会显示打开的端口,及可以访问的资源,按住control单击鼠标即可访问(home为主页,其他后缀为db.json中的所有可访问资源)
json文件书写方法
{
"students":[
{"id":1,"name":"zs","age":"19","sex":1},
{"id":2,"name":"ls","age":"20","sex":0},
{"id":3,"name":"ww","age":"18","sex":1}
],
"tiezis":[
{"id":1,"studentId":1,"title":"123","content":"123123123"},
{"id":2,"studentId":3,"title":"456","content":"456456456"},
{"id":3,"studentId":2,"title":"789","content":"789789789"},
{"id":4,"studentId":1,"title":"000","content":"000000000"}
],
"pingluns":[
{"id":1,"tieziId":1,"content":"123真好看"},
{"id":2,"tieziId":2,"content":"456真好看"},
{"id":3,"tieziId":1,"content":"123真好看"},
{"id":4,"tieziId":3,"content":"789真好看"},
{"id":5,"tieziId":3,"content":"789真好看"},
{"id":6,"tieziId":4,"content":"000真好看"}
],
"zhushi":[
{"id":1,"content":"我是注释,貌似json不支持注释,我就写写在路由里了"},
{"id":2,"content":"json内部第一层应该是对象或者数组格式,因为这后期这个名字会被作为路由的名字来使用"},
{"id":3,"content":"如果路由是数组,可以加s代表多个"},
{"id":4,"content":"id属性是必须的,因为这是相当于索引(类似数组下标),id从1开始"},
{"id":5,"content":"如果想做关联,可以在其他路由中使用 关联路由名字+Id 的方式关联到路由,例如 studentId,注意可以去掉关联路由的s,后期可以使用关联调用,例如:http://localhost:3000/students/1/tiezis 可以访问students的第一个id(zs)的所有帖子"},
{"id":6,"content":"json内部全都是双引号书写,不要使用单引号,使用英文状态下符号"}
]
}
原生AJAX使用
get方法
- 在get请求中,参数通过url地址传递,使用?连接需要传递的参数
- 在get请求中无需设置请求头,无需设置响应体,send无需传参或者传null即可
使用get获取上面index.json内容(首先使用jsons-erver打开一个本地服务器)
// 创建实例对象
const xhr = new XMLHttpRequest()
// 调用open方法,使用get方法,并且筛选所有age=18的成员
xhr.open('get', 'http://localhost:3000/students?age=18')
// 调用send方法
xhr.send()
// 监听状态变化
xhr.onreadystatechange = function () {
// 状态值等于4执行打印获取结果
if (this.readyState === 4) {
console.log(this.responseText)
}
}
post方法
- 请求体承载需要提交的数据
- 需要设置请求头,一边服务器接收数据
- 需要使用send方法传递参数
// 创建实例对象
const xhr = new XMLHttpRequest()
// 调用open方法,使用post方法,向students内部添加数据
xhr.open('post', 'http://localhost:3000/students')
// 设置请求头,以url的形式进行添加
// xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// // send内部使用字符串的方式添加数据
// // 是不是很类似于get的数据请求?没错,就是类似于get
// xhr.send("name=lulu&age=19&sex=1")
// 使用json格式设置请求头,就可以使用对象字面量的格式传递数据了
xhr.setRequestHeader('Content-Type', 'application/json')
// 下两种方法都可以实现添加数据的效果
// 方法1:使用对象字面量的模版字符串
// xhr.send(`{
// "name":"lala",
// "age":19,
// "sex":0
// }`)
// 方法2:使用JSON的stringify方法把对象转换为字符串
xhr.send(JSON.stringify({
name: "heihei",
age: 19,
sex: 0
}))
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
console.log(this.responseText)
}
}
// 注意:id不需要用添加,程序会自动添加一个id
处理相应数据的渲染
把客户端返回来的数据呈现到界面上
- 如果数据较简单,可以使用字符串拼接操作
- 如果数据比较复杂可以使用模版字符串或者模版引擎
// 获取元素
const box = document.getElementById('box')
// 创建实例对象并使用get方法获得数据
const x = new XMLHttpRequest()
x.open('get', 'http://localhost:3000/students')
x.send()
// 添加状态改变事件
x.onreadystatechange = function () {
if (this.readyState === 4) {
// 调用添加元素函数,并把获取到的数据转为对象
add(JSON.parse(this.responseText))
}
}
function add(data) {
// 遍历数据
for (const i of data) {
// 创建新的tr元素,并且更改trinnerHTML
const newTr = document.createElement('tr')
newTr.innerHTML = `<td>${i.id}</td><td>${i.name}</td><td>${i.age}</td><td>${i.sex === 0?'女':'男'}</td>`
// 把tr添加给box
box.appendChild(newTr)
}
}
封装AJAX函数(库)
- 这里主要是为了了解封装的过程,一般情况在开发中都是使用第三方提供的AJAX库,因为它们可能更加严谨。
- 为了在后续的开发过程中可以更方便的使用这套API,一般的做法都是将其封装到一个函数中以便调用。
// AJAX库函数
// 功能:根据传递参数,向数据库获取或者添加数据
// 参数1:请求方法(get和post)
// 参数2:请求url地址
// 参数3:请求参数
// 参数4:回调函数
function ajax(method, url, obj, fun) {
// 将参数1转大写,方便后期进行判断
method = method.toUpperCase()
// 创建一个ajax的实例对象,并且使用IE6兼容写法
const xhr = window.XMLHttpRequest ?
new XMLHttpRequest() :
new ActiveXObject('Microsoft.XMLHTTP')
// 创建一个空数组,用于接收参数
let att = []
// 遍历参数,将参数的每一项添加进数组
for (const i in obj) {
att.push(`${i}=${obj[i]}`)
}
// 数转字符串,使用=连接,模拟传参格式
const str = att.join('&')
// 调用open方法,参数使用三元表达式,判断是否是get方法,方法不同传参不同
xhr.open(method, method === 'GET' ? url + '?' + str : url)
// 判断是否为post方法,如果是设置请求头
if (method === 'POST') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
}
// 调用send方法,根据方法不同使用不同的参数
xhr.send(method === 'GET' ? null : str)
// 监听readyState变化事件,当数值等于4执行
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
// 调用回调函数,参数使用JSON.parse转回对象方便使用
fun(JSON.parse(this.responseText))
}
}
}
// 验证函数是否能正常执行
ajax('get', 'http://localhost:3000/students', {
age: 19
}, function (data) {
console.log(data)
})
ajax('post', 'http://localhost:3000/students', {
age: 19,
name: 'laolao',
sex: 0
}, function (data) {
console.log(data)
})