面试必考:全面解析跨域及其解决方案
跨域问题是前端开发中常见且必须掌握的知识点之一。本文将详细介绍跨域的概念、手写JSONP和CORS跨域代码及其原理,如何在Vue3项目中替换Mock数据接口为真实后端数据接口,以及总结九种常见的跨域解决方案。
一、什么是跨域?
跨域是指浏览器因同源策略的限制,无法访问不同源(协议、域名、端口任一不同)的资源。例如,前端页面运行在 http://example.com
,但需要访问 http://api.example.com
的数据时,就会遇到跨域问题。
二、手写JSONP跨域代码及原理讲解
原理
JSONP(JSON with Padding)是一种非正式的数据传输格式,它通过 <script>
标签的 src
属性来实现跨域请求。因为 <script>
标签不受同源策略限制。
实现代码
前端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JSONP Example</title> </head> <body> <h1>JSONP Cross-Origin Request</h1> <button onclick="fetchData()">Fetch Data</button> <script> function fetchData() { const script = document.createElement('script'); script.src = 'http://localhost:3000/data?callback=handleResponse'; document.body.appendChild(script); } function handleResponse(data) { console.log('Received data:', data); } </script> </body> </html>
后端代码(Node.js示例):
const express = require('express'); const app = express(); const port = 3000; app.get('/data', (req, res) => { const callback = req.query.callback; const data = { message: 'Hello, JSONP!' }; res.send(`${callback}(${JSON.stringify(data)})`); }); app.listen(port, () => { console.log(`Server running at http://localhost:${port}`); });
三、手写跨域资源共享(CORS)处理跨域及原理
原理
CORS(Cross-Origin Resource Sharing)是一种机制,它使用额外的HTTP头来告诉浏览器,允许从其他域加载资源。服务器通过设置适当的HTTP响应头,来告诉浏览器哪些域可以访问资源。
实现代码
前端代码:
fetch('http://localhost:3000/data', { method: 'GET', headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => console.log('Received data:', data)) .catch(error => console.error('Error:', error));
后端代码(Node.js示例):
const express = require('express'); const app = express(); const port = 3000; app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); // 允许所有域访问 res.header('Access-Control-Allow-Methods', 'GET, POST'); // 允许的方法 res.header('Access-Control-Allow-Headers', 'Content-Type'); // 允许的请求头 next(); }); app.get('/data', (req, res) => { res.json({ message: 'Hello, CORS!' }); }); app.listen(port, () => { console.log(`Server running at http://localhost:${port}`); });
四、Vue3通用后台管理如何替换Mock数据接口为真实的后端数据接口
在Vue3项目中,我们可以通过修改API请求地址,将Mock数据接口替换为真实后端数据接口。
示例
假设我们有一个Vue3项目,之前使用Mock数据:
// api.js (使用Mock数据) export const getData = () => { return new Promise((resolve) => { resolve({ data: 'Mock data' }); }); };
现在,我们将其替换为真实后端接口:
// api.js (使用真实后端数据) import axios from 'axios'; const BASE_URL = 'http://localhost:3000'; export const getData = () => { return axios.get(`${BASE_URL}/data`); };
然后在组件中调用:
<template> <div> <button @click="fetchData">Fetch Data</button> <p>{{ data }}</p> </div> </template> <script> import { getData } from './api'; export default { data() { return { data: '' }; }, methods: { fetchData() { getData() .then(response => { this.data = response.data.message; }) .catch(error => { console.error('Error:', error); }); } } }; </script>
五、九种跨域方案的汇总
- JSONP:通过
<script>
标签实现跨域,只支持GET请求。 - CORS:通过设置HTTP头允许跨域请求,支持复杂请求。
- 服务器代理:如使用Node.js中间件(http-proxy-middleware)或Nginx代理请求,绕过浏览器的同源策略。
- WebSocket:WebSocket协议不受同源策略限制,可以实现跨域通信。
- PostMessage:通过
window.postMessage
实现不同窗口间的数据传递。 - 跨域资源嵌入:通过
<iframe>
、<img>
、<link>
、<script>
等标签加载跨域资源。 - document.domain:适用于主域相同子域不同的跨域,通过设置相同的
document.domain
实现。 - window.name:通过改变窗口的
name
属性实现跨域数据传递。 - 跨域请求伪造(CORS Preflight):通过简单请求或预检请求绕过CORS限制。
1. JSONP示例
详见上文。
2. CORS示例
详见上文。
3. 服务器代理示例(Node.js中间件)
const { createProxyMiddleware } = require('http-proxy-middleware'); const express = require('express'); const app = express(); app.use('/api', createProxyMiddleware({ target: 'http://backend-server.com', changeOrigin: true, })); app.listen(3000);
4. WebSocket示例
const socket = new WebSocket('ws://localhost:3000'); socket.onopen = () => { console.log('WebSocket connection opened'); socket.send('Hello Server!'); }; socket.onmessage = (event) => { console.log('Received:', event.data); };
后端代码(Node.js示例):
const WebSocket = require('ws'); const server = new WebSocket.Server({ port: 3000 }); server.on('connection', ws => { ws.on('message', message => { console.log('Received:', message); ws.send('Hello Client!'); }); });
5. PostMessage示例
<!-- parent.html --> <iframe id="child" src="child.html" style="display:none;"></iframe> <script> const child = document.getElementById('child').contentWindow; child.postMessage('Hello from parent!', 'http://child.com'); window.addEventListener('message', (event) => { if (event.origin === 'http://child.com') { console.log('Received from child:', event.data); } }); </script> <!-- child.html --> <script> window.addEventListener('message', (event) => { if (event.origin === 'http://parent.com') { console.log('Received from parent:', event.data); event.source.postMessage('Hello from child!', event.origin); } }); </script>
6. 跨域资源嵌入示例
<!-- index.html --> <script src="http://cross-origin.com/script.js"></script>
7. document.domain示例
<!-- parent.example.com --> <script> document.domain = 'example.com'; // 访问子域内容 </script>
8. window.name示例
<!-- page1.html --> <script> window.name = 'data from page1'; location.href = 'http://cross-origin.com/page2.html'; </script> <!-- page2.html --> <script> console.log(window.name); // 'data from page1' </script>
9. 跨域请求伪造(CORS Preflight)示例
后端代码:
const express = require('express'); const app = express(); app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (req.method === 'OPTIONS') { return res.sendStatus(204); // 对预检请求直接返回204状态码 } next(); }); app.post('/api', (req, res) => { res.json({ message: 'Data received' }); }); app.listen(3000, () => { console.log('Server running at http://localhost:3000'); });