JavaScript解決跨域的三種方法小結(jié)
前言
什么是跨域問(wèn)題
在Web應(yīng)用中,當(dāng)一個(gè)網(wǎng)頁(yè)的腳本試圖去請(qǐng)求另一個(gè)域名下的資源時(shí),就會(huì)遇到跨域問(wèn)題??缬騿?wèn)題是由瀏覽器的同源策略所引起的。換句話(huà)說(shuō):后端返回給瀏覽器的數(shù)據(jù)會(huì)被瀏覽器的同源策略給攔截下來(lái)。
同源策略要求資源的協(xié)議、域名和端口號(hào)都必須相同,才能確保數(shù)據(jù)的安全性。如果不滿(mǎn)足這個(gè)條件,請(qǐng)求將被瀏覽器拒絕,從而導(dǎo)致跨域問(wèn)題的出現(xiàn)。
- 同源策略: 協(xié)議號(hào)-域名-端口號(hào) 都相同的地址,瀏覽器才認(rèn)為是同源
協(xié)議號(hào):域名:端口號(hào) / 路徑 https://192.168.31.45:8080/user https://192.168.31.45:8080/list
上面這個(gè)例子雖然它們的路徑不一樣但是協(xié)議號(hào)、域名、端口號(hào)都相同,所以它們就是同源的
跨域問(wèn)題的原因
跨域問(wèn)題主要是由于瀏覽器的安全策略限制引起的。同源策略的目的是保護(hù)用戶(hù)的隱私和數(shù)據(jù)安全,防止惡意網(wǎng)站獲取用戶(hù)的敏感信息或進(jìn)行未授權(quán)的操作。 通過(guò)限制跨域請(qǐng)求,瀏覽器有效地減少了許多網(wǎng)絡(luò)攻擊的風(fēng)險(xiǎn),例如跨站腳本攻擊(XSS)和跨站請(qǐng)求偽造(CSRF)。
JSONP
JSONP(JSON with Padding)是一種用于解決跨域請(qǐng)求的技術(shù),它利用了 <script> 標(biāo)簽可以跨域加載資源的特性。
下面是 JSONP 解決跨域請(qǐng)求的基本原理:
- 前端發(fā)起 JSONP 請(qǐng)求: 前端頁(yè)面通過(guò)動(dòng)態(tài)創(chuàng)建
<script>標(biāo)簽,設(shè)置其src屬性為包含回調(diào)函數(shù)的 URL。通常這個(gè) URL 是指向另一個(gè)域名下的服務(wù)器接口。 - 服務(wù)端返回?cái)?shù)據(jù): 服務(wù)端接收到 JSONP 請(qǐng)求后,會(huì)將數(shù)據(jù)包裝在回調(diào)函數(shù)中返回給前端。這樣前端頁(yè)面就可以獲得跨域請(qǐng)求返回的數(shù)據(jù)。
- 前端處理數(shù)據(jù): 前端頁(yè)面定義好與回調(diào)函數(shù)同名的 JavaScript 函數(shù),當(dāng)服務(wù)端返回?cái)?shù)據(jù)時(shí),會(huì)執(zhí)行這個(gè)函數(shù)并傳入返回的數(shù)據(jù)作為參數(shù),從而實(shí)現(xiàn)跨域數(shù)據(jù)的獲取和處理。
前端代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">獲取數(shù)據(jù)</button>
<script>
// 定義一個(gè)函數(shù) jsonp,用于發(fā)送 JSONP 請(qǐng)求
function jsonp(url, cb) {
return new Promise((resolve, reject) => {
// 創(chuàng)建一個(gè) script 標(biāo)簽
const script = document.createElement('script');
// 設(shè)置 script 的 src 屬性,包含了請(qǐng)求的 URL 和回調(diào)函數(shù)名稱(chēng)
script.src = `${url}?cb=${cb}`; // http://localhost:3000?cb='callback'
// 將 script 添加到文檔中
document.body.appendChild(script); // 瀏覽器自動(dòng)請(qǐng)求 src 中的內(nèi)容
// 定義一個(gè)全局函數(shù),用于處理返回的數(shù)據(jù)
window[cb] = (data) => {
resolve(data)
}
})
}
// 獲取按鈕元素
let btn = document.getElementById('btn');
// 綁定點(diǎn)擊事件
btn.addEventListener('click', () => {
// 發(fā)送 JSONP 請(qǐng)求
jsonp('http://localhost:3000', 'callback')
.then(res => {
console.log('后端的返回結(jié)果:' + res);
})
})
</script>
</body>
</html>
后端代碼:
const Koa = require('koa');
const app = new Koa();
// 定義中間件函數(shù) main,處理請(qǐng)求并返回?cái)?shù)據(jù)
const main = (ctx, next) => {
console.log(ctx.query); // 輸出請(qǐng)求參數(shù)對(duì)象 { cb: 'callback' }
// 從請(qǐng)求參數(shù)中獲取回調(diào)函數(shù)名稱(chēng)
const cb = ctx.query.cb;
// 準(zhǔn)備要返回給前端的數(shù)據(jù)
const data = '給前端的數(shù)據(jù)';
// 構(gòu)造帶有回調(diào)函數(shù)名稱(chēng)的字符串,格式為 'callback("給前端的數(shù)據(jù)")'
const str = `${cb}('${data}')`;
// 將構(gòu)造好的字符串作為響應(yīng)體返回給前端
ctx.body = str;
}
// 將 main 中間件注冊(cè)到 Koa 應(yīng)用中
app.use(main);
// 監(jiān)聽(tīng) 3000 端口,啟動(dòng)服務(wù)器
app.listen(3000, () => {
console.log('listening on port 3000');
})
優(yōu)點(diǎn):
- 簡(jiǎn)單易用: JSONP 實(shí)現(xiàn)簡(jiǎn)單,只需在前端添加一個(gè)
<script>標(biāo)簽即可完成跨域請(qǐng)求,無(wú)需復(fù)雜的配置。 - 兼容性好: JSONP 能夠兼容各種瀏覽器,包括早期版本的瀏覽器,因?yàn)樗峭ㄟ^(guò)動(dòng)態(tài)創(chuàng)建
<script>標(biāo)簽實(shí)現(xiàn)的。 - 支持跨域請(qǐng)求: JSONP 可以在不同域之間進(jìn)行數(shù)據(jù)通信,解決了傳統(tǒng) AJAX 請(qǐng)求受同源策略限制的問(wèn)題。
缺點(diǎn):
- 安全性問(wèn)題: JSONP 存在安全風(fēng)險(xiǎn),因?yàn)樗峭ㄟ^(guò)在前端動(dòng)態(tài)加載腳本來(lái)獲取數(shù)據(jù),可能會(huì)被用于注入惡意腳本,導(dǎo)致安全漏洞。
- 只支持 GET 請(qǐng)求: JSONP 只能發(fā)起 GET 請(qǐng)求,無(wú)法支持其他類(lèi)型的 HTTP 請(qǐng)求,如 POST、PUT 等。
- 依賴(lài)服務(wù)端支持: JSONP 需要服務(wù)端返回?cái)?shù)據(jù)時(shí)將其包裹在一個(gè)回調(diào)函數(shù)中,因此需要服務(wù)端提供支持,如果服務(wù)端不支持 JSONP 格式返回?cái)?shù)據(jù),則無(wú)法使用該方法。
CORS(跨域資源共享)
CORS是一種機(jī)制,允許服務(wù)器在響應(yīng)中攜帶一個(gè)特殊的標(biāo)頭,以告知瀏覽器該服務(wù)器允許哪些源的網(wǎng)頁(yè)訪問(wèn)其資源。 可以總結(jié)為一句話(huà):后端通過(guò)設(shè)置響應(yīng)頭來(lái)告訴瀏覽器不要拒絕接受后端的響應(yīng)。
前端代碼:在用戶(hù)點(diǎn)擊按鈕時(shí),通過(guò)發(fā)送跨域請(qǐng)求獲取服務(wù)器返回的數(shù)據(jù),并將數(shù)據(jù)打印到瀏覽器的控制臺(tái)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">獲取數(shù)據(jù)</button>
<script>
let btn = document.getElementById('btn');
btn.addEventListener('click', () => {
fetch('http://localhost:3000') // 發(fā)送跨域請(qǐng)求到服務(wù)器
.then(res => res.json())
.then((res) => {
console.log(res); // 打印返回的數(shù)據(jù)到控制臺(tái)
})
})
</script>
</body>
</html>
代碼實(shí)現(xiàn)了以下功能:
- 在 HTML 中定義了一個(gè)按鈕元素,id 為 "btn",用于觸發(fā)發(fā)送數(shù)據(jù)請(qǐng)求。
- 在 JavaScript 部分,使用
fetch函數(shù)發(fā)送 GET 請(qǐng)求到指定的服務(wù)器地址 "http://localhost:3000"。 - 在成功接收到服務(wù)器響應(yīng)后,使用
.then方法將響應(yīng)解析為 JSON 格式。 - 在第二個(gè)
.then方法中,處理解析后的數(shù)據(jù),并將其輸出到瀏覽器的控制臺(tái)。
后端代碼 :Node.js 服務(wù)器代碼,創(chuàng)建了一個(gè) HTTP 服務(wù)器,監(jiān)聽(tīng)在端口 3000。當(dāng)接收到請(qǐng)求時(shí),返回一個(gè)包含 "hello cors" 消息的 JSON 數(shù)據(jù)。
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {
// cros實(shí)現(xiàn)原理
'Access-Control-Allow-Origin': 'http://127.0.0.1:5500' // 允許來(lái)自指定地址的跨域請(qǐng)求
})
let data = {
msg: "hello cors"
}
res.end(JSON.stringify(data)) // 返回?cái)?shù)據(jù)給前端頁(yè)面
})
server.listen(3000, () => {
console.log('listening on port 3000');
})
后端代碼第6行在服務(wù)端設(shè)置響應(yīng)頭來(lái)控制跨域訪問(wèn)。
常見(jiàn)的響應(yīng)頭包括:
Access-Control-Allow-Origin:指定允許訪問(wèn)的域名。例如,
Access-Control-Allow-Origin: *表示允許所有域名訪問(wèn),而
Access-Control-Allow-Origin:'http://127.0.0.1:5500'表示只允許特定域名訪問(wèn)。Access-Control-Allow-Methods:指定允許的HTTP方法。Access-Control-Allow-Headers:指定允許的自定義請(qǐng)求頭。
CORS支持各種類(lèi)型的HTTP請(qǐng)求,包括GET、POST等。
domain
我們還可以使用 Domain 方法來(lái)解決一些特定情況下的跨域訪問(wèn)問(wèn)題。 在跨域通信時(shí),還需要注意以下幾點(diǎn):
頁(yè)面的域名必須滿(mǎn)足 Domain 方法的限制,即二級(jí)域名相同(如 example.com)。
父級(jí)頁(yè)面需要在設(shè)置 document.domain 之前定義需要共享的變量或?qū)ο蟆?/p>
子級(jí)頁(yè)面可以通過(guò) window.parent 來(lái)訪問(wèn)父級(jí)頁(yè)面的屬性或變量,但需要確保父級(jí)頁(yè)面已經(jīng)加載完成并且兩者的域名設(shè)置已生效。
下面舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明 Domain 方法的用法:
假設(shè)有兩個(gè)頁(yè)面分別位于不同子域名下,一個(gè)是 parent.example.com,另一個(gè)是 child.example.com。我們希望這兩個(gè)頁(yè)面能夠進(jìn)行跨域通信。
在父級(jí)頁(yè)面 parent.example.com/index.html 中的代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Parent Page</title>
</head>
<body>
<h1>Parent Page</h1>
<iframe src="http://child.example.com/child.html"></iframe>
<script>
document.domain = 'example.com';
var messageFromParent = 'Hello from parent page!';
</script>
</body>
</html>
在子級(jí)頁(yè)面 child.example.com/child.html 中的代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Child Page</title>
</head>
<body>
<h1>Child Page</h1>
<script>
document.domain = 'example.com';
var messageFromChild = 'Hello from child page!';
// 訪問(wèn)父級(jí)頁(yè)面定義的變量
var message = window.parent.messageFromParent;
console.log('Message from parent: ' + message);
</script>
</body>
</html>
在這個(gè)例子中,父級(jí)頁(yè)面和子級(jí)頁(yè)面分別設(shè)置了相同的 document.domain 為 'example.com',以實(shí)現(xiàn)二級(jí)域名相同。
父級(jí)頁(yè)面定義了一個(gè)名為 messageFromParent 的變量,子級(jí)頁(yè)面在加載后通過(guò) window.parent 來(lái)訪問(wèn)父級(jí)頁(yè)面定義的變量,并打印出父級(jí)頁(yè)面的消息。
通過(guò)設(shè)置相同的 document.domain,父子頁(yè)面之間就可以進(jìn)行跨域通信,實(shí)現(xiàn)數(shù)據(jù)共享和交互。
postMessage
使用 postMessage() 方法結(jié)合 <iframe> 元素可以實(shí)現(xiàn)跨域通信,這是一種常見(jiàn)的技術(shù)。通過(guò)在父窗口和嵌套的 <iframe> 之間使用 postMessage() 方法,可以安全地在不同源之間進(jìn)行通信。
<iframe> 可以用于解決跨域通信的問(wèn)題,其原理是利用瀏覽器中同源策略的限制,將不同域的內(nèi)容加載到獨(dú)立的 <iframe> 中,通過(guò) postMessage 方法進(jìn)行跨文檔通信。
在 a.html 文件中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>a.html</h2>
<!-- 創(chuàng)建一個(gè) iframe 元素來(lái)加載 b.html -->
<iframe src="http://127.0.0.1:5500/postMessage/b.html" frameborder="0" id="iframe"></iframe>
<script>
// 向 b.html 發(fā)送數(shù)據(jù)
let iframe = document.getElementById('iframe');
iframe.onload = function() {
// 準(zhǔn)備要發(fā)送的數(shù)據(jù)
let data = {
name: 'Tom'
};
// 通過(guò) postMessage 方法向 iframe 發(fā)送數(shù)據(jù)
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://127.0.0.1:5500');
}
// 監(jiān)聽(tīng)來(lái)自 b.html 的消息
window.addEventListener('message', function(e) {
console.log(e.data);
});
</script>
</body>
</html>
在 b.html 文件中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h4>b.html</h4>
<script>
// 監(jiān)聽(tīng)來(lái)自父頁(yè)面的消息
window.addEventListener('message', function(e) {
console.log(JSON.parse(e.data));
if (e.data) {
// 收到消息后延遲 2 秒發(fā)送回應(yīng)給父頁(yè)面
setTimeout(function() {
window.parent.postMessage('我接受到', 'http://127.0.0.1:5500');
}, 2000);
}
});
</script>
</body>
</html>
優(yōu)點(diǎn):
- 安全性: 使用
<iframe>結(jié)合 postMessage 進(jìn)行跨域通信是一種相對(duì)安全的方法,能夠避免常見(jiàn)的跨站腳本攻擊(XSS)。 - 靈活性: 可以在不同域之間傳遞數(shù)據(jù),實(shí)現(xiàn)更豐富的交互體驗(yàn),如單點(diǎn)登錄、共享數(shù)據(jù)等。
- 適用性廣泛: 跨域通信是 Web 開(kāi)發(fā)中常見(jiàn)需求,而
<iframe>結(jié)合 postMessage 是一種通用且有效的解決方案。
缺點(diǎn):
- 復(fù)雜性: 跨域通信涉及到多個(gè)文檔之間的交互,需要額外的處理和編碼,增加了開(kāi)發(fā)的復(fù)雜度。
- 性能開(kāi)銷(xiāo): 使用
<iframe>進(jìn)行跨域通信可能會(huì)引入額外的網(wǎng)絡(luò)請(qǐng)求和資源加載,對(duì)頁(yè)面加載性能有一定影響。 - 兼容性: 舊版本的瀏覽器可能對(duì) postMessage 支持不完整,需要做兼容性處理。
以上就是JavaScript解決跨域的三種方法的詳細(xì)內(nèi)容,更多關(guān)于JavaScript解決跨域的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實(shí)現(xiàn)隨機(jī)點(diǎn)名小程序
這篇文章主要介紹了JavaScript實(shí)現(xiàn)隨機(jī)點(diǎn)名小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
躲避這些會(huì)改變?cè)瓟?shù)組JavaScript數(shù)組方法讓開(kāi)發(fā)流暢無(wú)阻
JavaScript中有些數(shù)組的操作方法并不符合我們預(yù)期,容易導(dǎo)致想象不到的結(jié)果,因此,為避免這種情況的發(fā)生,本文將介紹哪些原生數(shù)組方法能改變?cè)瓟?shù)組以及我對(duì)于如何更好地使用數(shù)組方法的建議2023-05-05
JavaScript庫(kù) 開(kāi)發(fā)規(guī)則
1. 保持無(wú)侵入性,標(biāo)記不想知道你的JavaScript代碼;2. 嚴(yán)禁修改和擴(kuò)展Object.prototype!;3. 對(duì)JavaScript內(nèi)建對(duì)象的擴(kuò)展越少越好;4. 跟隨標(biāo)準(zhǔn);5. 或著跟隨主導(dǎo) ;6. 保持靈活;7. 管理內(nèi)存;8. 淘汰瀏覽器嗅探;9. 小巧更佳……2009-01-01
22種JavaScript中數(shù)組常用API總結(jié)
在前端開(kāi)發(fā)中,數(shù)組是一種常見(jiàn)且重要的數(shù)據(jù)結(jié)構(gòu),本文主要介紹了前端中數(shù)組常用的API,包括添加、刪除、截取、合并、轉(zhuǎn)換等操作,希望對(duì)大家有所幫助2023-05-05
小程序異步問(wèn)題之多個(gè)網(wǎng)絡(luò)請(qǐng)求依次執(zhí)行并依次收集請(qǐng)求結(jié)果
這篇文章主要介紹了小程序異步問(wèn)題之多個(gè)網(wǎng)絡(luò)請(qǐng)求依次執(zhí)行并依次收集請(qǐng)求結(jié)果,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05
JavaScript使用FileSystemObject對(duì)象寫(xiě)入文本文件內(nèi)容的方法
這篇文章主要介紹了JavaScript使用FileSystemObject對(duì)象寫(xiě)入文本文件內(nèi)容的方法,實(shí)例分析了javascript使用ActiveXObject的技巧與常見(jiàn)問(wèn)題的解決方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08
簡(jiǎn)單談?wù)刯avascript Date類(lèi)型
Date對(duì)象,是操作日期和時(shí)間的對(duì)象。Date對(duì)象對(duì)日期和時(shí)間的操作只能通過(guò)方法。本文就給大家簡(jiǎn)單講述一下DATE類(lèi)型的使用。2015-09-09
JS簡(jiǎn)單判斷是否在微信瀏覽器打開(kāi)的方法示例
這篇文章主要介紹了JS簡(jiǎn)單判斷是否在微信瀏覽器打開(kāi)的方法,結(jié)合實(shí)例形式分析了javascript針對(duì)瀏覽器相關(guān)信息的獲取與判定操作技巧,需要的朋友可以參考下2019-01-01

