JS Web API AJAX

ajax原理和常用插件库

XMLHttpRequest(ajax核心API)

  • XMLHttpRequest():用于创建 XMLHttpRequest 对象,XMLHttpRequest 对象用于和服务器交换数据

onreadystatechange事件

  • onreadystatechange事件:和image对象的onload/onerror类似,都是对象状态发生改变时触发

open()

  • open():初始化一个请求,规定请求的类型、URL 以及是否异步处理请求。
    • 参数1:请求的类型,”GET”或者”POST”
    • 参数2:url,文件在服务器上的位置
    • 参数3默认为true,表示服务器请求是异步进行的,也就是脚本执行send()方法后不等待服务器的执行结果,而是继续执行脚本代码;当参数3为false时,服务器请求是同步进行的,也就是脚本执行send()方法后等待服务器的执行结果的返回,若在等待过程中超时,则不再等待,继续执行后面的脚本代码!

send()

  • send():将请求发送到服务器。如果请求是异步的(默认),那么该方法将在请求发送后立即返回。
    • 参数string:仅用于 POST 请求

readyState属性

  • XMLHttpRequest对象的readyState属性存有 XMLHttpRequest对象 的状态
属性值 含义
0 (请求未初始化)还没有调用send()方法
1 (载入,服务器连接已建立)已调用send()方法,正在发送请求
2 (载入完成,请求已接收)send()方法执行完成,已经接收到全部响应内容
3 (交互,请求处理中)正在解析响应内容
4 (完成,请求已完成,且响应已就绪)响应内容解析完成,可以在客户端调用

status属性

  • XMLHttpRequest对象的status属性,其实就是http响应的状态码
  • 必须在readyState属性值为4时才能去判断status属性,否则拿不到status属性值
属性值 含义
2xx 表示成功处理请求,如200
3xx 需要重定向,浏览器直接跳转,如301 302 304
4xx 客户端请求错误,如404 403
5xx 服务器端错误

responseText 与 responseXML 属性

  • 使用 XMLHttpRequest 对象的 responseText 或 responseXML 属性 可获得来自服务器的响应数据。
    • responseText 获得字符串形式响应数据(如果请求未成功或尚未发送,则返回 null)
    • responseXML 获得 XML 形式响应数据

AJAX例子

  1. 创建XMLHttpRequest对象:使用XMLHttpRequest()来new一个XMLHttpRequest对象
  2. 判断对象状态:通过XMLHttpRequest对象的onreadystatechange事件(readyState还有status属性)判断对象状态(请求是否初始化、服务器连接是否建立、请求是否接收、请求是否处理中、请求是否已完成且响应已就绪)。
  3. 获取响应数据:判断到“请求已完成且响应已就绪”时可使用XMLHttpRequest 对象的 responseText 或 responseXML 属性获得响应数据
  4. 向服务器发送请求:使用XMLHttpRequest 对象的 open() 和 send() 方法向服务器发送请求。
    可参考这篇博客

get请求

  • 一般将open()和send()都放到onreadystatechange事件之后来写。
    get请求例子

post请求

post请求例子


浏览器的同源策略

  • 同源策略发送ajax请求时,浏览器要求当前网页和server必须同源(为了安全)
    • 注意:同源是浏览器规定的,在服务端是可以发起跨域攻击的,比如爬虫,因为server端并无同源策略
    • 同源:协议、域名、端口,三者必须一致
      • 比如:前端http://a.com:8080/;server:https://b.com/api/xxx(默认443端口)那么这前后端是三者都不一致
  • 同源策略限制了跨域时的以下行为:
    • Cookie、LocalStorage 和 IndexDB 跨域无法读取,只有同源才能获取他们
    • DOM 和 JS 对象跨域无法获取
    • Ajax请求跨域时无效,可发送,但浏览器会拒绝接受响应
  • 为什么要有同源与不同源?这个作者写的非常好
    • 出于安全考虑,浏览器不允许页面向不同源的接口请求数据,因为如果 接口 和 网页不同源,浏览器认为是2个不同的 服务器
    • 总结说人话: 不同的服务器中内容是不可控的,不允许访问是浏览器为了保护你的电脑安全
    • 举个栗子: 你去肯德基店里点餐,店员只允许你点肯德基的产品(炸鸡,可乐,上校鸡块),如果此时你在肯德基店里面点麦当劳的产品,浏览器会认为你是坏人,就会让保安把你赶出去

加载图片 CSS js可无视同源策略

1
2
3
<img src=跨域的图片地址/>
<link href=跨域的css地址/>
<script src=跨域的js地址> </script>
  • 比如我们经常引用的js/CSS的库,<link/> <script>直接使用cdn地址就是跨域的。
  • <script>可实现JSONP
  • 而图片比较特殊,如果该图片做了防盗链,则无法显示(比如百度)
  • <img/>可用于使用第三方统计服务来统计打点(比如统计访问次数),也就是把需要的参数放入img,在地址处使用第三方统计服务,这样就可以避免跨域问题

跨域

  • 跨域,是指浏览器不能执行其他网站的脚本,即,跨域名,跨端口,跨协议。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制
  • 可通过jsonp、CORS、nginx反向代理实现跨域
  • 关于为什么会有跨域问题可参考这里和上面提到的“为什么要有同源与不同源”,跨域问题 是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同域名的路径,这能有效的阻止跨站攻击
    • 注意:浏览器只有使用 ajax发送请求 才会出现跨域,href属性与src属性不会出现跨域,所以如果你通过改变window.location.href跳转新页面并不会造成跨域问题

实现跨域的前提

  • 所有的跨域,都必须经过server端允许和配合(包括前端jsonp)
  • 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
  • 需要服务端如何配合:
    • jsonp:
      • 服务器端需要根据客户端(浏览器)传递的回调函数名,将数据包装在回调函数中返回给客户端(浏览器)
      • 设置响应头的 Content-Type 为 application/javascript
    • CORS:
      • 服务端需要设置响应头:服务端需要在响应中返回特定的响应头来允许跨域请求
      • 处理预检请求:服务端需要响应这个预检请求并设置相应的响应头
        • 注意:jsonp发送的是get请求,自然不会触发预检请求(这就是CORS造成的多发请求)
      • 处理实际请求:服务端需要处理实际的跨域请求,并根据情况设置适当的响应头
    • nginx反向代理: (proxy反向代理类似)
      • 服务器端需要配置 Nginx 反向代理规则,将客户端请求转发到目标服务器的相应位置
      • 需要处理跨域请求头,允许指定的域名访问资源
      • 可选:配置 SSL 证书,以及根据需求进行其他代理参数的配置

实现跨域的常见方式

注意:如果是协议和端口造成的跨域问题,前端代码无法直接解决协议和端口造成的跨域问题。需要在服务器端进行相应的配置或使用其他后端技术来解决跨域请求的限制

如何选择处理方式

  • jsonp: 曾经的跨域杀手,专治各种跨域问题。现在慢慢的淡出历史舞台
    • 毕竟操作复杂且没CORS安全
  • CORS: 目前的主流方案,也是最简单的方案
    • 不用前端操作,全部后端处理
  • nginx / proxy 反向代理: 一般不使用代理来解决跨域问题
    • 使用反向代理需要额外的服务器配置和维护,因此在决定使用反向代理时需要权衡复杂性和可维护性。在许多情况下,CORS是更简单和直接的解决方案,但反向代理可以处理一些更复杂的情况和需求
    • 适用场景:
      • 访问受限的外部API:当您的前端应用需要访问受限的外部API时,而该API不支持CORS或需要特定的安全控制,您可以设置反向代理来处理请求。代理服务器将前端请求发送到API,然后将响应返回给前端,充当前端和API之间的中介。
      • 多个前端应用与单个后端服务:如果您有多个独立的前端应用,它们需要与单个后端服务进行通信,但这些前端应用部署在不同的域上,可以使用反向代理来统一处理这些前端应用的请求,以确保它们可以访问后端服务。
      • 安全和认证控制:反向代理可以用于添加额外的安全和认证控制。它可以在请求到达后端之前执行身份验证、授权和其他安全控制,以确保请求是合法的并且具备适当的权限。
      • 负载均衡:反向代理还可以用于负载均衡。多个后端服务器可以部署在后代理服务器的后面,并且代理服务器可以根据负载均衡算法将请求分发到这些后端服务器上,以提高性能和可伸缩性。
      • 复杂路由和请求重定向:反向代理可以实现复杂的路由和请求重定向逻辑,将前端请求重定向到不同的后端服务或资源。

JSONP与CORS区别

  • CORS:
    • 服务器返回响应头,前端无需任何处理
    • 简单快捷,支持所有请求方式
  • JSONP:
    • 浏览器:自定义响应回调函数,使用script标签的src请求
    • 利用浏览器的src属性没有跨域这一限制特点
    • 服务器:接收callback参数(函数名),返回函数调用
    • 处理步骤复杂,并且只支持get请求
    • 原因:get请求参数直接在url后面拼接,而post请求参数是放在请求体中

jsonp

  • 访问https://imooc.com/,服务端一定返回一个html文件吗?
    并不是,服务器可以任意动态拼接数据返回,只要符合html格式要求
    同理,<script src="https://imooc.com/getData.js">也不一定返回一个js文件,服务器可以任意动态拼接数据返回,只要符合js格式要求即可【详细见例子】
  • 注意: jsonp 只能发起GET请求需要服务的支持
    • 因为JSONP通过添加<script>标签来获取数据,而<script>标签通常只支持GET请求。浏览器会根据<script>标签的src属性来发送GET请求,并下载相应的资源
  • 要了解jsonp原理,首先一定要明白script标签的src属性做了什么事情
    • scr属性会给服务器发送请求, 请求一个js文件
    • 浏览器会解析执行这个js文件里面的代码
    • 如果浏览器直接返回js代码,浏览器会立即执行
  • jsonp的核心原理:
    • 如果script标签的src属性的请求,服务器返回的是一个函数调用,则浏览器会执行这个函数
    • 这是浏览器script标签的一个的漏洞(历史遗留问题)
  • jsonp原理
    • <script>绕过跨域限制,所以script中src地址可以是跨域地址服务器可以任意动态拼接数据返回。所以**<script>就可以获得跨域的数据,只要服务端愿意返回**
  • 实际开发中jsonp的工作流程:
    • 设置script标签的src属性,向一个不同源的接口发送一个get请求
    • src属性发送请求时,在参数中额外携带一个callback的参数,参数值是一个在页面中预先定于好的函数名
      • callback: 这是发明jsonp技术的人提出的一个君子之约,只要是jsonp前端程序员都统一将参数名定义为callback (PS:别的参数也行,只要和服务器协商好 )
      • callback属性值: 预先定义的函数名,这个函数必须要在script标签之前定义
    • 服务器接收到请求之后,获取callback的参数值
    • 服务器将要响应的数据拼接成 函数调用格式,通过传参的方式将响应数据返回给浏览器
  • 例子(原理)
    • 在这用一简单例子讲解jsonp原理,真实使用可用jquery
    • 简单例子
    • 先定义一个全局函数callback用于打印跨域返回的内容,此函数已定义,可随时调用
    • 然后在引入的script中设置src为跨域地址,该地址将返回一个执行callback函数
    • 那么返回的代码会立即执行callback函数,并将相应内容打印
    • 结果
  • 【扩展例子】:
    • 跨域动态传递数据跨域动态传递数据
    • 函数名可两域不同:我们可将callback定义为abc后传给跨域文件函数名可两域不同
    • 动态插入script标签:现在是静态插入的,也可以和其他标签一样动态插入

jquery实现jsonp

和发ajax非常类似
jquery实现jsonp


CORS(纯服务端)

  • 总结:前端不需要额外设置,后端需要在响应头中设置 CORS 相关字段
  • 工作原理: 服务器 在返回响应报文的时候,在响应头中 设置允许的header,使得服务器对浏览器说: “老铁我是个好人,不要拒绝我!”————(原帖)

  • 可参考阮一峰的跨域资源共享 CORS 详解
  • CORS是一个W3C标准,全称是”跨域资源共享“(Cross-origin resource sharing)。它规范化的跨域请求解决方案,安全可靠。
  • 允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
  • CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
    • 浏览器端:目前,所有浏览器都支持该功能(IE10以下不行)。整个CORS通信过程,都是浏览器自动完成,不需要用户参与
    • 服务端:CORS通信与AJAX没有任何差别,因此你不需要改变以前的业务逻辑。只不过,浏览器会在请求中携带一些头信息,我们需要以此判断是否运行其跨域,然后在响应头中加入一些信息即可。这一般通过过滤器完成即可。
  • 优势:在服务端进行控制是否允许跨域,可自定义规则,支持各种请求方式
  • 缺点:会产生额外的请求
    • 对于某些跨域请求,浏览器会首先发送一个预检请求,以确定是否允许实际请求。这个预检请求会产生额外的网络开销
  • 实现方法: cors是纯服务端的操作,通过服务端设置http header可实现允许跨域:cors
  • 如果使用cors允许跨域,则跨域请求和发送ajax请求时完全一样的,不需要再考虑跨域问题
  • CORS是一种新标准,支持同源通信,也支持跨域通信fetch是实现CORS通信的
  • 虽然原理比较复杂,但是使用可以比较简单。浏览器端都有浏览器自动完成,我们无需操心。服务端可以通过拦截器统一实现,不必每次都去进行跨域判定的编写。所以可直接使用SpringMVC写好的CORS的跨域过滤器:CorsFilter ,内部已经实现了刚才所讲的判定逻辑,我们直接用就好了。

反向代理

  • 思路:把跨域变为不跨域
  • 常用场景:前后端分离的项目,前端请求后端服务时属于跨域,会出现无法访问接口的情况,当a域名访问a域名下某个接口时通过反向代理转发到b域名下某个对应接口
  • 优点:
    • 集中处理跨域问题:反向代理可以统一处理前端应用程序发出的跨域请求。这意味着前端应用程序不必担心跨域问题,代理服务器负责处理请求并将响应返回给前端。
    • 隐藏后端API的细节:反向代理可以隐藏后端API的实际细节,例如后端服务器的域名和端口。这有助于增加项目的安全性,并简化前端应用程序的配置。
    • 路由和负载均衡:反向代理可以实现路由和负载均衡,将前端请求路由到不同的后端服务器上,以提高性能和可伸缩性。
    • 安全性控制:反向代理可以实施额外的安全性控制,例如认证、授权和请求筛选,以确保请求是合法的并具备适当的权限。
      • 前端的主要任务是将必要的安全信息和标识(如认证令牌、用户权限、会话标识等)添加到请求中,并将这些请求发送到反向代理服务器。前端不需要实施安全性控制的具体逻辑,而是依赖于反向代理服务器来执行这些安全性控制
    • 缓存和优化:代理服务器可以缓存响应,以减少对后端服务器的重复请求,从而提高性能。
  • 缺点:
    • 虽然反向代理在前后端分离项目中有许多优点,但它也需要额外的服务器配置和维护。因此,在选择是否使用反向代理时,团队需要综合考虑项目的需求和复杂性

nginx反向代理

  • 可参考博客“博客项目登录(前端联调)”中的“nginx的反向代理配置”
  • 思路:利用nginx反向代理把跨域变为不跨域
  • 优点:支持各种请求方式
  • 缺点:需要在nginx进行额外配置,语义不清晰
  • 例子:前后端分离的项目,当有前端部署在自己的服务器上,后端也部署在自己的服务器上的情况就会造成前端无法访问后端接口的情况,这时就需要Nginx做一个代理的配置。当a域名访问b域名下某一个接口的时候,先让a域名访问a域名下的某一个url在通过Nginx转发到b域名下的某个接口,这样就让浏览器感觉好像都是在请求同一个服务器下的资源。

proxy反向代理

  • 可参考博客《axios使用和简单封装》
  • 例子:
    开发环境中,假设前端服务是http://localhost:8089/,后端接口是存在http://test01.7debao.com/服务上的
    那么前端打开页面时打开的就是http://localhost:8089/,前端页面中使用ajax请求获取数据时就会去获取http://localhost:8089/下的对应路径的数据
    而后端接口是存在http://test01.7debao.com/服务上的,所以数据其实保存在http://test01.7debao.com/下的对应路径中,这就导致无法获取数据
    此时使用proxy进行反向代理,结合axios配置给接口加前缀/api,遇到/api开头的url时就通过proxy分配到后端服务请求数据,遇到/开头的url时就分配到前端服务显示页面,这样就没问题了
  • 注意:生产和测试环境一般前后端代码部署在同一服务器同一域名下,接口请求不存在跨域问题,自然不需要设置分发代理

ajax相关面试题

手写简易ajax

手写简易ajax
运行结果

模拟404情况,换成不存在的地址:
模拟404情况
运行结果

跨域的实现方式

  1. jsonp原理
  2. cors(纯服务端)
  3. nginx反向代理

实际项目中 ajax 的常用插件

  • jquery:老旧,未使用Promise,容易回调地狱jquery
  • fetch:比XMLHttpRequest更加简洁,但兼容性不好
    • 简单的fetch请求
    • 使用fetch发送POST请求
    • 注意:从 fetch() 返回的 Promise 不会被标记为 reject, 即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
    • 不用了解太细,知道大概和去哪查就行
  • axios可参考《axios使用和简单封装》
,