Skip to content

引言

ajax 对于前端来说是一个特别基础也特别实用的一个功能,基本上我们目前访问的很多网页都有用到 ajax 的功能。接下来开始讲解关于 AJAX 请求,以及关于跨域的一些内容。

正文

一、AJAX 请求

Ajax ,即 Asynchronous Javascript And XML(异步 JavaScript 和 XML)。

在实现 Ajax 之前,我们先来了解下 XMLHttpRequestXMLHttpRequest 是网页实现 AJAX 最主要的一个 API 。可能有很多同学知道 AJAX ,也用过 AJAX ,但是却不知道它是基于 XMLHttpRequest 来实现的。

那么接下来我们用 XMLHttpRequest 这个 API 来模拟一个 getpost 请求。

1、模拟 get 和 post 请求

(1)模拟 get 请求:

js
/**
 * 使用xhr模拟实现GET请求
 */
const xhr = new XMLHttpRequest();
xhr.open('GET', '/test.json', true); //false表示同步请求,true表示异步请求
xhr.onreadystatechange = function () {
  // 这里的函数异步执行
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      // console.log(
      //     JSON.parse(xhr.responseText)
      // );
      alert(xhr.responseText);
    }
  }
};
// 因为是get请求,所以只要发送null就好
xhr.send(null);
/**
 * 使用xhr模拟实现GET请求
 */
const xhr = new XMLHttpRequest();
xhr.open('GET', '/test.json', true); //false表示同步请求,true表示异步请求
xhr.onreadystatechange = function () {
  // 这里的函数异步执行
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      // console.log(
      //     JSON.parse(xhr.responseText)
      // );
      alert(xhr.responseText);
    }
  }
};
// 因为是get请求,所以只要发送null就好
xhr.send(null);

(2)模拟 post 请求:

js
/**
 * 使用xhr模拟实现post请求
 */
const xhr = new XMLHttpRequest();
//模拟请求一个登录接口
xhr.open('POST', '/login', true); //false表示同步请求,true表示异步请求
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(JSON.parse(xhr.responseText));
      alert(xhr.responseText);
    } else {
      console.log('其他情况');
    }
  }
};

const postData = {
  userName: 'zhangsan',
  password: 'xxx',
};

xhr.send(JSON.stringify(postData));
/**
 * 使用xhr模拟实现post请求
 */
const xhr = new XMLHttpRequest();
//模拟请求一个登录接口
xhr.open('POST', '/login', true); //false表示同步请求,true表示异步请求
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(JSON.parse(xhr.responseText));
      alert(xhr.responseText);
    } else {
      console.log('其他情况');
    }
  }
};

const postData = {
  userName: 'zhangsan',
  password: 'xxx',
};

xhr.send(JSON.stringify(postData));

2、封装一个简易的 AJAX

js
function ajax(url) {
  const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onreadystatechange = function () {
      //状态码的解析详细看第二点
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.responseText));
        } else if (xhr.status === 404) {
          reject(new Error('404 not found'));
        }
      }
    };
    xhr.send(null);
  });
  return p;
}

const url = '你的json数据路径';
ajax(url)
  .then((res) => console.log(res))
  .catch((err) => console.error(err));
function ajax(url) {
  const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onreadystatechange = function () {
      //状态码的解析详细看第二点
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.responseText));
        } else if (xhr.status === 404) {
          reject(new Error('404 not found'));
        }
      }
    };
    xhr.send(null);
  });
  return p;
}

const url = '你的json数据路径';
ajax(url)
  .then((res) => console.log(res))
  .catch((err) => console.error(err));

二、状态码

看完上面的模拟过程之后,我们来讲解其中的几个知识点。

1、xhr.readyState

readyState 状态值readyState 含义
0(未初始化)- 还没有调用 send()方法
1(载入)- 已调用 send()方法,正坐在发送请求
2(载入完成)- send()方法执行完成,已经接收到全部响应内容
3(交互)- 正在解析响应内容
4(完成)响应内容解析完成,可以在客户端调用

2、xhr.status

status 状态值status 含义
2xx表示成功处理请求,如 200
3xx需要重定向,浏览器直接跳转,如 301 302 304
4xx客户端请求错误,如 404 403
5xx服务器端错误

三、跨域

1、同源策略

(1)同源策略是什么

同源策略是浏览器自带的一种安全策略,它是指网址中的协议域名端口三个都相同时才能互相访问,即若协议、域名、端口有一个不相同时,浏览器禁止页面加载或执行与自身不同域的脚本。

(2)为什么浏览器会有同源策略?

因为如果没有同源策略,别人就可以轻松的获取我们网站的 cookie 信息,或是对网页进行 DOM 操作;

这是一件非常恐怖的事情,尤其是 cookie 信息,它里面存在着 sessionID ,这是与服务端的 session 会话的重要凭证,如果被别人得到了 cookie ,有很大可能会造成数据被盗取等后果。

(3)同源策略限制内容有哪些?

  • 存储在浏览器中的数据,如 localStroageCookieIndexedDB 不能通过脚本跨域访问;
  • 不能通过脚本操作不同域下的 DOM
  • 不能通过 ajax 请求不同域的数据。

(4)加载图片、js 和 css 时可以无视同源策略

html
<img src="跨域的图片地址" />
<link href="跨域的css地址" />
<script src="跨域的js地址"></script>
<img src="跨域的图片地址" />
<link href="跨域的css地址" />
<script src="跨域的js地址"></script>

如以上代码所示,当我们在加载以上类型的 图片、css和js 时,可以无视同源策略。因为像 图片、css文件和js文件 一般可使用 cdn 来进行缓存,而 cdn 一般是外域。同时, js 文件也可以通过 JSONP 来实现跨域。

2、跨域解决方案

(1)跨域是什么

  • 所有的跨域,都必须经过 server 端允许和配合;
  • 未经 server 端允许就实现跨域,说明浏览器有漏洞,是一种危险信号。

(2)解决跨域的方式

1)JSONP(客户端操作)

① JSONP 的原理

JSONP(JSON with Padding)是数据格式 JSON 的一种“使用模式”,可以让网页从别的网域要数据。

根据 XmlHttpRequest 对象受到同源策略的影响,而利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 数据,而这种使用模式就是所谓的 JSONP

JSONP 抓到的数据并不是 JSON ,而是任意的 JavaScript ,用 JavaScript 解释器运行而不是用 JSON 解析器解析。

所以,通过 Chrome 查看所有 JSONP 发送的 Get 请求都是 js 类型,而非 XHR

② JSONP 包含两部分:回调函数和数据

回调函数是当响应到来时要放在当前页面被调用的函数。

数据就是传入回调函数中的 json 数据,也就是回调函数的参数了。

javascript
function handleResponse(response) {
  console.log('The responsed data is: ' + response.data);
}
var script = document.createElement('script');
script.src = 'http://www.baidu.com/json/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
/*handleResonse({"data": "zhe"})*/
//原理如下:
//当我们通过script标签请求时
//后台就会根据相应的参数(json,handleResponse)
//来生成相应的json数据(handleResponse({"data": "zhe"}))
//最后这个返回的json数据(代码)就会被放在当前js文件中被执行
//至此跨域通信完成
function handleResponse(response) {
  console.log('The responsed data is: ' + response.data);
}
var script = document.createElement('script');
script.src = 'http://www.baidu.com/json/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
/*handleResonse({"data": "zhe"})*/
//原理如下:
//当我们通过script标签请求时
//后台就会根据相应的参数(json,handleResponse)
//来生成相应的json数据(handleResponse({"data": "zhe"}))
//最后这个返回的json数据(代码)就会被放在当前js文件中被执行
//至此跨域通信完成

③ 缺点:

  • 只能使用Get 请求
  • 不能注册successerror等事件监听函数,不能很容易的确定 JSONP 请求是否失败。
  • JSONP 是从其他域中加载代码执行,容易受到跨站请求伪造的攻击,其安全性无法确保。

2)CORS(服务器操作)

① cors 的原理

CORS (Cross-Origin Resource Sharing),即跨域资源共享,是一种浏览器技术的规范,提供了 Web 服务从不同域传来沙盒脚本的方法,以避开浏览器的同源策略,确保安全的跨域数据传输。现代浏览器使用 CORSAPI 容器如 XMLHttpRequest 来减少 HTTP 请求的风险来源。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。

cors 的跨域方法一般是服务端进行操作,服务端需要设置以下 http header

js
//设置允许跨域的域名称,不建议直接写“*”
response.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');

//填写允许跨域的http请求方法
//当 method = OPTIONS 时, 属于预检(复杂请求), 当为预检时, 可以直接返回空响应体, 对应的 http 状态码为 204
response.setHeader(
  'Access-Control-Allow-Methods',
  'PUT,POST,GET,DELETE,OPTIONS'
);

//设置需要支持的跨域请求头,如果设置为*,表明服务器支持所有头信息字段;也可设置为X-Request-With和Content-Type
response.setHeader(
  'Access-Control-Allow-Headers',
  'X-Request-With, Content-Type'
);
// 服务器收到请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

//表示具体请求中的媒体类型信息
response.setHeader('Content-Type', 'application/json;charset=utf-8');

//设置预检结果的缓存, 单位(秒)
response.setHeader('Access-Control-Max-Age', 86400);

/*如果需要支持 cookies,
 *Access-Control-Allow-Origin 不能设置为 *,
 *并且 Access-Control-Allow-Credentials 需要设置为 true
 *(注意前端请求需要设置 withCredentials = true)
 */
response.setHeader('Access-Control-Allow-Credentials', 'false');
//设置允许跨域的域名称,不建议直接写“*”
response.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');

//填写允许跨域的http请求方法
//当 method = OPTIONS 时, 属于预检(复杂请求), 当为预检时, 可以直接返回空响应体, 对应的 http 状态码为 204
response.setHeader(
  'Access-Control-Allow-Methods',
  'PUT,POST,GET,DELETE,OPTIONS'
);

//设置需要支持的跨域请求头,如果设置为*,表明服务器支持所有头信息字段;也可设置为X-Request-With和Content-Type
response.setHeader(
  'Access-Control-Allow-Headers',
  'X-Request-With, Content-Type'
);
// 服务器收到请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

//表示具体请求中的媒体类型信息
response.setHeader('Content-Type', 'application/json;charset=utf-8');

//设置预检结果的缓存, 单位(秒)
response.setHeader('Access-Control-Max-Age', 86400);

/*如果需要支持 cookies,
 *Access-Control-Allow-Origin 不能设置为 *,
 *并且 Access-Control-Allow-Credentials 需要设置为 true
 *(注意前端请求需要设置 withCredentials = true)
 */
response.setHeader('Access-Control-Allow-Credentials', 'false');

结束语

以上文章浅谈了 ajax 以及常用的跨域方案,没有深究到很细节层面的内容。希望对大家有帮助!

关于 Ajax 以及跨域的一些信息就讲到这里啦!如有疑问欢迎提issue勘误~

Released under the MIT License.