Fetch API(Fetch Application Programming Interface)是一個現代的、基于Promise的網絡請求接口,用于在瀏覽器環境中發起網絡請求并處理響應。它是對傳統XMLHttpRequest
的改進,提供了更簡潔、靈活和強大的功能,廣泛應用于前端開發中。
1. Fetch API 的基本概念
Fetch API 是一個用于從網絡獲取資源的接口,它提供了一個全局的fetch()
方法,該方法返回一個Promise
對象。通過fetch()
方法,開發者可以輕松地發送HTTP請求,獲取服務器的響應,并處理返回的數據。
Fetch API 的設計目標是提供一個更簡潔、更靈活的網絡請求方式,同時支持現代的異步編程模式(基于Promise)。它支持多種HTTP方法(如GET、POST、PUT、DELETE等),并且可以處理各種類型的響應數據(如JSON、文本、Blob等)。
2. Fetch API 是異步的
Fetch API 是異步的,主要原因如下:
-
網絡請求的特性:網絡請求需要時間來完成,瀏覽器需要等待服務器響應。如果網絡請求是同步的,那么在請求完成之前,瀏覽器的主線程會被阻塞,導致頁面無法響應用戶的其他操作,這會嚴重影響用戶體驗。
-
基于 Promise 的設計:Fetch API 返回一個
Promise
對象。Promise
是 JavaScript 中用于處理異步操作的機制,它允許在不阻塞主線程的情況下處理異步任務。
2.1.異步操作的典型代碼
fetch('https://api.example.com/data').then(response => {if (!response.ok) {throw new Error(`HTTP error! Status: ${response.status}`);}return response.json();}).then(data => {console.log(data); // 處理返回的數據}).catch(error => {console.error('Fetch error:', error);});
在這個例子中:
-
fetch()
發起一個網絡請求,并立即返回一個Promise
。 -
.then()
方法用于處理Promise
的成功結果。 -
.catch()
方法用于處理Promise
的失敗結果。 -
瀏覽器不會等待
fetch()
完成,而是繼續執行后續代碼。當網絡請求完成時,Promise
會觸發.then()
或.catch()
中的回調函數。
2.2. 使用?async/await
?讓異步操作看起來像同步?
雖然 Fetch API 是異步的,但可以通過 async/await
語法讓代碼看起來像是同步的。async/await
是 JavaScript 中處理異步操作的一種更簡潔的方式,它基于 Promise
,但可以讓代碼的結構更接近同步代碼。
使用?async/await
?的代碼
async function fetchData() {try {const response = await fetch('https://api.example.com/data');if (!response.ok) {throw new Error(`HTTP error! Status: ${response.status}`);}const data = await response.json();console.log(data); // 處理返回的數據} catch (error) {console.error('Fetch error:', error);}
}fetchData();
在這個例子中:
-
async
關鍵字用于聲明一個異步函數。 -
await
關鍵字用于等待一個Promise
完成。 -
await fetch()
會暫停函數的執行,直到fetch()
的Promise
完成。如果Promise
成功,await
會返回Promise
的結果;如果Promise
失敗,會拋出錯誤。 -
try...catch
用于捕獲異步操作中可能拋出的錯誤。
雖然代碼看起來像是同步的,但實際上仍然是異步的。await
只是讓代碼在邏輯上更直觀,但它不會阻塞主線程。瀏覽器仍然可以在等待 fetch()
完成時執行其他任務。
?2.3. 同步操作與異步操作的區別
同步操作
-
特點:代碼按順序執行,每一步操作必須等待前一步操作完成。
-
問題:如果某個操作耗時較長(如網絡請求),會阻塞主線程,導致頁面卡頓。
異步操作
-
特點:代碼不會按順序執行,某些操作可以在后臺完成,主線程可以繼續執行其他任務。
-
優點:不會阻塞主線程,用戶體驗更好。
3. Fetch API 的基本用法
3.1 基本語法
fetch(url, options).then(response => {// 處理響應}).catch(error => {// 處理錯誤});
-
url
:請求的資源地址,通常是字符串形式的URL。 -
options
:可選參數,用于配置請求的細節,例如請求方法、請求頭、請求體等。
?4. 瀏覽器同源檢查(Same-Origin Policy)
4.1 什么是同源策略?
同源策略(Same-Origin Policy)是瀏覽器的一種安全機制,用于限制不同來源(origin)的文檔或腳本之間的交互。它旨在防止惡意網站竊取用戶數據或干擾其他網站的正常運行。
“同源”是指兩個資源的協議(protocol)、域名(domain)和端口號(port)完全相同。如果這三個部分有任何一個不同,就被視為跨域。
例如:
-
同源:
http://example.com
和http://example.com/path
是同源的,因為它們的協議、域名和端口號都相同。 -
跨域:
http://example.com
和https://example.com
是跨域的,因為協議不同(http
vshttps
)。 -
跨域:
http://example.com
和http://sub.example.com
是跨域的,因為域名不同(example.com
vssub.example.com
)。 -
跨域:
http://example.com
和http://example.com:8080
是跨域的,因為端口號不同(默認端口80 vs 8080)。
4.2 同源策略的作用
-
保護用戶隱私:防止惡意網站通過腳本訪問其他網站的敏感信息(如用戶的登錄狀態、個人數據等)。
-
防止XSS攻擊:限制不同來源的腳本之間的交互,避免惡意腳本注入。
-
防止CSRF攻擊:確保只有同源的請求可以操作敏感數據。
?5. 跨域(Cross-Origin)
5.1 什么是跨域?
跨域是指兩個資源的來源(origin)不同。當一個網頁嘗試與另一個來源的資源交互時,就會觸發跨域問題。由于同源策略的限制,瀏覽器會阻止這種跨域操作。
5.2 跨域的常見場景
-
跨域AJAX請求:嘗試從一個域向另一個域發起HTTP請求。
5.3 解決跨域問題的方法?
5.3.1請求響應頭解決跨域問題
通過在服務器端設置特定的HTTP響應頭,允許瀏覽器從特定的源(或所有源)訪問資源。這種方法基于瀏覽器的CORS(跨域資源共享)機制。
關鍵響應頭
-
Access-Control-Allow-Origin
:指定允許跨域請求的域名。可以設置為具體域名(如http://example.com
),也可以設置為*
(允許所有域名)。 -
Access-Control-Allow-Methods
:指定允許的HTTP方法(如GET, POST, PUT, DELETE
)。 -
Access-Control-Allow-Headers
:指定允許的請求頭。 -
Access-Control-Allow-Credentials
:指定是否允許攜帶Cookie等認證信息。如果設置為true
,Access-Control-Allow-Origin
不能為*
。 -
Access-Control-Max-Age
:指定預檢請求結果的緩存時間。
以下是一個Node.js服務器的示例,展示如何設置響應頭以允許跨域請求:
const http = require('http');
const server = http.createServer((req, res) => {res.writeHead(200, {'Access-Control-Allow-Origin': 'http://example.com', // 允許特定域名訪問'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE','Access-Control-Allow-Headers': 'Content-Type, Authorization','Access-Control-Allow-Credentials': 'true'});res.end('hello world');
});
server.listen(3000);
5.3.2 代理解決跨域問題
通過設置一個同域的代理服務器,將前端的跨域請求轉發到目標服務器。瀏覽器認為請求是同源的,從而繞過跨域限制。
1.使用Nginx作為代理服務器
server {listen 80;server_name yourdomain.com;location /api/ {proxy_pass http://backend-server:3000/; # 轉發到后端服務proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;}
}
-
說明:前端請求
/api
路徑時,Nginx會將請求代理到目標服務器。
2.使用Node.js中間件?
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();app.use('/api', createProxyMiddleware({target: 'http://example.com',changeOrigin: true
}));app.listen(3000);
-
說明:所有發送到
/api
的請求都會被代理到http://example.com
。
總結
-
請求響應頭解決跨域問題:通過服務器端設置CORS響應頭,適用于需要靈活控制跨域策略的場景。
-
代理解決跨域問題:通過代理服務器轉發請求,適用于開發環境或需要統一管理跨域請求的場景。
6.面試問題?
1.什么是同源策略?為什么要設置同源策略?
同源策略是一種瀏覽器的安全機制,它要求只有當協議、域名和端口號完全相同的兩個頁面才能互相訪問DOM或發起AJAX請求。它的主要目的是防止惡意網站通過腳本訪問其他網站的敏感信息,從而保護用戶的隱私和網站的安全。
2. 什么是跨域?常見的跨域場景有哪些?
跨域是指兩個資源的來源不同,例如協議、域名或端口號不同。常見的跨域場景包括:從一個域向另一個域發起AJAX請求、嵌入不同源的資源(如圖片、腳本、樣式表等),以及嘗試訪問嵌入頁面(如iframe)的DOM。
3. 如何解決跨域問題?
解決跨域問題的方法有多種:
-
JSONP:通過動態創建
<script>
標簽來加載跨域資源,適用于GET請求。 -
CORS:通過在服務器響應中添加
Access-Control-Allow-Origin
等HTTP頭來允許跨域請求。 -
代理服務器:通過設置一個同域的代理服務器,將跨域請求轉發到目標服務器。
-
文檔域:適用于主域相同但子域不同的情況,通過設置
document.domain
來實現跨域。
4. 什么是CORS?它是如何工作的?
CORS是一種跨域解決方案,允許服務器通過HTTP頭明確指定哪些外部域可以訪問其資源。當瀏覽器發起跨域請求時,會在請求中添加Origin
頭。服務器檢查這個頭,決定是否允許請求。如果允許,服務器會在響應中添加Access-Control-Allow-Origin
頭,指定允許的來源。?
5.在vue項目中最常用的一種解決跨域方式是什么 ?
在 Vue 項目中,最常用且推薦的解決跨域問題的方式是通過 代理服務器。這種方式簡單高效,尤其適合開發環境,因為它不需要修改后端代碼,也不需要在前端代碼中添加額外的跨域處理邏輯。?
1. 使用 Vue CLI 的代理功能
Vue CLI 提供了一個非常方便的代理功能,可以在開發環境中輕松解決跨域問題。它基于 Webpack 的 devServer.proxy
配置,允許你將特定的請求轉發到目標服務器。
配置步驟
-
在
vue.config.js
中配置代理 如果你的 Vue 項目使用了 Vue CLI,可以在項目的根目錄下創建或修改vue.config.js
文件,添加代理配置。// vue.config.js module.exports = {devServer: {proxy: {'/api': {target: 'http://example.com', // 目標服務器地址changeOrigin: true, // 允許跨域pathRewrite: {'^/api': '' // 重寫路徑,去掉/api前綴}}}} };
在這個配置中:
-
/api
是前端請求的路徑前綴。 -
target
是目標服務器的地址。 -
changeOrigin
設置為true
,可以避免跨域問題。 -
pathRewrite
用于重寫路徑,去掉/api
前綴,這樣目標服務器不會收到/api
路徑。
2.前端代碼中的請求?
在前端代碼中,你可以直接使用 /api
作為請求路徑,Vue CLI 會自動將其轉發到目標服務器。
// 使用 fetch 或 axios 發起請求
fetch('/api/data').then(response => response.json()).then(data => console.log(data)).catch(error => console.error(error));
或者使用 axios
:
import axios from 'axios';axios.get('/api/data').then(response => console.log(response.data)).catch(error => console.error(error));