這是之前面試的時(shí)候面試官提問的一道面試題。
具體題目是:為什么表單提交不會(huì)出現(xiàn)跨域,而使用 Ajax 發(fā)送 post 請(qǐng)求時(shí)卻會(huì)出現(xiàn)跨域的情況。
那什么情況下會(huì)出現(xiàn)跨域:
協(xié)議 + 域名 + 端口 三者只要有一個(gè)不一樣,就會(huì)出現(xiàn)跨域。
那為什么表單能夠跨域發(fā)送請(qǐng)求,而 Ajax 卻不能發(fā)送跨域請(qǐng)求
歸根結(jié)底:跨域是為了阻止用戶讀取到另一個(gè)域名下的內(nèi)容
而 Ajax 可以獲取響應(yīng),但瀏覽器認(rèn)為這不安全,所以攔截了響應(yīng)
但是表單并不會(huì)獲取新的內(nèi)容,所以可以發(fā)起跨域請(qǐng)求。
前者是發(fā)送跨域請(qǐng)求給到后端,并不去接收服務(wù)器返回的信息
后者是發(fā)送跨域請(qǐng)求給到后端,并接收服務(wù)器返回的信息
那該如何解決跨域
#方法一:使用 JSONP
原理是利用<script>
標(biāo)簽的跨域特性,可以不受限制地從其他域中加載資源
js
代碼解讀
復(fù)制代碼function jsonp(options) { var script = document.createElement('script'); // 參數(shù)處理 var params = ''; for (var attr in options.data) { params += '&' + attr + '=' + options.data[attr]; } // 設(shè)置回調(diào)函數(shù) var successCallback = `successCallback`; window[successCallback] = options.success; script.src = options.url + '?callback=' + successCallback + params; document.body.appendChild(script);}
代碼解釋:
請(qǐng)求成功后,前端得執(zhí)行回調(diào)函數(shù),但 script 腳本是執(zhí)行不到 success() 方法的。
這是因?yàn)?nbsp;success() 方法 并不是 全局函數(shù),所以需要將 success() 方法 改成全局函數(shù)
js
代碼解讀
復(fù)制代碼var successCallback = `successCallback`;window[successCallback] = options.success;
并在請(qǐng)求參數(shù)的基礎(chǔ)上,需要添加 callback
參數(shù),值對(duì)應(yīng)需要回調(diào)的函數(shù)名
js
代碼解讀
復(fù)制代碼script.src = options.url + '?callback=' + successCallback + params;
使用:
js
代碼解讀
復(fù)制代碼var btn = document.getElementById('btn');btn.addEventListener('click', function() { jsonp({ url: 'http://localhost:3001/getUserInfo', data: { name: '浩浩' }, success: function(data) { alert('UserInfo:' + JSON.stringify(data)); } })});
JSONP 優(yōu)缺點(diǎn)
簡(jiǎn)單兼容性好,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題
僅支持get方法具有局限性,不安全可能會(huì)遭受 XSS 攻擊
#方法二:CORS
CORS 是一個(gè) W3C 標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。 CORS 需要瀏覽器和服務(wù)器同時(shí)支持。但是目前基本上瀏覽器都支持,所以我們只要保證服務(wù)器端服務(wù)器實(shí)現(xiàn)了 CORS 接口,就可以跨源通信。
后端解決跨域問題,就是在服務(wù)器端給響應(yīng)添加頭信息
Name | Required | Comments |
---|---|---|
Access-Control-Allow-Origin | 必填 | 允許請(qǐng)求的域 |
Access-Control-Allow-Methods | 必填 | 允許請(qǐng)求的方法 |
Access-Control-Allow-Headers | 可選 | 預(yù)檢請(qǐng)求后,告知發(fā)送請(qǐng)求需要有的頭部 |
Access-Control-Allow-Credentials | 可選 | 表示是否允許發(fā)送cookie,默認(rèn)false; |
Access-Control-Max-Age | 可選 | 本次預(yù)檢的有效期,單位:秒; |
在 node 上處理
js
代碼解讀
復(fù)制代碼// 方式一const Koa = require('koa')const app = new Koa()app.all("*", (req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "content-type"); // 關(guān)鍵點(diǎn) next()})
js
代碼解讀
復(fù)制代碼// 方式二const Koa = require('koa')const app = new Koa()app.all("*", (req, res, next) => { // 設(shè)置域名跨域 res.header("Access-Control-Allow-Origin", "http://127.0.0.1"); // 跨域允許的請(qǐng)求方式 res.header("Access-Control-Allow-Headers", "DELETE,PUT,POST,GET,OPTIONS"); // 關(guān)鍵點(diǎn) next()})
在 Nginx 上處理
js
代碼解讀
復(fù)制代碼location /example { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods *; # add_header Access-Control-Allow-Methods GET,POST,OPTIONS;}