文章目录

  • Ajax概述
  • 原生Ajax尝试
    • 请求的发送与请求头配置
    • JSON支持
    • IE缓存问题
    • 请求的取消与重发
  • jQuery的Ajax
  • 使用Axios发送Ajax[简易]
  • 使用fetch发送请求
  • Ajax跨域
    • JSONP解决跨域问题
    • CORS解决跨域问题
  • Axios的理解与使用
    • JSON Server的使用
    • Axios基本使用
    • 拦截器
    • 取消请求
  • Axios源码分析
    • 目录结构
    • 实现axios,使得既可以axios()也可以axios.xx()
    • Axios类的实现
    • 手写一个简易版的axios
    • 请求发送函数
    • 请求取消函数
    • 总结(八股)

Ajax概述

Ajax简介:Ajax(Async JavaScript and XML)在网页不刷新的情况下向服务器发送请求,获取响应,实现懒加载的过程

XML简介:XML与HTML类似,都是一种基于标签标记语言

  • HTML的标签都是预定义的,一般用来描述网页
  • XML没有预定义标签,标签名都是直接写来用的,一般用来存储数据,曾经的Ajax使用XML传输数据

AJAX的优缺点

  • 无需刷新就可以与服务器发送接受请求
  • 可以根据用户的事件动态更新页面内容
  • 没有浏览历史(无法后退)
  • 存在跨域问题(A网站向B网站发送内容)
  • 对SEO优化不友好

HTTP协议(详见计算机网络)
规定了浏览器与万维网服务之间的通信,规定了请求与响应,此处主要了解请求与响应报文的格式与参数

  • 请求报文结构

    • 请求行

      • 请求类型: Get/Post/Put…
      • URL: /xxx?name=zhangsan&passwd=lisi
      • HTTP协议版本: HTTP/1.1…
    • 请求头
      • Host: www.liukairui.cc
      • Cookie: username=admin
      • Content-type: text…
      • User-Agent: Chrome90
    • 空行
    • 请求体
      • 如果是GET请求,那么请求体是空的
      • 如果是POST请求,那么请求体可以是非空的
  • 响应报文结构
    • 响应行

      • HTTP协议版本: HTTP/1.1…
      • 响应状态码: 200…
      • 响应状态字符串: OK
    • 响应头
      • Content-type: text…
      • Content-encoding: gzip
      • Content-length: 1024
    • 空行
    • 响应体: 例如HTML内容

Chrome查看报文

  • F12-Network-选中包
  • 对于GET请求,可以看到
    • Header选项卡中有四个部分

      • General: 请求地址,请求方式,状态码,服务器IP,同源策略
      • Response Headers响应头
      • Request Headers请求头
      • Query String Paramenters将请求url的内容进行解析
    • Response: 响应体
    • Preview: 预览响应体
  • 对于POST请求,可以看到
    • Header选项卡中有四个部分

      • General: 请求地址,请求方式,状态码,服务器IP,同源策略
      • Response Headers响应头
      • Request Headers请求头
      • Query String Parameters: 请求体
    • Response: 响应体
    • Preview: 预览响应体

原生Ajax尝试

Ajax技术可以理解为手动在JS中进行http请求,获取响应报文,根据响应报文修改文件,与之前不同的是,之前是浏览器向服务器发送请求,服务器发送响应,浏览器刷新页面。现在是JS进行请求,JS自己处理结果,我们需要的是一套可以进行Http请求的JSAPI

请求的发送与请求头配置

GET部分

  • 服务端配置
    服务端使用的是NodeJS,我们需要Express处理http请求,其他的都不需要

    const express = require("express")
    var app=express()app.get("/server",(req,res)=>{// 配置Ajax同源策略,允许跨域访问res.setHeader('Access-Control-Allow-Origin','*')// 发回响应体res.send("Wow Ajax working...")
    });app.listen(,()=>{console.log("work on");
    })
    

    Node代码实现了收到一个/server请求,发回数据,由于但是没有设置页面的路由,我们需要手动打开网页

  • HTML页面

    <!DOCTYPE html>
    <html lang="en">
    <head><meta charset="UTF-8">
    </head>
    <body><button>发送AJAX</button><div class="result" style="border:solid;width: 200px;height: 200px;"></div>
    </body>
    <script>// 选择元素const btn = document.querySelector("body > button")var txtbox = document.querySelector(".result")// 绑定事件btn.onclick=function(){// 创建对象const xhr = new XMLHttpRequest();// 初始化对象http不可以省略xhr.open('GET',"http://127.0.0.1:9000/server")// 发送请求xhr.send();// 当xhr状态发生改变的时候时间// readystate表示状态分别是// 0: 没有初始化,1: open结束, 2: send结束, 3: 收到部分结果, 4: 收到所有结构xhr.onreadystatechange = function(){if(xhr.readyState===4 && xhr.status >= 200 && xhr.status < 300){// 打印测试内容console.log(xhr.status);        // 状态码console.log(xhr.statusText);    // 状态字符console.log(xhr.getAllResponseHeaders());   // 响应头 console.log(xhr.response);      // 响应体// 修改元素txtbox.innerHTML=xhr.response}}}
    </script>
    </html>
    
  • 总结
    我们使用Ajax实际上就是使用了一系列JS的API,包括四个步骤

    • const xhr = new XMLHttpRequest();创建一个Ajax请求
    • xhr.open('GET',"http://127.0.0.1:9000/server")初始化一个Ajax对象
    • xhr.send();发送这个对象
    • xhr.onreadystatechange绑定对象变化进行操作

    我们有几个变量表示对象状态

    • xhr.readystate: 0: 没有初始化,1: open结束, 2: send结束, 3: 收到部分结果, 4: 收到所有结构
    • xhr.status: 响应状态码
    • xhr.statusText: 响应状态字符
    • xhr.getAllResponseHeaders: 响应头
    • xhr.response: 响应体

POST部分

  • 服务端设置

    app.post("/",(req,res)=>{res.setHeader('Access-Control-Allow-Origin','*')res.send("OK");
    });
    

    服务端只是把get修改为了post

  • HTML设置
    事件监听函数内部修改

    const xhr = new XMLHttpRequest();
    xhr.open('POST', "http://127.0.0.1:9000");
    // 我们在这里配置我们需要发送的信息
    xhr.send('user=Liu&passwd=hey');
    xhr.onreadystatechange = function () {...}
    

设置请求头

  • 设置预定义的请求头
    只需要在请求处进行修改,例如

    xhr.open(...)
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    xhr.send(...)
    
  • 自定义请求头
    在HTML部分

    xhr.open(...)
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    xhr.setRequestHeader('defMe', 'LiuKaieui')      // 举例,参数分别是键值
    xhr.send(...)
    

    此时,出于浏览器安全设置,我们无法发送包,并报错数据头,我们需要修改服务端

    app.all("/",(req,res)=>{      // 其次,由于浏览器会使用get校验服务器权限,所以必须写Get方法,我们直接写成allres.setHeader('Access-Control-Allow-Origin','*')res.setHeader('Access-Control-Allow-Headers','*')   // 首先要修改这里,支持所有头res.send("OK");
    });
    

JSON支持

我们希望服务端向网页传送一个对象,但是默认是不支持的,很容易想到的方法就是服务端把对象转换为JSON,客户端讲JSON字符串转化为对象,有两种方法实现

  • 手动实现
    服务端只需要讲对象转换为JSON传出即可

    app.all("/json-server",(req,res)=>{let tmp = {"name": "Liu","Age": 12};res.setHeader('Access-Control-Allow-Origin','*')res.send(JSON.stringify(tmp));// 实际上不stringfy也可以
    });
    

    网页只需要把收到的对讲转化为对象就可以了(事件监听内部)

    const rxh=new XMLHttpRequest();
    rxh.open("POST","http://127.0.0.1:9000/json-server")
    rxh.send();
    rxh.onreadystatechange=function(){if(rxh.readyState===4 && rxh.status >=200 && rxh.status <300){console.log(JSON.parse(rxh.response));}
    }
    
  • 设置响应头实现
    设置响应类型后,我们就不需要手动转换了,浏览器收到JSON字符串后会自动转换,reponse就是一个对象

    const rxh=new XMLHttpRequest();
    rxh.responseType='json';     // 设置响应类型,无需设置header
    rxh.open("POST","http://127.0.0.1:9000/json-server")
    rxh.send();
    rxh.onreadystatechange=function(){if(rxh.readyState===4 && rxh.status >=200 && rxh.status <300){console.log(rxh.response);}
    }
    

IE缓存问题

IE(10-)会对Ajax的请求结果进行缓存,会导致下次Ajax请求得到缓存的结果,IE根据请求的url/body进行判断是否使用缓存,我们只需要在请求的时候加一个时间戳("...?t="+Date.now()),很多工具都自动实现了这个功能

请求的取消与重发

超时自动取消

可以制定获取请求的最长时间,超时后浏览器会自动取消请求。方法设置XMLHttpRequest的属性:.timeout: 网络超时时间(ms),.ontimeout: 超时回调函数函数名, .onerror: 网络错误回调函数名

  • 设置服务器, 我们设置一个2000ms的延迟模拟网络延时

    app.get("/",(req,res)=>{res.setHeader('Access-Control-Allow-Origin','*')setTimeout(()=>{res.send("You Get Response")},3000);
    })
    
  • 设置Ajax函数(事件内部的部分)
    var hrx = new XMLHttpRequest();// 设置超时时间,设置为2000ms的时候必然超时,4000应该不超时hrx.timeout=4000;// 超时回调函数hrx.ontimeout=()=>{alert("Network Too Slow")}// 网络错误回调函数hrx.onerror=()=>{alert("Network ERROR")}// 之后一切正常hrx.open("GET","http://127.0.0.1:9000");hrx.send();hrx.onreadystatechange=function(){if(hrx.readyState === 4 && hrx.status >= 200 && hrx.status < 300){txtBox.innerHTML=hrx.response}}
    

手动取消请求

  hrx.abord();

就可以直接取消

Ajax 重新发送请求

我们应该设置用户连点的时候取消上一次的请求以减小服务器压力,只要设置一个flag标记是否正在发送即可

jQuery的Ajax

  • jQuery有三个函数实现Ajax请求,分别是$.get(),$.post(),$.ajax()
  • $.get()$.post()类似,调用方法是
      $.get(url链接,{要发送的对象},(d)=>{收到对象的回调函数, d是获取的内容},"JSON"/"xml"/"html"/"text"/"script"/"json"/"jsonp" //收到数据的类型,例如这里如果写了"JSON",那么服务器发送JSON字符串,这边收到后会自动转换为对象)
    
  • $.ajax()是一个通用的方法
    $.ajax({                                // 所有的参数一起是一个对象url:"http://127.0.0.1:9000",        // 请求链接data:{"a":100,"b":200},             // 传输数据对象type:"GET",                         // 传输方式dataType:"JSON",                    // 数据类型success: (d)=>{console.log(d)},     // 成功回调函数error:()=>{console.log("ERR")},     // 失败回调函数timeout:2000,                       // 超时时间headers:{                           // 请求头 可以是标准的,也可以是自定义的A:100                           // 如果是自定义的要在服务端进行设置,见`使用原生...>设置请求头>自定义请求头`}
    })
    

    参数还有很多,详见文档

get/post使用简单,ajax功能多,按情况使用即可

使用Axios发送Ajax[简易]

是一个热门的AJAX请求库,支持promise, 支持NodeJS,支持取消请求,支持拦截器,简易的使用方式是

axios.get("http://127.0.0.1:9000",{   // 请求地址是一个单独的参数params:{                          // 请求的参数,也就是.com?A=1&b=2那部分,使用Axios你可以不用拼串id:100,un:7},headers:{                         // 自定义请求头name: 123},
});

获取请求结果是要使用Promise直接then(), Axios进行post的时候还可以使用params进行链接定制,但是参数列表有所不同axios.post(url,{data对象},{参数对象})

也可以使用axios函数直接发送

// 发送 POST 请求
axios({method: 'post',url: '/user/12345',data: {firstName: 'Fred',lastName: 'Flintstone'}
});

详见官方文档

使用fetch发送请求

fetch是一个window的对象,可以发送Ajax请求,返回一个Promise对象,fetch是前端发展的一种新技术产物。可以简单的理解为是XMLHttpRequest的参数简化版

Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。这种功能以前是使用 XMLHttpRequest实现的。Fetch提供了一个更好的替代方法,可以很容易地被其他技术使用,例如 Service Workers。Fetch还提供了单个逻辑位置来定义其他HTTP相关概念,例如CORS和HTTP的扩展。fetch代表着更先进的技术方向,但是目前兼容性不是很好,在项目中使用的时候得慎重。

格式是fetch(input[, init]);

  • input写url/requert对象
  • init写配置对象,有(详见文档)
    • method: 请求使用的方法,如 GET、POST。
    • headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
    • body: 请求的 body 信息:可能是一个 Blob、BufferSource (en-US)、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
    • mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
fetch('http://example.com/movies.json').then(function(response) {return response.json();})

在使用fetch的时候需要注意:

  • 当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
  • 默认情况下,fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。

Ajax跨域

同源策略是网景提出的浏览器安全策略,他要求Ajax请求的网页与目标服务器url,端口,协议是一致的,Ajax默认遵守这个策略,违背同源就是跨域

JSONP解决跨域问题

JSONP是一个非官方的跨域解决方案,只支持GET请求,利用了<script>标签的特性实现跨域,HTML的很多标签本身就是支持跨域的,例如<img><script>会引用外部的文件

首先要在浏览器中定义一个函数用于更新内容,例如我想在跨域请求后将结果更新到#box

function process_JSONP(t){$("#box").text(t)
}

跨域被请求端设置(请求端为127.0.0.1)

app.get("/jsonp-server",(req,res)=>{str = JSON.stringfy({"name":"我是一个被请求的对象"})res.send(`process_JSONP(${str})`);        // JS拼串
})

请求端在process_JSONP定义后引用JS

<script src="http://127.0.0.1/jsonp-server">

这样,服务端返回字符串被当作了js,执行了process_JSONP()命令,对网页进行了更新

原生JSONP实例

我们想要实现的功能是,点击#A,浏览器发送跨域请求,服务器发送结果,收到结果后,如果是true,更新.stat背景色为绿色,否则为红色

前端JS设置

function process_jsonp(stat){     // 获取后的处理函数if(stat)document.querySelector(".stat").style = "background-color:#bfa;"else document.querySelector(".stat").style = "background-color:#f11;"
}
btnA = document.querySelector("#A");
btnA.onclick=()=>{const jsLab = document.createElement("script");     // 创建一个script标签jsLab.src = "http://127.0.0.1:9000/jsonp";          // 设置一个script标签的srcdocument.body.appendChild(jsLab)                    // 将script标签添加到网页
}

后端只返回true

因为我们只能写url,不能指定请求头,请求体,我们只能实现get请求

jQuery实现JSONP实例

jQuery的get/post函数默认当然是不支持跨域的,但是jQuery还有一个getJSON函数,这个函数本来是用来请求JSON,但是这个函数在jQuery底层实现的时候是先对参数的url进行解析,然后使用了上面这种script标签的方法获取JSON对象,所以这个方法是支持跨域的,我们可以利用这个特性。

首先了解函数功能,参数列表是getJSON(url,callbackFunction),当函数发现url字符串有callback=?,他会自动替换成callback=一个字符串,之后jQuery会注册一个名字叫这个字符串的方法,这个方法内容就是第二个参数于是我们利用这个特性实现JSONP

我们的目标是: 前端点击#A,后端发回消息,前端将.stat的内容替换为响应结果

前端代码

$("#A").click(()=>{$.getJSON("http://127.0.0.1:9000/jsonp?callback=?",(d)=>{     // 这里callback=?会在请求的时候被替换$(".stat").text(d)})
})

后端代码

app.get("/jsonp",(req,res)=>{let cb = req.query.callback;          // 获取请求中的随机子复查u年res.send(`${cb}("Wow You Get!")`);    // 调用函数
})

当然这个方法是不适合大量数据请求的

CORS解决跨域问题

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种官方的跨域解决方案,不需要在客户端进行任何操作,在服务端进行修改就可以直接支持get/post

只需要在服务器上加上响应头Access-Control-Allow-Origin: *即可,如果想要设置允许特定的跨域请求,例如只允许127.0.0.1:5050的,那么只需要修改为Access-Control-Allow-Origin: 127.0.0.1:5050

实例

#btnB点击console显示获取的跨域结果

前端JS

btnB.onclick = ()=>{const xhr = new XMLHttpRequest();xhr.open("GET","http://127.0.0.1:9000/jsonp");xhr.send();xhr.onreadystatechange=()=>{if(xhr.readyState === 4 && xhr.status >=200 && xhr.status<300)console.log(xhr.response)}
}

后端JS

app.get("/jsonp",(req,res)=>{res.setHeader('Access-Control-Allow-Origin','*')res.send(`Wow You Get!`);
})

CORS不止定义了这一个响应头,还有

  • Access-Control-Allow-Origin: 指示请求的资源能共享给哪些域。
  • Access-Control-Allow-Credentials: 指示当请求的凭证标记为 true 时,是否响应该请求。
  • Access-Control-Allow-Headers: 用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。
  • Access-Control-Allow-Methods: 指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。
  • Access-Control-Expose-Headers: 指示哪些 HTTP 头的名称能在响应中列出。
  • Access-Control-Max-Age: 指示预请求的结果能被缓存多久。
  • Access-Control-Request-Headers: 用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。
  • Access-Control-Request-Method: 用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。
  • Origin: 指示获取资源的请求是从什么域发起的。

详见MDN文档

Axios的理解与使用

前置内容: Promise & AJAX

JSON Server的使用

JSON Server是一个利用RESTful API快速搭建的http服务框架, 可以无代码创建一个http服务,这个服务可以在请求的时候返回指定的JSON对象

  • 安装:
sudo npm install -g json-server
  • 创建文件: ./db.json,随便写入一个对象,例如

    {"posts": [{"id": 1,"title": "json-server","author": "typicode"}],"comments": [{"id": 1,"body": "some comment","postId": 1}],j s o n
    

    上面这个对象的意思是,当访问localhost:3000/posts的时候返回对应数组对象,当访问http://localhost:3000/posts?id=1的时候返回一个数组,数组中包含id=1的对象,其余以此类推

  • 启动服务: 同路径下执行
    json-server --watch db.json
    

Axios基本使用

Axios是一个基于Promise的http客户端,运行在浏览器/NodeJS上,向远程发送Ajax/http请求, 支持请求响应拦截器,支持请求响应的数据解析,支持取消请求,支持JSON处理,支持扩展攻击的防护

  • 可以使用npm安装在Node上(或在项目打包的时候)
  • 可以使用script标签引入

Axios被引入后与jQuery类似,只有一个axios函数

尝试实现Get/Post/Put/Delete请求

实现点击不同请求方式的按钮,发送不同请求到JSON-Server

前端JS

let btn_get = document.querySelectorAll("button")[0];
let btn_post = document.querySelectorAll("button")[1];
let btn_delete = document.querySelectorAll("button")[2];
let btn_put = document.querySelectorAll("button")[3];
let box_txt = document.querySelector(".stat");btn_get.onclick = ()=>{j s o naxios({method  : 'GET',url     : 'http://127.0.0.1:3000/posts',}).then((d)=>{box_txt.innerHTML = JSON.stringify(d.data);});
} // JSON-Server文档写到,post可以实现添加对象,直接使用post就可以将请求体添加到对象
btn_post.onclick = ()=>{axios({method  : 'POST',url     : 'http://127.0.0.1:3000/posts',// 请求体data    : {title: "Test1",author:"Test1"}}).then((d)=>{j s o nbox_txt.innerHTML = JSON.stringify(d.data);});
} // JSON-Server文档表示,put可以实现修改对象,使用put, 在链接上指定ID, 将修改成的内容作为data传参即可
btn_put.onclick = ()=>{j s o nmethod  : 'PUT',url     : 'http://127.0.0.1:3000/posts/2',// 请求体data    : {title: "Test1ANN",author:"Test1BNN"}}).then((d)=>{j s o ntringify(d.data);});
}j s o n
// JSON-Server文档表示,delete可以实现修改对象,使用delete, 在链接上指定ID即可
btn_delete.onclick = ()=>{axios({method  : 'DELETE',url     : 'http://127.0.0.1:3000/posts/2',}).then((d)=>{box_txt.innerHTML = JSON.stringify(d.data);});
}

后端使用数据

{"posts": [{"id": 1,"title": "json-server","author": "typicode"}],
}

发送get请求的时候会返回对象,post会增加对象,put会修改对象,delete会删除对象,这是json-server定义的行为方式

除了Axios对象之外,我们还可以使用Axios的方法实现请求的发送,有两种,一个是Axios.request(参数与Axios一样),还有一种是以请求方法命名的例如Axios.post(url,{post_body},{configure}),详见文档

Axios响应数据结构

{"data": {...},                    // 响应体对象"status": 201,                    // 响应状态码"statusText": "Created",          // 响应内容"headers": {                      // 响应头"cache-control": "no-cache","content-length": "48","content-type": "application/json; charset=utf-8","expires": "-1","location": "http://127.0.0.1:3000/posts/9","pragma": "no-cache"},"config": {                       // 请求配置"url": "http://127.0.0.1:3000/posts","method": "post"// ...},"request": {}                     // 原生的Ajax对象
}

Axios请求对象数据结构

这里的请求对象指的是Axios(config),Axios.request(config),Axios.post(url,{post_body},config)…中的config对象,参数有

翻译自文档,英语好可以不看

{// 访问的URL,如果设置了baseurl(后面会有),可以直接写后半部分url: 'http://127.0.0.1:3000/posts',// url: '/posts',// 请求方式method: 'get', // default// baseurl是url最基础的部分,如果这里有填数据,那么请求的时候会把url替换成baseurl+url 除非url被写为绝对路径baseURL: 'https://127.0.0.1:3000',// 在发送请求之前对数据进行处理的函数// 仅支持 'PUT', 'POST', 'PATCH' and 'DELETE'// 这是一个函数列表,最后一个函数必须返回字符串/JS的Buffer/JS的Buffer数组transformRequest: [function (data, headers) {// ...return data;}],// 对响应结果进行处理transformResponse: [function (data) {// ...return data;}],// 配置请求头信息headers: {'X-Requested-With': 'XMLHttpRequest'},// 设置url参数,例如Get的时候向下面这么写,就转化为?ID=12345params: {ID: 12345},// 参数序列化(格式化)函数paramsSerializer: function (params) {return Qs.stringify(params, {arrayFormat: 'brackets'})},// 请求体设置: 支持对象或者字符串,Axios最后都会转换成字符串,仅支持 'PUT', 'POST', 'DELETE , and 'PATCH'data: {firstName: 'Fred'},  // data: 'Country=Brasil&City=Belo Horizonte',// 设置超时时间, 超时后自动取消, 单位mstimeout: 1000,// 跨域请求时候设置是否携带cookiewithCredentials: false, // default// 设置请求适配器adapter: function (config) {/* ... */},// http的基础认证(有的页面要输入访问的用户名密码)auth: {username: 'janedoe',password: 's00pers3cret'},// 设置响应体格式responseType: 'json', // default// 字符集设置responseEncoding: 'utf8', // default// 跨域请求时cookie名设置 防止跨站攻击xsrfCookieName: 'XSRF-TOKEN', // default// 跨域请求头设置xsrfHeaderName: 'X-XSRF-TOKEN', // default// 上传时回调函数onUploadProgress: function (progressEvent) {// ...},// 下载时回调函数onDownloadProgress: function (progressEvent) {// ...},// 响应的最大长度maxContentLength: 2000,// 响应体最大长度maxBodyLength: 2000,// 如何定义响应成功validateStatus: function (status) {return status >= 200 && status < 300; // default},
bu kanrects: 5, // default// 设置socket文件位置socketPath: null, // default// 请求设置httpAgent: new http.Agent({ keepAlive: true }),httpsAgent: new https.Agent({ keepAlive: true }),// 代理设置,一般用于Node爬虫proxy: {protocol: 'https',host: '127.0.0.1',port: 9000,auth: {username: 'mikeymike',password: 'rapunz3l'}},// 请求取消函数cancelToken: new CancelToken(function (cancel) {}),bu kan否解压结果(仅用于Node)decompress: true // default// 可能会在较新版本中删除的向后兼容性过渡选项transitional: {silentJSONParsing: true; // default value for the current Axios versionforcedJSONParsing: true;clarifyTimeoutError: false;}
}

Axios的默认配置

可以设置config的默认值

直接设置axios.default.config参数 = "XX",例如axios.default.method = "GET"

创建实例对象,发送Axios请求

推荐用于测试请求的API: https://api.apiopen.top/

我们可以创建一个叫做duanzi的对象,用于发送请求

const duanzi = axios.create({baseURL:'https://api.apiopen.top/getSingleJoke',timeout:3000,method:"get"
})      duanzi({params:{sid:28654780}
}).then(d=>{console.log(d.data)})

我们使用了axios.creat()函数创建了一个duanzi对象用来获取段子,并且添加了一些参数,这个段子对象是一个函数,这个函数与Axios()的功能是一样的,相当于就是一个设置了默认参数作用域的Axios

我们可以调用函数发送Axios请求,这里参数列表设置sid是测试API文档要求的,我们还可以仿照axios.get()使用duanzi.get()

拦截器

  • 拦截器就是一个函数,有两类拦截器,请求拦截器和响应拦截器
  • 拦截器与中间件较为相似,就是在发送请求之前与收到响应之后对config/data进行判断,并抛出resolve/reject
  • 当拦截器抛出结构后,Axios会执行promise链上的对应步骤(继续发送请求还是取消请求等等可以暂时不管)
  • 拦截器与transformRequest/transformResponse有一定的区别,拦截器的返回结果是一个Promise对象,出错可以直接执行Promise链上的对应函数,但是transformRequest/transformResponse是一个函数列表,返回值是发送/收到的数据,前者是用于判定请求是否合法决定要不要继续运行的,后者是在合法基础上对数据进行处理的,有一定的区别,之后我们将做实验证明他们的关系

一个简单使用

先看一个简单的使用如下,不需要理解到底发生了上面

axios.interceptors.request.use(function(config){// 参数就是我们Axios请求中的config,我们可以对他进行判断,修改console.log("请求拦截器 成功");// 这里要返回一个对象用于设置请求return config;
},function(error){console.log("请求拦截器 失败");// 返回一个Promise的refuse对象return Promise.reject(error)
});axios.interceptors.response.use(function(response){// 参数是Axios的默认请求结果console.log("响应拦截器 成功");// 在这里不是必须返回发来的reponse,可以是任意的return response;
},function(error){console.log("响应拦截器 失败");return Promise.reject(error)
});axios.request({method  :   "GET",url     :   "http://127.0.0.1:3000/posts"
}).then(d=>{console.log(d.data)
})

得到的结果是

请求拦截器 成功
响应拦截器 成功
// 获得的数据

我们大致知道了刚刚运行的顺序是请求拦截器运行,返回config,发收请求,响应拦截器运行,返回结果,打印

拦截器的基本结构

请求拦截器

axios.interceptors.request.use(function(config){// 成功的处理return config;
},function(error){// 失败的处理return Promise.reject(error)
});
  • 参数
    请求拦截器有两个回调函数

    • 如果成功,那么执行第一个回调函数,这个回调函数的参数是上一个Promise发来的config
    • 如果失败,那么执行第二个回调函数,这个回调函数的参数是错误的reject
    • 如果某个回调函数判定当前数据有误,返回Promise.reject
    • 如果某个回调函数判定当前数据正确,返回Promise.resolve (直接return config,必须有config用于下一个函数调用)
  • 注意
    • 这个函数包含了两个回调函数,成功后要运行的函数与失败后要运行的函数,但是什么是成功与失败?
    • 如果学过Promise可以看出来这两个函数很像是Promise.then().catch()
    • 实际上就是这样的,请求拦截器是Axios的Promise链上的一个元素,所以在一个请求的Promise链上可以存在多个请求拦截器或者响应拦截器
    • 某一个请求拦截器的第一个回调函数(也就是我们所说的"成功"函数),会被调用当且仅当Promise链上的上一个元素得到了Resolve的返回值
    • 某一个请求拦截器的第二个回调函数(也就是我们所说的"失败"函数),会被调用当且仅当Promise链上的上一个元素得到了Reject的返回值
    • 第一个被执行的的请求器是直接调用第一个回调函数,也就是有如下链
      • Axios的请求Promise链上一个环节调用第一个被执行的请求拦截器的第一个回调函数(成功的),如果判断结果是可以执行(也就是返回处理后的config)那么相当于得到了resolve,如果有误则返回Promise.reject()
      • 下一个被执行的请求拦截器查看上一个的返回值,如果是reject执行他的第二个回调函数,否则执行第一个
      • 执行到最后一个拦截器之后,如果返回是resolve那么Axios发送请求,否则直接执行第一个响应拦截器的第二个回调函数(失败的)
    • 由于是Promise的调用,完全存在这种情况,有两个请求拦截器
      • 第一个拦截器第一个回调函数直接返回reject
      • 第二个拦截器第二个回调函数直接返回resolve(也就是return一个config对象)
      • 正常发送请求

响应拦截器

axios.interceptors.response.use(function(response){// 成功的处理return response;
},function(error){// 失败的处理return Promise.reject(error)
});
  • 参数
    响应拦截器有两个回调函数

    • 如果成功,那么执行第一个回调函数,这个回调函数的参数是上一个Promise发来的自定义对象,这个对象会作为响应的结果返回
    • 如果失败,那么执行第二个回调函数,这个回调函数的参数是错误的reject
    • 如果某个回调函数判定当前数据有误,返回Promise.reject
    • 如果某个回调函数判定当前数据正确,返回Promise.resolve (直接return 想返回的数据,可以与response有很大区别)

注意

  • 如果代码中存在多个请求拦截器或者响应拦截器,那么会代码中出现的次序逆序执行请求拦截器/顺序执行响应拦截器, 例如
axios.interceptors.request.use(function(config){console.log("请求拦截器1 成功");return config;
},function(error){console.log("请求拦截器1 失败");return Promise.reject;
});axios.interceptors.request.use(function(config){console.log("请求拦截器2 成功");return config;
},function(error){console.log("请求拦截器2 失败");return Promise.reject;axios.interceptors.response.use(function(response){console.log("响应拦截器1 成功");return response;
},function(error){console.log("响应拦截器1 失败");return Promise.reject(error)
});axios.interceptors.response.use(function(response){console.log("响应拦截器2 成功");return response;
},function(error){console.log("响应拦截器2 失败");return Promise.reject(error)
});axios(...)

我们看到的结果将是

请求拦截器2 成功
请求拦截器1 成功
响应拦截器1 成功
响应拦截器2 成功
  • 我们再在代码中加入奇怪的东西,熟练Promise链
// 请求部分
axios.interceptors.request.use(function(config){console.log("请求拦截器1 成功函数进入");// 经过一系列检查我觉得他没问题,给他正常通过// 这是最后一个请求拦截器,一旦通过就正常发送请求了return config                       // [ Falg1 ]// 这是最后一个请求拦截器,一旦失败就不请求直接进入失败拦截器了// return Promise.reject(config);   // [ Falg2 ]
},function(error){console.log("请求拦截器1 失败函数进入");// 这是最后一个请求拦截器,一旦失败就不请求直接进入失败拦截器了return Promise.reject(error);
});axios.interceptors.request.use(function(config){console.log("请求拦截器2 成功函数进入");return config;
},function(error){console.log("请求拦截器2 失败函数进入");// 虽然上一步拒绝了,但是我故意让他变为成功,我用了完整的写法return Promise.resolve(error);
});axios.interceptors.request.use(function(config){console.log("请求拦截器3 成功函数进入");// 经过一系列检查我拒绝了这个config,在拒绝信息写入config方便变化,实际应该写错误信息return Promise.reject(config)
},function(error){console.log("请求拦截器3 失败函数进入");return Promise.reject(error)
});// 响应部分
axios.interceptors.response.use(function(response){console.log("响应拦截器1 成功函数进入");// 收到了数据并且直接返回了return response;
},function(error){console.log("响应拦截器1 失败函数进入");// 收到了请求拦截器的拒绝,跳过发送,直接到这里,返回拒绝return Promise.reject(error)
});axios.interceptors.response.use(function(response){console.log("响应拦截器2 成功函数进入");// 先看看数据console.log(response)// 这是个奇怪的函数会反转结果return Promise.reject(response)
},function(error){console.log("响应拦截器2 失败函数进入");// 先看看数据console.log(error)// 这是个奇怪的函数会反转结果return error;
});axios.interceptors.response.use(function(response){console.log("响应拦截器3 成功函数进入");// 既然成功了,那让我们返回响应的数据,但是返回这么多又没啥用,我们直接返回一个Happy吧return "Happy";
},function(error){console.log("响应拦截器3 失败函数进入");// 既然失败了,那让我们返回失败的信息,但是返回这么多又没啥用,我们直接返回一个Sad吧return Promise.reject("Sad")
});axios.request({method  :   "GET",url     :   "http://127.0.0.1:3000/posts",      // 是前面json-server的服务
}).then(d=>{console.log("请求成功了",d)
}).catch(e=>{console.log("请求失败了",e);
})

尝试执行函数并理解Promise链的调用关系,之后尝试注释掉[ Flag1 ]行,取消注释[ Flag2 ]行,查看结果

结果1

请求拦截器3 成功函数进入
请求拦截器2 失败函数进入
请求拦截器1 成功函数进入
响应拦截器1 成功函数进入
响应拦截器2 成功函数进入
{data: Array(8), status: 200, statusText: "OK", headers: {…}, config: {…}, …}
响应拦截器3 失败函数进入
请求失败了 Sad

结果2

请求拦截器3 成功函数进入
请求拦截器2 失败函数进入
请求拦截器1 成功函数进入
响应拦截器1 失败函数进入
响应拦截器2 失败函数进入
{url: "http://127.0.0.1:3000/posts", method: "get", headers: {…}, transformRequest: Array(1), transformResponse: Array(1), …}
响应拦截器3 成功函数进入
请求成功了 Happy
  • 拦截器与transformXXX的执行顺序是 请求拦截器,transformRequest,transformResponse,响应拦截器, 对上面的代码稍加修改接可以看到
axios.interceptors.request.use(function(config){console.log("请求拦截器 成功");return config;
},function(error){console.log("请求拦截器 失败");return Promise.reject(error)
});axios.interceptors.response.use(function(response){console.log("响应拦截器 成功");return response;
},function(error){console.log("响应拦截器 失败");return Promise.reject(error)
});axios.request({method  :   "GET",url     :   "http://127.0.0.1:3000/posts",transformRequest: [(d)=>{console.log("TFReq");return d;}],transformResponse: [(d)=>{console.log("TFRes");return d;}]
}).then(d=>{console.log(d.data)
})

结果是

请求拦截器 成功
TFReq
TFRes
响应拦截器 成功
// 返回的数据

取消请求

非常简单

let cancelFlag = null;  // 请求取消函数
// 请求按钮
btn_req.onclick=(e)=>{axios({method  :   "GET",url     :   "http://127.0.0.1:3000/posts",// 多加入一行,cancelTocken是一个回调函数,对象是axios的新取消,设置请求取消函数cancelToken: new axios.CancelToken((c)=>{cancelFlag = c;})}).then(()=>{console.log("OK")})
};// 取消请求按钮
btn_can.onclick=(e)=>{cancelFlag();console.log("Cancel")
};

设置json-server响应延迟2000ms

json-server --watch db.json --delay 2000

Axios源码分析

直接在./node_modules/axios获取Axios源码,删除无用文件(例如证书)

目录结构

.
├── dist                            // 打包后的文件
│   ├── axios.js                    // 未压缩的
│   ├── axios.map                   // 对应文件
│   ├── axios.min.js                // 压缩后的
│   └── axios.min.map               // 对应文件
├── index.d.ts                      // ts使用版本文件
├── index.js                        // 包入口文件
├── lib                             // 核心目录
│   ├── adapters                    // 自定义请求的是适配器
│   │   ├── http.js                 // 用于在Node中发送请求的适配器
│   │   └── xhr.js                  // 用于在浏览器中发送Ajax的适配器
│   ├── axios.js                    // 入口文件
│   ├── cancel                      // 与取消相关文件
│   │   ├── Cancel.js               // Cancel构造函数
│   │   ├── CancelToken.js          // 创建取消请求的构造函数
│   │   └── isCancel.js             // 判断某一个值是不是由取消产生的结果
│   ├── core                        // 核心功能文件
│   │   ├── Axios.js                // Axios的构造函数文件
│   │   ├── buildFullPath.js        // 构造完整URL的文件
│   │   ├── createError.js          // 创建Error对象
│   │   ├── dispatchRequest.js      // 发送请求
│   │   ├── enhanceError.js         // 更新错误对象
│   │   ├── InterceptorManager.js   // 拦截器管理器构造函数
│   │   ├── mergeConfig.js          // 合并配置的文件
│   │   ├── settle.js               // 根据http响应状态码更新Promise状态
│   │   └── transformData.js        // 对结果格式化
│   ├── defaults.js                 // 默认配置文件
│   ├── helpers                     // 功能函数
│   │   ├── bind.js                 // 用于创建函数,修改函数允许时this指向
│   │   ├── buildURL.js             // 构造url,加入参数(例如get的parame)
│   │   ├── combineURLs.js          // 合并url
│   │   ├── cookies.js              // 操作cookie
│   │   ├── deprecatedMethod.js     // 如果使用了某个过时方法在控制台waring
│   │   ├── isAbsoluteURL.js        // 判断url为绝对路径
│   │   ├── isAxiosError.js         // 判断url正误
│   │   ├── isURLSameOrigin.js      // 判断url同源
│   │   ├── normalizeHeaderName.js  // 统一请求头,例如小写字母转大写
│   │   ├── parseHeaders.js
│   │   └── spread.js               // 对请求进行批量处理
│   └── utils.js                    // 比较杂的工具函数文件
└── package.json

实现axios,使得既可以axios()也可以axios.xx()

在发送请求的时候我们既可以axios(...),也可以使用axios.get(),也就是说axios既是一个函数,也是一个对象。这点与jQuery相似,即可$(),也可以$.XX(),实际上他们实现的方法都是相同的

Axios包的入口文件是./index.js

module.exports = require('./lib/axios');

我们看./lib/axios

'use strict';               // 启动严格模式var utils = require('./utils');         // 导入工具函数
var bind = require('./helpers/bind');   // 导入bind
var Axios = require('./core/Axios');    // 导入Axios对象
var mergeConfig = require('./core/mergeConfig');    // 导入合并配置文件
var defaults = require('./defaults');               // 导入默认配置/*** 创建一个类axios的函数* 注意他返回的不是Axios的实例* 他的目的是返回一个duanzi = axios.creat()中和duanzi一样的对象** @参数 axios的默认配置参数,也就是写 duanzi = axios.config({...})的时候()中的config* @返回 类似axios对象*/
function createInstance(defaultConfig) {// ! Context是一个Axios对象var context = new Axios(defaultConfig);// bind是函数绑定的工具函数,在./helpers/bind,可以绑定函数,修改this// 这句话的意思相当于是instance=Axios.prototype.request instance运行时候this是context// Axios.prototype.request是用来发送请求的,这个函数调用dispatchRequest.js// dispatchRequest.js 调用xhr/http.js进行请求// ! instance目前是一个函数, 只有Requestvar instance = bind(Axios.prototype.request, context);// utils位于./utils,可以将对象的方法进行复制// 实际上就是执行了一次遍历进行深拷贝// 第三个参数是某个被拷贝的参数是函数,要把函数运行的this修改成context,不填就不改// ! instance目前有了context的函数和Request函数utils.extend(instance, Axios.prototype, context);// ! instance绑定上context的对象utils.extend(instance, context);// 相当于instance = Axios对象+请求函数return instance;
}// 获得一个axios
var axios = createInstance(defaults);/*** 下面这些就是为axios绑定Axios没有的方法* 平时我们创建一个 duanzi = axios.creat() 获得的就够就没有这些方法*/// 绑定Axios方法
axios.Axios = Axios;// 用于创建新实例的方法
axios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};// 绑定方法
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');axios.all = function all(promises) {return Promise.all(promises);
};
axios.spread = require('./helpers/spread');axios.isAxiosError = require('./helpers/isAxiosError');// 导出axios
module.exports = axios;module.exports.default = axios;

很不错的创建工具函数的方法,我有Axios对象,我希望构造一个axios变量,本身是一个函数,也是一个对象,作为函数可以快速实现Axios的某个功能,作为对象可以包含定义一系列不常用的Axios方法,可以创建一个新的实例,做普通实例没有的方法,这个与jQuery的$原理是一样的

  • 定义好Axios对象
  • 创建一个axios变量,默认值就是默认配置下的Axios实例
  • 完善结构,将全局暴露的函数导出为axios的属性

JSDoc注释

源码中注释是这么写的

/*** Create an instance of Axios** @param {Object} defaultConfig The default config for the instance* @return {Axios} A new instance of Axios*/

我们点击一个@的时候可以进行跳转,这是JSDoc注释规范,

Axios类的实现

看./lib/core/Axios.js文件(可以先看./InterceptorManager.js)

'use strict';                       // 严格模式var utils = require('./../utils');  // 引用工具函数
var buildURL = require('../helpers/buildURL');            // 构建url的模块
var InterceptorManager = require('./InterceptorManager'); // 拦截器管理器模块
var dispatchRequest = require('./dispatchRequest');       // 发送请求模块
var mergeConfig = require('./mergeConfig');               // 默认配置模块(例如默认method是get就是来自这里)/*** Axios的实现** @param {Object} instanceConfig 传入config,例如duanzi = axios.creat(config)的config就是用在了这里*/
function Axios(instanceConfig) {this.defaults = instanceConfig;       // 用来放配置文件this.interceptors = {                 // 两个拦截器,分别是两个拦截器管理器对象request: new InterceptorManager(),response: new InterceptorManager()};
}/*** 发送请求的模块** @param {Object} config 这是例如axios.get({config})的时候的config*/
Axios.prototype.request = function request(config) {// 存在两种情况的请求,一种是//    axios({config})//    axios.get("http://127.0.0.1",{config})// 我们要处理两种情况if (typeof config === 'string') {// 处理第二种情况config = arguments[1] || {};config.url = arguments[0];} else {// 方式不传参 config=nullconfig = config || {};}// 将config与axios的默认/自定义实例的config合并config = mergeConfig(this.defaults, config);// 填写请求类型if (config.method) {// 如果config有指定就格式化一下config.method = config.method.toLowerCase();} else if (this.defaults.method) {// 如果config没有指定,但是axios实例指定了,例如在duanzi = axios.creat({method="get"})config.method = this.defaults.method.toLowerCase();} else {// 否则默认getconfig.method = 'get';}/*** 构建拦截器串* 使用一个数组* 第一个参数是一个发送请求的返回Promise的函数,来自./dispatchRequest.js* 后面留空用来候补,暂时不要管这个反着的顺序*/// 注意这里的undefine是必须的,我们知道这个chain中的函数都是成对出现的// [请求拦截器2.resolve,请求拦截器2.reject,请求拦截器1.resolve,请求拦截器1.reject,请求发送函数,undefine,响应拦截器1.resolve,响应拦截器1.reject,响应拦截器2.resolve,响应拦截器2.reject]// 如果请求1 resolve了,会跳到请求发送函数,如果请求1 reject了,那么下一个是undefine,undefine无法执行就会掷出一个error到下一个catch,也就是相应拦截器1 reject// 一定会跳转到undefine是因为执行的时候是promise = promise.then(chain.shift(), chain.shift());执行的var chain = [dispatchRequest, undefined];// 构建一个resolve的Promise用来开启Promise链,config是resolve的结果,传参到链var promise = Promise.resolve(config);/** * 遍历请求拦截器管理器* 是一个,管理器是一个对象(伪数组),自己封装了forEach方法(伪数组本不可以用forEach),封装了use方法* 管理器伪数组中的元素是对象,每个对象有两个子对象* {*    fulfilled: fulfilled,*    rejected: rejected* }* 分别是axios.interceptors.request.use()的两个回调函数*/ this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {// 数组的unshift方法是向前添加一个元素chain.unshift(interceptor.fulfilled, interceptor.rejected);});// 遍历响应器拦截器this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {chain.push(interceptor.fulfilled, interceptor.rejected);});// 执行这个Promise链while (chain.length) {promise = promise.then(chain.shift(), chain.shift());}return promise;
};Axios.prototype.getUri = function getUri(config) {config = mergeConfig(this.defaults, config);return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};// 定义axios.get...函数, 不包含请求体的协议
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {Axios.prototype[method] = function(url, config) {return this.request(mergeConfig(config || {}, {method: method,url: url,data: (config || {}).data}));};
});// 定义axios.post...函数,包含请求体的协议
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {Axios.prototype[method] = function(url, data, config) {return this.request(mergeConfig(config || {}, {method: method,url: url,data: data}));};
});module.exports = Axios;

注意Promise链的实现

var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {chain.push(interceptor.fulfilled, interceptor.rejected);
});while (chain.length) {promise = promise.then(chain.shift(), chain.shift());
}

手写一个简易版的axios

// 封装工具函数 对应的是 ./lib/utils.js
function Utiles(){};
// 合并配置文件函数 对应 merge()
Utiles.prototype.mergeConfig=(...arg)=>{if(!arg.length)return {};arg[0]=(typeof(arg[0])=='string')?{"url":arg[0]}:arg[0];let config = {};arg.forEach((d,i)=>{for(dd in d)config[dd]=d[dd];});return config;
}let utiles=new Utiles;// Axios部分 ./lib/core/Axios.js
function Axios(config){// 配置文件的默认设置,例如默认mothod是get,加了一个是测试用的,对应./lib/defaults.js// 这个method与人家实现不同this.defaultConfig = {"globalDefault": "I'm Axios default setting","method": "GET"}// 某一个方法的默认配置this.methodDefaultConfig = {"GET":{"defaultMethodConfig":"GET"},"POST":{"defaultMethodConfig":"POST"},"PUT":{"defaultMethodConfig":"PUT"},"DELETE":{"defaultMethodConfig":"DELETE"},}// 实例的配置config = config||{};// 请求配置就是实例的配置this.ReqConfig = config;// 两个拦截器this.interceptor={// TODOreqInterceptor: [],resInterceptor: [],}
}// 执行请求拦截器链
Axios.prototype.reqinterceptorChain=function(chrInt){let len = this.interceptor.reqInterceptor.length;// 如果没有指定就用默认配置resolve开始请求chrInt = chrInt || Promise.resolve(this.ReqConfig);while(len--){// 防止len作为闭包传输导致的len--修改lenlet tmp = len;// Promise链chrInt = chrInt.then(d=>this.interceptor.reqInterceptor[tmp][0](d),e=>this.interceptor.reqInterceptor[tmp][1](e));}return chrInt;
}Axios.prototype.resinterceptorChain=function(chrInt){let len = this.interceptor.reqInterceptor.length;// 上面没给请求成功还是失败了chrInt = chrInt || Promise.reject("上头没给传参数");for(let i = 0; i<len;i++){let tmp = i;chrInt = chrInt.then(d=>this.interceptor.resInterceptor[tmp][0](d),e=>this.interceptor.resInterceptor[tmp][1](e));}return chrInt;
}// 添加一个请求拦截器,实际上就是加入两个回调函数
Axios.prototype.addReqInt=function(reslv,rejct){this.interceptor.reqInterceptor.push([reslv,rejct]);
}Axios.prototype.addResInt=function(reslv,rejct){this.interceptor.resInterceptor.push([reslv,rejct]);
}// 请求函数
Axios.prototype.request = function(config){// 合并配置文件,注意,这里必须在这里合并,并且不能合并到this.reqconfig否则同时进行多个axios的时候会出现下一个覆盖上一个配置// 注意绑定顺序config = config||{};config = utiles.mergeConfig(this.defaultConfig,this.ReqConfig,config,this.methodDefaultConfig[config["method"]]);// 返回一个Promise链return this.reqinterceptorChain(Promise.resolve(config))        // 请求拦截器.then((d)=>{// 请求拦截器获得reject不执行// 正式发请求,这里为了测试在config中加入了want字段,want=true获得200,=false获得404// 相当于省略了实际的 config解析处理 dispatchRequest.js 响应码的判断 if(config.want||Math.random()>0.5)return Promise.resolve({"statCode": 200,"data":"假装我是一个响应结果, 方法是"+config.method})elsereturn Promise.reject({"statCode": 404,"data":"假装我是一个错误, 方法是"+config.method})}).then(// 拦截器resolve && 请求到了200d=>this.resinterceptorChain(Promise.resolve(d))).catch(// 请求拦截器reject || 请求到了404e=>this.resinterceptorChain(Promise.reject(e)))
}// 为配置文件增加method
Axios.prototype.get = function(config){config = config || {};config["method"] = "GET";return this.request(config)
}Axios.prototype.post = function(config){config = (config || {});config["method"] =  "POST";return this.request(config)
}Axios.prototype.put = function(config){config = (config || {});config["method"] =  "PUT";return this.request(config)
}Axios.prototype.delete = function(config){config = (config || {});config["method"] =  "DELETE";return this.request(config)
}// 封装axiosfunction buildInstance(config){config = config||{};let context = new Axios(config);                            // 先做一个Axios实例context出来let instance= Axios.prototype.request.bind(context);        // 把instance直接绑为context.request 实现 axios(config) 现在他只是函数, 不能执行axios.get()['get','post','put','delete','addReqInt','addResInt'].forEach((d,i)=>{      // 实现axios.get()... 但是不能axios.creat()instance[d]=Axios.prototype[d].bind(context);})Object.keys(context).forEach((d,i)=>{                       // 绑定context的对象,例如拦截器列表instance[d]=context[d];})return instance;
}let axios = buildInstance();                                    // axios是一个没有config的实例
axios.Axios = Axios;
axios.creat = buildInstance;                                    // 绑定creat
// 这里还应该绑一些Axios没有但是axios有的// 创建实例duanzilet duanzi = axios.creat({"type": "duanzi"
})// 加入拦截器
// 如果想让请求拦截器给一个reject,修改往下数3,4行
duanzi.addReqInt((d)=>{console.log("请求拦截器 3 进入成功回调",d);return d;// return Promise.reject("我是最后一个请求拦截器,我故意挂你的");
},(e)=>{console.log("请求拦截器 3 进入失败回调",e);return e;
});duanzi.addReqInt((d)=>{console.log("请求拦截器 2 进入成功回调",d);return Promise.reject(d);
},(e)=>{console.log("请求拦截器 2 进入失败回调",e);return e;
});duanzi.addReqInt((d)=>{console.log("请求拦截器 1 进入成功回调",d);return Promise.reject(d);
},(e)=>{console.log("请求拦截器 1 进入失败回调",e);return e;
});duanzi.addResInt((d)=>{console.log("响应拦截器 1 进入成功回调",d);return Promise.reject(d);
},(e)=>{console.log("响应拦截器 1 进入失败回调",e);return e;
});duanzi.addResInt((d)=>{console.log("响应拦截器 2 进入成功回调",d);return Promise.reject(d);
},(e)=>{console.log("响应拦截器 2 进入失败回调",e);return e;
});duanzi.addResInt((d)=>{console.log("响应拦截器 3 进入成功回调",d);return d;
},(e)=>{console.log("响应拦截器 3 进入失败回调",e);return Promise.reject(e);
});// 使用实例
duanzi({"want":false,"flag":"A"
}).then((d)=>{console.log("Success",d)
})
.catch((e)=>{console.log("Error",e);
})duanzi.post({"want":false,"flag":"B"
}).then((d)=>{console.log("Success",d)
})
.catch((e)=>{console.log("Error",e);
})// 使用axios
axios({method: "我看看能不能这么写",want: true
}).then((d)=>{console.log("*Success",d)
})
.catch((e)=>{console.log("*Error",e);
})axios.delete({want: true
}).then((d)=>{console.log("*Success",d)
})
.catch((e)=>{console.log("*Error",e);
})

请求发送函数

文件在./lib/core/dispatchRequest.js

'use strict';                         // 严格模式var utils = require('./../utils');    // 工具函数
var transformData = require('./transformData');   // 转JSON
var isCancel = require('../cancel/isCancel');     // 取消判定
var defaults = require('../defaults');            // 默认配置/*** 取消*/
function throwIfCancellationRequested(config) {if (config.cancelToken) {config.cancelToken.throwIfRequested();}
}/*** 请求发送函数** @param {object} config 配置文件* @returns {Promise} 返回结果Promise*/
module.exports = function dispatchRequest(config) {throwIfCancellationRequested(config);// 配置请求头config.headers = config.headers || {};// 数据转换config.data = transformData(config.data,config.headers,config.transformRequest);// 配置头config.headers = utils.merge(config.headers.common || {},config.headers[config.method] || {},config.headers);// 配置对应协议必须的请求头utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],function cleanHeaderConfig(method) {delete config.headers[method];});// 选择请求器是xhr还是http,也接受用户配置var adapter = config.adapter || defaults.adapter;// 调用xhr/http获得结果,转换结果,返回resolve/rejectreturn adapter(config).then(function onAdapterResolution(response) {throwIfCancellationRequested(config);// Transform response dataresponse.data = transformData(response.data,response.headers,config.transformResponse);return response;}, function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);// Transform response dataif (reason && reason.response) {reason.response.data = transformData(reason.response.data,reason.response.headers,config.transformResponse);}}return Promise.reject(reason);});
};

请求取消函数

前端操作代码

let cancelFlag = null;
btn_req.onclick=(e)=>{axios({method  :   "GET",url     :   "http://127.0.0.1:3000/posts",cancelToken: new axios.CancelToken((c)=>{cancelFlag = c;})}).then(()=>{console.log("OK")})
};btn_can.onclick=(e)=>{cancelFlag();console.log("Cancel")
};

Cancel对象(./lib/cancel/CancelToken.js)

/*** A `CancelToken` is an object that can be used to request cancellation of an operation.** @class* @param {Function} executor The executor function.*/
function CancelToken(executor) {if (typeof executor !== 'function') {throw new TypeError('executor must be a function.');}var resolvePromise;this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve;});var token = this;executor(function cancel(message) {if (token.reason) {// Cancellation has already been requestedreturn;}token.reason = new Cancel(message);resolvePromise(token.reason);});
}

xhr适配器158-170行(./lib/adapters/xhr.js)

if (config.cancelToken) {// Handle cancellationconfig.cancelToken.promise.then(function onCanceled(cancel) {if (!request) {return;}request.abort();reject(cancel);// Clean up requestrequest = null;});
}

首先在前端发送请求的时候需要配置

cancelToken: new axios.CancelToken((c)=>{cancelFlag = c;
})

相当于在创建请求的时候创建了一个CancelTocken对象,保存在config

构造函数如下

function CancelToken(executor) {// 如果没有传入一个函数直接报错if (typeof executor !== 'function') {throw new TypeError('executor must be a function.');}// 定义一个resolvePromise变量var resolvePromise;// 对象具有一个promise,默认下是padding因为里面的函数没有返回// promise会resolve当且仅当resolve也就是resolvePromise被执行this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve;});var token = this;                 // 拷贝this传入闭包// executor是传入的函数,我们要执行这个传入的函数,并且这个传入函数的参数是一个函数// 从前端看就是cancel被赋值成了这个函数function cancel(message)executor(function cancel(message) {// 如果reason存在就直接返回if (token.reason) {// Cancellation has already been requestedreturn;}// 否则构建一个新tockentoken.reason = new Cancel(message);// 执行这个函数,就是将promise变成resolveresolvePromise(token.reason);});
}

我们看下promise变成resolve之后发生什么,在xhr.js中,在初始化的时候有一段代码

if (config.cancelToken) {// Handle cancellationconfig.cancelToken.promise.then(function onCanceled(cancel) {if (!request) {return;}request.abort();reject(cancel);// Clean up requestrequest = null;});
}

如果定义了cancelToken那么设置promise的then,如果没有请求就返回,请求了就abort请求,返回reject,同时设置请求为null

这样做虽然有点绕,但是实现了

  • Axios将promise.resolve()通过构造函数的回调函数传递给了前端
  • Axios将底层取消的代码通过config放在了适配器的js文件中
  • 代码的分离

模式是:

构造函数

function cancelTocken(executor){// 判定前端是否传递了回调函数用于取消if (typeof executor !== 'function')throw new TypeError('executor must be a function.');var resolvePromise;               // 暴露resolve函数this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve;       // 放置Promise,将resolve给暴露变量});// 前端赋值的结果executor((message)=>{resolvePromise(message);});
}

前端调用

let cancel = null;
let ct = new cancelTocken((d)=>{cancel = d;})
if(xxx)cancel("加入你想传递一些信息")

底层代码

if(存在这么个promise){这个promise.then((d)=>{// 传递的信息就到d变量了})
}

总结(八股)

  • Axios与axios的关系

    • axios不是Axios实例
    • axios拥有Axios实例的全部方法
    • axios绑定了Axios.request
    • axios还扩展了creat()的结果
  • instance和axios的区别
    • instance是Axios实例+request
    • axios在instance上扩展了creat,Axios,isCancel…方法
  • axios的整体流程
#mermaid-svg-7LJ5IZoX1JRgOXgb .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .label text{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .node rect,#mermaid-svg-7LJ5IZoX1JRgOXgb .node circle,#mermaid-svg-7LJ5IZoX1JRgOXgb .node ellipse,#mermaid-svg-7LJ5IZoX1JRgOXgb .node polygon,#mermaid-svg-7LJ5IZoX1JRgOXgb .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-7LJ5IZoX1JRgOXgb .node .label{text-align:center;fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .node.clickable{cursor:pointer}#mermaid-svg-7LJ5IZoX1JRgOXgb .arrowheadPath{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-7LJ5IZoX1JRgOXgb .flowchart-link{stroke:#333;fill:none}#mermaid-svg-7LJ5IZoX1JRgOXgb .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-7LJ5IZoX1JRgOXgb .edgeLabel rect{opacity:0.9}#mermaid-svg-7LJ5IZoX1JRgOXgb .edgeLabel span{color:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-7LJ5IZoX1JRgOXgb .cluster text{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-7LJ5IZoX1JRgOXgb .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-7LJ5IZoX1JRgOXgb text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-7LJ5IZoX1JRgOXgb .actor-line{stroke:grey}#mermaid-svg-7LJ5IZoX1JRgOXgb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .sequenceNumber{fill:#fff}#mermaid-svg-7LJ5IZoX1JRgOXgb #sequencenumber{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb #crosshead path{fill:#333;stroke:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .messageText{fill:#333;stroke:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-7LJ5IZoX1JRgOXgb .labelText,#mermaid-svg-7LJ5IZoX1JRgOXgb .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-7LJ5IZoX1JRgOXgb .loopText,#mermaid-svg-7LJ5IZoX1JRgOXgb .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-7LJ5IZoX1JRgOXgb .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-7LJ5IZoX1JRgOXgb .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-7LJ5IZoX1JRgOXgb .noteText,#mermaid-svg-7LJ5IZoX1JRgOXgb .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-7LJ5IZoX1JRgOXgb .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-7LJ5IZoX1JRgOXgb .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-7LJ5IZoX1JRgOXgb .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-7LJ5IZoX1JRgOXgb .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb .section{stroke:none;opacity:0.2}#mermaid-svg-7LJ5IZoX1JRgOXgb .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-7LJ5IZoX1JRgOXgb .section2{fill:#fff400}#mermaid-svg-7LJ5IZoX1JRgOXgb .section1,#mermaid-svg-7LJ5IZoX1JRgOXgb .section3{fill:#fff;opacity:0.2}#mermaid-svg-7LJ5IZoX1JRgOXgb .sectionTitle0{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .sectionTitle1{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .sectionTitle2{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .sectionTitle3{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-7LJ5IZoX1JRgOXgb .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb .grid path{stroke-width:0}#mermaid-svg-7LJ5IZoX1JRgOXgb .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-7LJ5IZoX1JRgOXgb .task{stroke-width:2}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskText:not([font-size]){font-size:11px}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-7LJ5IZoX1JRgOXgb .task.clickable{cursor:pointer}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskText0,#mermaid-svg-7LJ5IZoX1JRgOXgb .taskText1,#mermaid-svg-7LJ5IZoX1JRgOXgb .taskText2,#mermaid-svg-7LJ5IZoX1JRgOXgb .taskText3{fill:#fff}#mermaid-svg-7LJ5IZoX1JRgOXgb .task0,#mermaid-svg-7LJ5IZoX1JRgOXgb .task1,#mermaid-svg-7LJ5IZoX1JRgOXgb .task2,#mermaid-svg-7LJ5IZoX1JRgOXgb .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskTextOutside0,#mermaid-svg-7LJ5IZoX1JRgOXgb .taskTextOutside2{fill:#000}#mermaid-svg-7LJ5IZoX1JRgOXgb .taskTextOutside1,#mermaid-svg-7LJ5IZoX1JRgOXgb .taskTextOutside3{fill:#000}#mermaid-svg-7LJ5IZoX1JRgOXgb .active0,#mermaid-svg-7LJ5IZoX1JRgOXgb .active1,#mermaid-svg-7LJ5IZoX1JRgOXgb .active2,#mermaid-svg-7LJ5IZoX1JRgOXgb .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-7LJ5IZoX1JRgOXgb .activeText0,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeText1,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeText2,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeText3{fill:#000 !important}#mermaid-svg-7LJ5IZoX1JRgOXgb .done0,#mermaid-svg-7LJ5IZoX1JRgOXgb .done1,#mermaid-svg-7LJ5IZoX1JRgOXgb .done2,#mermaid-svg-7LJ5IZoX1JRgOXgb .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-7LJ5IZoX1JRgOXgb .doneText0,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneText1,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneText2,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneText3{fill:#000 !important}#mermaid-svg-7LJ5IZoX1JRgOXgb .crit0,#mermaid-svg-7LJ5IZoX1JRgOXgb .crit1,#mermaid-svg-7LJ5IZoX1JRgOXgb .crit2,#mermaid-svg-7LJ5IZoX1JRgOXgb .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-7LJ5IZoX1JRgOXgb .activeCrit0,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeCrit1,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeCrit2,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-7LJ5IZoX1JRgOXgb .doneCrit0,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneCrit1,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneCrit2,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-7LJ5IZoX1JRgOXgb .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-7LJ5IZoX1JRgOXgb .milestoneText{font-style:italic}#mermaid-svg-7LJ5IZoX1JRgOXgb .doneCritText0,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneCritText1,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneCritText2,#mermaid-svg-7LJ5IZoX1JRgOXgb .doneCritText3{fill:#000 !important}#mermaid-svg-7LJ5IZoX1JRgOXgb .activeCritText0,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeCritText1,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeCritText2,#mermaid-svg-7LJ5IZoX1JRgOXgb .activeCritText3{fill:#000 !important}#mermaid-svg-7LJ5IZoX1JRgOXgb .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-7LJ5IZoX1JRgOXgb g.classGroup text .title{font-weight:bolder}#mermaid-svg-7LJ5IZoX1JRgOXgb g.clickable{cursor:pointer}#mermaid-svg-7LJ5IZoX1JRgOXgb g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-7LJ5IZoX1JRgOXgb g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-7LJ5IZoX1JRgOXgb .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-7LJ5IZoX1JRgOXgb .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-7LJ5IZoX1JRgOXgb .dashed-line{stroke-dasharray:3}#mermaid-svg-7LJ5IZoX1JRgOXgb #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb .commit-id,#mermaid-svg-7LJ5IZoX1JRgOXgb .commit-msg,#mermaid-svg-7LJ5IZoX1JRgOXgb .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-7LJ5IZoX1JRgOXgb g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-7LJ5IZoX1JRgOXgb g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-7LJ5IZoX1JRgOXgb g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-7LJ5IZoX1JRgOXgb .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-7LJ5IZoX1JRgOXgb .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-7LJ5IZoX1JRgOXgb .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-7LJ5IZoX1JRgOXgb .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-7LJ5IZoX1JRgOXgb .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-7LJ5IZoX1JRgOXgb .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-7LJ5IZoX1JRgOXgb .edgeLabel text{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7LJ5IZoX1JRgOXgb .node circle.state-start{fill:black;stroke:black}#mermaid-svg-7LJ5IZoX1JRgOXgb .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-7LJ5IZoX1JRgOXgb #statediagram-barbEnd{fill:#9370db}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-state .divider{stroke:#9370db}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-7LJ5IZoX1JRgOXgb .note-edge{stroke-dasharray:5}#mermaid-svg-7LJ5IZoX1JRgOXgb .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-7LJ5IZoX1JRgOXgb .error-icon{fill:#522}#mermaid-svg-7LJ5IZoX1JRgOXgb .error-text{fill:#522;stroke:#522}#mermaid-svg-7LJ5IZoX1JRgOXgb .edge-thickness-normal{stroke-width:2px}#mermaid-svg-7LJ5IZoX1JRgOXgb .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-7LJ5IZoX1JRgOXgb .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-7LJ5IZoX1JRgOXgb .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-7LJ5IZoX1JRgOXgb .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-7LJ5IZoX1JRgOXgb .marker{fill:#333}#mermaid-svg-7LJ5IZoX1JRgOXgb .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-7LJ5IZoX1JRgOXgb {color: rgba(0, 0, 0, 0.75);font: ;}

yes
no
axios.creat
creatInstance
axios
执行别名
config
axios.prototype.request
request.Interceptors
adapter
cancel
axios.reject
axios.fulfiled
responseInterceptor
axios.then/catch
  • 拦截器是什么

    • 在发送与响应前后的函数
    • 请求拦截器传送config
    • 响应拦截器传送response
  • 请求转换器的作用
    • 完全可以由拦截器做
    • 对config/response进行预处理

尚硅谷 Ajax与Axios的使用与关键源码 笔记相关推荐

  1. Asp.Net+Ajax实现的宝宝RSS阅读器源码下载

    Asp.Net+Ajax实现的宝宝RSS阅读器源码, 适合Ajax初学者学习,功能很简单,用户可以添加RSS地址到数据库,但没有实现RSS地址及名称删除与修改. 当阅读器页面打开时,从数据库获取RSS ...

  2. 尚硅谷ajax学习记录

    AJAX Ajax即Asynchronous Javascript And XML(异步JavaScript和XML) 环境搭建 安装node.js C:\Users\xlgui>node -v ...

  3. ajax的auto是true,一个AJAX自动完成功能的js封装源码[支持中文]

    Screenshots Pagination Internationalization Rich format Text selection源码下载 有个问题,等高手解决,就是怎么搞都不能支持中文,不 ...

  4. Vue3.0尚硅谷(讲师:张天禹)视频学习笔记

    一.创建Vue3.0工程 1.使用vue-cli创建 官方文档:​https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create​ ...

  5. 尚硅谷李立超老师讲解web前端网课笔记

    HTML的重要知识点: 初学web笔记一(语义化标签) h标签表示一个标题,p标签表示一个段落,hgroup用于标题分组,em标签用于语意加重,blockquote表示长引用(会自动换行),q表示短引 ...

  6. springcloud 尚硅谷周阳 源码+笔记+脑图

    https://github.com/OT-mt/cloud2020/tree/master/cloud2020

  7. 《尚硅谷高级技术之JUC高并发编程》学习笔记11—— Fork / Join 框架

    文章目录 Fork / Join 框架简介 fork() 方法 join() 方法 Fork / Join 框架的异常处理 入门案例 总结 Fork / Join 框架简介 Fork / Join 它 ...

  8. 尚硅谷TypeScript教程(李立超老师)学习笔记

    一.TypeScript简介 1.TypeScript是以JavaScript为基础构造的语言,是JavaScript的超集 2.扩展了JavaScript,并添加了类型. 3.TS代码需要通过编译器 ...

  9. php+ajax+js注册源码,将Ajax封装至js文件中(用户注册源码实例)

    register.jsp 用户注册 登录 请选择默认身份: 发布者 接收者 规范制定者 法律顾问 注册 register.js var xmlHttp=false; function createXM ...

最新文章

  1. 数百个CV实战项目与必备7本书5000页中英文CV书籍以及算法工程师必备资料免费送啦~...
  2. Collections 类
  3. GoogleReader的Likes操作数据如何获取?
  4. inspect模块---检查活动对象
  5. 《Java编程艺术》目录
  6. 浙江省计算机二级excel函数,浙江省计算机二级excel练习5操作要求
  7. java. oracle 存储文件,oracle调用java类遍历磁盘文件
  8. Daily scrum 11.22
  9. 计算机网络管理2018版,2018~2019学年度第二学期“计算机网络管理”专业技能竞赛火热进行中...
  10. c# string 转 datetime_tesseract || PDF转PNG转txt
  11. oracle安装无响应,求教 pl/sql连接本机数据库是未响应问题
  12. ref: 九大数据仓库方案特点比较
  13. (CFD)投影法求解二维不可压缩N-S方程
  14. 使用C语言gets函数和gets_s函数,读取含空格的字符串
  15. gg修改器怎么能让服务器检测不到,gg修改器怎么绕过检测 | 手游网游页游攻略大全...
  16. win10右键卡顿原因_右键菜单反应慢?win10系统解决右键菜单卡顿方法
  17. python爬取哔哩哔哩视频_python爬取哔哩哔哩小视频
  18. GooglePlay应用上架流程
  19. 2021年秋招面试真题以及面试技巧分享
  20. 毕业论文引用参考文献的脚注方法

热门文章

  1. 计算机系统基础无符号短整型,输入短整型数,比较对应的无符号数的大小,输出最大值最小值...
  2. 小霸王学习机G-BASIC F-BASIC模拟器下载地址
  3. grafana二次开发在win10环境下的准备
  4. 双线性池化【BiLinear Pooling】
  5. 《Spring Boot开发:从0到1》大纲结构
  6. 如何使夜游项目更具有参观性
  7. 通信相关中英对照术语
  8. 电子驱鼠器电路(一)
  9. python遇到天猫反爬虫_Python--天猫详情页爬虫
  10. 苏州软件企业认证有哪些规范性要求