前端跨域深度解析:从原理到实战的完整指南 🧐

130 阅读6分钟

跨域问题是前端工程师在前后端联调时的高频痛点。本文将从基础概念出发,通过代码实例详解跨域原理及解决方案,帮你彻底掌握这一核心技能。

一、跨域的本质:什么是同源策略? 🔐

1.1 同源的定义

同源需同时满足三个条件:

  • 协议相同(如都是 http 或 https)
  • 域名相同(如都是localhostwww.example.com
  • 端口相同(如都是 8080 或 5173)

1.2 跨域场景示例

前端地址后端地址是否跨域原因
http://localhost:5173http://localhost:8080端口不同
www.baidu.comwww.baidu.com协议不同
www.baidu.comwww.google.com域名不同
http://localhost:5173http://localhost:5173/api完全同源

1.3 为什么需要同源策略?

浏览器的同源策略是一种安全机制,用于:

  • 防止恶意网站读取另一个网站的 Cookie/localStorage

  • 阻止未授权的跨域数据访问

  • 保护用户敏感信息

⚠️ 关键知识点:跨域请求会正常到达服务器,但响应会被浏览器拦截!

二、跨域解决方案一:JSONP 🔧

JSONP(JSON with Padding)是利用<script>标签不受同源策略限制的特性实现的跨域方案。

2.1 JSONP 的工作原理

  1. 前端定义一个处理数据的回调函数
  2. 通过<script>标签的 src 属性发送 GET 请求,携带回调函数名
  3. 后端返回 "函数调用 + 数据" 的 JavaScript 代码
  4. 浏览器执行返回的代码,触发回调函数处理数据

2.2 手写 JSONP 封装(前端实现)

function getJSONP({url, params={}, callback='callback'}) {
    return new Promise((resolve, reject) => {
        // 1. 创建script标签
        const script = document.createElement('script');
        
        // 2. 处理参数,拼接回调函数名
        params = { ...params, callback }; // 合并参数,确保包含回调函数
        const paramArr = [];
        for (const key in params) {
            paramArr.push(`${key}=${encodeURIComponent(params[key])}`);
        }
        
        // 3. 构建请求URL
        script.src = `${url}?${paramArr.join('&')}`;
        
        // 4. 定义全局回调函数
        window[callback] = function(data) {
            resolve(data); // 传递数据
            document.body.removeChild(script); // 清理DOM
            delete window[callback]; // 移除全局函数,避免污染
        };
        
        // 5. 处理错误
        script.onerror = function() {
            reject(new Error('JSONP请求失败'));
            document.body.removeChild(script);
            delete window[callback];
        };
        
        // 6. 发送请求
        document.body.appendChild(script);
    });
}

// 使用示例
getJSONP({
    url: 'http://localhost:3000/say',
    params: { wd: 'I love you' },
    callback: 'handleResponse'
}).then(data => {
    console.log('JSONP返回数据:', data);
}).catch(err => {
    console.error('请求失败:', err);
});

2.3 后端配合实现(Node.js)

const http = require('http');
const server = http.createServer((req, res) => {
    if (req.url.startsWith('/say')) {
        // 解析URL参数
        const url = new URL(req.url, `http://${req.headers.host}`);
        const wd = url.searchParams.get('wd'); // 获取请求参数
        const callback = url.searchParams.get('callback'); // 获取回调函数名
        
        // 构建响应数据
        const responseData = {
            code: 0,
            message: `收到消息:${wd}`,
            time: new Date().toLocaleString()
        };
        
        // 设置响应头,返回JavaScript类型
        res.writeHead(200, { 'Content-Type': 'application/javascript' });
        // 返回"函数调用"格式的响应
        res.end(`${callback}(${JSON.stringify(responseData)})`);
    } else {
        res.writeHead(404);
        res.end('Not Found');
    }
});

server.listen(3000, () => {
    console.log('JSONP服务器运行于 http://localhost:3000');
});

2.4 JSONP 的优缺点分析

✅ 优点:

  • 兼容性极佳,支持所有浏览器(包括 IE)

  • 实现简单,无需复杂配置

❌ 缺点:

  • 仅支持 GET 请求,无法用于 POST/PUT 等请求
  • 存在安全风险(全局函数可能被劫持)
  • 依赖后端配合,前后端耦合度高

三、跨域解决方案二:CORS 🌟

CORS(Cross-Origin Resource Sharing)是 W3C 标准推荐的跨域方案,通过服务器设置响应头实现跨域授权。

3.1 CORS 的基本原理

服务器通过在响应头中设置Access-Control-Allow-Origin等字段,告知浏览器允许指定来源的跨域请求,浏览器收到后会放行响应数据。

3.2 两种请求类型

  • 简单请求

    • 方法为 GET/POST/HEAD
    • Content-Type 为 text/plain、multipart/form-data、application/x-www-form-urlencoded
    • 无自定义请求头
  • 复杂请求

    • 方法为 PUT/DELETE/PATCH 等

    • 包含自定义请求头(如 Authorization)

    • Content-Type 为 application/json 等非简单类型

⚠️ 复杂请求会先发送预检请求(OPTIONS) ,验证服务器是否允许跨域。

3.3 后端 CORS 配置(Node.js)

const http = require('http');
const server = http.createServer((req, res) => {
    // 核心配置:允许的源(可设置为具体域名或*)
    res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500');
    
    // 允许的请求方法
    res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
    
    // 允许的请求头(复杂请求需要)
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
    
    // 处理预检请求
    if (req.method === 'OPTIONS') {
        res.writeHead(204); // 204表示无内容,成功处理预检
        res.end();
        return;
    }
    
    // 处理业务请求(PATCH示例,属于复杂请求)
    if (req.url === '/api/test' && req.method === 'PATCH') {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({
            code: 0,
            msg: 'CORS跨域请求成功',
            data: { id: 1, name: '测试数据' }
        }));
    }
});

server.listen(8000, () => {
    console.log('CORS服务器运行于 http://localhost:8000');
});

3.4 前端使用示例

// 发送复杂请求(PATCH方法)
fetch('http://localhost:8000/api/test', {
    method: 'PATCH',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token123' // 自定义请求头
    },
    body: JSON.stringify({ name: '前端请求数据' })
})
.then(res => res.json())
.then(data => {
    console.log('CORS响应数据:', data);
})
.catch(err => {
    console.error('请求失败:', err);
});

3.5 CORS 的优缺点分析

✅ 优点:

  • 支持所有 HTTP 方法,满足各种业务需求

  • 安全性高,可精确控制允许的来源和方法

  • 前端无需特殊处理,像同域请求一样使用

  • 支持携带 Cookie(需配置 withCredentials)

❌ 缺点:

  • 部分老旧浏览器不支持(IE10 + 支持)
  • 复杂请求会增加一次预检请求的开销

四、两种方案的对比与选择 🆚

特性JSONPCORS
请求类型支持仅 GET所有类型
安全性较低(易受 XSS 攻击)较高(精细控制)
前后端耦合高(需后端特殊处理)低(仅后端配置)
浏览器兼容性所有浏览器IE10 + 及现代浏览器
实现复杂度简单中等(复杂请求需额外配置)

选择建议:

  1. 新项目优先使用CORS,符合现代 Web 标准
  2. 需兼容古老浏览器(如 IE9 及以下)时使用JSONP
  3. 仅简单 GET 请求场景,JSONP 可作为轻量化方案

五、实战常见问题与解决 🔍

  1. JSONP 请求 404:检查 URL 是否正确,后端是否正确解析回调参数
  2. CORS 预检失败:确保后端正确处理 OPTIONS 请求,并配置Access-Control-Allow-Methods
  3. 响应被拦截但请求成功:打开浏览器控制台的 Network 面板,查看 Response Headers 是否包含正确的 CORS 配置
  4. JSONP 全局函数冲突:使用随机生成的回调函数名(如callback_123456)避免冲突

六、总结 📚

跨域问题的本质是浏览器的同源策略限制,解决思路主要有两类:

  • 绕过限制(如 JSONP 利用 script 标签特性)
  • 授权允许(如 CORS 通过响应头配置)

在实际开发中,CORS 凭借其完整性和安全性成为主流方案,建议作为首选。掌握跨域解决方案,能让你在前后端联调中更游刃有余,提升开发效率!