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