前言
大家好,我是林三心。 用最通俗易懂的話講最難的知識點 是我的座右銘, 基礎是進階的前提 是我的初衷。
咱們做前端的,平時跟後端對接介面那是必須的事情,但是可能很多同學忽略了一個對接過程中可能會發生的問題—— 跨域 ,那跨域到底是啥呢?為什麽會跨域呢?又怎麽才能解決呢?
為什麽跨域?
為什麽會出現跨域問題呢?那就不得不講瀏覽器的
同源策略
了,它規定了
協定號-網域名稱-埠號
這三者必須都
相同
才符合
同源策略
如有有一個
不相同
,就會出現跨域問題,不符合
同源策略
導致的後果有
1、
LocalStorge、SessionStorge、Cookie
等瀏覽器記憶體無法跨域存取
2、
DOM節點
無法跨域操作
3、
Ajax請求
無法跨域請求
註意點:一個IP是可以註冊多個不同網域名稱的,也就是多個網域名稱可能指向同一個IP,即使是這樣,他們也不符合
同源策略
跨域的時機?
跨域發生在什麽時候呢?我考過很多位同學,得到了兩種答案
1、請求一發出就被瀏覽器的跨域報錯攔下來了(大多數人回答)
2、請求發出去到後端,後端返回數據,在瀏覽器接收後端數據時被瀏覽器的跨域報錯攔下來
那到底是哪種呢?我們可以驗證下,咱們先
npm i nodemon -g
,然後建立一個
index.js
,然後
nodemon index
起一個node服務
// index.js http://127.0.0.1:8000
const http = require('http');
const port = 8000;
http.createServer(function (req, res) {
const { query } = urllib.parse(req.url, true);
console.log(query.name)
console.log('到後端嘍')
res.end(JSON.stringify('林三心'));
}).listen(port, function () {
console.log('server is listening on port ' + port);
})
再建立一個
index.html
,用來寫前端的請求程式碼,咱們就寫一個簡單的
AJAX請求
吧
// index.html http://127.0.0.1:5500/index.html
<script>
//步驟一:建立異步物件
var ajax = new XMLHttpRequest();
//步驟二:設定請求的url參數,參數一是請求的型別,參數二是請求的url,可以帶參數
ajax.open('get', 'http://127.0.0.1:8000?name=前端過來的林三心');
//步驟三:發送請求
ajax.send();
//步驟四:註冊事件 onreadystatechange 狀態改變就會呼叫
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && ajax.status == 200) {
//步驟五 如果能夠進到這個判斷 說明 數據 完美的回來了,並且請求的頁面是存在的
console.log(ajax.responseText);//輸入相應的內容
}
}
</script>
最終,前端確實是跨域報錯了。但這不是結果,我們要想知道是哪一個答案,關鍵在於看後端的node服務那裏有沒有輸出,就一目了然了。所以,答案2才是對的。
同域情況 && 跨域情況?
前面提到了
同源策略
,滿足
協定號-網域名稱-埠號
這三者
都相同
就是
同域
,反之就是
跨域
,會導致
跨域報錯
,下面透過幾個例子讓大家鞏固一下對
同域和跨域
的認識把!
解決跨域的方案
跨域其實是一個很久的問題了,對應的解決方案也有很多,一起接著往下讀吧!!!
JSONP
前面咱們說了,因為瀏覽器
同源策略
的存在,導致存在
跨域問題
,那有沒有不受
跨域問題
所束縛的東西呢?其實是有的,以下這三個標簽
載入資源路徑
是不受束縛的
1、script標簽:
<script src="載入資源路徑"></script>
2、link標簽:
<link herf="載入資源路徑"></link>
3、img標簽:
<img src="載入資源路徑"></img>
而JSONP就是利用了
script
的
src
載入不受束縛,從而可以擁有從
不同的域
拿到數據的能力。但是JSONP需要前端後端配合,才能實作最終的
跨域獲取數據
。
JSONP通俗點說就是:利用script的src去發送請求,將一個方法名
callback
傳給後端,後端拿到這個方法名,將所需數據,透過字串拼接成新的字串
callback(所需數據)
,並行送到前端,前端接收到這個字串之後,就會自動執行方法
callback(所需數據)
。老規矩,先上圖,再上程式碼。
後端程式碼
// index.js http://127.0.0.1:8000
const http = require('http');
const urllib = require('url');
const port = 8000;
http.createServer(function (req, res) {
const { query } = urllib.parse(req.url, true);
if (query && query.callback) {
const { name, age, callback } = query
const person = `${name}今年${age}歲啦!!!`
const str = `${callback}(${JSON.stringify(person)})`// 拼成callback(data)
res.end(str);
} else {
res.end(JSON.stringify('沒東西啊你'));
}
}).listen(port, function () {
console.log('server is listening on port ' + port);
})
前端程式碼
// index.html http://127.0.0.1:5500/index.html
const jsonp = (url, params, cbName) => {
returnnewPromise((resolve, reject) => {
const script = document.createElement('script')
window[cbName] = (data) => {
resolve(data)
document.body.removeChild(script)
}
params = { ...params, callback: cbName }
const arr = Object.keys(params).map(key =>`${key}=${params[key]}`)
script.src = `${url}?${arr.join('&')}`
document.body.appendChild(script)
})
}
jsonp('http://127.0.0.1:8000', { name: '林三心', age: 23 }, 'callback').then(data => {
console.log(data) // 林三心今年23歲啦!!!
})
JSONP的缺點就是,需要前後端配合,並且只支持
get請求方法
WebSocket
WebSocket是什麽東西?其實我也不怎麽懂,但是我也不會像別人一樣把MDN的資料直接復制過來,因為復制過來相信大家也是看不懂的。
我理解的WebSocket是一種協定(跟http同級,都是協定),並且他可以進行跨域通訊,為什麽他支持跨域通訊呢?我這裏找到一篇文章WebSocket憑啥可以跨域?,講的挺好
後端程式碼
先安裝
npm i ws
// index.js http://127.0.0.1:8000
const Websocket = require('ws');
const port = 8000;
const ws = new Websocket.Server({ port })
ws.on('connection', (obj) => {
obj.on('message', (data) => {
data = JSON.parse(data.toString())
const { name, age } = data
obj.send(`${name}今年${age}歲啦!!!`)
})
})
前端程式碼
// index.html http://127.0.0.1:5500/index.html
functionmyWebsocket(url, params) {
returnnewPromise((resolve, reject) => {
const socket = new WebSocket(url)
socket.onopen = () => {
socket.send(JSON.stringify(params))
}
socket.onmessage = (e) => {
resolve(e.data)
}
})
}
myWebsocket('ws://127.0.0.1:8000', { name: '林三心', age: 23 }).then(data => {
console.log(data) // 林三心今年23歲啦!!!
})
結果如下
Cors
Cors,全稱是
Cross-Origin Resource Sharing
,意思是
跨域資源共享
,Cors一般是由後端來開啟的,一旦開啟,前端就可以跨域存取後端。
為什麽後端開啟Cors,前端就能跨域請求後端呢?我的理解是:前端跨域存取到後端,後端開啟Cors,發送
Access-Control-Allow-Origin: 網域名稱
欄位到前端(其實不止一個),前端瀏覽器判斷
Access-Control-Allow-Origin
的網域名稱如果跟前端網域名稱一樣,瀏覽器就不會實行跨域攔截,從而解決跨域問題。
後端程式碼
// index.js http://127.0.0.1:8000
const http = require('http');
const urllib = require('url');
const port = 8000;
http.createServer(function (req, res) {
// 開啟Cors
res.writeHead(200, {
//設定允許跨域的網域名稱,也可設定*允許所有網域名稱
'Access-Control-Allow-Origin': 'http://127.0.0.1:5500',
//跨域允許的請求方法,也可設定*允許所有方法
"Access-Control-Allow-Methods": "DELETE,PUT,POST,GET,OPTIONS",
//允許的header型別
'Access-Control-Allow-Headers': 'Content-Type'
})
const { query: { name, age } } = urllib.parse(req.url, true);
res.end(`${name}今年${age}歲啦!!!`);
}).listen(port, function () {
console.log('server is listening on port ' + port);
})
前端程式碼
// index.html http://127.0.0.1:5500/index.html
//步驟一:建立異步物件
var ajax = new XMLHttpRequest();
//步驟二:設定請求的url參數,參數一是請求的型別,參數二是請求的url,可以帶參數
ajax.open('get', 'http://127.0.0.1:8000?name=林三心&age=23');
//步驟三:發送請求
ajax.send();
//步驟四:註冊事件 onreadystatechange 狀態改變就會呼叫
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && ajax.status == 200) {
//步驟五 如果能夠進到這個判斷 說明 數據 完美的回來了,並且請求的頁面是存在的
console.log(ajax.responseText);//輸入相應的內容
}
}
結果如下
Node介面代理
還是回到
同源策略
,同源策略它只是瀏覽器的一個策略而已,它是限制不到後端的,也就是
前端-後端
會被同源策略限制,但是
後端-後端
則不會被限制,所以可以透過Node介面代理,先存取已設定Cors的後端1,再讓後端1去存取後端2獲取數據到後端1,後端1再把數據傳到前端
後端2程式碼
// index.js http://127.0.0.1:8000
const http = require('http');
const urllib = require('url');
const port = 8000;
http.createServer(function (req, res) {
console.log(888)
const { query: { name, age } } = urllib.parse(req.url, true);
res.end(`${name}今年${age}歲啦!!!`)
}).listen(port, function () {
console.log('server is listening on port ' + port);
})
建立一個
index2.js
,並
nodemon index2.js
後端1程式碼
// index2.js http://127.0.0.1:8888
const http = require('http');
const urllib = require('url');
const querystring = require('querystring');
const port = 8888;
http.createServer(function (req, res) {
// 開啟Cors
res.writeHead(200, {
//設定允許跨域的網域名稱,也可設定*允許所有網域名稱
'Access-Control-Allow-Origin': 'http://127.0.0.1:5500',
//跨域允許的請求方法,也可設定*允許所有方法
"Access-Control-Allow-Methods": "DELETE,PUT,POST,GET,OPTIONS",
//允許的header型別
'Access-Control-Allow-Headers': 'Content-Type'
})
const { query } = urllib.parse(req.url, true);
const { methods = 'GET', headers } = req
const proxyReq = http.request({
host: '127.0.0.1',
port: '8000',
path: `/?${querystring.stringify(query)}`,
methods,
headers
}, proxyRes => {
proxyRes.on('data', chunk => {
console.log(chunk.toString())
res.end(chunk.toString())
})
}).end()
}).listen(port, function () {
console.log('server is listening on port ' + port);
})
前端程式碼
// index.html http://127.0.0.1:5500
//步驟一:建立異步物件
var ajax = new XMLHttpRequest();
//步驟二:設定請求的url參數,參數一是請求的型別,參數二是請求的url,可以帶參數,動態的傳遞參數starName到伺服端
ajax.open('get', 'http://127.0.0.1:8888?name=林三心&age=23');
//步驟三:發送請求
ajax.send();
//步驟四:註冊事件 onreadystatechange 狀態改變就會呼叫
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && ajax.status == 200) {
//步驟五 如果能夠進到這個判斷 說明 數據 完美的回來了,並且請求的頁面是存在的
console.log(ajax.responseText);//輸入相應的內容
}
}
結果如下
Nginx
其實
Nginx
跟
Node介面代理
是一個道理,只不過Nginx就不需要我們自己去搭建一個中間服務
先下載 nginx [1] ,然後將nginx目錄下的nginx.conf修改如下:
server{
listen 8888;
server_name 127.0.0.1;
location /{
proxy_pass 127.0.0.1:8000;
}
}
最後透過命令列
nginx -s reload
啟動nginx
後端程式碼
// index.js http://127.0.0.1:8000
const http = require('http');
const urllib = require('url');
const port = 8000;
http.createServer(function (req, res) {
const { query: { name, age } } = urllib.parse(req.url, true);
res.end(`${name}今年${age}歲啦!!!`);
}).listen(port, function () {
console.log('server is listening on port ' + port);
})
前端程式碼
// index.html http://127.0.0.1:5500
//步驟一:建立異步物件
var ajax = new XMLHttpRequest();
//步驟二:設定請求的url參數,參數一是請求的型別,參數二是請求的url,可以帶參數,動態的傳遞參數starName到伺服端
ajax.open('get', 'http://127.0.0.1:8888?name=林三心&age=23');
//步驟三:發送請求
ajax.send();
//步驟四:註冊事件 onreadystatechange 狀態改變就會呼叫
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && ajax.status == 200) {
//步驟五 如果能夠進到這個判斷 說明 數據 完美的回來了,並且請求的頁面是存在的
console.log(ajax.responseText);//輸入相應的內容
}
}
結果如下
postMessage
場景:
http://127.0.0.1:5500/index.html
頁面中使用了
iframe標簽
內嵌了一個
http://127.0.0.1:5555/index.html
的頁面
雖然這兩個頁面存在於一個頁面中,但是需要
iframe標簽
來巢狀才行,這兩個頁面之間是無法進行通訊的,因為他們
埠號
不同,根據
同源策略
,他們之間存在
跨域問題
那應該怎麽辦呢?使用
postMessage
可以使這兩個頁面進行通訊
// http:127.0.0.1:5500/index.html
<body>
<iframesrc="http://127.0.0.1:5555/index.html"id="frame"></iframe>
</body>
<script>
document.getElementById('frame').onload = function () {
this.contentWindow.postMessage({ name: '林三心', age: 23 }, 'http://127.0.0.1:5555')
window.onmessage = function (e) {
console.log(e.data) // 林三心今年23歲啦!!!
}
}
</script>
// http://127.0.0.1:5555/index.html
<script>
window.onmessage = function (e) {
const { data: { name, age }, origin } = e
e.source.postMessage(`${name}今年${age}歲啦!!!`, origin)
}
</script>
document.domain && iframe
場景:
a.sanxin.com/index.html
與
b.sanxin.com/index.html
之間的通訊
其實上面這兩個正常情況下是無法通訊的,因為他們的
網域名稱
不相同,屬於跨域通訊
那怎麽辦呢?其實他們有一個共同點,那就是他們的二級網域名稱都是
sanxin.com
,這使得他們可以透過
document.domain && iframe
的方式來通訊
由於本菜鳥暫時沒有伺服器,所以暫時使用本地來模擬
// http://127.0.0.1:5500/index.html
<body>
<iframesrc="http://127.0.0.1:5555/index.html"id="frame"></iframe>
</body>
<script>
document.domain = '127.0.0.1'
document.getElementById('frame').onload = function () {
console.log(this.contentWindow.data) // 林三心今年23歲啦!!!
}
</script>
// http://127.0.0.1:5555/index.html
<script>
// window.name="林三心今年23歲啦!!!"
document.domain = '127.0.0.1'
var data = '林三心今年23歲啦!!!';
</script>
結果如下
結語
如果你覺得此文對你有一丁點幫助,點個贊,鼓勵一下林三心哈哈。或者可以加入我的摸魚群,想進學習群,摸魚群,請加我的vx,我會定時直播模擬面試,答疑解惑