AJAX、axios、JSONP、CORS

11/20/2020 JavaScriptAxiosCORS跨域

部分借鉴至:博客园-Mr_慕白 => HTTP 报文格式 (opens new window)
部分借鉴至:CSDN-HansExploration => JSONP 原理详解 (opens new window)

参考:阮一峰 浏览器同源政策及其规避方法 (opens new window)
参考:阮一峰 跨域资源共享 CORS 详解 (opens new window)
参考:MDN 跨域资源共享 (opens new window)

# 文章目录

# 一、什么是 AJAX

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
通过 AJAX 可以在浏览器中向服务器发送异步请求
最大的优势:无刷新获取数据。
AJAX 不是新的编程语言,而是一种将现有的标准组合在一起使用的新方式。

# 1.1 优点

  • 可以无需刷新页面而与服务器端进行通信。
  • 允许你根据用户事件来更新部分页面内容。

# 1.2 缺点

  • 没有浏览历史,不能回退
  • 存在跨域问题(同源)
  • SEO 不友好

# 1.3 常用的 AJAX 事件及方法:

事件名 事件描述
abort 中止已发出的请求 XMLHttpRequest.abort()
error 当 request 遭遇错误时触发。(断网的时候)
load XMLHttpRequest 请求成功完成时触发。.
loadend 当请求结束时触发, 无论请求成功 ( load) 还是失败 (abort 或 error)。
loadstart 接收到响应数据时触发。
progress 当请求接收到更多数据时,周期性地触发。。
timeout 请求超时
方法名 方法描述
XMLHttpRequest.upload 只读 XMLHttpRequestUpload,代表上传进度。
XMLHttpRequest.open() 初始化一个请求。
XMLHttpRequest.send() 发送请求。

# 二、HTTP 报文格式

在这里插入图片描述
在这里插入图片描述

# 2.1 请求报文

HTTP 请求报文由请求行(request line)、请求头部(header)、空行和请求数据 4 个部分组成。

  1. 请求行:请求方法

    • 请求行中包括请求方法(GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT),其中最常用的为 GET、POST。
  2. 请求头:请求头部由关键字/值对组成,每行一对

    • User-Agent : 产生请求的浏览器类型
    • Accept : 客户端希望接受的数据类型,比如 Accept:text/xml(application/json)表示希望接受到的是 xml(json)类型
    • Content-Type:发送端发送的实体数据的数据类型。比如,Content-Type:text/htmlapplication/json)表示发送的是 html 类型。
    • Host : 请求的主机名,允许多个域名同处一个 IP 地址,即虚拟主机
  3. 空行

    • 请求头之后是一个空行,通知服务器以下不再有请求头
  4. 请求体

    • GET 没有请求数据,POST 有。
    • 与请求数据相关的最常使用的请求头是 Content-Type 和 Content-Length 。

# 2.2 响应报文

HTTP 响应报文和请求报文的结构类似,也是由四个部分组成: 状态行、消息报头、空行、响应体。

状态行:

  • 状态行也由三部分组成:服务器 HTTP 协议版本,响应状态码,状态码的文本描述

格式:HTTP-Version Status-Code Reason-Phrase CRLF
比如:HTTP/1.1 200 OK

状态码:由 3 位数字组成,第一个数字定义了响应的类别。
1xx:指示信息,表示请求已接收,继续处理。
2xx:成功,表示请求已被成功接受,处理。
3xx:重定向。
4xx:客户端错误。
5xx:服务器端错误,服务器未能实现合法的请求。

# 2.3 HTTP 请求的一个完整过程

  • 建立 TCP 连接(之前可能还有一次 DNS 域名解析)
  • 三次握手建立 TCP 完成后,客户端向服务器发送请求命令,比如 GET https://www.baidu.com?name=xx&addr=xx HTTP1.1
  • 客户端发送请求头信息,发送完了 header 后会接着发送一个空白行,GET 请求没有数据,POST 请求要发送 body 数据
  • 服务器接收到以上信息后,开始处理业务,处理完有了结果以后,服务器开始应答
  • 服务器返回响应头信息,发送完 response header 以后,再发送一个空白行
  • 然后服务器向客户端发送数据
  • 发送完了服务器四次挥手关闭 TCP 连接

# 三、使用 node 的 express 框架 演示 Ajax 请求

注意:
首先你的电脑应该安装 node 和
淘宝镜像:npm install \-g cnpm \--registry=https://registry.npm.taobao.org

接下来我们开始演练 Ajax 请求

# 3.1 初始化项目

然后我们开始初始化我们的项目。

  1.      `npm init --yes` 初始化
    
  2.      `cnpm i -D express` 安装 express 框架
    
  3.      新建文件 `server.js`并输入如下内容,以后我们的服务器都是这个文件开启的
    
// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
app.get("/", (request, response) => {
  // 设置响应
  response.send("hello express");
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

接下来在本地打开 127.0.0.1:8000 ,你将看到如下内容
在这里插入图片描述

# 四、案例

# 4.1 GET(带参) 方式 发送 ajax 获取数据

需求:

  • 点击按钮发送 Ajax 请求获取数据,不刷新页面往 div 内添加获取到的数据。

目录结构:
在这里插入图片描述

GET.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>GET Ajax</title>
    <style>
      #result {
        width: 200px;
        height: 200px;
        border: 1px solid black;
      }
    </style>
  </head>

  <body>
    <button>点击发送请求</button>
    <div id="result"></div>
    <script>
      let button = document.querySelector("button");
      let result = document.getElementById("result");
      button.onclick = function() {
        // 1.创建对象
        let xhr = new XMLHttpRequest();
        // 2.初始化
        xhr.open("GET", "http://127.0.0.1:8000/server?name=pengsir&sex=boy");
        // 3.发送
        xhr.send();
        // 4.注册事件
        xhr.onreadystatechange = function() {
          // readystate: 0 1 2 3 4
          // 0:请求未初始化 最开始就是 0
          // 1: 服务器连接已建立 => open 方法调用完毕
          // 2: 请求已接收 =>send() 已经调用完毕
          // 3: 请求处理中
          // 4: 请求已完成,且响应已就绪
          if (xhr.readyState === 4 && xhr.status === 200) {
            // 处理结果 行 头 行 体
            // 1.响应行
            console.log(xhr.status); // 状态码 200
            console.log(xhr.statusText); // 状态字符串 OK
            // 2.所有响应头
            console.log(xhr.getAllResponseHeaders());
            // content-length: 10
            // content-type: text/html; charset=utf-8

            // 3.响应体
            console.log(xhr.response); // hello Ajax
            result.innerHTML = xhr.response;
          }
        };
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

server.js

// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
app.get("/server", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");

  // 设置响应体
  response.send("hello Ajax");
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

结果:
在这里插入图片描述
在这里插入图片描述

# 4.2 POST(带参) 方式 发送 ajax 获取数据

POST 请求的参数在 send()方法中设置 可以是任意的数据类型,只要服务端能处理

post.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AJAX POST</title>
    <style>
      #result {
        width: 200px;
        height: 200px;
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <button>点击发送请求</button>
    <div id="result"></div>
    <script>
      let button = document.querySelector("button");
      let result = document.getElementById("result");
      button.onclick = function() {
        // 1.创建对象
        let xhr = new XMLHttpRequest();
        // 2.初始化
        xhr.open("POST", "http://127.0.0.1:8000/server");
        // 3.发送
        // POST请求的参数在 send()方法中设置 可以是任意的数据类型,只要服务端能处理
        xhr.send([1, 2, 3, 4, 5]);
        // 4.注册事件
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.response);
            result.innerHTML = xhr.response;
          }
        };
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

server.js

// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
// POST 请求
app.post("/server", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");

  // 设置响应体
  response.send("hello Ajax");
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 4.3 POST 设置请求头

请求头设置在 open() 之后 send()之前。

 <script>
    let button = document.querySelector('button')
    let result = document.getElementById('result')
    button.onclick = function () {
      // 1.创建对象
      let xhr = new XMLHttpRequest()
      // 2.初始化
      xhr.open('POST', 'http://127.0.0.1:8000/server')
      // xhr.setRequesetHeader(请求头name,请求头value)
      // POST专用:普通的表单提交默认是通过这种方式。form表单数据被编码为key/value格式发送到服务器。
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
      // 自定义 自定义的需要在服务器设置 response.setHeader('Access-Control-Allow-Headers','*') 和服务器对于的路由,这里是直接设置为all
      xhr.setRequestHeader('name','goodstudy')

      // 3.发送
      // POST请求的参数在 send()方法中设置 可以是任意的数据类型,只要服务端能处理
      xhr.send([1, 2, 3, 4, 5])
      // 4.注册事件
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log(xhr.response);
          result.innerHTML = xhr.response
        }
      }
    }
  </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

server.js

// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
// POST 请求
app.all("/server", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");
  // 接受所有响应头
  response.setHeader("Access-Control-Allow-Headers", "*");
  // 设置响应体
  response.send("hello Ajax");
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4.4 响应 JSON 格式的数据

前提:服务端需要先把 JSON 格式转化为字符串格式

两种方案:

  • 方案一:设置响应头(open 之前) :
    • xhr.responseType = 'json'
  • 方案二:手动转化
    • let data = JSON.parse(xhr.response)

json.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JSON</title>
    <style>
      #result {
        width: 200px;
        height: 200px;
        border: 1px solid black;
      }
    </style>
  </head>

  <body>
    <button>点击发送请求</button>
    <div id="result"></div>
    <script>
      let button = document.querySelector("button");
      let result = document.getElementById("result");
      button.onclick = function() {
        // 1.创建对象
        let xhr = new XMLHttpRequest();
        // 设置响应体数据的类型
        // 方案一:
        xhr.responseType = "json";

        // 2.初始化
        xhr.open("GET", "http://127.0.0.1:8000/json-server");
        // 3.发送
        xhr.send();
        // 4.注册事件
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4 && xhr.status === 200) {
            // 由于服务器返回的是 字符串形式的 JSON 需要转化,

            // 方案二:
            // let data = JSON.parse(xhr.response)
            // result.innerHTML = data.name + data.age

            console.log(xhr.response);
            result.innerHTML = xhr.response.name + xhr.response.age;
          }
        };
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

server.js

// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
//响应JSON
app.all("/json-server", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");
  // 响应一个JSON 数据
  const data = {
    name: "pengsir",
    age: 18,
  };
  let str = JSON.stringify(data);
  // 设置响应体
  response.send(str);
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 4.5 IE 缓存问题及解决方案

在默认情况下,IE 会针对请求地址缓存 Ajax 请求的结果。换句话说,在缓存过期之前,针对相同地址发起的多个 Ajax 请求,只有第一次会真正发送到服务端。在某些情况下,这种默认的缓存机制并不是我们希望的(比如获取实时数据)

  • 解决方案一:设置随机数

    • 在 AJAX 请求的页面后加个随机函数,URL 后加上 t=Math.random()

    • 或者 +new Date() 、 Date.now() 都行

    • 解决方案二:设置请求头

    • 在 XMLHttpRequest 发送请求之前加上 xhrObj .setRequestHeader(“If-Modified-Since”,“0”)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>IE缓存</title>
  <style>
    #result {
      width: 200px;
      height: 200px;
      border: 1px solid black;
    }
  </style>
</head>

<body>
  <button>点击发送请求</button>
  <div id="result"></div>
  <script>
    let button = document.querySelector('button')
    let result = document.getElementById('result')
    button.onclick = function () {
      // 1.创建对象
      let xhr = new XMLHttpRequest()
      // 2.初始化
      xhr.open('GET', 'http://127.0.0.1:8000/server?random='+Date.now())
      // 3.发送
      xhr.send()
      // 4.注册事件
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log(xhr.response);
          result.innerHTML = xhr.response
        }
      }
    }
  </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

server.js

// 1.引入 express
const express = require("express");
// 2.创建应用对象
const app = express();
// 3.创建路由规则
app.get("/server", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");

  // 设置响应体
  response.send("hello Ajax 2");
});
// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4.6 请求超时与网络异常

提示:这里的网络异常(断网)有一个比较好的演示方法:

在 chrome 开发者工具中有一个 offline 可以模拟断网。
offline 就是断网的意思。
在这里插入图片描述

timeout.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>请求超时与网络异常</title>
  </head>
  <body>
    <button>点击发送请求</button>
    <div id="result"></div>
    <script>
      let button = document.querySelector("button");
      let result = document.getElementById("result");
      button.onclick = function() {
        // 1.创建对象
        let xhr = new XMLHttpRequest();
        // 超时设置
        xhr.timeout = 2000;
        // 超时回调
        xhr.ontimeout = function() {
          alert("超时了~");
        };
        // 网络异常 断网~
        xhr.onerror = function() {
          alert("你的网络已断开连接!");
        };
        // 2.初始化
        xhr.open("GET", "http://127.0.0.1:8000/timeout");
        // 3.发送
        xhr.send();
        // 4.注册事件
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.response);
            result.innerHTML = xhr.response;
          }
        };
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

server.js

// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
//延时响应
app.all("/timeout", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");
  setTimeout(() => {
    response.send("timeout 延时响应");
  }, 3000);
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

效果:
在这里插入图片描述

# 4.7 取消请求

xhrObj.abort 可以取消发送的 Ajax 请求。

cancel.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>document</title>
</head>
<body>
  <button id="send">发送请求</button>
  <button id="cancel">取消请求</button>
  <script>
    let send = document.querySelector('#send')
    let cancel = document.querySelector('#cancel')

    let xhr = null;
    send.onclick = function () {
      // 1.创建对象
      xhr = new XMLHttpRequest()
      // 2.初始化
      xhr.open('GET', 'http://127.0.0.1:8000/timeout')
      // 3.发送
      xhr.send()
      // 4.注册事件
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log(xhr.response);
        }
      }
    }
    cancel.onclick = function () {
      xhr.abort()
    }
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

server.js

// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
//延时响应
app.all("/timeout", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");
  setTimeout(() => {
    response.send("timeout 延时响应");
  }, 3000);
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

效果:
在这里插入图片描述

# 4.8 重复请求与节流阀

当我们的用户重复点击一个按钮的时候,把还未响应的请求关闭,发送新的请求。

节流阀.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>document</title>
  </head>
  <body>
    <button id="send">发送请求</button>
    <button id="cancel">取消请求</button>
    <script>
      let send = document.querySelector("#send");

      let xhr = null;
      // 节流阀表示变量
      let isCancel = false;
      send.onclick = function() {
        // 判断是否正在发送请求 如果是则取消重新发送
        if (isCancel) xhr.abort();
        xhr = new XMLHttpRequest();
        // 修改表示变量的值
        isCancel = true;
        xhr.open("GET", "http://127.0.0.1:8000/timeout");
        xhr.send();
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4) {
            // 修改表示变量的值
            isCancel = false;
            if (xhr.status === 200) {
              console.log(xhr.response);
            }
          }
        };
      };
      cancel.onclick = function() {
        xhr.abort();
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

server.js

// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
// 延时响应
app.all("/timeout", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");
  setTimeout(() => {
    response.send("timeout 延时响应");
  }, 3000);
  // 设置响应体
});
// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 五、axios

这里简单演示一下 axios 怎么发送 Ajax 请求。

首先新建一个axios.html的文件
在该文件中引入 axios

你也可以前往 BootCDN (opens new window)自行查找相应 CDN:
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.0/axios.js"></script>

axios.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>axios</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.0/axios.js"></script>
  </head>

  <body>
    <button>GET</button>
    <button>POST</button>
    <button>AJAX</button>
    <script>
      let btns = document.querySelectorAll("button");

      // baseURL
      axios.defaults.baseURL = "http://127.0.0.1:8000";
      // GET请求
      btns[0].onclick = function() {
        axios.get("/axios", {
          params: {
            id: 100,
            vip: 7,
          },
          // 请求头
          headers: {
            name: "pengsir",
            age: 20,
          },
        });
      };
      // POST 请求
      btns[1].onclick = function() {
        axios.post(
          "/axios",
          { id: 200, vip: 9 },
          {
            // 请求头参数
            headers: {
              height: 180,
              weight: 180,
            },
            // 请求体
            data: {
              username: "admnin",
              pwd: 123456,
            },
          }
        );
      };
      // Ajax
      btns[2].onclick = function() {
        axios({
          // 请求方法
          method: "POST",
          // url
          url: "/axios",
          // url参数
          params: {
            vip: 100,
            info: "text",
          },
          // 头信息
          headers: {
            a: 100,
            b: 200,
          },
          // 请求体参数
          data: {
            username: "admin",
            pwd: "admin",
          },
        }).then((res) => {
          // 状态码
          console.log(res.status);
          // 状态字符串
          console.log(res.statusText);
          // 响应头信息
          console.log(res.headers);
          // 响应体
          console.log(res.data);
        });
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

server.js

// 1.引入 express
const express = require("express");

// 2.创建应用对象
const app = express();

// 3.创建路由规则
app.all("/axios", (request, response) => {
  // 设置允许跨域
  response.setHeader("Access-Control-Allow-Origin", "*");
  // 响应所有头
  response.setHeader("Access-Control-Allow-Headers", "*");

  response.send("timeout 延时响应");
  // 设置响应体
});
// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete:http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 六、同源策略

如果两个 URL 的 协议(Protocol)、端口(port)、域名(host) 都相同的话,则这两个 URL 是同源。

违背同源策略就是跨域。

# 七、跨域解决方案

这篇文章讲的很好:JSONP 跨域 (opens new window)

# 7.1 原生实现 JSONP

注意:由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了 foo 函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用 JSON.parse 的步骤。

<!DOCTYPE html>
<html lang="en">
<head>
  <title></title>
  <script type="text/javascript">
    // 得到航班信息查询结果后的回调函数
    var customCallback = function (data) {
      console.log(typeof data) // object
      console.log('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.remaining + ' 张。');
    };
    // 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
    var url = "http://127.0.0.1:8000/jsonp?code=CA1998&callback=customCallback";
    // 创建script标签,设置其属性
    var script = document.createElement('script');
    script.setAttribute("type","text/javascript");
    script.setAttribute('src', url);
    // 把script标签加入head,此时调用开始
    document.getElementsByTagName('head')[0].appendChild(script);
  </script>
</head>

<body>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

服务端:

// 1.引入 express
const express = require("express");
// 2.创建应用对象
const app = express();
// 3.创建路由规则
app.get("/jsonp", (request, response) => {
  let { info, callback } = request.query;
  let obj = {
    price: 18888,
    remaining: 5,
  };
  let str = JSON.stringify(obj);
  console.log(typeof str); // string
  response.send(`${callback}(${str})`);
  // 设置响应体
});
// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start complete: http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 7.2 jQuery 实现 JSONP

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
     <title>Untitled Page</title>
      <script type="text/javascript" src="jquery.min.js"></script>
      <script type="text/javascript">
     jQuery(document).ready(function(){
        $.ajax({
             type: "get",
             async: false,
             url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",
             dataType: "jsonp",
             jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
             jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
             success: function(json){
                 alert('您查询到航班信息:票价: ' + json.price + ' 元,余票: ' + json.tickets + ' 张。');
             },
             error: function(){
                 alert('fail');
             }
         });
     });
     </script>
     </head>
  <body>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

是不是有点奇怪?为什么我这次没有写 flightHandler 这个函数呢?而且竟然也运行成功了!
这就是 jQuery 的功劳了,jquery 在处理 jsonp 类型的 ajax 时(虽然 jquery 也把 jsonp 归入了 ajax,但其实它们真的不是一回事儿),自动帮你生成回调函数并把数据取出来供 success 属性方法来调用,是不是很爽呀?

这里针对 ajax 与 jsonp 的异同再做一些补充说明:

1、ajax 和 jsonp 这两种技术在调用方式上”看起来”很像,目的也一样,都是请求一个 url,然后把服务器返回的数据进行处理,因此 jquery 和 ext 等框架都把 jsonp 作为 ajax 的一种形式进行了封装。

2、但 ajax 和 jsonp 其实本质上是不同的东西。ajax 的核心是通过 XmlHttpRequest 获取非本页内容,而 jsonp 的核心则是通过 script 标签 动态添加

# 7.3 CORS 跨域

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。

CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。

整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

CORS 相关信息请访问 阮一峰 老师的:跨域资源共享 CORS 详解 (opens new window)

cors.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CORS</title>
</head>
<body>
  <button>点击发送请求</button>
  <script>
    let button = document.querySelector('button')
    button.onclick = function () {
      // 1.创建对象
      xhr = new XMLHttpRequest()
      // 2.初始化
      xhr.open('GET', 'http://127.0.0.1:8000/cors')
      // 3.发送
      xhr.send()
      // 4.注册事件
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log(xhr.response);
        }
      }
    }
  </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

server.js

// 1.引入 express
const express = require("express");
// 2.创建应用对象
const app = express();
// 3.创建路由规则
app.get("/cors", (request, response) => {
  // 设置响应头

  // 响应所有请求
  // response.setHeader("Access-Control-Allow-Origin",'*')
  // 响应特定请求
  response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
  response.setHeader("Access-Control-Allow-Headers", "*"); //响应所有请求
  response.setHeader("Access-Control-Allow-Methods", "*"); // 支持头信息自定义

  response.send("hello cors");
  // 设置响应体
});
// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start complete: http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 八、AJAX 实现文件上传(带进度条)

效果图:
在这里插入图片描述

准备:
cnpm i express
cnpm i cors
cnpm i \-S formidable
cnpm i \-S nodemon

注意:可能会出现虽然安装了,但是报 找不到模块的错误 ,目前我的解决办法是找不到的模块: 本地和全局都安装
文件路径:
在这里插入图片描述

user.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 引入 bootstrap -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/css/bootstrap.min.css"
      integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
      crossorigin="anonymous"
    />
  </head>

  <body>
    <input type="file" id="file" class="my-5" />
    <br />
    <button id="btn" class="btn btn-success">0%</button>
    <div class="progress">
      <div
        id="progress"
        class="progress-bar progress-bar-striped progress-bar-animated"
        style="width: 0%"
      >
        &nbsp;
      </div>
    </div>
    <script>
      let file = document.getElementById("file");
      let progress = document.getElementById("progress");
      let btn = document.getElementById("btn");

      file.onchange = function() {
        // 创建空的 FormData 对象
        let formData = new FormData();
        // 把选中的文件 添加到 formData 上
        formData.append("userFile", this.files[0]);

        let xhr = new XMLHttpRequest();
        xhr.open("post", "http://127.0.0.1:8000/upload?random=" + Date.now());
        xhr.upload.onprogress = function(e) {
          // loaded 已经上传了多少
          // total 总大小
          let fileProgress = Math.floor((e.loaded / e.total) * 100);

          btn.innerHTML = fileProgress + "%";
          progress.style.width = fileProgress + "%";
          progress.innerHTML = fileProgress + "%";
        };

        xhr.send(formData);

        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.responseText);
          }
        };
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

server.js

// 1.引入相应的包
const express = require("express");
const formidable = require("formidable"); //文件上传处理模块
const path = require("path"); // 路径模块

// 2.创建应用对象
const app = express();

// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, "public")));
// 开启跨域支持
app.use(require("cors")());

// 3.创建路由规则
// 文件上传路由
app.post("/upload", (req, res) => {
  // 1.创建 formidable  表单解析对象
  const form = new formidable.IncomingForm();
  // 2.设置客户端上传文件的存储路径
  form.uploadDir = path.join(__dirname, "public", "uploads");
  // 3.保留上传的文件后缀名
  form.keepExtensions = true;

  // 解析客户端传递过来的 FormData对象
  // err错误对象,fields 表单普通请求参数,files文件上传相关信息
  form.parse(req, (err, fields, files) => {
    res.send("ok");
  });
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete: http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 九、AJAX 上传图片即使预览

效果:
在这里插入图片描述
核心思想是把上传到服务器的地址返回。
前端做一点优化, img.onload 加载之后才插入到 DOM 节点中。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 引入 bootstrap -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/css/bootstrap.min.css"
      integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
      crossorigin="anonymous"
    />
    <style>
      #img-box img {
        width: 500px;
        object-fit: cover;
      }
    </style>
  </head>

  <body>
    <input type="file" id="file" class="my-5" />
    <div id="img-box"></div>
    <button id="btn" class="btn btn-success">0%</button>

    <div class="progress">
      <div
        id="progress"
        class="progress-bar progress-bar-striped progress-bar-animated"
        style="width: 0%"
      >
        &nbsp;
      </div>
    </div>
    <script>
      let file = document.getElementById("file");
      let progress = document.getElementById("progress");
      let btn = document.getElementById("btn");

      file.onchange = function() {
        // 创建空的 FormData 对象
        let formData = new FormData();
        // 把选中的文件 添加到 formData 上
        formData.append("userFile", this.files[0]);

        let xhr = new XMLHttpRequest();
        xhr.open("post", "http://127.0.0.1:8000/upload?random=" + Date.now());
        xhr.upload.onprogress = function(e) {
          // loaded 已经上传了多少
          // total 总大小
          let fileProgress = Math.floor((e.loaded / e.total) * 100);

          btn.innerHTML = fileProgress + "%";
          progress.style.width = fileProgress + "%";
          progress.innerHTML = fileProgress + "%";
        };

        xhr.send(formData);

        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4 && xhr.status === 200) {
            let result = JSON.parse(xhr.response);

            let imgBox = document.querySelector("#img-box");
            let img = document.createElement("img");
            img.src = "http://127.0.0.1:8000" + result.path;

            // 等图片加载完整再插入到DOM
            img.onload = function() {
              imgBox.appendChild(img);
            };
          }
        };
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

server.js

// 1.响应包
const express = require("express");
const formidable = require("formidable"); //文件上传处理模块
const path = require("path"); // 路径模块

// 2.创建应用对象
const app = express();

// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, "public")));
// 开启跨域支持
app.use(require("cors")());

// 3.创建路由规则
// 文件上传路由
app.post("/upload", (req, res) => {
  // 1.创建 formidable  表单解析对象
  const form = new formidable.IncomingForm();
  // 2.设置客户端上传文件的存储路径
  form.uploadDir = path.join(__dirname, "public", "uploads");
  // 3.保留上传的文件后缀名
  form.keepExtensions = true;

  // 解析客户端传递过来的 FormData对象
  // err错误对象,fields 表单普通请求参数,files文件上传相关信息
  form.parse(req, (err, fields, files) => {
    // files.userFile.path 拿到的是上传到服务器的地址,对它进行分割 客户端只能访问服务器开启的目录
    // res.send(files.userFile.path)
    res.send({
      // \uploads\upload_28fd1641511229d1b62bd8d1105cdfe3.png
      path: files.userFile.path.split("public")[1],
    });
  });
});

// 4.监听端口启动服务
app.listen(8000, () => {
  console.log("Server start compolete: http://127.0.0.1:8000");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
最后更新于: 2021年9月15日星期三晚上10点10分
Faster Than Light
Andreas Waldetoft / Mia Stegmar