Web/18-前端面试/06-跨域通信类.md

509 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

> 本文最初发表于[博客园](https://www.cnblogs.com/smyhvae/p/8523576.html),并在[GitHub](https://github.com/smyhvae/Web)上持续更新**前端的系列文章**。欢迎在GitHub上关注我一起入门和进阶前端。
> 以下是正文。
## 前言
从本章起,对代码的要求没之前那么高了,但是,要求你对知识面的掌握要足够宽。
前端通信类的问题,主要包括以下内容:
- 1、什么是**同源策略**及限制
同源策略是一个概念,就一句话。有什么限制,就三句话。能说出来即可。
- 2、**前后端如何通信**
如果你不准备估计也就只能说出ajax。这个可以考察出知识面。
- 3、如何创建**Ajax**
Ajax在前后端通信中经常用到。做业务时可以借助第三方的库比如vue框架里的库、jQuery也有封装好的方法。但如果让你用原生的js去实现该怎么做
这就是考察你的动手能力,以及框架原理的掌握。如果能写出来,可以体现出你的基本功。是加分项。
- 4、**跨域通信**的几种方式
这部分非常重要。无非就是问你:什么是跨域、跨域有什么限制、**跨域有几种方式**。
下面分别讲解。
## 同源策略的概念和具体限制
**同源策略**限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。来自MDN官方的解释
具体解释:
1`源`包括三个部分协议、域名、端口http协议的默认端口是80。如果有任何一个部分不同则`源`不同,那就是跨域了。
2`限制`:这个源的文档没有权利去操作另一个源的文档。这个限制体现在:(要记住)
- Cookie、LocalStorage和IndexDB无法获取。
- 无法获取和操作DOM。
- 不能发送Ajax请求。我们要注意Ajax只适合**同源**的通信。
## 前后端如何通信
主要有以下几种方式:
- Ajax不支持跨域。
- WebSocket不受同源策略的限制支持跨域。
- CORS不受同源策略的限制支持跨域。一种新的通信协议标准。可以理解成是**同时支持同源和跨域的Ajax**。
## 如何创建Ajax
> 关于Ajax请求可以看本人的基础文章[Ajax入门和发送http请求](https://github.com/smyhvae/Web/blob/master/08-Ajax/02-Ajax%E5%85%A5%E9%97%A8%E5%92%8C%E5%8F%91%E9%80%81http%E8%AF%B7%E6%B1%82.md)
在回答 Ajax 的问题时,要回答以下几个方面:
- 1、XMLHttpRequest 的工作原理
- 2、兼容性处理
XMLHttpRequest只有在高级浏览器中才支持。在回答问题时这个兼容性问题不要忽略。
- 3、事件的触发条件
- 4、事件的触发顺序
XMLHttpRequest有很多触发事件每个事件是怎么触发的。
### 发送 Ajax 请求的五个步骤XMLHttpRequest的工作原理
1创建XMLHttpRequest 对象。
2使用open方法设置请求的参数。open(method, url, 是否异步)。
3发送请求。
4注册事件。 注册onreadystatechange事件状态改变时就会调用。
如果要在数据完整请求回来的时候才调用,我们需要手动写一些判断的逻辑。
5获取返回的数据更新UI。
### 发送 get 请求和 post 请求
get请求举例
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Ajax 发送 get 请求</h1>
<input type="button" value="发送get_ajax请求" id='btnAjax'>
<script type="text/javascript">
// 绑定点击事件
document.querySelector('#btnAjax').onclick = function () {
// 发送ajax 请求 需要 五步
// 1创建异步对象
var ajaxObj = new XMLHttpRequest();
// 2设置请求的参数。包括请求的方法、请求的url。
ajaxObj.open('get', '02-ajax.php');
// 3发送请求
ajaxObj.send();
//4注册事件。 onreadystatechange事件状态改变时就会调用。
//如果要在数据完整请求回来的时候才调用,我们需要手动写一些判断的逻辑。
ajaxObj.onreadystatechange = function () {
// 为了保证 数据 完整返回,我们一般会判断 两个值
if (ajaxObj.readyState == 4 && ajaxObj.status == 200) {
// 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
// 5.在注册的事件中 获取 返回的 内容 并修改页面的显示
console.log('数据返回成功');
// 数据是保存在 异步对象的 属性中
console.log(ajaxObj.responseText);
// 修改页面的显示
document.querySelector('h1').innerHTML = ajaxObj.responseText;
}
}
}
</script>
</body>
</html>
```
post 请求举例:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Ajax 发送 get 请求</h1>
<input type="button" value="发送put_ajax请求" id='btnAjax'>
<script type="text/javascript">
// 异步对象
var xhr = new XMLHttpRequest();
// 设置属性
xhr.open('post', '02.post.php');
// 如果想要使用post提交数据,必须添加此行
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 将数据通过send方法传递
xhr.send('name=fox&age=18');
// 发送并接受返回值
xhr.onreadystatechange = function () {
// 这步为判断服务器是否正确响应
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
</script>
</body>
</html>
```
### onreadystatechange 事件
注册 onreadystatechange 事件后,每当 readyState 属性改变时,就会调用 onreadystatechange 函数。
readyState存有 XMLHttpRequest 的状态。从 0 到 4 发生变化)
- 0: 请求未初始化
- 1: 服务器连接已建立
- 2: 请求已接收
- 3: 请求处理中
- 4: 请求已完成,且响应已就绪
### 事件的触发条件
![](http://img.smyhvae.com/20180307_1443.png)
### 事件的触发顺序
![](http://img.smyhvae.com/20180307_1445.png)
上图的参考链接:
- [你真的会使用XMLHttpRequest吗](https://segmentfault.com/a/1190000004322487)
### 实际开发中用的 原生Ajax请求
```javascript
var util = {};
//获取 ajax 请求之后的json
util.json = function (options) {
var opt = {
url: '',
type: 'get',
data: {},
success: function () {
},
error: function () {
},
};
util.extend(opt, options);
if (opt.url) {
//IE兼容性处理浏览器特征检查。检查该浏览器是否存在XMLHttpRequest这个api没有的话就用IE的api
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');
var data = opt.data,
url = opt.url,
type = opt.type.toUpperCase();
dataArr = [];
}
for (var key in data) {
dataArr.push(key + '=' + data[key]);
}
if (type === 'GET') {
url = url + '?' + dataArr.join('&');
xhr.open(type, url.replace(/\?$/g, ''), true);
xhr.send();
}
if (type === 'POST') {
xhr.open(type, url, true);
// 如果想要使用post提交数据,必须添加此行
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(dataArr.join('&'));
}
xhr.onload = function () {
if (xhr.status === 200 || xhr.status === 304) { //304表示用缓存即可。206表示获取媒体资源的前面一部分
var res;
if (opt.success && opt.success instanceof Function) {
res = xhr.responseText;
if (typeof res === 'string') {
res = JSON.parse(res); //将字符串转成json
opt.success.call(xhr, res);
}
}
} else {
if (opt.error && opt.error instanceof Function) {
opt.error.call(xhr, res);
}
}
};
}
```
Ajax 的推荐链接:<https://segmentfault.com/a/1190000006669043>
## 跨域通信的几种方式
方式如下:
- 1、JSONP
- 2、WebSocket
- 3、CORS
- 4、Hash
- 5、postMessage
上面这五种方式,在面试时,都要说出来。
### 1、JSONP
面试会问JSONP的原理是什么怎么实现的
在CORS和postMessage以前我们一直都是通过JSONP来做跨域通信的。
**JSONP的原理**:通过`<script>`标签的异步加载来实现的。比如说实际开发中我们发现head标签里可以通过`<script>`标签的src里面放url加载很多在线的插件。这就是用到了JSONP。
**JSONP的实现**
比如说,客户端这样写:
```html
<script src="http://www.smyhvae.com/?data=name&callback=myjsonp"></script>
```
上面的src中`data=name`是get请求的参数`myjsonp`是和后台约定好的函数名。
服务器端这样写:
```bash
myjsonp({
data: {}
})
```
于是本地要求创建一个myjsonp 的**全局函数**,才能将返回的数据执行出来。
**实际开发中前端的JSONP是这样实现的**
```html
<script>
var util = {};
//定义方法:动态创建 script 标签
/**
* [function 在页面中注入js脚本]
* @param {[type]} url [description]
* @param {[type]} charset [description]
* @return {[type]} [description]
*/
util.createScript = function (url, charset) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
charset && script.setAttribute('charset', charset);
script.setAttribute('src', url);
script.async = true;
return script;
};
/**
* [function 处理jsonp]
* @param {[type]} url [description]
* @param {[type]} onsucess [description]
* @param {[type]} onerror [description]
* @param {[type]} charset [description]
* @return {[type]} [description]
*/
util.jsonp = function (url, onsuccess, onerror, charset) {
var callbackName = util.getName('tt_player'); //事先约定好的 函数名
window[callbackName] = function () { //根据回调名称注册一个全局的函数
if (onsuccess && util.isFunction(onsuccess)) {
onsuccess(arguments[0]);
}
};
var script = util.createScript(url + '&callback=' + callbackName, charset); //动态创建一个script标签
script.onload = script.onreadystatechange = function () { //监听加载成功的事件,获取数据
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = script.onreadystatechange = null;
// 移除该script的 DOM 对象
if (script.parentNode) {
script.parentNode.removeChild(script);
}
// 删除函数或变量
window[callbackName] = null; //最后不要忘了删除
}
};
script.onerror = function () {
if (onerror && util.isFunction(onerror)) {
onerror();
}
};
document.getElementsByTagName('head')[0].appendChild(script); //往html中增加这个标签目的是把请求发送出去
};
</script>
```
### 2、WebSocket
WebSocket的用法如下
```javascript
//
var ws = new WebSocket('wss://echo.websocket.org'); //创建WebSocket的对象。参数可以是 ws 或 wss后者表示加密。
//把请求发出去
ws.onopen = function (evt) {
console.log('Connection open ...');
ws.send('Hello WebSockets!');
};
//对方发消息过来时,我接收
ws.onmessage = function (evt) {
console.log('Received Message: ', evt.data);
ws.close();
};
//关闭连接
ws.onclose = function (evt) {
console.log('Connection closed.');
};
```
Websocket的推荐链接<http://www.ruanyifeng.com/blog/2017/05/websocket.html>
面试一般不会让你写这个代码,一般是考察你是否了解 WebSocket概念知道有这么回事即可。
### 3、CORS
CORS 可以理解成是**既可以同步、也可以异步**的Ajax。
fetch 是一个比较新的API用来实现CORS通信。用法如下
```javascript
// url必选options可选
fetch('/some/url/', {
method: 'get',
}).then(function (response) { //类似于 ES6中的promise
}).catch(function (err) {
// 出错了,等价于 then 的第二个参数,但这样更好用更直观
});
```
- CORS的推荐链接<http://www.ruanyifeng.com/blog/2016/04/cors.html>
推荐链接里有详细的配置。
另外如果面试官问“CORS为什么支持跨域的通信
答案跨域时浏览器会拦截Ajax请求并在http头中加Origin。
### 4、Hash
url的`#`后面的内容就叫Hash。**Hash的改变页面不会刷新**。这就是用 Hash 做跨域通信的基本原理。
补充url的`?`后面的内容叫Search。Search的改变会导致页面刷新因此不能做跨域通信。
**使用举例:**
**场景**:我的页面 A 通过iframe或frame嵌入了跨域的页面 B。
现在我这个A页面想给B页面发消息怎么操作呢
1首先在我的A页面中
```javascript
//伪代码
var B = document.getElementsByTagName('iframe');
B.src = B.src + '#' + 'jsonString'; //我们可以把JS 对象,通过 JSON.stringify()方法转成 json字符串发给 B
```
2然后在B页面中
```javascript
// B中的伪代码
window.onhashchange = function () { //通过onhashchange方法监听url中的 hash 是否发生变化
var data = window.location.hash;
};
```
### 5、postMessage()方法
> H5中新增的postMessage()方法可以用来做跨域通信。既然是H5中新增的那就一定要提到。
**场景**:窗口 A (`http:A.com`)向跨域的窗口 B (`http:B.com`)发送信息。步骤如下。
1在A窗口中操作如下向B窗口发送数据
```javascript
// 窗口A(http:A.com)向跨域的窗口B(http:B.com)发送信息
Bwindow.postMessage('data', 'http://B.com'); //这里强调的是B窗口里的window对象
```
2在B窗口中操作如下
```javascript
// 在窗口B中监听 message 事件
Awindow.addEventListener('message', function (event) { //这里强调的是A窗口里的window对象
console.log(event.origin); //获取 url。这里指http://A.com
console.log(event.source); //获取A window对象
console.log(event.data); //获取传过来的数据
}, false);
```
## 我的公众号
想学习<font color=#0000ff>**代码之外的软技能**</font>?不妨关注我的微信公众号:**生命团队**id`vitateam`)。
扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:
![](http://img.smyhvae.com/2016040102.jpg)