前后端交互 Ajax,Fetch

1,037 阅读13分钟

前后端交互 Ajax、Fetch.png

一、Ajax

它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验。

1. 应用场景

  1. 页面上拉加载更多数据
  2. 列表数据无刷新分页
  3. 表单离开焦点数据验证
  4. 搜索框提示文字下拉列表

2. 运行原理

Ajax 相当于浏览器发送请求与接收响应的代理人,以实现在不影响用户浏览页面的情况下,局部更新页面数据,从而提高用户体验。

ajax原理

3. 实现步骤

3.1 创建 Ajax 对象

var xhr = new XMLHttpRequest();

3.2 告诉 Ajax 请求地址以及请求方式

 xhr.open('get', 'http://www.example.com');

3.3 发送请求

xhr.send();

3.4 获取服务器端给与客户端的响应数据

 xhr.onload = function () {
     console.log(xhr.responseText);
 }

4. 请求参数

4.1 GET 请求方式

xhr.open('get', 'http://www.example.com?name=zhangsan&age=20');

4.2 POST 请求方式

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('name=zhangsan&age=20');

在 http 请求与响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为对象字符串进行传输。

JSON.parse() // 将 json 字符串转换为json对象
JSON.stringify() // 将json对象转换为json字符串

5. 请求报文

在 HTTP 请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,这些数据和信息要遵守规定好的格式。

请求报文

6. 请求参数的格式

6.1 application/x-www-form-urlencoded

 name=zhangsan&age=20&sex=男

6.2 application/json

 {name: 'zhangsan', age: '20', sex: '男'}

在请求头中指定 Content-Type 属性的值是 application/json,告诉服务器端当前请求参数的格式是 json。

注意: get 请求是不能提交 json 对象数据格式的,传统网站的表单提交也是不支持 json 对象数据格式的。

7. 获取服务器端响应

7.1 Ajax 状态码

在创建ajax对象,配置ajax对象,发送请求,以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是ajax状态码。

0:请求未初始化(还没有调用open()) 1:请求已经建立,但是还没有发送(还没有调用send()) 2:请求已经发送 3:请求正在处理中,通常响应中已经有部分数据可以用了 4:响应已经完成,可以获取并使用服务器的响应了

xhr.readyState // 获取Ajax状态码

7.2 onreadystatechange 事件

当 Ajax 状态码发生变化时将自动触发该事件。

在事件处理函数中可以获取 Ajax 状态码并对其进行判断,当状态码为 4 时就可以通过 xhr.responseText 获取服务器端的响应数据了。

// 当Ajax状态码发生变化时
 xhr.onreadystatechange = function () {
     // 判断当Ajax状态码为4时
     if (xhr.readyState == 4) {
         // 获取服务器端的响应数据
         console.log(xhr.responseText);
     }
 }

7.3 两种获取服务器端响应方式的区别

区别描述onload事件onreadystatechange事件
是否兼容IE低版本不兼容兼容
是否需要判断Ajax状态码不需要需要
被调用次数一次多次

8. Ajax 错误处理

8.1 xhr.status 获取http状态码

可以判断服务器端返回的状态码,分别进行处理。

8.2 网络中断,请求无法发送到服务器端。

会触发xhr对象下面的onerror事件,在onerror事件处理函数中对错误进行处理。

8.3 常见 http 状态码

  • 1xx 服务器收到请求
  • 2xx 请求成功,如 200
  • 3xx 重定向,如 302
    • 301 永久重定向(配合 location, 浏览器自动处理)
    • 302 临时重定向(配合 location, 浏览器自动处理)
    • 304 资源未被修改(已经请求过资源,资源未过期)
  • 4xx 客户端错误,如 404
    • 403 没有权限,服务器不想让你访问
    • 404 资源没找到
  • 5xx 服务端错误,如 500
    • 500 服务器错误

    • 504 网关超时, 是指服务器作为网关或代理,但是没有及时从上游服务器收到请求

9. 低版本 IE 浏览器的缓存问题

在低版本的 IE 浏览器中,Ajax 请求有严重的缓存问题,即在请求地址不发生变化的情况下,只有第一次请求会真正发送到服务器端,后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了,客户端依然拿到的是缓存中的旧数据。

在请求地址的后面加请求参数,保证每一次请求中的请求参数的值不相同。

xhr.open('get', 'http://www.example.com?t=' + Math.random());

10. Ajax 封装

ajax 发送一次请求代码过多,发送多次请求代码冗余且重复。我们将请求代码封装到函数中,发请求时调用函数即可。

请求参数要考虑的问题

  • 请求参数位置问题
    • get 请求放在请求地址后
    • post 放在 send 方法中
  • 请求参数格式问题
    • application/x-www-form-urlencoded
    • application/json
   // 对象的属性名称可以对属性进行描述
        ajax({
            // 请求类型
            type: 'post',
            
            // 请求地址
            url: 'http://localhost:3000/json',
            
            // 请求参数
            data: {
                username: 'zhangsan',
                id: '12345'
            },
            
            // 请求格式
            header: {
                'Content-Type': 'application/json'
            },
            
            // 请求成功执行回调函数回调函数
            success: function(data, xhr) {
                console.log('这里是success函数');
                console.log(data);
            },
        });
// 封装 ajax 函数
        function ajax(options) {
            // 存储的是默认值
            var defaults = {
                type: 'get',
                url: '',
                data: {},
                header: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                success: function() {},
                error: function() {}
            };

            Object.assign(defaults, options);

            // 创建 ajax 对象
            var xhr = new XMLHttpRequest();
            // 拼接请求参数的变量
            var params = '';
            // 循环用户传递进来的对象格式参数
            for (var attr in defaults.data) {
                // 将参数转换为字符串格式
                params += attr + '=' + defaults.data[attr] + '&';
            }

            // 截取字符串最后的 & 
            params = params.substr(0, params.length - 1);

            // 判断请求方式
            if (defaults.type == 'get') {
                defaults.url = defaults.url + '?' + params;
            }
            // 配置ajax对象 
            xhr.open(defaults.type, defaults.url);

            // 如果请求方式为post
            if (defaults.type == 'post') {
                var contentType = defaults.header['Content-Type'];
                // 设置请求参数格式的类型
                xhr.setRequestHeader('Content-Type', contentType);

                if (contentType == 'application/json') {
                    // 向服务器端传递 json 数据格式的参数
                    // 参数必须是传递字符串
                    xhr.send(JSON.stringify(defaults.data));
                } else {
                    // 向服务器端传递普通类型的请求参数
                    xhr.send(params);
                }
            } else {
                // 发送请求
                xhr.send();
            }
            // 监听xhr对象 下面onload事件
            // 当xhr对象接收完响应数据后触发
            xhr.onload = function() {

                // xhr.getResponseHeader();
                // 获取响应头中的数据

                var contentType = xhr.getResponseHeader('Content-Type');

                // 服务器端返回数据
                var responseText = xhr.responseText;
                // 如果响应类型中包含application/json
                if (contentType.includes('application/json')) {
                    // 将json 字符串转换为json对象
                    responseText = JSON.parse(responseText);
                }

                // 当http状态码等于200的时候
                if (xhr.status == 200) {
                    // 请求成功,调用处理成功情况的函数
                    defaults.success(responseText, xhr);
                } else {
                    // 请求失败,调用处理失败情况的函数
                    defaults.error(responseText, xhr);
                }
            }
        }

11. 同源策略

11.1 Ajax请求限制

Ajax 只能向自己的服务器发送请求。比如现在有一个A网站、有一个B网站,A网站中的 HTML 文件只能向A网站服务器中发送 Ajax 请求,B网站中的 HTML 文件只能向 B 网站中发送 Ajax 请求,但是 A 网站是不能向 B 网站发送 Ajax请求的,同理,B 网站也不能向 A 网站发送 Ajax请求。

11.2 什么是同源

如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,其中只要有一个不相同,就是不同源

协议://域名:端口
www.example.com/dir/page.ht…是否同源原因
www.example.com/dir2/other.…同源
example.com/dir/other.h…不同源域名不同
v2.www.example.com/dir/other.h…不同源域名不同
www.example.com:81/dir/other.h…不同源端口不同
www.example.com/dir/page.ht…不同源不同源

11.3 同源政策的目的

同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的 Cookie,B网站是不能访问的。

随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送 Ajax 请求,如果请求,浏览器就会报错。

11.4 解决同源策略方案

  • JSONP
  • CORS 跨域资源共享
  • 服务器代理
1. 使用 JSONP 解决同源限制问题

Ajax 不能跨域,但 img的src(获取图片),link 的href(获取css),script的src(获取javascript)这三个都不符合同源策略,它们可以跨域获取数据。这里要介绍的JSONP就是利用 script 的src来实现跨域获取数据的。

JSONP 是 json with padding 的缩写,它不属于 Ajax 请求,但它可以模拟 Ajax 请求。

  1. 将不同源的服务器端请求地址写在 script 标签的 src 属性中
<script src="www.example.com"></script>/
  1. 服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。
const data = 'fn({name: "张三", age: "20"})';
res.send(data);
  1. 在客户端全局作用域下定义函数 fn
function fn (data) { }
  1. 在 fn 函数内部对服务器端返回的数据进行处理
function fn (data) { console.log(data); }

JSONP 代码优化

  1. 客户端需要将函数名称传递到服务器端。
  2. 将 script 请求的发送变成动态请求。
  3. 封装 jsonp 函数,方便请求发送。
  4. 服务器端代码优化之 res.jsonp 方法。
<script src="http://localhost:3000/province?jsonp=jsonp"></script>
2. CORS 跨域资源共享

CORS:全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服了 Ajax 只能同源使用的限制。

CORS

 origin: http://localhost:3000
 Access-Control-Allow-Origin: 'http://localhost:3000'
 Access-Control-Allow-Origin: '*'

Node 服务器端设置响应头示例代码:

// 拦截所有请求
app.use((req, res, next) => {
    // 1. 允许那些客户端访问我
    // * 代表允许所有的客户端访问我
    // res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
    res.header('Access-Control-Allow-Origin', '*');
    // 2. 允许客户端使用哪些请求方法访问我
    res.header('Access-Control-Allow-Methods', 'get,post');
    next();
});
3. 访问非同源数据 服务器端解决方案

同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制

非同源访问

const request = require('request');

app.get('/server', (req, res) => {
	// 想非同源服务器访问数据
    request('http://localhost:3001/cross', (err, response, body) => {
        res.send(body);
    });
});

11.5 withCredentials属性

在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。 withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false Access-Control-Allow-Credentials:true 允许客户端发送请求时携带cookie

 xhr.withCredentials = true;
// 服务器端设置
app.use((req, res, next) => {
  // 1. 允许那些客户端访问我
  // * 代表允许所有的客户端访问我
  // 注意: 如果跨域请求时值不能为 * 号,要传递具体的域名信息
  res.header("Access-Control-Allow-Origin", "http://localhost:3000");
  // 2. 允许客户端使用哪些请求方法访问我
  res.header("Access-Control-Allow-Methods", "get,post");
  // 3. 允许客户端发送跨域请求时携带 cookie信息
  res.header("Access-Control-Allow-Credentials", true);
  next();
});

cookie

二、FormData

1. FormData 对象的作用

  • 模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。

  • 异步上传二进制文件

2. 使用

  1. 准备 HTML 表单
<form id="form">
     <input type="text" name="username" />
     <input type="password" name="password" />
     <input type="button"/>
</form>
  1. 将 HTML 表单转化为 formData 对象
var form = document.getElementById('form'); 
var formData = new FormData(form);
  1. 提交表单对象
xhr.send(formData);

注意:

  1. Formdata 对象不能用于 get 请求,因为对象需要被传递到 send 方法中,而 get 请求方式的请求参数只能放在请求地址的后面。

  2. 服务器端 bodyParser 模块不能解析 formData 对象表单数据,我们需要使用 formidable 模块进行解析。

3. FormData 对象的实例方法

  1. 获取表单对象中属性的值
// 获取 key 为 key 的第一个值
formData.get('key');
 // 获取 key 为 key 的所有值,返回值为数组类型
formData.getAll('key');
  1. 设置表单对象中属性的值
formData.set('key', 'value');
  1. 删除表单对象中属性的值
formData.delete('key');
  1. 向表单对象中追加属性值
formData.append('key', 'value');

注意: set 方法与 append 方法的区别是,在属性名已存在的情况下,set 会覆盖已有键名的值,append会保留两个值。

4. FormData 二进制文件上传

<input type="file" id="file"/>
 var file = document.getElementById('file')
// 当用户选择文件的时候
 file.onchange = function () {
     // 创建空表单对象
     var formData = new FormData();
     // 将用户选择的二进制文件追加到表单对象中
     formData.append('attrName', this.files[0]);
     // 配置ajax对象,请求方式必须为post
     xhr.open('post', 'www.example.com');
     xhr.send(formData);
 }

5. FormData 文件上传进度展示

 // 当用户选择文件的时候
 file.onchange = function () {
     // 文件上传过程中持续触发onprogress事件
     xhr.upload.onprogress = function (ev) {
         // 当前上传文件大小/文件总大小 再将结果转换为百分数
         // 将结果赋值给进度条的宽度属性 
         bar.style.width = (ev.loaded / ev.total) * 100 + '%';
     }
 }

6. FormData 文件上传图片即时预览

在我们将图片上传到服务器端以后,服务器端通常都会将图片地址做为响应数据传递到客户端,客户端可以从响应数据中获取图片地址,然后将图片再显示在页面中。

xhr.onload = function () {
     var result = JSON.parse(xhr.responseText);
     var img = document.createElement('img');
     img.src = result.src;
     img.onload = function () {
         document.body.appendChild(this);
     }
 }

三、jQuery 中的 Ajax

1. $.ajax()方法

1.1 发送Ajax请求

$.ajax({
     type: 'get',
     url: 'http://www.example.com',
     data: { name: 'zhangsan', age: '20' },
     contentType: 'application/x-www-form-urlencoded',
     beforeSend: function () { 
         return false
     },
     success: function (response) {},
     error: function (xhr) {}
});

1.2 发送jsonp请求。

$.ajax({
    url: 'http://www.example.com',
    // 指定当前发送jsonp请求
    dataType: 'jsonp',
    // 修改callback参数名称
    jsonp: 'cb',
    // 指定函数名称
    jsonCallback: 'fnName',
    success: function (response) {} 
})

2. serialize方法

serialize()序列化表单元素为字符串。

serializeArray()序列化表单元素为JSON数据。

3. $.get()、$.post()方法

.get方法用于发送get请求,.get方法用于发送get请求,.post方法用于发送post请求。

$.get('/base', 'name=zhangsan&age=30', function(response) {});
$.post('http://www.example.com', {name: 'lisi', age: 22}, function (response) {})

4. 全局事件

只要页面中有Ajax请求被发送,对应的全局事件就会被触发

.ajaxStart()     // 当请求开始发送时触发
.ajaxComplete()  // 当请求完成时触发

NProgress: 纳米级进度条,使用逼真的涓流动画来告诉用户正在发生的事情!

<link rel='stylesheet' href='nprogress.css'/>
<script src='nprogress.js'></script>
NProgress.start();  // 进度条开始运动 
NProgress.done();   // 进度条结束运动
// 当页面中有 ajax请求发送时触发
$(document).on('ajaxStart', function() {
    NProgress.start();
})

// 当页面请求完成时触发
$(document).on('ajaxComplete', function() {
    NProgress.done();
});

四、RESTful 风格的 API

一种新的 API 设计方法(早已推广使用)

传统 API 设计:把每个 url 当作一个功能

Restful API 设计:把每个 url 当作一个唯一的资源

1. 传统的 methods 和 现在的 methods 对比

methods传统的 methods现在的 methods
get获取服务器的数据获取数据
post像服务器提交数据增加数据
patch / put更新数据
delete删除数据

2. 如何设计成一个资源?

2.1 尽量不用 url 参数

  • 传统 API 设计: /api/list?pageIndex = 2
  • Restfux API: /api/list/2

2.2 用 method 表示操作类型传统 API 设计

  • post 请求 /api/create-blog
  • post 请求 /api/update-blog?id=100
  • get 请求 /api/get-blog?id=100

Restfux API 设计

  • post 请求 /api/blog
  • patch 请求 /api/blog/100
  • get 请求 /api/blog/100

五、Fetch

MDN Fetch

Fetch API是新的ajax解决方案 Fetch会返回Promise, fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象

用法: fetch(url, options).then()

缺点:

  1. 发送网络请求需要自己来配置Header的Content-Type,不会默认携带cookie等
  2. 错误处理相对麻烦(只有网络错误才会reject,HTTP状态码404或者500不会被标记为reject)
  3. 不支持取消一个请求,不能查看一个请求的进度等等

1. FetchAPI基本用法

1.1 get

 fetch('http://localhost:3000/books?id=123', {
      //get 请求可以省略不写 默认的是GET 
      method: 'get'
 }).then(function(data) {
      // text()方法数据 fetchAPI 的一部分, 它返回一个Promise实例对象,用于获取后台返回的数据
      return data.text();
  }).then(function(data) {
  	  // 在这个then里面我们能拿到最终的数据  
      console.log(data)
   });

1.2 delete

fetch('http://localhost:3000/books/789', {
      method: 'delete'
})

1.3 post

fetch('http://localhost:3000/books', {
     method: 'post',
     body: 'uname=list&pwd=123',
     // 配置请求头 
     headers: {
     	'Content-Type': 'application/x-www-form-urlencoded'
     }
})

1.4 put

fetch('http://localhost:3000/books/456', {
     method: 'put',
     body: JSON.stringify({
     	uname: '张三',
        pwd: '123'
     }),
     // 配置请求头 
     headers: {
     	'Content-Type': 'application/json'
     }
})

2. 响应格式

.text() 将获取到的数据 转换成字符串, 它返回一个Promise实例对象 .json() 将获取到的数据使用 json 转换对象, 它返回一个Promise实例对象