1.跨域

实现方式:jsonp,CORS,websocket,postMessage

jsonp

一句话阐述下JSONP原理:动态生成一个JavaScript标签,其src由接口url、请求参数、callback函数名拼接而成,利用js标签没有跨域限制的特性实现跨域请求。

Tips:1.callback函数要绑定在window对象上 2.服务端返回数据有特定格式要求:callback函数名+'('+JSON.stringify(返回数据) +')' 3.不支持post,因为js标签本身就是一个get请求

const jsonp = function(url, data) {
  return new Promise((resolve, reject) => {
    let dataString = url.indexOf("?") === -1 ? "?" : "&";
    let callbackName = `jsonp${Date.now()}`;
    url += `${dataString}callback=${callbackName}`;
    if (data) {
      // 如果有请求参数依次添加到url
      for (let k in data) {
        url += `&${k}=${data[k]}`;
      }
    }
    let jsNode = document.createElement("script");
    jsNode.src = url;
    // 添加节点到document上,进行请求
    document.body.appendChild(jsNode);
    // 触发callback,然后删除js标签和window上的callback
    window[callbackName] = result => {
      delete window[callbackName];
      document.body.removeChild(jsNode);
      if (result) {
        resolve(result);
      } else {
        reject(new Error("error"));
      }
    };
    // js加载异常情况
    jsNode.addEventListener("error", () => {
      delete window[callbackName];
      document.body.removeChild(jsNode);
      reject("js标签加载失败");
    });
  });
};

jsonp("http://192.168.0.103:8081/jsonp", { a: 1, b: "ok" })
  .then(result => {
    console.log(result);
  })
  .catch(err => {
    console.error(err);
  });
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

CORS

浏览器把CORS分为两种,有简单请求和非简单请求:

1) 请求方法是以下三种方法之一:
HEAD
GET
POST2HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
1
2
3
4
5
6
7
8
9
10

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

开发

一般实际开发项目中,也是使用CORS实现代理过程,在开发环境使用webpack的Proxy属性实现代理,部署到线上时使用Ngnix实现反向代理:

正向代理是代理客户端,反向代理是代理服务器

正向代理服务器位于客户端和服务器之间,为了向服务器获取数据,客户端要向代理服务器发送一个请求,并指定目标服务器,代理服务器将目标服务器返回的数据转交给客户端。

反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。

CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。Ajax请求需要使用xhr.withCredentials = true;

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

使用WebSocket实现跨域

web sockets: 是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用) web sockets原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为websocket协议。

<script>
var socket = new WebSockt('ws://www.test.com');
//http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
    var data = event.data;
}
1
2
3
4
5
6
7

利用iframe实现跨域

  1. location.hash:假设 localhost:8080 下有文件 index.html 要和 localhost:8081 下的 data.html 传递消息,index.html 首先创建一个隐藏的 iframe,iframe 的 src 指向 localhost:8081/data.html,这时的 hash 值就可以做参数传递。data.html 收到消息后通过 parent.location.hash 值来修改 index.html 的 hash 值,从而达到数据传递。(Chrome 不允许修改 parent.location.hash 的值,所以要借助于 localhost:8080 域名下的一个代理 iframe 的 proxy.html 页面)

  2. window.name:window.name 属性的神奇之处在于 name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。localhost:8080index.html 在请求数据端 localhost:8081/data.html 时,我们可以在该页面新建一个 iframe,该 iframe 的 src 指向数据端地址(利用 iframe 标签的跨域能力),数据端文件设置好 window.name 的值。

    但是由于 index.html 页面与该页面 iframe 的 src 如果不同源的话,则无法操作 iframe 里的任何东西,所以就取不到 iframe 的 name 值,所以我们需要在 data.html 加载完后重新换个 src 去指向一个同源的 html 文件,或者设置成 'about:blank;' 都行,这时候我只要在 index.html 相同目录下新建一个 proxy.html 的空页面即可。

  3. postmessage:创建一个 iframe,使用 iframe 的一个方法 postMessage 可以向 http://localhost:8081/data.html 发送消息,然后监听 message,可以获得其文档发来的消息。

// postMessage/client/index.html 对应 localhost:8080/index.html
<iframe src="http://localhost:8081/data.html" style='display: none;'></iframe>
<script>
	window.onload = function() {
		let targetOrigin = 'http://localhost:8081';
		window.frames[0].postMessage('index.html 的 data!', targetOrigin);
	}
	window.addEventListener('message', function(e) {
		console.log('index.html 接收到的消息:', e.data);
	});
</script>
1
2
3
4
5
6
7
8
9
10
11

请求数据端逻辑:

// postMessage/server/data.html 对应 localhost:8081/data.html
<script>
	window.addEventListener('message', function(e) {
		if(e.source != window.parent) {
			return;
		}
		let data = e.data;
		console.log('data.html 接收到的消息:', data);
		parent.postMessage('data.html 的 data!', e.origin);
	});
</script>
1
2
3
4
5
6
7
8
9
10
11

服务器代理

你访问 http://127.0.0.1:3000/topics 的时候,服务器收到请求,会代你发送请求 https://cnodejs.org/api/v1/topics 最后将获取到的数据发送给浏览器。

const url = require('url');
const http = require('http');
const https = require('https');

const server = http.createServer((req, res) => {
	const path = url.parse(req.url).path.slice(1);
	if(path === 'topics') {
		https.get('https://cnodejs.org/api/v1/topics', (resp) => {
			let data = "";
			resp.on('data', chunk => {
				data += chunk;
			});
			resp.on('end', () => {
				res.writeHead(200, {
					'Content-Type': 'application/json; charset=utf-8'
				});
				res.end(data);
			});
		})		
	}
}).listen(3000, '127.0.0.1');
console.log('启动服务,监听 127.0.0.1:3000');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

2.原生Ajax和Fetch的区别

原生Ajax:

var xhr = new XMLHTTPRequest();
// method表示通过什么方式进行服务器访问,包括get和post;url表示访问服务器的地址;
// async表示是否异步,包括true和false(注意:true表示异步)
xhr.open(method, url, async);
// content表示向服务器发送的数据
xhr.send(content);

xhr.onreadystatechange = function() {
  if (xhr.readystate == 4) {
    if (xhr.status == 200) {
      console.log(xhr.responseText);
    }
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

img

img

3.GET/POST的区别

  • GET一般用于获取/查询信息,POST用于更新信息,本质都属于TCP链接

  • GET对数据长度有限制,当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的,这个限制来源于浏览器(URL 的最大长度是 2048 个字符)。POST无限制

  • GET后退按钮/刷新无害,POST数据会被重新提交

  • get请求过程:(2次交互)

    • 浏览器请求tcp连接(第一次握手)
    • 服务器答应进行tcp连接(第二次握手)
    • 浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
    • 服务器返回200 ok响应。

    post请求过程:(3次交互)

    • 浏览器请求tcp连接(第一次握手)
    • 服务器答应进行tcp连接(第二次握手)
    • 浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
    • 服务器返回100 continue响应
    • 浏览器开始发送数据
    • 服务器返回200 ok响应

4.浏览器输入URL到页面加载完毕发生了什么

  1. 输入网址发送到DNS服务器,获取域名对应的web服务器的IP地址
  2. 与服务器建立TCP连接,向服务器发送http请求
  3. 服务器返回指定url对应的数据(200),重定向地址(301,302),或者错误信息(404)
  4. 浏览器下载服务器返回的数据和html源文件
  5. 浏览器生成DOM树,解析CSS和JS,渲染页面