1. 概述

1.1 是什么

Ajax = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)

Ajax 不是新的编程语言,而是一种用于创建快速动态网页的技术

Ajax 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,使网页实现异步更新

传统的网页(不使用 Ajax)如果需要更新内容,必需重载整个网页

Ajax 不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行

XMLHttpRequest只是实现 Ajax 的一种方式

1.2 为什么

以前数据都是写在代码里固定的, 无法随时变化

现在数据可以从服务器上进行获取,让数据变活

1.3 入门程序

需求:从服务器获取省份列表数据,展示到页面上

步骤:

引入 axios

基本语法

axios({

url: "目标资源地址",

}).then(result => {

// 对服务器返回的数据做后续处理

});

示例

2. axios

2.1 URL

URL:统一资源定位符,简称网址,用于定位网络中的资源(网页,图片,数据,视频,音频等)

组成:协议,域名,资源路径(比较重要的三部分)

http 协议:超文本传输协议,规定了浏览器和服务器传递数据的格式

域名:标记服务器在互联网当中的方位,网络中有很多服务器,你想访问哪一台,需要知道它的域名

资源路径:一个服务器内有多个资源,用于标识你要访问的资源具体的位置

查询参数:携带给服务器额外信息,让服务器返回想要的某一部分数据而不是全部数据

格式:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2

参数名一般是后端规定的,值前端看情况传递即可

axios 如何携带查询参数?

axios({

url: "目标资源地址",

params: {

参数名: 值,

},

}).then(result => {

// 对服务器返回的数据做后续处理

});

示例 1:获取“河北省”下属的城市列表

示例 2:根据输入的省份名字和城市名字,查询下属地区列表



地区列表:

2.2 数据提交

常用请求方法

请求方法

操作

GET

获取数据(默认请求方式)

POST

提交数据

PUT

修改数据(全部)

DELETE

删除数据

PATCH

修改数据(部分)

axios 如何提交数据到服务器

axios({

url: "目标资源地址",

method: "请求方法",

data: {

参数名: 值,

},

}).then(result => {

// 对服务器返回的数据做后续处理

});

示例:注册账号,提交用户名和密码到服务器保存

2.3 axios 错误处理

axios({

// ...请求选项

})

.then(result => {

// 处理成功数据

})

.catch(error => {

// 处理失败错误

});

示例:

2.4 案例-用户登录

样式

框架

欢迎-登录

2.5 form-serialize 插件

快速收集目标表单范围内表单元素的值

引入 form-serialize 插件

使用 serialize 函数

参数 1:要获取的 form 表单标签对象(要求表单元素有 name 属性,用来作为收集的数据中属性名)

参数 2:配置对象

hash:

true - 收集出来的是一个 JS 对象

false - 收集出来的是一个查询字符串

empty:

true - 收集空值

false - 不收集空值

示例:收集登录表单里用户名和密码



2.6 Bootstrap 弹框

2.6.1 属性控制

引入 bootstrap.css 和 bootstrap.js

准备弹框标签,确认结构(可以从 Bootstrap 官方文档的 Modal 里复制基础例子)- 运行到网页后,逐一对应标签和弹框每个部分对应关系

通过自定义属性,通知弹框的显示和隐藏,语法如下:

代码实现

2.6.2 js 控制

为什么需要 js 方式控制?

当显示/隐藏之前,需要执行一些 JS 逻辑代码,就需要引入 JS 控制弹框显示/隐藏的方式

例如:

点击编辑姓名按钮,在弹框显示之前,在输入框填入默认姓名

点击保存按钮,在弹框隐藏之前,获取用户填入的名字并打印

语法

// 创建弹框对象

const modalDom = document.querySelector("css选择器");

const modal = new bootstrap.Modal(modelDom);

// 显示弹框

modal.show();

// 隐藏弹框

modal.hide();

示例

2.7 案例-图书管理

黑马接口文档

结构

图书管理

序号 书名 作者 出版社 操作

index.js

渲染图书列表

/**

* 目标1:渲染图书列表

* 1.1 获取数据

* 1.2 渲染数据

*/

const creator = "老李";

function getBooksList() {

// 1.1 获取数据

axios({

url: "http://hmajax.itheima.net/api/books",

params: {

creator,

},

}).then(result => {

// console.log(result.data.data)

// 1.2 渲染数据

const list = result.data.data

.map((item, index) => {

return `

${index + 1}

${item.bookname}

${item.author}

${item.publisher}

删除

编辑

`;

})

.join("");

document.querySelector(".list").innerHTML = list;

});

}

getBooksList();

新增图书

/**

* 目标2:新增图书

* 2.1新增弹框->显示和隐藏

* 2.2收集表单数据,并提交到服务器保存

* 2.3刷新图书列表

*/

const addModalDom = document.querySelector(".add-modal");

const addModal = new bootstrap.Modal(addModalDom);

// 2.1 新增弹框->显示和隐藏

document.querySelector(".add-btn").addEventListener("click", () => {

// 2.2 获取输入框的数据

const addForm = document.querySelector(".add-form");

const formData = serialize(addForm, { hash: true, empty: true });

console.log(formData);

// 2.3 提交到服务器保存

axios({

url: "http://hmajax.itheima.net/api/books",

method: "post",

data: {

...formData,

creator,

},

}).then(result => {

console.log(result);

// 2.4 重新渲染页面

getBooksList();

// 重置表单

addForm.reset();

// 点击保存,隐藏模态框

addModal.hide();

});

});

删除图书

/**

* 目标3:删除图书

* 3.1删除元素绑定点击事件->获取图书id

* 3.2调用删除接口

* 3.3刷新图书列表

*/

// 3.1 删除元素绑定点击事件(事件委托)->获取图书id

document.querySelector(".list").addEventListener("click", e => {

if (e.target.classList.contains("del")) {

const theId = e.target.parentNode.dataset.id;

// 3.2 调用删除接口

axios({

url: `http://hmajax.itheima.net/api/books/${theId}`,

method: "delete",

}).then(() => {

// 3.3 刷新图书列表

getBooksList();

});

}

});

编辑图书

/**

* 目标4:编辑图书

* 4.1编辑弹框->显示和隐藏

* 4.2获取当前编辑图书数据->回显到编辑表单中

* 4.3提交保存修改,并刷新列表

*/

const editModalDom = document.querySelector(".edit-modal");

const editModal = new bootstrap.Modal(editModalDom);

document.querySelector(".list").addEventListener("click", e => {

if (e.target.classList.contains("edit")) {

const theId = e.target.parentNode.dataset.id;

// 4.2 获取当前编辑图书数据->回显到编辑表单中

axios({

url: `http://hmajax.itheima.net/api/books/${theId}`,

method: "get",

}).then(result => {

// 数据对象"属性"和标签"类名"一致

// 遍历数据对象,使用属性去获取对应的标签,快速赋值

const bookObj = result.data.data;

// console.log(bookObj)

const keys = Object.keys(bookObj);

keys.forEach(key => {

document.querySelector(`.edit-form .${key}`).value = bookObj[key];

});

});

// 4.1 编辑弹框->显示和隐藏

editModal.show();

}

});

document.querySelector(".edit-btn").addEventListener("click", () => {

const editForm = document.querySelector(".edit-form");

const editData = serialize(editForm, { hash: true, empty: true });

// 4.3 提交保存修改,并刷新列表

axios({

url: `http://hmajax.itheima.net/api/books/${editData.id}`,

method: "put",

data: {

...editData,

creator,

},

}).then(() => {

getBooksList();

});

editModal.hide();

});

总结

渲染列表(查)

获取数据

渲染数据

新增图书(增)

弹框(显示/隐藏)

收集数据&提交保存

刷新页面列表

删除图书(删)

绑定点击事件(获取要删除的图书 id)

调用删除接口(让服务器删除此数据)

成功后重新获取并刷新列表

编辑图书(改)

弹框(显示/隐藏)

表单(数据回显)

点击修改收集数据,提交到服务器保存

重新获取并刷新列表

2.8 图片上传

把本地的图片上传到网页上显示

实现步骤

获取图片文件对象:e.target.files[0]

使用 FormData 表单数据对象装入

const fd = new FormData();

fd.append(参数名, 值);

提交表单数据对象,使用服务器返回图片 url 网址

示例:更换背景图片

index.js

document.querySelector(".bg-ipt").addEventListener("change", e => {

// 1. 选择图片上传,设置body背景

const fd = new FormData();

fd.append("img", e.target.files[0]);

axios({

url: "http://hmajax.itheima.net/api/uploadimg",

method: "post",

data: fd,

}).then(result => {

// console.log(result)

const imgUrl = result.data.data.url;

document.body.style.backgroundImage = `url(${imgUrl})`;

// 2. 上传成功时,"保存"图片url网址

localStorage.setItem("bgImg", imgUrl);

});

});

// 3. 网页运行后,获取url网址使用

const imgUrl = localStorage.getItem("bgImg");

document.body.style.backgroundImage = `url(${imgUrl})`;

2.9 案例-个人信息设置

结构

操作成功

  • 基本设置
  • 安全设置
  • 账号绑定
  • 新消息通知

基本设置

头像

index.js

信息渲染

/**

* 目标1:信息渲染

* 1.1 获取用户的数据

* 1.2 回显数据到标签上

* */

const creator = "小明";

axios({

url: "http://hmajax.itheima.net/api/settings",

params: {

creator,

},

}).then(result => {

// console.log(result.data.data)

const userObj = result.data.data;

const keys = Object.keys(userObj);

keys.forEach(key => {

// 头像和性别比较特殊,需要单独处理

if (key === "avatar") {

document.querySelector(".prew").src = userObj[key];

} else if (key === "gender") {

const gRadioList = document.querySelectorAll(".gender");

// 0男,1女,刚好与数组下标对应

const gNum = userObj[key];

gRadioList[gNum].checked = true;

} else {

document.querySelector(`.${key}`).value = userObj[key];

}

});

});

修改头像

/**

* 目标2:修改头像

* 2.1 获取头像文件

* 2.2 提交服务器并更新头像

*/

document.querySelector(".upload").addEventListener("change", e => {

const fd = new FormData();

fd.append("img", e.target.files[0]);

fd.append("creator", creator);

axios({

url: "http://hmajax.itheima.net/api/avatar",

method: "put",

data: fd,

}).then(result => {

document.querySelector(".prew").src = result.data.data.avatar;

});

});

修改数据,并提示

/**

* 目标3:提交表单

* 3.1 收集表单信息

* 3.2 交到服务器保存

*/

/**

* 目标4:结果提示

* 4.1 创建toast对象

* 4.2 调用show方法->显示提示框

*/

document.querySelector(".submit").addEventListener("click", () => {

// 3.1 收集表单信息

const form = document.querySelector(".user-form");

const formData = serialize(form, { hash: true, empty: true });

formData.creator = creator;

formData.gender = +formData.gender;

// 3.2 提交到服务器保存

axios({

url: "http://hmajax.itheima.net/api/settings",

method: "put",

data: formData,

}).then(result => {

// 4.1 创建toast对象

const toastDom = document.querySelector(".my-toast");

const toast = new bootstrap.Toast(toastDom);

// 4.2 调用show方法->显示提示框

toast.show();

});

});

2.10 请求方式别名

为了方便起见,已经为所有支持的请求方法提供了别名

axios.request(config)

axios.get(url[, config])

axios.delete(url[, config])

axios.head(url[, config])

axios.options(url[, config])

axios.post(url[, data[, config]])

axios.put(url[, data[, config]])

注:在使用别名方法时, url、method、data 这些属性都不必在配置中指定

2.11 Axios API

2.11.1 axios 实例

可以使用自定义配置新建一个实例

const instance = axios.create({

baseURL: "https://some-domain.com/api/",

timeout: 1000,

headers: { "X-Custom-Header": "foobar" },

});

一些实例方法

axios#request(config)

axios#get(url[, config])

axios#delete(url[, config])

axios#head(url[, config])

axios#options(url[, config])

axios#post(url[, data[, config]])

axios#put(url[, data[, config]])

axios#patch(url[, data[, config]])

axios#getUri([config])

2.11.2 请求配置

这些是创建请求时可以用的配置选项,只有url是必需的

若没指定method,请求默认使用GET

{

// url:用于请求的服务器URL

url: '/user',

// method:创建请求时使用的方法,默认为get

method: 'get',

// baseURL:自动加在url前面,通过它可以传递相对地址

baseURL: 'https://some-domain.com/api/',

// transformRequest:允许在向服务器发送前,修改请求数据

// 它只能用于'PUT', 'POST'和'PATCH'这几个请求方法

// 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData或Stream

// 可以修改请求头

transformRequest: [function (data, headers) {

// 对发送的 data 进行任意转换处理

return data;

}],

// transformResponse:在传递给then/catch前,允许修改响应数据

transformResponse: [function (data) {

// 对接收的 data 进行任意转换处理

return data;

}],

// 自定义请求头

headers: {'X-Requested-With': 'XMLHttpRequest'},

// params:与请求一起发送的URL参数

// 必须是一个简单对象或URLSearchParams对象

params: {

ID: 12345

},

// data:作为请求体被发送的数据

// 仅适用'PUT', 'POST', 'DELETE'和'PATCH'请求方法

// 在没有设置`transformRequest`时,则必须是以下类型之一:

// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams

// - 浏览器专属: FormData, File, Blob

// - Node 专属: Stream, Buffer

data: {

firstName: 'Fred'

},

// 发送请求体数据的可选语法

// 请求方式 post

// 只有 value 会被发送,key 则不会

data: 'Country=Brasil&City=Belo Horizonte',

// timeout:指定请求超时的毫秒数

// 如果请求时间超过`timeout`的值,则请求会被中断,默认为0,表示永不超时

timeout: 1000,

// withCredentials:表示跨域请求时是否需要使用凭证,默认false

withCredentials: false,

// adapter:允许自定义处理请求,这使测试更加容易

// 返回一个`promise`并提供一个有效的响应(参见 lib/adapters/README.md)

adapter: function (config) {

/* ... */

},

// auth:HTTP Basic Auth

auth: {

username: 'janedoe',

password: 's00pers3cret'

},

// responseType:浏览器将要响应的数据类型

// 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'

// 浏览器专属:'blob'

responseType: 'json', // 默认值

// responseEncoding:用于解码响应的编码 (Node.js专属)

// 注意:忽略`responseType`的值为 'stream',或者是客户端请求

responseEncoding: 'utf8', // 默认值

// `xsrfCookieName`是`xsrf token`的值,被用作`cookie`的名称

xsrfCookieName: 'XSRF-TOKEN', // 默认值

// `xsrfHeaderName`是带有`xsrf token`值的http请求头名称

xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值

// `onUploadProgress`允许为上传处理进度事件(浏览器专属)

onUploadProgress: function (progressEvent) {

// 处理原生进度事件

},

// `onDownloadProgress`允许为下载处理进度事件(浏览器专属)

onDownloadProgress: function (progressEvent) {

// 处理原生进度事件

},

// `maxContentLength`定义了node.js中允许的HTTP响应内容的最大字节数

maxContentLength: 2000,

// `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数

maxBodyLength: 2000,

// `validateStatus`定义了对于给定的HTTP状态码是 resolve 还是 reject promise

// 返回`true`(或者设置为`null`或`undefined`),则promise将会resolved,否则是rejected

validateStatus: function (status) {

return status >= 200 && status < 300; // 默认值

},

// maxRedirects:定义了在node.js中要遵循的最大重定向数,设置为0,则不会进行重定向

maxRedirects: 5, // 默认值

// `socketPath` 定义了在node.js中使用的UNIX套接字

// e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程

// 只能指定 `socketPath` 或 `proxy`

// 若都指定,则使用 `socketPath`

socketPath: null, // default

// `httpAgent`和`httpsAgent`分别定义了在 node.js 中执行 http 和 https 请求时使用的自定义代理

// 这样就可以添加默认情况下未启用的选项,如`keepAlive`

httpAgent: new http.Agent({ keepAlive: true }),

httpsAgent: new https.Agent({ keepAlive: true }),

// `proxy` 定义了代理服务器的主机名,端口和协议

// 可以使用常规的`http_proxy`和`https_proxy` 环境变量

// 使用`false`可以禁用代理功能,同时环境变量也会被忽略

// `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据

// 这将设置一个`Proxy-Authorization`请求头,它会覆盖`headers`中已存在的自定义`Proxy-Authorization`请求头

// 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`

proxy: {

protocol: 'https',

host: '127.0.0.1',

port: 9000,

auth: {

username: 'mikeymike',

password: 'rapunz3l'

}

},

// 详见https://axios-http.com/zh/docs/cancellation

cancelToken: new CancelToken(function (cancel) {

}),

// `decompress`:表示是否要自动解压缩响应正文

// 将其设置为"false",它不会解压缩响应,并会保留原始的"Content-Encoding"标头

// 如果设置为“true”,将从所有解压缩响应的响应对象中移除"Content-Encoding"标头

// - 仅适用于Node (XHR 无法关闭解压缩功能)

decompress: true // 默认值

}

2.11.3 响应结构

一个请求的响应包含以下信息

{

// data:由服务器提供的响应

data: {},

// status:来自服务器响应的 HTTP 状态码

status: 200,

// statusText:来自服务器响应的 HTTP 状态信息

statusText: 'OK',

// headers:服务器响应头

// 所有 header 名称都是小写,且可以使用方括号语法访问

// 例如: `response.headers['content-type']`

headers: {},

// config:是`axios`请求的配置信息

config: {},

// request:是生成此响应的请求

// 在node.js中它是最后一个ClientRequest实例 (in redirects),

// 在浏览器中则是 XMLHttpRequest 实例

request: {}

}

2.11.4 默认配置

🛠️ 全局 axios 默认值

axios.defaults.baseURL = "https://api.example.com";

axios.defaults.headers.common["Authorization"] = AUTH_TOKEN;

axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";

🛠️ 自定义实例默认值

// 创建实例时配置默认值

const instance = axios.create({

baseURL: "https://api.example.com",

});

// 创建实例后修改默认值

instance.defaults.headers.common["Authorization"] = AUTH_TOKEN;

🛠️ 配置的优先级

配置将会按优先级进行合并

它的顺序是:在lib/defaults.js中找到的库默认值,然后是实例的 defaults 属性,最后是请求的config参数,后面的优先级要高于前面的

// 使用库提供的默认配置创建实例

// 此时超时配置的默认值是 `0`

const instance = axios.create();

// 重写库的超时默认值

// 现在,所有使用此实例的请求都将等待2.5秒,然后才会超时

instance.defaults.timeout = 2500;

// 重写此请求的超时时间,因为该请求需要很长时间

instance.get("/longRequest", {

timeout: 5000,

});

2.11.5 拦截器

在请求或响应被 then 或 catch 处理前拦截它们

// 添加请求拦截器

axios.interceptors.request.use(

function (config) {

// 在发送请求之前做些什么

return config;

},

function (error) {

// 对请求错误做些什么

return Promise.reject(error);

}

);

// 添加响应拦截器

axios.interceptors.response.use(

function (response) {

// 2xx 范围内的状态码都会触发该函数

// 对响应数据做点什么

return response;

},

function (error) {

// 超出 2xx 范围的状态码都会触发该函数

// 对响应错误做点什么

return Promise.reject(error);

}

);

移除拦截器

const myInterceptor = axios.interceptors.request.use(function () {

/*...*/

});

axios.interceptors.request.eject(myInterceptor);

可以给自定义的 axios 实例添加拦截器

const instance = axios.create();

instance.interceptors.request.use(function () {

/*...*/

});

2.11.6 错误处理

axios.get("/user/12345").catch(function (error) {

if (error.response) {

// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围

console.log(error.response.data);

console.log(error.response.status);

console.log(error.response.headers);

} else if (error.request) {

// 请求已经成功发起,但没有收到响应

// `error.request` 在浏览器中是 XMLHttpRequest 的实例,

// 而在node.js中是 http.ClientRequest 的实例

console.log(error.request);

} else {

// 发送请求时出了点问题

console.log("Error", error.message);

}

console.log(error.config);

});

使用 validateStatus 配置选项,可以自定义抛出错误的 HTTP code

axios.get("/user/12345", {

validateStatus: function (status) {

return status < 500; // 处理状态码小于500的情况

},

});

使用 toJSON 可以获取更多关于 HTTP 错误的信息

axios.get("/user/12345").catch(function (error) {

console.log(error.toJSON());

});

2.11.7 取消请求

🛠️ AbortController

const controller = new AbortController();

axios

.get("/foo/bar", {

signal: controller.signal,

})

.then(function (response) {

//...

});

// 取消请求

controller.abort();

2.11.8 请求体编码

默认情况下,axios 将 JavaScript 对象序列化为JSON。 要以application/x-www-form-urlencoded格式发送数据

🛠️ 浏览器

URLSearchParams

const params = new URLSearchParams();

params.append("param1", "value1");

params.append("param2", "value2");

axios.post("/foo", params);

注:不是所有的浏览器都支持 URLSearchParams ,但是可以使用polyfill (确保 polyfill 全局环境)

可以使用qs 库编码数据

import qs from "qs";

const data = { bar: 123 };

const options = {

method: "POST",

headers: { "content-type": "application/x-www-form-urlencoded" },

data: qs.stringify(data),

url,

};

axios(options);

🛠️ Node.js

在 node.js 中, 可以使用querystring 模块

const querystring = require("querystring");

axios.post("http://something.com/", querystring.stringify({ foo: "bar" }));

或者从'url module'中使用'URLSearchParams'

const url = require("url");

const params = new url.URLSearchParams({ foo: "bar" });

axios.post("http://something.com/", params.toString());

也可以使用 qs 库

Form data

const FormData = require("form-data");

const form = new FormData();

form.append("my_field", "my value");

form.append("my_buffer", new Buffer(10));

form.append("my_file", fs.createReadStream("/foo/bar.jpg"));

axios.post("https://example.com", form, { headers: form.getHeaders() });

或者, 使用一个拦截器

axios.interceptors.request.use(config => {

if (config.data instanceof FormData) {

Object.assign(config.headers, config.data.getHeaders());

}

return config;

});

2.11.9 axios 二次封装

为什么

方便管理和维护

请求的 url 地址统一管理

某些接口需要传递 headers

request.js

// 1. 引入axios

import axios from 'axios';

// 2. 创建axios对象

const service = axios.create();

// 3. 请求拦截器(前端给后端发送数据,没有到后端)

// 做的事:headers给后端传递token

service.interceptors.request.use(

config => {

return config;

}, error => {

Promise.reject(error);

}

});

// 4.响应拦截器(后端给前端返回数据,前端到后端了)

service.interceptors.response.use(

(response) => {

// 这里是对响应的简化 data = response.data

const { data, meta } = response.data

if (meta.status === 200 || meta.status === 201){

//回传的数据

return data

} else {

ElMessage.error(meta.msg)

return Promise.reject(new Error(meta.msg))

}

},

error => {

error.response && ElMessage.error(error.response.data)

return Promise.reject(new Error(error.response.data))

}

)

export default service

2.11.10 API 解耦

封装

import request from "@/utils/request";

export function getsliders() {

return request({

url: "/api/slider/getsliders",

});

}

使用

3. Ajax 原理

3.1 XMLHttpRequest

Ajax 是浏览器与服务器通信的技术,采用 XMLHttpRequest 对象相关代码

axios 是对 XHR 相关代码进行了封装,让我们只关心传递的接口参数

学习 XHR 也是了解 axios 内部与服务器交互过程的真正原理

语法:

const xhr = new XMLHttpRequest();

xhr.open("请求方法", "请求url网址");

xhr.addEventListener("loadend", () => {

// 响应结果

console.log(xhr.response);

});

xhr.send();

示例 1:获取所有省份列表并展示到页面上

示例 2:地区查询



地区列表:

示例 3:数据提交

3.2 Promise

3.2.1 对于异步的理解

示例

console.log("开始"); // 立即执行,输出 "开始"

setTimeout(() => {

// 设置一个异步任务,延迟 1000 毫秒后执行

console.log("异步任务完成"); // 这个代码在 1 秒后执行

}, 1000);

console.log("结束"); // 立即执行,输出 "结束"

输出顺序

开始:第一个console.log立即执行,输出"开始"

结束:第二个console.log立即执行,输出"结束"

异步任务完成:在设置的 1 秒之后,setTimeout中的回调函数才被调用,输出"异步任务完成"

异步体现

非阻塞:在setTimeout调用后,代码并没有等待 1 秒,而是继续执行后面的console.log('结束')。这说明setTimeout是非阻塞的

执行顺序:即使setTimeout设置了一个 1 秒的延迟,它的回调函数并不会立即执行,而是被放入事件队列中,等待主线程空闲时再执行。这导致"结束"会在"异步任务完成"之前输出

总结

异步允许代码在等待某个操作(如定时器)时继续执行其他代码,而不会阻塞整个程序的执行

3.2.2 概述

是什么

表示(管理)一个异步操作最终状态和结果值的对象

Promise 的好处是什么?

逻辑更清晰(成功或失败会关联后续的处理函数)

了解 axios 函数内部运作的机制

解决回调函数地狱问题

Promise 管理异步任务的语法

// 1. 创建 Promise 对象

const p = new Promise((resolve, reject) => {

// 2. 执行异步任务-并传递结果

// 成功调用: resolve(值) 触发 then() 执行

// 失败调用: reject(值) 触发 catch() 执行

});

// 3. 接收结果

p.then(result => {

// 成功

}).catch(error => {

// 失败

});

示例

/**

* 目标:使用Promise管理异步任务

*/

const p = new Promise((resolve, reject) => {

setTimeout(() => {

// resolve('模拟AJAX请求-成功结果')

reject(new Error("模拟AJAX请求-失败结果"));

}, 2000);

});

p.then(result => {

console.log(result);

}).catch(error => {

console.log(error);

});

3.2.3 三种状态

待定(pending):初始状态,既没有被兑现,也没有被拒绝

已兑现(fulfilled):操作成功完成

已拒绝(rejected):操作失败

改变 Promise 对象状态后,内部触发对应回调函数传参并执行

注:每个 Promise 对象一旦被兑现/拒绝,那么状态无法再被改变

3.2.4 案例-获取省份列表

3.2.5 封装简易 axios

步骤

定义 myAxios 函数,接收配置对象,返回 Promise 对象

发起 XHR 请求,默认请求方法为 GET

调用成功/失败的处理程序

使用 myAxios 函数,获取省份列表展示

核心语法

function myAxios(config) {

return new Promise((resolve, reject) => {

// XHR 请求

// 调用成功/失败的处理程序

});

}

myAxios({

url: "目标资源地址",

})

.then(result => {})

.catch(error => {});

示例:获取省份列表

示例:获取地区列表

示例:注册用户

3.2.6 回调函数地狱

概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱

缺点:可读性差,异常无法捕获,耦合性严重,牵一发动全身

省份:

城市:

地区:

3.2.7 Promise 链式调用

概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束

细节:then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果

好处:通过链式调用,解决回调函数嵌套问题

const p = new Promise((resolve, reject) => {

setTimeout(() => {

resolve("北京市");

}, 1000);

});

const p2 = p.then(result => {

console.log(result);

return new Promise((resolve, reject) => {

setTimeout(() => {

resolve(`${result} --- 北京`);

}, 1000);

});

});

p2.then(result => {

console.log(result);

});

解决回调函数地狱

省份:

城市:

地区:

3.2.8 async 函数和 await

在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值

省份:

城市:

地区:

捕获错误

try {

// 要执行的代码

// 如果try里某行代码报错后,try中剩余的代码不会执行了

} catch (error) {

// error 接收的是

错误消息;

// try 里代码,如果有错误,直接进入这里执行

}

示例

省份:

城市:

地区:

3.3 事件循环(EventLoop)

作用:事件循环负责执行代码,收集和处理事件以及执行队列中的子任务

原因:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型

概念:执行代码和收集异步任务的模型,在调用栈空闲时,反复调用任务队列里回调函数的执行机制

JavaScript 内代码如何执行?

执行同步代码,遇到异步代码交给宿主浏览器环境执行,异步有了结果后,把回调函数放入任务队列排队当调用栈空闲后,反复调用任务队列里的回调函数

练习

/**

* 目标:阅读并回答执行的顺序结果

*/

console.log(1);

setTimeout(() => {

console.log(2);

}, 0);

function myFn() {

console.log(3);

}

function ajaxFn() {

const xhr = new XMLHttpRequest();

xhr.open("GET", "http://hmajax.itheima.net/api/province");

xhr.addEventListener("loadend", () => {

console.log(4);

});

xhr.send();

}

for (let i = 0; i < 1; i++) {

console.log(5);

}

ajaxFn();

document.addEventListener("click", () => {

console.log(6);

});

myFn();

结果:1 5 3 2 4 点击一次 document 就会执行一次打印 6

3.4 宏任务与微任务

异步任务分为

宏任务:由浏览器环境执行的异步代码

微任务:由 JS 引擎环境执行的异步代码

事件循环模型

console.log(1);

setTimeout(() => {

console.log(2);

}, 0);

const p = new Promise((resolve, reject) => {

resolve(3);

});

p.then(res => {

console.log(res);

});

console.log(4);

注:宏任务每次在执行同步代码时,产生微任务队列,清空微任务队列任务后,微任务队列空间释放!下一次宏任务执行时,遇到微任务代码,才会再次申请微任务队列空间放入回调函数消息排队

总结:一个宏任务包含微任务队列,他们之间是包含关系,不是并列关系

经典面试题

// 目标:回答代码执行顺序

console.log(1);

setTimeout(() => {

console.log(2);

const p = new Promise(resolve => resolve(3));

p.then(result => console.log(result));

}, 0);

const p = new Promise(resolve => {

setTimeout(() => {

console.log(4);

}, 0);

resolve(5);

});

p.then(result => console.log(result));

const p2 = new Promise(resolve => resolve(6));

p2.then(result => console.log(result));

console.log(7);

结果:1 7 5 6 2 3 4

3.5 Promise.all 静态方法

作用:合并多个 Promise 对象,等待所有同时成功完成(或某一个失败),做后续逻辑

语法:

const p = Promise.all([Promise对象, Promise对象, ...]) // 需要传入一个可迭代的数据

p.then(result => {

// result 结果: [Promise对象成功结果, Promise对象成功结果, ...]

}).catch(error => {

// 第一个失败的 Promise 对象,抛出的异常对象

})

示例:同时请求“北京”,“上海”,“广州”,“深圳”的天气并在网页尽可能同时显示

    案例:商品分类

    目标:把所有商品分类“同时”渲染到页面上

    获取所有一级分类数据

    遍历 id,创建获取二级分类请求

    合并所有二级分类 Promise 对象

    等待同时成功后,渲染页面

    案例:学习反馈

    框架

    学习反馈

    热门校区

    地区选择

    您的称呼

    宝贵建议

    index.js

    /**

    * 目标1:完成省市区下拉列表切换

    * 1.1 设置省份下拉菜单数据

    * 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单

    * 1.3 切换城市,设置地区下拉菜单数据

    */

    // 1.1 设置省份下拉菜单数据

    axios({

    url: "http://hmajax.itheima.net/api/province",

    }).then(result => {

    const str = result.data.list.map(pname => ``).join("");

    document.querySelector(".province").innerHTML = `` + str;

    });

    // 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单

    document.querySelector(".province").addEventListener("change", async e => {

    const result = await axios({

    url: "http://hmajax.itheima.net/api/city",

    params: {

    pname: e.target.value,

    },

    });

    // console.log(result)

    const str = result.data.list

    .map(cname => {

    return ``;

    })

    .join("");

    document.querySelector(".city").innerHTML = `` + str;

    // 清空地区下拉菜单

    document.querySelector(".area").innerHTML = ``;

    });

    // 1.3 切换城市,设置地区下拉菜单数据

    document.querySelector(".city").addEventListener("change", async e => {

    const result = await axios({

    url: "http://hmajax.itheima.net/api/area",

    params: {

    pname: document.querySelector(".province").value,

    cname: e.target.value,

    },

    });

    const str = result.data.list.map(aname => ``);

    document.querySelector(".area").innerHTML = `` + str;

    });

    /**

    * 目标2:收集数据提交保存

    * 2.1监听提交的点击事件

    * 2.2依靠插件收集表单数据

    * 2.3基于axios提交保存,显示结果

    */

    document.querySelector(".submit").addEventListener("click", async () => {

    try {

    const form = document.querySelector(".info-form");

    const data = serialize(form, { hash: true, empty: true });

    const result = await axios({

    url: "http://hmajax.itheima.net/api/feedback",

    method: "post",

    data,

    });

    alert(result.data.message);

    } catch (error) {

    // console.dir(error)

    alert(error.response.data.message);

    }

    });