AJAX--XMLHttpRequest & 跨域

594 阅读3分钟

XMLHttpRequest

XMLHttpRequest 是浏览器内置的一个构造函数

作用:基于 new 出来的 XMLHttpRequest 实例对象,可以发起 Ajax 的请求

axios 中的 axios.get()、axios.post()axios() 方法,都是基于 XMLHttpRequest(简称:XHR) 封装出来的

使用 XMLHttpRequest 发起 GET 请求

主要的 4 个实现步骤:

创建 xhr 对象

调用 xhr.open() 函数

调用 xhr.send() 函数

监听 load 事件

1651148348013.png

示例

    <script>
        // 1 创建 xhr对象
        const xhr = new XMLHttpRequest()
        // 2 调用 open方法 指定 请求类型,url
        xhr.open('get', 'http://www.itcbc.com:3006/api/getbooks')
        // 3 发送出去 send
        xhr.send()
         // 4 监听onload  数据响应事件
        xhr.addEventListener('load', function(){
            // console.log(this.response);
            // 字符串转对象 可用可不用
            const obj = JSON.parse(this.response)
            console.log(obj);
        })
    </script>

请求时携带URL参数 或 提交请求体

URL参数,只能拼接在 URL 地址 后面

请求体,放到 send() 里面

1651149187597.png

提交请求体数据,需指定Content-Type头

当需要提交请求体数据时,需要在 xhr.open() 之后,调用 xhr.setRequestHeader() 函数,指定请求体的编码格

<script>
      const xhr = new XMLHttpRequest();
      xhr.open('post', 'http://www.itcbc.com:3006/api/addbook');
      // post 三种不同数据格式的参数
      // 1 a=b&c=d   同时也需要指定 content-type 才行!!
      // 2 对象格式  {a:"b",c:"d"} 同时也需要指定 content-type 才行!!
      // 3 formdata 数据
      const data = {
        bookname: '2从入门到入土2',
        author: '我自己',
        publisher: '新闻出版社',
        appkey: 'wanshao1234',
      };
      // 设置对应的 content-type
      xhr.setRequestHeader("Content-type","application/json");
      const str =JSON.stringify(data);
      xhr.send(str); // 传递 a=b&c=d
      xhr.addEventListener('load', function () {
        console.log(this.response);
      });
    </script>

数据交换格式

数据交换格式,就是服务器端客户端之间数据传输的格式

两种数据交换格式: XML(很少用) JSON(主流)

1651149455488.png

JSON

JSON(全称:JavaScript Object Notation)是一种数据交换格式,它本质上是用字符串的方式来表示对象或数组类型的数据。例如:

1651149496959.png

JSON 数据

用字符串的方式来表示的对象或数组类型的数据,叫做 JSON 数据。

JSON 数据的格式有两种: 对象格式 数组格式

JSON 的语法要求

使用 JSON 定义 JSON 格式的数据时,要遵守以下的 6 条规则:

属性名必须使用双引号包裹

字符串类型的值必须使用双引号包裹

JSON 中不允许使用单引号表示字符串

JSON 中不能写注释

JSON 的最外层必须是对象数组格式(其他类型也可以,但多数是对象或数组格式)

不能使用 undefined函数作为 JSON 的值

对象格式的 JSON 数据

对象格式的 JSON 数据,最外层使用 { } 进行包裹,内部的数据为 "key": "value" 的键值对结构。其中:

key 必须使用英文的双引号进行包裹

value 的值只能是字符串、数字、布尔值、null、数组、对象类型(可选类型只有这 6 种)

1651149777663.png

数组格式的 JSON 数据

数组格式的 JSON 数据,最外层使用 [ ] 进行包裹,内部的每一项数据之间使用英文的 , 分隔。其中: 每一项的值类型只能是字符串、数字、布尔值、null、数组、对象这 6 种类型之一。

1651149827172.png

把 JSON 数据转换为 JS 数据

调用浏览器内置的 JSON.parse() 函数,可以把 JSON 格式的字符串转换为 JS 数据,例如:

1651149877887.png

把 JS 数据转换为 JSON 数据

调用浏览器内置的 JSON.stringify() 函数,可以把 JS 数据转换为 JSON 格式的字符串,例如:

1651149921897.png

序列化和反序列化

把真实数据转换为字符串的过程,叫做序列化

把字符串转换为真实数据的过程,叫做反序列化

1651150882418.png

把 XMLHttpRequest 请求到的 JSON 数据反序列化为 JS 对象

在 xhr 对象的 load 事件中,通过 xhr.response 访问到的是 JSON 格式的字符串数据。可以调用 JSON.parse() 函数将 xhr.response 转化为 JS 对象。示例代码如下:

1651150979470.png

补充 - JSON 文件

后缀名是 .json 的文件叫做 JSON 文件,专门用来存储 JSON 格式的数据。例如:

1651151016835.png

注意: .json 结尾的文件,一般都是项目的配置文件

后面的 Node.js 中也会使用到 package.json 这个配置文件

封装自己的 Ajax 函数

处理 data 请求体数据 - 判断 options.data 是否为 FormData 格式

如何判断一个对象是否为某个构造函数的实例?

答案:对象 instanceof 构造函数

1651162255177.png

如何判断某个值是否为字符串类型? 答案:typeof 值 === 'string'

1651162284347.png

如何判断某个值是否为对象类型? 答案:typeof 值 === 'object'

1651162313444.png

ajax代码的封装

<script src="./lib/jquery.js"></script>
    <script>
      // 使用了 axios 来发送ajax请求
      // 我们应该使用原生的ajax代码 来封装成 axios 调用模样 axios({url,method,params,data}).then  || promise(有点东西-node)

      // jq中 也是封装过 ajax代码
      $.ajax({
        url: 'http://www.itcbc.com:3006/api/getbooks',
        // type: 'get',
        type: 'post',
        // data: {
        //   appkey: 'wanshao1234',
        // },
        data: 'appkey=wanshao1234',
        success(result) {
          console.log(result);
        },
      });

      /* 
      1 type  可能是get 也可能是post
      2 data 3种(查询字符串 json  formdata )

      3 希望封装的代码 什么样式 
        ajax({url,type,data,success})
        {
        url: 'http://www.itcbc.com:3006/api/getbooks',
        type: 'post',
        data: "appkey=wanshao1234",
        success(result) {
          console.log(result);
        }
       */
    </script>
  <script>
        /* 
      1 ajax  是一个函数(正解) 还是 一个对象 
      2 它接受一个 参数 格式  什么格式(对象)
      3 需要在ajax函数中 写完 整个原生ajax 发送请求的代码 
       */
        const obtion = {
            url:'http://www.itcbc.com:3006/api/getbooks',
            type:'get',
            data:'appkey=lin123',
            success(result){
                // result 等于 要等于响应的数据 =  对象格式 
                console.log(result);
            }
        }
        // 里面方法   option.success(2);
        axios(obtion)

        function axios(config){
            const xhr = new XMLHttpRequest()
            xhr.open(config.type,config.url + '?' + config.data)
            xhr.send()
            xhr.addEventListener('load',function(){
                //  响应的数据 this.response
          // console.log(this.response);

                const obj = JSON.parse(this.response)
                config.success(obj)
            })
        }
    </script>

ajax代码的封装.-get-不携带参数

<script>
      const option = {
        url: 'http://www.itcbc.com:3006/api/getbooks',
        type: 'get',
        // data: 'appkey=wanshao1234',
        success(result) {
          console.log(result);
        },
      };

      ajax(option);

      ajax({
        url: 'http://www.itcbc.com:3006/api/getbooks',
        type: 'get',
        data: 'appkey=wanshao1234',
        success(result) {
          console.log(result);
        },
      });

      function ajax(config) {
        console.log(config.data);
        // 封装的时候考虑到用户 (可能带参数 , 可能不带参数)
        const xhr = new XMLHttpRequest();
        if (config.data) {
          // 有传递参数
          xhr.open(config.type, config.url + '?' + config.data);
        } else {
          xhr.open(config.type, config.url);
        }
        xhr.send();
        xhr.addEventListener('load', function () {
          const obj = JSON.parse(this.response);
          config.success(obj);
        });
      }
    </script>

ajax代码的封装.-get-不携带参数

<script>
      // 形参默认值
      // 解构
      // function func(msg="hello") {
      //   console.log(msg);
      // }

      // func();// 输出默认值
      // func('你好'); /// 你好

      // const option = {
      //   type: 'get',
      //   data: 'a=1',
      // };
      // const { data } = option;
      // console.log(data); // a=1

      // function func(config) {
      //   const {data}=config;
      //   console.log(data);
      // }
      // function func({ data = '1' ,type}) {
      //   // 解构 同时也设置了默认值
      //   // console.log(data);
      //   console.log(type);
      // }

      // func(option);

      const option = {
        url: 'http://www.itcbc.com:3006/api/getbooks',
        type: 'get',
        // data: 'appkey=wanshao1234',
        success(result) {
          console.log(result);
        },
      };

      ajax(option);

      ajax({
        url: 'http://www.itcbc.com:3006/api/getbooks',
        type: 'get',
        data: 'appkey=wanshao1234',
        success(result) {
          console.log(result);
        },
      });

      function ajax({ url, type, data = '', success }) {
        // 封装的时候考虑到用户 (可能带参数 , 可能不带参数)
        const xhr = new XMLHttpRequest();
        xhr.open(type, url + '?' + data);
        // 如果 data没有值  url =  http://www.itcbc.com?
        // 如果 data有值  url =  http://www.itcbc.com?appkey=wanshao1234
        xhr.send();
        xhr.addEventListener('load', function () {
          const obj = JSON.parse(this.response);
          success(obj);
        });
      }
    </script>

ajax代码的封装.-get-对象格式的参数

<script>
        // 1
        const option = {
            url: 'http://www.itcbc.com:3006/api/getbooks',
            type: 'get',
            success(result) {
                console.log(result);
            }
        }
        ajax(option)
        // 2
        ajax({
            url: 'http://www.itcbc.com:3006/api/getbooks',
            type: 'get',
            data: 'appkey=lin123',
            success(result) {
                console.log(result);
            }
        })
        // 3
        ajax({
            url: 'http://www.itcbc.com:3006/api/getbooks',
            type: 'get',
            data: {
                appkey: 'lin123',
                bookname: '人生长恨水长东',
            },
            success(result) {
                console.log(result);
            }
        })

        function ajax({ url, type, data = '', success }) {
            const xhr = new XMLHttpRequest()
            // 判断方式写法一
            // if (typeof data === 'object') {
            //     data = new URLSearchParams(data)
            //     data.toString()
            // }
            // 判断方式写法2
            if (typeof data === 'object') {
                // data是一个对象
                data = new URLSearchParams(data).toString();
            }
            // 判断方式写法3
            // (typeof data === 'object')&&(data = new URLSearchParams(data).toString())
            xhr.open(type, url + '?' + data)
            xhr.send()
            xhr.addEventListener('load', function () {
                const obj = JSON.parse(this.response)
                success(obj)
            })
        }

ajax代码-post

<script>
      ajax({
        url: 'http://www.itcbc.com:3006/api/getbooks',
        type: 'get',
        success(result) {
          console.log(result);
        },
      });
      ajax({
        url: 'http://www.itcbc.com:3006/api/getbooks',
        type: 'post',
        success(result) {
          console.log(result);
        },
      });

      function ajax({ url, type, data = '', success }) {
        const xhr = new XMLHttpRequest();
        // 判断 请求类型
        if (type === 'get') {
          // get请求的相关的代码
          if (typeof data === 'object') {
            data = new URLSearchParams(data).toString(); // 转成字符串
          }
          xhr.open(type, url + '?' + data);
          xhr.send();
        } else if (type === 'post') {
          // post请求的相关的代码
          xhr.open(type, url);
          xhr.send();
        }
        xhr.addEventListener('load', function () {
          const obj = JSON.parse(this.response);
          success(obj);
        });
      }
    </script>

封装一个Ajax 函数,调用它可以发起 GET 或 POST 请求:

<input type="file" accept="image/*" />
    <script>
      /* 
      判断当前data的数据类型
      1 字符串类型 
              xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
              xhr.send(data); // 传递 a=b&c=d
      2 对象类型
              xhr.setRequestHeader("Content-type","application/json");
              const str =JSON.stringify(data);
              xhr.send(str); // 传递 a=b&c=d
      3 formdata
              xhr.send(formdata);
       */

      //  判断当前数据 字符串类型 typeof
      // console.log( typeof data === "string" ); // 字符串类型

      // console.log( typeof data === "object" ); // 对象类型

      // console.log(typeof data);

      // 判断你儿子 是不是亲生
      //  儿子   instanceof  爸爸
      //  实例   instanceof  构造函数
      // console.log( data instanceof FormData );
      // console.log(data instanceof Array);

      const input = document.querySelector('input');
      input.addEventListener('change', function () {
        const file = this.files[0];
        const formdata = new FormData();
        formdata.append('avatar', file);

        ajax({
          url: 'http://www.itcbc.com:3006/api/formdata',
          type: 'post',
          data:formdata,
          success(result) {
            console.log(result);
          },
        });
      });

      function ajax({ url, type, data = '', success }) {
        const xhr = new XMLHttpRequest();
        // 判断 请求类型
        if (type === 'get') {
          // get请求的相关的代码
          if (typeof data === 'object') {
            data = new URLSearchParams(data).toString();
          }
          xhr.open(type, url + '?' + data);
          xhr.send();
        } else if (type === 'post') {
          // post请求的相关的代码
          xhr.open(type, url);

          // 判断是不是字符串
          if (typeof data === 'string') {
            xhr.setRequestHeader(
              'Content-type',
              'application/x-www-form-urlencoded'
            );
            xhr.send(data);
          } else if (typeof data === 'object') {
            // 判断是不是对象

            // 判断是不是 FormData 实例
            if (data instanceof FormData) {
              // 是 FormData 实例
              xhr.send(data);
            } else {
              // 普通的对象
              xhr.setRequestHeader('Content-type', 'application/json');
              const str = JSON.stringify(data);
              xhr.send(str); // 传递 a=b&c=d
            }
          }
        }
        xhr.addEventListener('load', function () {
          const obj = JSON.parse(this.response);
          success(obj);
        });
      }
    </script>

防抖 & 节流

防抖(debounce)指的是:频繁触发某个操作时,只执行最后一次。

1651162762268.png 防抖的应用场景

场景:搜索框只在输入完后,才执行查询的请求。

好处:这样可以有效减少请求的次数,节省网络资源。

 /* 
      防抖  防止抖动
      1 用在输入框中 实现 不用用户按下回车键 就发送请求
      2 技术原理
        1 用新的一次输入来清除上一次的延时器 
        2 同时开启一个新的延时器 
      
       */
      getData();
      // change事件  输入框的值发生改变-输入框失去焦点 才触发
      // input 事件

      // 定义一个 演示器 id
      let timeid; // 钻 石 城 堡

      const input = document.querySelector('input');
      input.addEventListener('input', function (event) {
        clearTimeout(timeid);
        // 开启了一个延时器 里面代码 1s后会执行
        timeid = setTimeout(function () {
          const value = input.value.trim();
          const queryStr = `?bookname=${value}`;
          getData(queryStr);
        }, 1000);
      });

节流

节流(throttle)指的是:单位时间内,频繁触发同一个操作,只会触发 1 次。

1651162881951.png

/* 
      节流
      上一次的业务没有结束的话 不允许开启下一次业务
      使用场景 移动端分页  -  倒计时按钮 等等 
      
       */
      
      
      
      // 这一次请求还没有结束 就不能开启下一个请求
      // 业务 分页 业务

      // 开关
      let isLoadding = false; // 有没有请求在发送当中

      // 点击按钮的时候先判断 isLoadding true还是false
      //  true 请求在发送中  return
      //  false 没有请求
      //   先设置 isLoadding true
      //   发送请求出去
      //  请求回来了  设置 isLoadding = false

		 document.querySelector('button').addEventListener('click', function () {
        if (isLoadding) {
          return;
        }

        isLoadding = true;

        // 发送请求的时候  禁用按钮
        // this.disabled=true;
        getData();
      });
      function getData(query = '') {
        console.log('请求发送出去');
        axios({
          method: 'get',
          url: 'http://www.itcbc.com:3006/api/getbooks' + query,
          // params:{},
        }).then((result) => {
          console.log('数据回来了');
          // document.querySelector('button').disabled=false
          isLoadding = false;
        });
      }

同源策略 & 跨域

同源指的是两个 URL 地址具有相同的协议、主机名、端口号

例如,下表给出了相对于 www.test.com/index.html 页面的 5 个同源检测结果

1651195251248.png

同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能

浏览器的同源策略规定:不允许非同源的 URL 之间进行资源的交互

1651195388776.png

同源指的是两个 URL 的协议、主机名、端口号完全一致,反之,则是跨域

出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。例如:

网页:http:// www.test.com / index.html

接口:http:// www.api.com /userlist

受到同源策略的限制,上面的网页请求下面的接口会失败!

浏览器对跨域请求的拦截过程

浏览器允许发起跨域请求。但跨域请求回来的数据,会被浏览器拦截,无法被页面获取到!示意图如下:

1651196672817.png

突破浏览器跨域限制的两种方案

JSONPCORS 是实现跨域数据请求的两种技术方案。

1651196747069.png

注意:目前 JSONP 在实际开发中很少会用到,CORS 是跨域的主流技术解决方案

CORS 的概念

CORS解决跨域数据请求的终极解决方案,全称是 Cross-origin resource sharing

CORS 技术需要浏览器服务器同时支持,二者缺一不可:

浏览器要支持 CORS 功能(主流的浏览器全部支持,IE 不能低于 IE10)

服务器要开启 CORS 功能(需要后端开发者为接口开启 CORS 功能)

CORS 的原理

服务器端通过 Access-Control-Allow-Origin 响应头,来告诉浏览器当前的 API 接口是否允许跨域请求。

1651197090712.png

CORS 的两个主要优势

CORS 是真正的 Ajax 请求,支持 GET、POST、DELETE、PUT、PATCH 等这些常见的 Ajax 请求方式

只需要后端开启 CORS 功能即可,前端的代码无须做任何改动

JSONP

JSONP实现跨域数据请求的一种技术解决方案。它只支持 GET 请求,不支持 POST、DELETE 等其它请求。

在实际开发中很少被使用 在面试中可能会问到 JSONP 的原理

JSONP 不是真正的 Ajax 技术

在解决跨域问题时:

CORS 方案用到了 XMLHttpRequest 对象,发起的是纯正的 Ajax 请求

JSONP 方案没有用到 XMLHttpRequest 对象,因此,JSONP 不是真正的 Ajax 技术

结论:只要用到了 XMLHttpRequest 对象,发起的就是 Ajax 请求!

JSONP 的底层实现原理

JSONP 在底层,用到了 < script> 标签的 src 属性!

原因:

< script> 标签的 src 属性,不受浏览器同源策略的限制

可以把非同源的 JavaScript 代码请求到本地,并执行

JSONP 的底层实现原理 - P1

把非同源的 JavaScript 代码请求到本地,并执行:

1651198849562.png

1651198874901.png

JSONP 的底层实现原理 - P2

如果请求回来的 JavaScript 代码只包含函数的调用,则需要程序员手动定义 show 方法。示例代码如下:

1651198900980.png

缺点:服务器响应回来的代码中,调用的函数名是写死的!

JSONP 的底层实现原理 - P3

在指定 < script> 标签的 src 属性时,可以通过查询参数中的 callback,自定义回调函数的名称:

1651198947257.png

JSONP 的底层实现原理 - P4

指定 < script> 标签的 src 属性时,还可以通过查询参数的方式,指定要发送给服务器的数据:

1651198989791.png

示例

    <!-- 随便加载别人公司的js文件 !!  -->
    <!-- <script src="./utils/04.js"></script> -->
    <script>
      // 前端拿到后端的数据

      // 希望前端自己来定义show方法的业务
      function show(option) {
        alert('我的数据拿到啦' + option.message);
      }
    </script>

    <!-- 自己来定义 后端起的函数名称 show  函数 自己来具体的业务 -->
    <script src="http://www.itcbc.com:3006/api/getscript2"></script>

    <!-- <script>
      // show里面的数据 就是后端想要给我们返回的数据
      show({ message: '你必须自己定义好叫做show的函数,否则就会出错' });
    </script> -->
    <script>
      // option后端想要给前端传递数据
      function show(option) {
        console.log('数据回来了, 后端帮我们调用的show 里面有数据');
        console.log(option);
      }
      /* 
      1 跨域的目的 拿到后端的数据  (前端和后端 跨域  不同源 直接发送ajax请求 失败)
      2 script标签的src属性不受同源策略的影响 跨域 
      3 浏览器对于script的支持 
        1 允许它下载指定路径的js代码 (js代码 由后端程序员来设定它内容)  
           show({data:['a','b','c']}) => show 后端自己命名 -  show中传递了真正的数据 
        2 而且还会执行 js代码 
          你前端需要自己来定义 show方法的内容 你前端的业务逻辑
      */
      // show();
      // let data = 1233;
      // show();
    </script>
	 <!-- 根据后端要求写的请求js的代码 -->
   	<script src="http://www.itcbc.com:3006/api/getscript2"></script>