前言
前端通信類的問題,主要包括以下內容:
- 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請求
在回答 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請求舉例:
<!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 請求舉例:
<!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: 請求已完成,且響應已就緒
事件的觸發條件
事件的觸發順序
上圖的參考鏈接:
- 你真的會使用XMLHttpRequest嗎?
實際開發中用的 原生Ajax請求
var util = {};//獲取 ajax 請求之后的jsonutil.json = function (options) {var opt = {url: '',type: 'get',data: {},success: function () {},error: function () {},};util.extend(opt, options);if (opt.url) {//IE兼容性處理:瀏覽器特征檢查。檢查該瀏覽器是否存在XMLHttpRequest這個api,沒有的話,就用IE的apivar 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); //將字符串轉成jsonopt.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的實現:
比如說,客戶端這樣寫:
<script src="http://www.smyhvae.com/?data=name&callback=myjsonp"></script>
上面的src中,data=name
是get請求的參數,myjsonp
是和后臺約定好的函數名。
服務器端這樣寫:
myjsonp({data: {}
})
于是,本地要求創建一個myjsonp 的全局函數,才能將返回的數據執行出來。
實際開發中,前端的JSONP是這樣實現的:
<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的用法如下:
//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
3、CORS
CORS 可以理解成是既可以同步、也可以異步*的Ajax。
fetch 是一個比較新的API,用來實現CORS通信。用法如下:
// 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頁面中:
//偽代碼var B = document.getElementsByTagName('iframe');B.src = B.src + '#' + 'jsonString'; //我們可以把JS 對象,通過 JSON.stringify()方法轉成 json字符串,發給 B
(2)然后,在B頁面中:
// 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窗口發送數據:
// 窗口A(http:A.com)向跨域的窗口B(http:B.com)發送信息Bwindow.postMessage('data', 'http://B.com'); //這里強調的是B窗口里的window對象
(2)在B窗口中操作如下:
// 在窗口B中監聽 message 事件Awindow.addEventListener('message', function (event) { //這里強調的是A窗口里的window對象console.log(event.origin); //獲取 :url。這里指:http://A.comconsole.log(event.source); //獲取:A window對象console.log(event.data); //獲取傳過來的數據}, false);
?