通信类

1. 什么是同源策略及限制

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。

源:协议+域名+端口,例如:

1
2
3
协议:HTTP
域名:www.xinxiaoyang.com
端口:默认 80

不同源之间的访问限制:

  • 无法获取 Cookie、LocalStorage 和 IndexDB 无法读取
  • 无法获取 DOM 节点
  • 不能发送 Ajax 请求

2. 前后端如何通信

  • Ajax:
    受到同源策略的限制
  • WebSocket:
    不受同源策略的限制
  • CORS:
    既支持同源通信,也支持不同源通信

3. 如何创建 Ajax

考察点:

  • XMLHttpRequest 对象的工作流程
  • 兼容性处理(IE和其他浏览器)
  • 事件的触发条件
  • 事件的触发顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
util.json = function(options){
var opt = {
url: '',
type: 'get',
data: {},
success: function(){}.
error: function(){},
}
util.extend(opt, options);

if(opt.url){
// 对象声明,考虑浏览器兼容
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP')
var data = opt.data,
url = opt.url,
type = opt.type.toUpperCase(),
dataArr = [];
for (var k in data) {
dataArr.push(k + '=' + data[k)
}
// GET 请求
if(type === 'GET'){
// 步骤1: GET 请求需要 url 拼接
url = url + '?' + dataArr.join('&');
// 步骤2:open 确定发送方式
xhr.open(type, url.replace()/\?$/g, ''), true);
// 步骤3:send 发送请求
xhr.send();
}
// POST 请求
if(type === 'POST'){
xhr.open(type, url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(dataArr.join('&'));
}
// 步骤4: 响应请求
xhr.onload = function() {
// 判断状态码(如果是媒体资源,需要添加 206)
if(xhr.status === 200 || xhr.status === 304) {
var res;
// 转换为 JSON 格式
if(opt.success && opt.success instanceof Function){
res = xhr.responseText;
if(typeof res === 'string') {
res = JSON.parse(res);
opt.success.call(xhr, res);
}
}
} else {
if(opt.error && opt.error instanceof Function) {
opt.error.call(xhr, res);
}
}
}
}
}

4. 跨域通信的几种方式

  • JSONP
  • Hash
  • postMessage
  • WebSocket
  • CORS

4.1 JSONP

原理:利用 <script> 标签是可以异步加载的

举个栗子,我的个人主页地址是:www.xinxiaoyang.com,但是我在页面中引用了 GA 的统计代码,引入的 JavaScript 地址是:https://www.google-analytics.com/analytics.js。很明显,请求的 JS 文件是跨域的,但是是被允许的。

i. 请求的 <script> 标签中写明回调函数名(callback),例如:

1
<script src="http://www.abc.com/?callback=jsonp></script>

ii. 在服务器端的 JS 文件中定义一个以 callback 为名称的函数,请求的数据以参数形式保存。例如:

1
2
3
4
5
6
7
<script>
jsonp({
data: {
...
}
});
</script>

iii. 使用时,客户端本地需要定义一个相同 callback 名的函数,用来处理返回的数据。

以下代码实现了 jsonp 的处理过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 动态创建 script 标签
util.createScript = function(url, charset) {
var script = document.createElement('script');
script.settAttrbute('type', 'text/javascript');
charset && script.setAttrbute('charset', charset);
script.setAttrbute('src', url);
script.async = true;
return script;
}

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.onload = script.onreadystatechange = function(){
if(!script.readySate || /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.getElementByTagName('head')[0],appendChidd(script);
}

缺点:

  • JSONP 只支持 GET 请求

4.2 Hash

原理:
注:Hash(#) 不会重新刷新页面,而Search(?) 会重新刷新页面

举个例子,假设以下场景:当前页面 A 通过 iframe 或 frame 嵌入跨域的页面 B

1
2
3
4
5
6
7
8
// 在 A 中伪代码如下:
var B = document.getElementByTagName('iframe');
B.src = B.src + '#' + 'data';

// 在 B 中伪代码如下:
window.onhashchange = function() {
var data = window.location.hash;
}

4.3 postMessage

H5 新属性

1
2
3
4
5
6
7
8
9
// 窗口 A(http:A.com) 向跨域的窗口 B(http://B.com) 发送信息
window.postMessage('data', 'http://B.com'); // 该 window 为 B 窗口下的

// 在窗口 B 中监听
window.addEvenListener('message', function(evnet){
console.log(evene.origin); // http://A.com,表示只接收 A 域名下的信息
console.log(event.source); // Bwindow,A 窗口的一个引用
console.log(event.data) // data
}, false);

4.4 WebSocket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// wss(加密)/ws(非加密):服务器地址
var ws = new WebSocket('wss://echo.websocket.org');

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.')
}

4.5 CORS(Cross-Origin Resource Sharing)

原理:浏览器拦截 Ajax 请求,添加 <Access-Control-Allow-*> 请求头

新的通信标准,可以理解为支持跨域通信的 Ajax,需要浏览器和服务器同时支持。

这里引入了一个新的 API:fetch。原生写法下,Ajax 只能通过 XMLHttpRequest 对象来实现,在新标准中,可以通过 fetch 来实现。

同源情况下,fetch 就是 Ajax,不同源情况下,需要添加配置。
具体配置项参见:http://www.ruanyifeng.com/blog/2016/04/cors.html

1
2
3
4
5
6
7
8
// CORS: url(必须),options(可选)
fetch('/some/url', {
method: 'get',
}).then(function(response){

}).catch(function(err){
// 出错了,等价于 then 的第二个参数,但这样更好用更直观
});