学习 Ajax 并学会浏览器跨域解决方案

学会 Ajax 并了解浏览器跨域解决方案

AJAX 全称为 Asynchronous JavaScript and XML,异步的 JS 和 XML,是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术

概述

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

访问

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

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

  • 特定元素的 href 或 src 属性

  • 表单提交

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

1、AJAX(Asynchronous JavaScript and XML,异步的JS和XML)

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

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

2、XML

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

现在已经被 JSON 取代了。

3、AJAX 的特点

(1) 优点

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

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

(2) 缺点

没有浏览历史,不能回退

存在跨域问题(同源策略)→ 对用户其实是优点,保护用户数据安全

SEO 不友好

搭建环境

使用 NodeJS 搭建后台,为后续演示做准备

1、安装

先下载安装 nodejs

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

node 初始化命令

接着输入命令,安装 express,express 比原生 Node 更加方便

express 安装

2、express 基本使用

(1) 编写代码

(2) 运行代码

在该目录下输入命令

eg:

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

3、准备服务端代码

新建文件 server.js

在这个代码中,我们设置了两个路由规则,一个是 '/',一个是 '/server',当我们访问 '/' 时,会返回一个页面,当我们访问 '/server' 时,会返回一个字符串

现在我们到浏览器中访问 http://localhost:8000/,可以看到返回的页面(看成是客户端)

之后可以通过 http://localhost:8000/server 访问到返回的字符串(看成服务端,这个是我们后面要用到的)

而且由于都是在 http://localhost:8000 下,所以不会出现跨域问题,便于我们演示学习

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

4、支持热更新

现在每次修改 server.js 都需要重新运行一遍命令,我们可以使用热更新,借助 reload 包——nodemon

它会自动检测 JS 代码变化,restart 服务

1、安装

2、利用 nodemon 执行文件

可以把这个命令写到 package.json 中,这样就可以直接使用 npm run dev 来启动服务了

快速上手

AJAX 基础

记住:一个构造函数(XMLHttpRequest)、两个方法(opensend)、一个事件(onreadystatechange

修改 app.get('/') 中的代码,即浏览器访问的页面,进行发送请求

现在浏览器访问的是 http://localhost:8000/,刷新之后,页面会是这样的

浏览器页面

点击按钮,可以在 Network 中看到请求

发送

接着在 onclick 函数中写代码,接收响应

点击按钮

发送请求得到响应

总结:Ajax 基础操作

  1. 创建 XMLHttpRequest 对象 const xhr = new XMLHttpRequest()

  2. 配置请求 xhr.open('GET', '/server')

  3. 监听请求 xhr.onreadystatechange = function(){...}(可以在里面判断状态码) 或 xhr.onload = function(){...}(状态码为 2xx)、xhr.onerror = function(){...}(状态码为 4xx 5xx)

  4. 发送请求 xhr.send()

  5. 在监听函数中处理响应 xhr.responseText(文本形式)

理解 readyState

onreadystatechange 是 XHR 状态改变时触发的事件,一共有五种状态

可以打印出来看下每个阶段的 readyState 值

readyState 在各阶段的值:

readyState
  1. 0new XMLHttpRequest 初始化 请求代理对象

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

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

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

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

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

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

readyState
状态描述
说明

0

UNSENT

请求代理对象(xhr)已经创建,但是尚未调用 open 方法

1

OPENED

open() 方法已经调用,建立了与服务端的连接

2

HEADERS_RECEIVED

send() 方法已经调用,已经可以获取状态行和响应头

3

LOADING

正在下载响应体,可能响应体为空或不完整

4

DONE

整个响应报文已经下载下来了,可以直接使用 responseText

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

可以用 xhr.onload 替代

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

例如:

log机制测试

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

AJAX 遵循 HTTP 协议

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

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

请求报文(四部分):

  • 请求行 - Method URL HTTP/1.1

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

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

  • 请求头

    • Host: wallleap.cn

    • Cookie: name=luwang

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

    • User-Agent: chrome 83

  • 空行(必须得有)

  • 请求体 (GET 请求这里为空,POST 可不为空)

    • username=admin&password=admin

响应报文(四部分):

  • 状态行 - HTTP/1.1 状态码 OK

    • HTTP/1.1 200 OK

    • HTTP/1.1 404 Not Found

    • HTTP/1.1 403 Forbidden

    • HTTP/1.1 401 Unauthorized

    • HTTP/1.1 500 Internal Server Error

  • 响应头

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

    • Content-length: 2048

    • Content-encoding: gzip

  • 空行

  • 响应体

例如:

这里 POST 的代码并没有实现,如果需要实现,可以在 server.js 中加入,代码在后面的 3、POST 请求

补充:

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

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 请求并传递参数

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

构建服务端数据,在 server.js 中加入

app.get('/') 中的 <body> 标签中的代码替换成

3、POST 请求

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

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

可以改为 all

测试

修改 app.post('/server'

修改 app.get('/') 中 response.send 的内容

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

新增 app.post('/login')

修改 app.get('/')response.send() 的代码

4、同步和异步

生活中:

同步:一个人在同一个时刻只能做一件事情,在执行一些耗时的操作(不需要看管)不去做别的事情,只是等待(必须要等得到结果才继续)

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

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

可以使用 console.time()console.timeEnd()

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

  • 中间写代码

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

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

可以得到结果,例如

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

总结:ajax 应该使用默认的异步方式执行,不要使用同步方式执行(甚至其他耗时操作也应当使用异步方式执行,避免阻塞)

5、响应数据格式

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

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

5.1 XML

一种数据描述手段

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

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

例如:一个学生的数据(姓名、年龄、性别、班级、学号)

server.js 中加入

测试

5.2 JSON

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

例如

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

来测试一下 JSON 的

server.js中添加

测试

注意:

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

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

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

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

生成一段数据

动态渲染数据到表格中

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

可以使用模板引擎

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

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

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

7、兼容方案

XMLHttpRequest 在老板浏览器(IE5/6)中有兼容问题,可以通过另一种方式代替(IE 都已经淘汰了,但有些单位还在用,可以了解一下)

8、补充

(1) response、responseText

都是获取的响应

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

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

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

在服务端添加一个:

解决

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

server.js中添加

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

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

(4) 取消请求

利用 abort() 方法取消请求

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

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

封装

1、AJAX 请求封装

封装的套路:

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

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

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

(4) send 需要传参

(5) 不应该在封装的函数中主观地处理响应结果,可以使用回调或 Promise

(6) 最终版本

不能确定 data 是否需要传,回调函数也不确定,所以都放在一个对象中

使用

2、jQuery 中的 Ajax

jQuery 中有一套专门针对 AJAX 的封装,功能十分完善,经常使用(之前真的非常好用)

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

(1) 通用方法 $.ajax

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

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

jQuery 中 ajax 的回调

(2) 高级封装

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

server.js中:

前端

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

可以写成链式的:

搭配 NProgress

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:

使用 cdn:

(4) 案例

server.js

axios 发送 Ajax 请求

4、fetch

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

fetch 发送 AJAX 请求

server.js

浏览器端

五、跨域

1、概念

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

  1. http://www.baidu.com

  2. https://www.baidu.com

  3. http://www.baidu.com:80

  4. https://baidu.com

  5. https://baidu.com:8000

  6. ftp://baidu.com

上面列出的,只有 1 和 3 是同源的(1 默认省略端口号 80),其他的都不是同源的

下面来一个同源的案例:

server.js 新增内容

运行起来

在这个目录下新建 index.html

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

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

跨域的案例:

在浏览器访问 http://localhost:8000,由于 localhost127.0.0.1 主机名不同,所以不同源

跨域会报错:

跨域

2、解决方案

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

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

可能可以解决跨域的方法
正常图片标签
尝试使用img标签解决跨域
正常link标签
尝试使用link标签解决跨域
尝试使用script标签解决跨域

初级跨域解决方案

服务器端将json用函数包裹返回
客户端使用该函数拿到数据

(1) JSONP

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

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

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

  • 原理演示:

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

html

server.js

server.js

前端代码:

封装成一个函数

(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

请求分为简单请求和复杂请求

  • 简单请求:请求方法是 GET、POST、HEAD,请求头只有 AcceptAccept-LanguageContent-LanguageContent-TypeDPRDownlinkSave-DataViewport-WidthWidthContent-Type 的值只能是 application/x-www-form-urlencodedmultipart/form-datatext/plain

  • 复杂请求:不满足简单请求的都是复杂请求(会先发送一个预检请求 OPTION)

但是在实际开发中,我们不需要关心这个,只需要在服务端设置好响应头就可以了。

测试:

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

(3) 服务器代理

服务器代理,就是在客户端请求的时候,先将请求发送给自己的服务器,然后自己的服务器再将请求发送给目标服务器,目标服务器响应以后,再将响应返回给客户端。

在 Nginx 中配置反向代理

六、XMLHttpRequestUpload

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestUpload

最后更新于