Luwang
文章54
标签57
分类25

UV
PV
Ajax学习笔记

Ajax学习笔记

一、概述

Web程序的最初的目的就是将信息(数据)放到公共的服务器,让所有的网络用户都可以通过浏览器访问

image-20200815183450778

在此之前,我们可以通过以下几种方式让浏览器发出服务端的请求,获得服务端的数据:

  • 地址栏输入地址,回车,刷新

  • 特定元素的href或src属性

  • 表单提交

这些方案都是我们无法通过或者很难通过代码的方式进行编程(对服务器发出请求并且接收服务端返回的响应),如果我们可以通过JavaScript直接发送网络请求,那么web的可能就会更多,随之能够实现的功能也会更多,至少不再是“单机游戏”。

1、AJAX(Asynchronous JavaScript and XML,异步的JS和XML),最早出现在2005年的Google Suggest,是在浏览器端进行网络编程(发送请求,接收响应)的技术方案,它使我们可以通过JavaScript直接获取服务端最新的内容而不必重新加载页面,让web更能接近桌面应用的用户体验。

说白了,AJAX就是浏览器提供的一套API,可以通过JavaScript调用,从而实现代码控制请求与响应,实现网络编程。(AJAX不是新的编程语言,而是一种将现有标准组合在一起使用的新的方式)

能力不够API凑。

对xxx进行编程指的就是用代码的方式操作它

2、XML

可扩展标记语言,被设计用来传输和存储数据,和HTML有点像,但是HTML中都是预定义标签,而XML中没有预定义标签,全都是自定义标签,用来表示一些数据。

现在已经被json取代了。

3、AJAX的特点

(1) 优点

可以无需刷新页面而与服务器端进行通信。

允许你根据用户事件来更新部分页面内容。

(2) 缺点

没有浏览历史,不能回退

存在跨域问题(同源)

SEO不友好

补充:搭建环境

使用Express搭建后台

Express中文网

1、安装

需要有node,没有的可以先下载

进入一个文件夹,输入命令创建node应用

npm init --yes

node初始化命令

接着输入命令,安装express

npm install express

express安装

2、基本使用

(1) 编写代码

// 1、引入express
const express = require('express')
const { response } = require('express')

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

// 3、创建路由规则 
// reques是对请求报文的封装;response是对响应报文的封装
app.get('/', (request, response) => { // 前端访问http://localhost:8000/
  // 设置响应
  response.send('Hello Express')
})

// 4、监听端口启动服务
app.listen(8000, () => {
  console.log('服务已经启动,8000端口监听中,请访问http://localhost:8000/')
})

(2) 运行代码

在该目录下输入命令

node 文件名.js

eg:

node expressTest.js

接着访问地址http://localhost:8000/即可

3、准备服务端代码

server.js

const express = require('express')
const { response } = require('express')
const app = express()
// 这里设置为get则请求方式为GET、'/server'则请求url需要加上这个
app.get('/server', (request, response) => {
  // 设置响应头
  response.setHeader('Access-Control-Allow-origin','*') // 设置允许跨域
  // 设置响应体
  response.send('Hello AJAX')
})
// 在这里接着加上接口
app.listen(8000, () => {
  console.log('服务已经启动,8000端口监听中,请访问http://localhost:8000/')
})

关闭上面那个,将这个启动:

node server.js

reload包——nodemon

自动检测js代码变化,restart服务

1、安装

npm install -g nodemon

2、利用nodemon执行文件

nodemon 文件名.js

例如:

nodemon server.js

二、快速上手

1、AJAX基础

(一个构造函数、两个方法、一个事件)

发送请求

<style>
#result{
    width:200px;
    height:100px;
    border:1px solid skyblue;
}
</style>
<button id="btn">点击发送请求</button>
<div id="result"></div>
<script>
btn.onclick = function(){
  // 涉及到AJAX操作的页面不能使用文件协议访问(文件的方式访问)
  // AJAX是一套API,核心提供的类型:XMLHttpRequest XML-->JSON(现在使用的是JSON格式的了)
  // 1、安装浏览器(用户代理)——创建对象
  var xhr = new XMLHttpRequest() // xhr就类似于浏览器的作用(发送请求接收响应)
  // 2、打开浏览器 输入网址——初始化 设置请求方式和url
  xhr.open('GET', 'http://127.0.0.1:8000/server')  // 这一步只是在搭桥铺路
  // 3、敲回车键 开始请求——发送
  xhr.send()  // send才是开始请求
  /* 上面三个是ajax核心代码 */
}
</script>

接收响应

<script>
  var xhr = new XMLHttpRequest()
  xhr.open('GET', 'http://127.0.0.1:8000/server') // 方式,url
  xhr.send()
  /* 因为响应需要时间,所以无法通过返回值的方式返回响应 */
  // var response = xhr.send()
  // console.log(response) // undefine

  // 4、等待响应——事件绑定 处理服务端返回的结果
  /* 如果需要捕获状态的变化,需要注意代码的执行顺序的问题(不要出现来不及的情况)
  * 因为客户端永远不知道服务端何时才能返回我们需要的响应,所以AJAX API采用事件的机制(通知的感觉)
  */
  xhr.onreadystatechange = function(){ // 建议事件使用addEventListener方式
    // 这个事件并不是只在响应时触发,XHR状态改变就触发
    console.log(this.readyState)
    if(this.readyState!==4) return
    // 所以下面就是为4的情况
    // 5、看结果
    console.log(this.responseText) // 获取响应内容(响应体)
    result.innerHTML = xhr.response
  }
</script>

ajax基础操作

2、理解readyState

onreadystatechange是XHR状态改变时触发的

<script>
  var xhr = new XMLHttpRequest()
  console.log(xhr.readyState) // 0
  xhr.open('GET', './time.php')
  console.log(xhr.readyState) // 1
  xhr.send()
  // console.log(xhr.readyState) // 1 取上面那个
  xhr.addEventListener('readystatechange', function(){
    // if(this.readyState !== 4) return
    // console.log(this.responseText)
    console.log(this.readyState) // 2 3 4
  })
</script>

readyState:

readyState代码

new XMLHttpRequest –> 0 初始化 请求代理对象

1–-> open方法已经调用,建立一个与服务端特定端口的连接

2 –-> 已经接收到了响应报文的响应头 console.log(this.getAllResponseHeaders()) 可以拿到响应头,拿不到响应体

拆分:console.log(this.getAllResponseHeaders().splite(‘\n’).splite(‘:’))

获取指定键:console.log(this.getAllResponseHeaders(‘data’))

3 –-> 正在下载响应报文的响应体,可能响应体为空或不完整

4 –-> 一切OK,整个响应报文已经下载下来console.log(this.responseText)

readyState

image-20200815183934889

所以应该在readyState的值为4时,才去处理后续的逻辑

可以用xhr.onload替代

<script>
  var xhr = new XMLHttpRequest()
  xhr.open('GET', 'time.php')
  xhr.send(null) // send可以传请求体,传null代表没有请求体
  xhr.onload = function (){ // 加载完成 H5中提供的XMLHttpRequest version 2.0定义的
    // 相当于readyState为4之后的
    console.log(this.responseText)
  }
</script>

ps: console.log(this) 显示readyState是2、3、4可展开来全都是4,这个是console.log的机制问题,展开的时候只会显示此时的状态

例如:

log机制测试

在浏览器上看,不展开没问题显示123,展开的一瞬间都是456

3、AJAX遵循HTTP协议

HTTP协议(Hypertext Transport Protocol,超文本传输协议)详细规定了浏览器和万维网服务器之间互相通信的规则。

本质上XMLHttpRequest就是JavaScript在web平台中发送HTTP请求的手段,所以我们发送出去的请求仍然是HTTP请求,同样符合HTTP约定的格式

请求报文:

  • 请求行 POST /s?ie=utf-8 HTTP/1.1

GET /s?ie=utf-8 HTTP/1.1

  • 请求头 Host: atguigu.com

Cookie: name=guigu

Content-Type: application/x-www-form-urlencoded

User-Agent: chrome 83

  • 空行(必须得有)

  • 请求体 (GET请求这里为空,POST可不为空) username=admin&password=admin

响应报文:

  • HTTP/1.1 200 OK

​ 404

​ 403

​ 401

​ 500

  • Content-Type: text/html;charset=utf-8

    Content-length: 2048

    Content-encoding: gzip

  • 空行

<html>
  <head>
  </head>
  <body>
    <h1>wallleap</h1>
  </body>
</html>

HTTP响应报文信息

<script>
  // 1、创建对象
  var xhr = new XMLHttpRequest()
  // 2、初始化 设置请求方法和url
xhr.open('POST', 'add.php') // 设置请求行
  xhr.setRequestHeader('Foo', 'Bar') // 设置一个请求头
  // 一旦请求体是urlencoded格式的内容,一定要设置请求头中的Content-Type为下面这个
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') // 设置第二请求头
  xhr.send('key1=value1&key2=value2') // 以urlencoded格式设置请求体
  // xhr.send('{"foo": "123"}') // 以json格式设置请求体,上面设置application/json
  xhr.onload = function (){
    // 响应行
    console.log(this.status) // 获取响应状态码
    console.log(this.statusText) // 获取响应状态描述
    // 获取响应头信息
    console.log(this.getResponseHeader('Content-Type')) // 指定响应头
    console.log(this.getAllResponseHeaders()) // 全部响应头
    // 获取响应体
    console.log(this.responseText) // 文本形式
    console.log(this.responseXML) // XML形式,了解即可
  }
</script>

补充:

xhr.addEventListener('readystatechange', function(){
    if(this.readyState === 4 && this.status === 200){
      console.log(this)
    }
})

可能有的人会同时判断状态码200,事实上没有必要,状态码404也需要处理,可以到里面嵌套,例如:

xhr.onreadystatechange=function(){
  if(this.readyState === 4){
    if(this.status >= 200  && xhr.status < 300){
      // 处理结果
    }else{
      ...
    }
  }
})

Chrome打开开发者模式

点击Network能够看到传输的文件

点击XHR查看

Request Headers——请求头

点击view source可以看到请求行

Response Header——响应头

点击view source可以看到响应行

Response——响应体

Preview——预览,对响应体解析之后的页面

三、具体用法

1、数据接口的概念

服务器端返回的响应就是一个JSON内容(返回的就是数据)

对于返回数据的地址一般我们称之为接口(形式上是web形式)

http://api.douban.com/v2/movie/top250

提供一定的能力,有输入有输出就可以称为接口

2、AJAX发送GET请求并传递参数

<script>
  var xhr = new XMLHttpRequest()
  // GET请求传递参数通常使用URL中的问号传递数据
  xhr.open('GET', 'http://127.0.0.1:8000/server?a=100&b=200&c=300')
  // 一般在GET请求时无需设置响应体,可以传null或者干脆不传
  xhr.send(null)
  xhr.onreadystatechange = function (){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  }
</script>
<!-- 一般情况下URL传递的都是参数性质的数据,而POST一般都是业务数据 -->

例子:将得到的四个用户名称{}放到ul>li中,点击li能够获取到该用户的年龄

<ul id="list"></ul>
<script>
  var listElement = document.getElementById('list')
  /* 发送请求获取到列表数据,呈现在页面上 */
  var xhr = new XMLHttpRequest()
  xhr.open('POST', 'user.php?id=2')
  xhr.send(null)
  xhr.onreadystatechange = function (){
    if(this.readyState !== 4) return
    var data = JSON.parse(this.responseText)
    // console.log(data)
    for(var i = 0; i < data.length; i++){
      // console.log(data[i])
      var liElement = document.createElement('li')
      liElement.innerHTML = data[i].name
      liElement.id = data[i].id
      listElement.appendChild(liElement)
      /* 给每一个li注册点击事件 */
      // 由于li是动态创建的,因此需要移到创建li的时候
      listElement.addEventListener('click', function (){
          // TODO: 通过AJAX操作获取服务端对应数据的信息
          // 获取当前被点击元素对应数据的id
          // console.log(this.id)
          var xhr1 = new XMLHttpRequest()
          xhr1.open('GET', 'users.php?id=' + this.id)
          xhr1.send()
          xhr1.onreadystatechange = function (){
            if(this.readyState !== 4) return
            var obj = JSON.parse(this.responseText)
            alert(obj.age)
          }
      })
    }
  }
</script>

3、POST请求

POST请求过程中,都是采用请求体承载需要提交的数据

// 1.创建对象
const xhr = new XMLHttpRequest()
// 2.初始化 设置类型与URL(open的第一个参数的作用就是设置请求的method)
xhr.open('POST','http://127.0.0.1:8000/server')
// 设置请求头信息
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded') // 设置请求头中的Content-Type为application/x-www-form-urlencoded——标识这次请求得请求体格式为urlencoded以便于服务端接收数据
// xhr.setRequestHeader('name','wallleap') // 也可以自定义
// 3.发送 POST方式需要提交到服务端的数据可以通过send方法的参数传递,格式:key1=value1&key2=value2
xhr.send("key1=value1&key2=value2")
// xhr.send("key1:value1&key2:value2")
// xhr.send("value1")  // --->可以随意写,但是需要按格式,方便后台处理
// 4.事件绑定
xhr.onreadystatechange = function(){
  // 判断
  if(xhr.readyState===4){
    if(xhr.status >= 200 && xhr.status < 300){
      // 处理服务端返回的结果
      result.innerHTML = xhr.response
    }
  }
}

由于server.js中只设置了get的允许跨域,因此需要在文件中加入允许post跨域的代码

// app.get('/server', (request, response) => {
app.post('/server', (request, response) => {
  // 设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  // 设置允许所有头信息,就比如上面设置的自定义响应头会报错,就需要加上这个
  // response.setHeader('Access-Control-Allow-Headers', '*');
  // 设置响应体
  response.send('HELLO AJAX POST');
});

可以改为all

//可以接收任意类型的请求(get/post/options/...)
app.all('/server', (request, response) => {
  //设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  //响应头
  response.setHeader('Access-Control-Allow-Headers', '*');
  //设置响应体
  response.send('HELLO AJAX POST');
});

测试

<style>
#result{
  width:200px;
  height:100px;
  border:solid 1px #903;
}
</style>
</head>
<body>
<div id="result"></div>
<script>
  //获取元素对象
  const result = document.getElementById("result");
  //绑定事件
  result.addEventListener("mouseover", function(){
    //1. 创建对象
    const xhr = new XMLHttpRequest();
    //2. 初始化 设置类型与 URL
    xhr.open('POST', 'http://127.0.0.1:8000/server');
    //设置请求头
    xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
    xhr.setRequestHeader('name','wallleap');
    //3. 发送
    xhr.send('a=100&b=200&c=300');
    // xhr.send('a:100&b:200&c:300');
    // xhr.send('1233211234567');
    //4. 事件绑定
    xhr.onreadystatechange = function(){
      //判断
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
          //处理服务端返回的结果
          result.innerHTML = xhr.response;
        }
      }
    }
  });
</script>

例子:点击登录按钮不刷新页面将数据传到后台

<style>
.loading{
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #555;
  text-align: center;
  padding-top: 200px;
  opacity: .5;
}
.loading::after{
  content: '加载中……';
  font-size: 60px;
  color: #fff;
}
</style>
<div class="loading"></div>
<table border="1">
  <tr>
    <td>用户名</td>
    <td><input type="text" name="" id="username"></td>
  </tr>
  <tr>
    <td>密码</td>
    <td><input type="password" name="" id="password"></td>
  </tr>
  <tr>
    <td></td>
    <td><button id="btn">登录</button></td>
  </tr>
</table>
<script>
  // 找一个合适的时机,做一件合适的事情(时间、内容)
  // 1、获取界面上的元素 value
  var textUsername = document.getElementById('username')
  var textPassword = document.getElementById('password')
  var btn = document.getElementById('btn')
  var loading = document.querySelector('.loading')
  btn.onclick = function (){
    loading.style.display = 'block'
    var username = textUsername.value
    var password = textPassword.value
    // 2、通过XHR发送一个POST请求
    var xhr = new XMLHttpRequest
    xhr.open('POST', 'login.php')
    // 一定注意:如果请求体是urlencoded格式,必须设置这个请求头!
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    // xhr.send('username=' + username + '&password=' + password)
    xhr.send(`username=${username}&password=${password}`)
    // 3、根据服务端的反馈,作出界面提示
    xhr.onreadystatechange = function (){
      if(this.readyState !== 4) return
      console.log(this.responseText)
      loading.style.display = 'none'
    }
  }
</script>

4、同步和异步

生活中:

同步:一个人在同一个时刻只能做一件事情,在执行一些耗时的操作(不需要看管)不去做别的事情,只是等待

异步:在执行一些耗时的操作(不需要看管)去做别的事,而不是等待

xhr.open()第三个参数(async)要求传入的是一个bool值,其作用就是设置此次请求是否采用异步方式执行,默认为true,如果需要同步执行可以通过传递false实现

image-20200815184831019

console.time(‘标识’) 启动一个秒表

中间写代码

console.timeEnd(‘标识’) 结束这个秒表

这样就能知道用了多长时间(标识名称得相同)

image-20200815184905369

如果采用同步方式执行,则代码会卡死在xhr.send()这一步

image-20200815184920339

send方法会不会出现等待情况(区分同异步)

image-20200815184938765

知道同步模式即可(已被遗弃)

image-20200815184955646

同步模式注册时间时机问题

image-20200815185017871

image-20200815185028276

5、响应数据格式

如果希望服务器返回一个复杂数据,该如何处理:

服务器发出何种格式的数据,这个格式如何在客户端用JavaScript解析

5.1 XML

一种数据描述手段

老掉牙的东西,现在项目中基本不使用

淘汰的原因:数据冗余太多

image-20200815185057257

image-20200815185106691

image-20200815185114823

5.2 JSON

也是一种数据描述手段,类似于JavaScript字面量方式

服务端采用JSON格式返回数据,客户端按照JSON格式解析数据

image-20200815185140660

image-20200815185147671

来测试一下json的

server.js中添加

//JSON 响应
app.all('/json-server', (request, response) => {
  //设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  //响应头
  response.setHeader('Access-Control-Allow-Headers', '*');
  //响应一个数据
  const data = {
    name: 'wallleap'
  };
  //对对象进行字符串转换
  let str = JSON.stringify(data);
  //设置响应体
  response.send(str);
});

测试

<style>
  #result{
    width:200px;
    height:100px;
    border:solid 1px #89b;
  }
</style>
<div id="result"></div>
<script>
  //绑定键盘按下事件
  window.onkeydown = function(){
    //发送请求
    const xhr = new XMLHttpRequest();
    //设置响应体数据的类型
    xhr.responseType = 'json'; // 自动转换
    //初始化
    xhr.open('GET','http://127.0.0.1:8000/json-server');
    //发送
    xhr.send();
    //事件绑定
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
          // console.log(xhr.response);
          // result.innerHTML = xhr.response;
          // 1. 手动对数据转化
          // let data = JSON.parse(xhr.response);
          // console.log(data);
          // result.innerHTML = data.name;
          // 2. 自动转换
          console.log(xhr.response);
          result.innerHTML = xhr.response.name;
        }
      }
    }
  }
</script>

注意:

不论是JSON,还是XML,只是在AJAX请求过程中用到,并不代表它们之间有必然的联系,它们只是数据协议罢了

不管服务器使用XML还是JSON本质上都是将数据返回给客户端

服务端应该设置一个合理的Content-Type

6、处理服务器端响应的数据

动态渲染数据到表格中

image-20200815185217284

image-20200815185225225

现在一般都不会这样操作,太繁琐了

模板引擎

image-20200815185251189

常见模板引擎列表:https://github.com/tj/consolidate.js#supported-template-engines

artTemplate: https://aui.github.io/art-template

模板引擎实际上就是一个API,模板引擎有很多种,使用方式大同小异,目的为了可以更容易地将数据渲染到HTML中

<table id="comment" border="1"></table>
<!-- // 1.选择一个模板引擎 https://github.com/tj/consolidate.js#supported-template-engines
// 2、下载模板引擎JS库
// 3、引入到页面中 -->
<script src="https://cdn.bootcdn.net/ajax/libs/art-template/4.13.2/lib/template-web.min.js"></script>
<!-- 
  script标签的特点:
  1、innerHTML永远不会显示在界面上(display: none;)
  2、如果type属性不是text/javascript,内部的内容不会作为JavaScript执行
 -->
<!-- // 4、准备一个模板 -->
<!-- 
  JavaScript中用变量保存(维护不方便、不能换行)
  ——> HTML中放到一个div中通过样式隐藏(添加误用元素、多余样式)
  ——> script标签type修改之后不会显示在页面中,建议text/x-开头
-->
<script id="tmpl" type="text/x-art-template">
{{each comments}}  
<tr>
  <td>{{$index+1}}</td>
  <td>{{$value.author}}</td>
  <td>{{$value.content}}</td>
  <td>{{$value.created}}</td>
</tr>
{{/each}}
</script>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'test.php')
xhr.send()
xhr.onreadystatechange = function (){
  if(this.readyState !== 4) return
  var res = JSON.parse(this.responseText)
  // // 5、准备一个数据
  var context = {comments: res.data}  // 上面的关键词就是comments
  console.log(context)
  var html = template('tmpl', context)
  console.log(html)
  document.getElementById('comment').innerHTML = html

  // 6、通过模板引擎的JS提供一个函数将模板和数据整合得到渲染结果HTML
  // 7、将渲染结果的HTML设置到某个元素的innerHTML中
}
</script>

7、兼容方案

XMLHttpRequest在老板浏览器(IE5/6)中有兼容问题,可以通过另一种方式代替

var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')

8、补充

(1) response、responseText

都是获取的响应

response: 获取到的结果或根据this.responseType的变化而变化(可以表述二进制数据)

responseText: 永远获取的是字符串形式的响应体

var xhr = new XMLHttpRequest
xhr.open('GET', 'test.php')
xhr.send()
xhr.responseType = 'json'  // 通过代码告诉请求代理对象,服务端响应给我们的是JSON
xhr.onreadystatechange = function (){
  if(this.readyState !== 4) return
  console.log(this)
  console.log(this.response)
  console.log(this.responseText)  // 由于设置了json,因此不存在
}

(2) IE缓存问题:IE浏览器会将ajax返回结果缓存起来,再次发送请求时,显示的是本地缓存,而不是最新的请求到的数据

在服务端添加一个:

//针对 IE 缓存
app.get('/ie', (request, response) => {
  //设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  //设置响应体
  response.send('HELLO AJAX IE');
});

解决

<style>
  #result{
    width:200px;
    height:100px;
    border:solid 1px #258;
  }
</style>
<button>点击发送请求</button>
<div id="result"></div>
<script>
  const btn = document.getElementsByTagName('button')[0];
  const result = document.querySelector('#result');

  btn.addEventListener('click', function(){
    const xhr = new XMLHttpRequest();
    xhr.open("GET",'http://127.0.0.1:8000/ie?t='+Date.now()); // 解决方案:加上参数,这样浏览器认为每次请求url都不一样
    xhr.send();
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status< 300){
          result.innerHTML = xhr.response;
        }
      }
    }
  })
</script>

(3) 请求超时和网络异常

server.js中添加

//延时响应
app.all('/delay', (request, response) => {
  //设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  response.setHeader('Access-Control-Allow-Headers', '*');
  // 手动设置一个延时效果
  setTimeout(() => {
    //设置响应体
    response.send('延时响应');
  }, 1000) // 3000
});

进行处理,在2s内还没有响应则取消

<style>
  #result{
    width:200px;
    height:100px;
    border:solid 1px #90b;
  }
</style>
<button>点击发送请求</button>
<div id="result"></div>
<script>
  const btn = document.getElementsByTagName('button')[0];
  const result = document.querySelector('#result');
  btn.addEventListener('click', function(){
    const xhr = new XMLHttpRequest();
    //超时设置 2s 设置
    xhr.timeout = 2000;
    //超时回调
    xhr.ontimeout = function(){
      alert("网络异常, 请稍后重试!!");
    }
    //网络异常回调
    xhr.onerror = function(){
      alert("你的网络似乎出了一些问题!");
    }
    xhr.open("GET",'http://127.0.0.1:8000/delay');
    xhr.send();
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status< 300){
          result.innerHTML = xhr.response;
        }
      }
    }
  })
</script>

网络异常可以利用浏览器调试中Network一栏,设置为Offline

(3) 取消请求

利用abort()方法取消请求

<button>点击发送</button>
<button>点击取消</button>
<script>
  //获取元素对象
  const btns = document.querySelectorAll('button');
  let x = null; // 由于第二个按钮也需要用到
  btns[0].onclick = function(){
    x = new XMLHttpRequest();
    x.open("GET",'http://127.0.0.1:8000/delay');
    x.send();
  }
  // abort
  btns[1].onclick = function(){
    x.abort();
  }
</script>

(3) ajax重复发送请求问题:用户频繁发送请求,对服务器压力很大

请求时,可以判断,如果前面有一条这样的请求,那么将前面的请求取消掉

<button>点击发送</button>
<script>
  //获取元素对象
  const btns = document.querySelectorAll('button');
  let x = null;
  //标识变量
  let isSending = false; // 是否正在发送AJAX请求
  btns[0].onclick = function(){
    //判断标识变量
    if(isSending) x.abort();// 如果正在发送, 则取消该请求, 创建一个新的请求
    x = new XMLHttpRequest();
    //修改 标识变量的值
    isSending = true;
    x.open("GET",'http://127.0.0.1:8000/delay');
    x.send();
    x.onreadystatechange = function(){
      if(x.readyState === 4){
        //修改标识变量
        isSending = false;
      }
    }
  }
</script>

四、封装

1、AJAX请求封装

封装的套路:

(1) 写一个相对比较完善的用例

var xhr = new XMLHttpRequest
xhr.open('GET', 'test.php', true)
xhr.send(null)
xhr.addEventListener('readystatechange', function(){
  if(this.readyState !== 4) return
  console.log(this.responseText)
})

(2) 写一个空函数,没有形参,将刚刚的用例直接作为函数的函数体

function ajax(){
  var xhr = new XMLHttpRequest
  xhr.open('GET', 'test.php', true)
  xhr.send(null)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  })
}

(3) 根据使用过程中的需求抽象参数

/*
Ajax请求 version1
method: 请求方式 GET/POST
url: 请求地址 'http://xxx.com/api'
*/
function ajax(method, url){
  var xhr = new XMLHttpRequest
  xhr.open(method, url, true)
  xhr.send(data)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  })
}
ajax('GET', 'test.php')

send需要传参

/*
Ajax请求 version2
method: 请求方式 GET/POST
url: 请求地址 'http://xxx.com/api'
params: 键值对字符串
*/
function ajax(method, url, params){
  var xhr = new XMLHttpRequest
  var data = null
  if(method === 'GET'){
    url += '?' + params
  }
  xhr.open(method, url, true)
  if(method === 'POST'){
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    data = params || null
  }
  xhr.send(data)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  })
}
ajax('GET', 'test.php')
ajax('POST', 'post.php', 'key1=value1&key2=value2')

send实参可以传对象

/*
Ajax请求 version3
method: 请求方式 GET/POST
url: 请求地址 'http://xxx.com/api'
params: 对象
*/
function ajax(method, url, params){
  var xhr = new XMLHttpRequest
  // 将Object类型的参数转换为 key1=value1&key2=value2的形式
  if(typeof params === 'object'){
    var tempArr = []
    for (var key in params){
      var value = params[key]
      tempArr.push(key + '=' + params[key])
      // tempArr => ['key1=value1', 'key2=value2']
    }
    params = tempArr.join('&')
    // tempArr => ['key1=value1&key2=value2']
  }
  if(method === 'GET'){
    params ? url += '?' + params : url
  }
  xhr.open(method, url, true)
  var data = null
  if(method === 'POST'){
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    data = params || null
  }
  xhr.send(data)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  })
}
ajax('GET', 'test.php')
ajax('GET', 'time.php', {id: 1})
ajax('POST', 'post.php', {key1: 'value1', key2: 'value2', key3: 'value3'})
ajax('POST', 'post.php', 'key1=value1&key2=value2')

不应该在封装的函数中主观地处理响应结果

/*
Ajax请求 version4
method: 请求方式 GET/POST/get/post
url: 请求地址 'http://xxx.com/api'
params: 对象
*/
function ajax(method, url, params){
  var res = null
  method= method.toUpperCase()
  var xhr = new XMLHttpRequest
  // 将Object类型的参数转换为 key1=value1&key2=value2的形式
  if(typeof params === 'object'){
    var tempArr = []
    for (var key in params){
      var value = params[key]
      tempArr.push(key + '=' + params[key])
      // tempArr => ['key1=value1', 'key2=value2']
    }
    params = tempArr.join('&')
    // tempArr => ['key1=value1&key2=value2']
  }
  if(method === 'GET'){
    params ? url += '?' + params : url
  }
  xhr.open(method, url, false) // 改为同步了
  var data = null
  if(method === 'POST'){
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    data = params || null
  }
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    // console.log(this.responseText)
    // return xhr.responseText // 无法再给外部函数返回结果,利用闭包、原型链
    res = this.responseText
  })
  xhr.send(data)
  // 由于是异步,会先执行(用同步,事件放到send前x不推荐)
  return res
}
console.log(ajax('post', 'post.php', {key1: 'value1', key2: 'value2', key3: 'value3'}))

不能使用同步模式

补充一个概念:

委托(或回调)

image-20200815185827212

函数可以理解为一个想要做的事情,函数体中约定了这件事情做的过程,直到调用时才开始工作。

将函数作为参数传递就像是将一件事情交给别人,这就是委托。

异步编程中回调/委托使用频率很高。(由于是异步的,你先执行,我告诉你做什么,我就不等了——等不及,你执行就行了)

image-20200815185845589

image-20200815185856765

回调地狱/黑洞:死循环

image-20200815185913313

最终版本

/*
发送一个Ajax请求 version5
@param (String) method  请求方式 GET/POST/get/post
@param (String) url     请求地址 'http://xxx.com/api'
@param (Object) params  请求参数
@param (Function) done  请求完成过后需要做的事情(委托/回调)
*/
function ajax(method, url, params, done){
  var res = null
  method= method.toUpperCase()
  var xhr = new XMLHttpRequest
  // 将Object类型的参数转换为 key1=value1&key2=value2的形式
  if(typeof params === 'object'){
    var tempArr = []
    for (var key in params){
      var value = params[key]
      tempArr.push(key + '=' + params[key])
    }
    params = tempArr.join('&')
  }
  if(method === 'GET'){
    params ? url += '?' + params : url
  }
  xhr.open(method, url, true)
  var data = null
  if(method === 'POST'){
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    data = params || null
  }
  xhr.send(data)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    res = this.responseText
    done(res)
  })
}

var ondone = function (res){
  console.log('准备执行')
  // console.log(res)
  alert(res)
  console.log('执行完成了')
}
ajax('post', 'post.php', {key1: 'value1', key2: 'value2'}, ondone)
ajax('get', 'time.php', {}, function(res){
  console.log(res)
})

2、jQuery中的Ajax

jQuery中有一套专门针对AJAX的封装,功能十分完善,经常使用,需要注意。

https://www.jquery123.com/category/ajax/

(1)通用方法$.ajax

底层接口(其他接口依赖于这个)

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 1、最基础的调用
$.ajax('./time.php', { // url: 
  type: 'POST',  // method: 请求方法
  success: function(res){
    // res => 拿到的只是响应体
    console.log(res)
  }
})
// 可以把url写到里面去
$.ajax({ 
  url: './time.php',// url: 
  type: 'POST',  // method: 请求方法
  data: {id: 1, name: '张三'}, // 用于提交到服务端的参数
  /*
    get方式通过url传递
    post方式按照请求体传递
  */
  success: function(res){
    // res => 拿到的只是响应体
    console.log(res)
  }
})
// res返回格式
$.ajax({ 
  url: './json.php',// url: 请求地址
  type: 'get',  // method: 请求方法
  success: function(res){
    // res会根据服务器响应的Content-Type自动转换为对象
    // 这是jQuery内部实现的
    console.log(res)
  }
})
// 指定响应体类型
$.ajax({ 
  url: './json.php',// url: 
  type: 'get',  // method: 请求方法
  // data: {id: 1, name: '张三'}, // 设置请求参数
  dataType: 'json', // 用于设置响应体的类型(与data参数没关系)
  success: function(res){
    // 一旦设置了dataType选项,就不再关心服务端响应的Content-Type了
    // 客户端会主观地认为服务端返回的就是json格式
    console.log(res)
  }
})
</script>

原生操作中不论请求状态码是多少都会触发回调

jQuery中ajax的回调

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// jQuery中ajax的回调
$.ajax({ 
  url: './time.php', // url
  data: {key1:value1, key2:value2}, // 参数
  type: 'POST',  // method: 请求方法
  dataType: 'json', // 响应体结果
  beforeSend: function(xhr){
    // 在所有发送请求得操作(open、send)之前执行
    console.log('beforeSend', xhr)
  }
  success: function(res){ // 成功的回调
    // 只有请求成功(状态码为200)才会执行这个函数
    // res => 拿到的只是响应体
    console.log(res)
  },
  // timeout: 2000, // 超时时间
  error: function(xhr){ // 失败的回调
    // 只有请求不正常才会执行(状态码不为200)
    console.log('error',xhr)
  },
  complete: function(xhr){
    // 不管成功还是失败都是完成,都会执行这个complete函数
    console.log('complete', xhr)
  }
})
</script>

(2) 高级封装

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 2、jQuery中ajax的快捷方法
$.get('time.php', function(res){
  console.log(res)
})
$.post('time.php', function(res){
  console.log(res)
})
// 服务端设置了json能转换对象,没设置不行
$.get('time.php', {id: 1}, function(res){
  console.log(res)
})
$.post('time.php', {id: 1}, function(res){
  console.log(res)
})
// 无视服务端Content-Type,视作JSON格式
$.getJSON('json.php', {id: 1}, function(res){
  console.log(res)
})
$.postJSON('json.php', {id: 1}, function(res){
  console.log(res)
})
明确请求的方式,根据方式选择快捷方法
</script>

image-20200815190206531

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 跳转其他页面,会白屏,但是有些页面只是部分不相同,可以局部刷新
$(function($){
  // 有一个独立的作用域,顺便确保页面加载完成执行
  $('a.item').on('click', function(){
    var url = $(this).attr('href')
    $('#main').load(url + ' #main>*') // 元素.load(链接+空格+选择器)
    // 阻止a的默认行为
    return false
  })
})
</script>

将jQuery的几种方式汇总一下:

server.js中:

//jQuery 服务
app.all('/jquery-server', (request, response) => {
    //设置响应头  设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    response.setHeader('Access-Control-Allow-Headers', '*');
    // response.send('Hello jQuery AJAX');
    const data = {name:'wallleap'};
    response.send(JSON.stringify(data));
});

前端

<link crossorigin="anonymous" href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <!-- crossorigin="anonymous":跨域源请求设置,加上之后向这个资源发送请求时不会携带当前域名下的cookie -->
<div class="container">
    <h2 class="page-header">jQuery发送AJAX请求 </h2>
    <button class="btn btn-primary">GET</button>
    <button class="btn btn-danger">POST</button>
    <button class="btn btn-info">通用型方法ajax</button>
</div>
<script>
  $('button').eq(0).click(function(){
    $.get('http://127.0.0.1:8000/jquery-server', {a:100, b:200}, function(data){
      console.log(data);
    },'json');
  });
  $('button').eq(1).click(function(){
    $.post('http://127.0.0.1:8000/jquery-server', {a:100, b:200}, function(data){
      console.log(data);
    });
  });
  $('button').eq(2).click(function(){
    $.ajax({
      //url
      url: 'http://127.0.0.1:8000/jquery-server',
      //参数
      data: {a:100, b:200},
      //请求类型
      type: 'GET',
      //响应体结果
      dataType: 'json',
      //成功的回调
      success: function(data){
        console.log(data);
      },
      //超时时间
      timeout: 2000,
      //失败的回调
      error: function(){
        console.log('出错啦!!');
      },
      //头信息
      headers: {
        c:300,
        d:400
      }
    });
  });
</script>

(3) 全局事件及配置NProgress显示加载进度

<style>
  .loading{
    display: none;
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background-color: rgba(85, 85, 85, .5);
    text-align: center;
    padding-top: 200px;
    color: #fff;
    font-size: 50px;
  }
</style>
<div class="loading">正在玩命加载中……</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 3、jQuery全局事件处理函数
/*
$.ajax({
  url: 'time.php',
  beforeSend: function(xhr){
    // 显示加载提示
    $('.loading').fadeIn()
    console.log('即将开始请求')
  },
  complete: function(xhr){
    // 结束提示
    $('.loading').fadeOut()
    console.log('请求结束了')
  }
})
*/
// 所有的ajax请求开始和结束
$(document).ajaxStart(function(){
  // 只要有ajax请求发生就会执行
  // 显示加载提示
  $('.loading').fadeIn()
  console.log('即将开始请求')
})
$(document).ajaxStop(function(){
  // 只要有ajax请求发生就会执行
  // 显示加载提示
  $('.loading').fadeOut()
  console.log('请求结束了')
})
$('body').on('click', function(){
  // $.ajax({
  //   url: 'time.php'
  // })
  $.get('time.php')
})
</script>

可以写成链式的:

$(document)
  .ajaxStart(function(){
    ……
  })
  .ajaxStop(function(){
    ……
  })

搭配NProgress

<link rel="stylesheet" href="https://unpkg.com/[email protected]/nprogress.css">
<script src="https://unpkg.com/[email protected]/nprogress.js"></script>
</head>
<body>
<div style="margin-top: 100px;text-align: center;">点击加载</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 3、jQuery全局事件处理函数搭配nprogress
// 所有的ajax请求开始和结束
$(document)
  .ajaxStart(function(){
    // 只要有ajax请求发生就会执行
    NProgress.start()
  })
  .ajaxStop(function(){
    // 只要有ajax请求发生就会执行
    NProgress.done()
  })
$('body').on('click', function(){
  $.get('time.php')
})
</script>
</body>

3、axios

(1) 使用教程:http://www.axios-js.com/zh-cn/docs/

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

(2) 特性

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

(3) 安装

使用 npm:

$ npm install axios

使用 bower:

$ bower install axios

使用 cdn:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

(4) 案例

server.js

//axios 服务
app.all('/axios-server', (request, response) => {
    //设置响应头  设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    response.setHeader('Access-Control-Allow-Headers', '*');
    // response.send('Hello jQuery AJAX');
    const data = {name:'wallleap'};
    response.send(JSON.stringify(data));
});

axios发送Ajax请求

<script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.js"></script>
<button>GET</button>
<button>POST</button>
<button>AJAX</button>
<script>
  // https://github.com/axios/axios
  const btns = document.querySelectorAll('button');
  //配置 baseURL
  axios.defaults.baseURL = 'http://127.0.0.1:8000';
  btns[0].onclick = function () {
    //GET 请求 get(url, 其它配置)
    // axios.get('http://127.0.0.1:8000/axios-server', {
    axios.get('/axios-server', { // 配置了baseURL
      //url 参数——>id=100&vip=7
      params: {
        id: 100,
        vip: 7
      },
      //请求头信息
      headers: {
        name: 'wallleap',
        age: 20
      }
    }).then(value => { // 基于promise 处理返回结果 value.config、data、headers、request、status、statusText
      console.log(value);
    });
  }
  btns[1].onclick = function () {
    // POST请求 post(url, 请求体, 其它配置)
    axios.post('/axios-server', { // 请求体
      username: 'admin',
      password: 'password'
    }, {
      //url 
      params: {
        id: 200,
        vip: 9
      },
      //请求头参数
      headers: {
        height: 180,
        weight: 180,
      }
    }).then(response=>{
      // 配置
      console.log(response.config);
      // XMLHttpRequest
      console.log(response.request);
      //响应状态码
      console.log(response.status);
      //响应状态字符串
      console.log(response.statusText);
      //响应头信息
      console.log(response.headers);
      //响应体
      console.log(response.data);
    })
  }
  btns[2].onclick = function(){
    // 通用方式 axios(对象) --> {method, url, 参数, 头信息, 请求体参数}
    axios({
      //请求方法
      method : 'POST',
      //url
      url: '/axios-server',
      //url参数
      params: {
        vip:10,
        level:30
      },
      //头信息
      headers: {
        a:100,
        b:200
      },
      //请求体参数
      data: {
        username: 'admin',
        password: 'password'
      }
    }).then(response=>{
      //响应状态码
      console.log(response.status);
      //响应状态字符串
      console.log(response.statusText);
      //响应头信息
      console.log(response.headers);
      //响应体
      console.log(response.data);
    }).catch(function (error) {
      console.log(error);
    })
  }
</script>

4、fetch

fetch使用:https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch

fetch发送AJAX请求

server.js

//fetch 服务
app.all('/fetch-server', (request, response) => {
    //设置响应头  设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    response.setHeader('Access-Control-Allow-Headers', '*');
    // response.send('Hello jQuery AJAX');
    const data = {name:'wallleap'};
    response.send(JSON.stringify(data));
});
<button>AJAX请求</button>
<script>
  //文档地址
  //https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/fetch
  const btn = document.querySelector('button');
  btn.onclick = function(){
    fetch('http://127.0.0.1:8000/fetch-server?vip=10', {
      //请求方法
      method: 'POST',
      //请求头
      headers: {
        name:'atguigu'
      },
      //请求体
      body: 'username=admin&password=admin'
    }).then(response => {
      // return response.text();
      return response.json();
    }).then(response=>{
      console.log(response);
    });
  }
</script>

五、跨域

1、概念

(1) 同源策略(Same-Origin Policy):最早由Netscape公司提出,是浏览器的一种安全策略,所谓同源是指协议、域名、端口完全相同,只有同源的地址才可以相互通过AJAX的方式请求。

下面来一个同源的案例:

重写一个server.js

const express = require('express');
const app = express();
app.get('/home', (request, response)=>{
    //响应一个页面
    response.sendFile(__dirname + '/index.html');
});
app.get('/data', (request, response)=>{
    response.send('用户数据');
});
app.listen(9000, ()=>{
    console.log("服务已经启动...");
});

运行起来

nodemon server.js

在这个目录下新建index.html

<h1>wallleap</h1>
<button>点击获取用户数据</button>
<script>
  const btn = document.querySelector('button');
  btn.onclick = function(){
    const x = new XMLHttpRequest();
    //这里因为是满足同源策略的, 所以 url 可以简写
    x.open("GET",'/data');
    //发送
    x.send();
    //
    x.onreadystatechange = function(){
      if(x.readyState === 4){
        if(x.status >= 200 && x.status < 300){
          console.log(x.response);
        }
      }
    }
  }
</script>

访问http://127.0.0.1:9000/home,可以访问这个index.html,点击按钮可以获取到数据

(2) 同源或者不同源说的是两个地址之间的关系,不同源地址之间请求我们称之为跨域请求。

image-20200815190429165

跨域的案例:

image-20200815190439633

跨域会报错:

image-20200815190455302

2、解决方案

不同源地址之间如果需要相互请求,必须服务端和客户端配合才能完成

尝试找到一种可以发送不同源请求的方式

可能可以解决跨域的方法

正常图片标签

尝试使用img标签解决跨域

正常link标签

尝试使用link标签解决跨域

尝试使用script标签解决跨域

初级跨域解决方案

服务器端将json用函数包裹返回

客户端使用该函数拿到数据

(1) JSONP

JSON with Padding,是一种借助于script标签发送跨域请求的技巧。这个是非官方的跨域解决方案,是程序员们机智地想出来的,只支持get请求。

其原理就是在客户端借助script标签请求服务端的一个动态网页(php等),服务端的这个动态网页返回一段带有函数调用的JavaScript全局函数调用的脚本,将原本需要返回给客户端的数据传递进去。

以后绝大多数情况都是采用JSONP的手段完成不同源地址之间的跨域请求。

  • 原理演示:

当前目录下,新建js/app.js

const data = {
    name: '测试jsonp'
};
/* 把这个挪走,到测试的html文件中
//处理数据
    function handle(data) {
        //获取 result 元素
        const result = document.getElementById('result');
        result.innerHTML = data.name;
    }
*/
handle(data);

html

<style>
  #result {
    width: 300px;
    height: 100px;
    border: solid 1px #78a;
  }
</style>
<div id="result"></div>
<script>
    //处理数据
    function handle(data) {
        //获取 result 元素
        const result = document.getElementById('result');
        result.innerHTML = data.name;
    }
</script>
<!-- <script src="http://127.0.0.1:5500/jsonp/js/app.js"></script> --> <!-- 利用file://方式访问这个html页面 -->
<!-- 进阶版 -->
<script src="http://127.0.0.1:8000/jsonp-server"></script>

server.js

//jsonp服务
app.all('/jsonp-server',(request, response) => {
    // response.send('console.log("hello jsonp")'); // 前端拿到js代码
    const data = {
        name: '测试jsonp'
    };
    //将数据转化为字符串
    let str = JSON.stringify(data);
    //返回结果
    response.end(`handle(${str})`); // 会返回一个函数调用,实参是想返回给前端的数据(前端需要先声明这个函数)
});
  • 用php演示一下

server.php

<?php
$conn = mysqli_connect('localhost', 'root', '123456', 'demo');
$query = mysqli_query($conn, 'select * from users');
while ($row = mysqli_fetch_assoc($query)) {
  $data[] = $row;
}
if (empty($_GET['callback'])) {
  header('Content-Type: application/json');
  echo json_encode($data);
  exit();
}
// 如果客户端采用的是 script 标记对我发送的请求
// 一定要返回一段 JavaScript
header('Content-Type: application/javascript');
$result = json_encode($data);
$callback_name = $_GET['callback'];
echo "typeof {$callback_name} === 'function' && {$callback_name}({$result})";

image-20200815191013064

  • 原生方式实践jsonp

server.js

//用户名检测是否存在
app.all('/check-username',(request, response) => {
    // response.send('console.log("hello jsonp")');
    const data = {
        exist: 1,
        msg: '用户名已经存在'
    };
    //将数据转化为字符串
    let str = JSON.stringify(data);
    //返回结果
    response.end(`handle(${str})`);
});

前端代码:

用户名: <input type="text" id="username">
<p></p>
<script>
  //获取 input 元素
  const input = document.querySelector('input');
  const p = document.querySelector('p');
  //声明 handle 函数
  function handle(data){
    input.style.border = "solid 1px #f00";
    //修改 p 标签的提示文本
    p.innerHTML = data.msg;
  }
  //绑定事件
  input.onblur = function(){
    //获取用户的输入值
    let username = this.value;
    //向服务器端发送请求 检测用户名是否存在
    //1. 创建 script 标签
    const script = document.createElement('script');
    //2. 设置标签的 src 属性
    script.src = 'http://127.0.0.1:8000/check-username';
    //3. 将 script 插入到文档中
    document.body.appendChild(script);
  }
</script>
  • 封装成一个函数
function jsonp (url, params, callback) {
  var funcName = 'jsonp_' + Date.now() + Math.random().toString().substr(2, 5)

  if (typeof params === 'object') {
    var tempArr = []
    for (var key in params) {
      var value = params[key]
      tempArr.push(key + '=' + value)
    }
    params = tempArr.join('&')
  }

  var script = document.createElement('script')
  script.src = url + '?' + params + '&callback=' + funcName
  document.body.appendChild(script)

  window[funcName] = function (data) {
    callback(data)

    delete window[funcName]
    document.body.removeChild(script)
  }
}

jsonp('http://localhost/jsonp/server.php', { id: 123 }, function (res) {
  console.log(res)
})

jsonp('http://localhost/jsonp/server.php', { id: 123 }, function (res) {
  console.log(res)
})
  • jQuery方式实践jsonp

server.js

// jQuery jsonp
app.all('/jquery-jsonp-server',(request, response) => {
  // response.send('console.log("hello jsonp")');
  const data = {
    name:'尚硅谷',
    city: ['北京','上海','深圳']
  };
  //将数据转化为字符串
  let str = JSON.stringify(data);
  //接收 callback 参数
  let cb = request.query.callback; // 函数名从前端获取
  //返回结果
  response.end(`${cb}(${str})`);
});

前端代码:

<style>
  #result{
    width:300px;
    height:100px;
    border:solid 1px #089;
  }
</style>
<script crossorigin="anonymous" src='https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js'></script>
<button>点击发送 jsonp 请求</button>
<div id="result">

</div>
<script>
  $('button').eq(0).click(function(){
    $.getJSON('http://127.0.0.1:8000/jquery-jsonp-server?callback=?', function(data){
      $('#result').html(`
                名称: ${data.name}<br>
                校区: ${data.city}
            `)
    });
  });
</script>

(2)CORS

HTTP访问控制(CORS)https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

Cross Origin Resource Share,跨域资源共享。CORS是官方的跨域解决方案,它的特点是不需要再客户端做任何特殊的操作,完全在服务器中进行处理,支持get和post请求(其他也支持)。跨域资源共享标准新增了一组HTTP首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。

CORS是通过设置一个响应头来告诉浏览器,这个请求允许跨域,浏览器收到该响应以后就会对响应放行。

server.js

app.all('/cors-server', (request, response)=>{
  //设置响应头
  response.setHeader("Access-Control-Allow-Origin", "*"); // 允许所有源站发送请求
  response.setHeader("Access-Control-Allow-Headers", '*'); // 允许携带的响应头
  response.setHeader("Access-Control-Allow-Method", '*'); // 允许请求方法
  // response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500"); // 允许单个
  response.send('hello CORS');
});

测试:

<style>
    #result{
        width:200px;
        height:100px;
        border:solid 1px #90b;
    }
</style>
<button>发送请求</button>
<div id="result"></div>
<script>
  const btn = document.querySelector('button');
  btn.onclick = function(){
    //1. 创建对象
    const x = new XMLHttpRequest();
    //2. 初始化设置
    x.open("GET", "http://127.0.0.1:8000/cors-server");
    //3. 发送
    x.send();
    //4. 绑定事件
    x.onreadystatechange = function(){
      if(x.readyState === 4){
        if(x.status >= 200 && x.status < 300){
          //输出响应体
          console.log(x.response);
        }
      }
    }
  }
</script>
// 允许远端访问(甚至直接打开文件的方式也可以file://……)
header('Access-Control-Allow-Origin: *') // 允许所有
header('Access-Control-Allow-Origin: http://localhost/index.html') // 允许单个

eg:

<?php

$conn = mysqli_connect('localhost', 'root', '123456', 'demo');

$query = mysqli_query($conn, 'select * from users');

while ($row = mysqli_fetch_assoc($query)) {
  $data[] = $row;
}

// 一行代码搞定
// 允许跨域请求
header('Access-Control-Allow-Origin: *');

header('Content-Type: application/json');
echo json_encode($data);

客户端

<script src="jquery.js"></script>
<script>
  $.get('http://localhost/cors.php', {}, function (res) {
    console.log(res)
  })
</script>

这种方案无序客户端作出任何变化(不用改代码),只是在被请求的服务端响应的时候添加一个Access-Control-Allow-Origin的响应头,表示这个资源是否允许指定域请求。

六、XMLHttpRequest2.0

暂作了解,无需着重看待

更易用,更强大。