引言
在我们写前端时,与后端接口进行对接是一个很常见的操作——后端以地址的形式给前端提供一个数据接口,而前端则需要将这个接口中的数据提取出来渲染到页面上。接口对接是一个很常规的操作,但是很多时候我们在进行接口对接时会出现一个问题——跨域。接下来我们来探讨一下究竟什么是跨域,以及跨域的一些解决方案。
什么是跨域?
首先我们先来演示一下跨域究竟是什么效果
如图,当前端向后端请求数据时,页面报错,表示接口请求被跨域机制拦截,这便是跨域的影响效果。
为什么会有跨域呢?(同源策略)
其实跨域是因为浏览器具有一个策略——同源策略,必须前后端的协议号、域名以及端口号都相同才符合同源策略。
协议号 域名 端口号
举个栗子:
这是一个百度的链接:https://www.baidu.com:8080/userInfo
哪怕前后端的前面三项中有一项不相同,也不符合同源策略。
跨域拦截发生在什么时候呢?是前端发送请求时?还是后端响应时?
我们写一段代码来进行验证:
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
<title>Document</title>
</head>
<body>
<button id="btn">获取数据</button>
<script>
let btn = document.getElementById('btn')
btn.addEventListener('click',()=>{
$.ajax({
url:'http://localhost:3000',
data:{
name:'小李'
},
method:'get',
success(res){
console.log(res);
}
})
})
</script>
</body>
</html>
后端代码
const Koa = require('koa')
const app = new Koa()
const main = (ctx,next)=>{
console.log(ctx.query.name);
ctx.body = 'hello world'
}
app.use(main)
app.listen(3000,()=>{
console.log('项目已启动');
})
运行结果
在后端项目已启动的情况下,我们点击前端获取数据按钮,发现页面因为跨域拦截报了错:
而后端成功打印了数据,说明后端成功接收到了前端的请求,前端的数据请求未被拦截:
由此可见,后端响应回来的数据在浏览器接收到的时候被跨域机制拦截下来,所以跨域通常发生在后端响应时。
跨域有哪些解决方案呢?
JSONP: 利用script的src属性加载资源时不受同源策略的影响这一特性(需要前后端配合)。
可是JSONP到底是怎样利用src属性实现不受同源策略影响的呢?让我们举个例子看看。
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
<title>Document</title>
</head>
<body>
<button id="btn">获取数据</button>
<script>
const jsonp = (url,params,cb)=>{
return new Promise((resolve,reject)=>{
const script = document.createElement('script')
params = {...params,cb:cb}
const arr = Object.keys(params).map(key => `${key}=${params[key]}`) //['name:'蜡笔小新'','age:'5'']
script.src = `${url}?${arr.join('&')}`
document.body.appendChild(script)
// 后端会返回一个函数给前端
// script.src === 'callback('蜡笔小新今年5岁')'
window[cb] = (data) => {
resolve(data)
}
})
}
let btn = document.getElementById('btn')
btn.addEventListener('click',()=>{
jsonp('http://localhost:3000',{name:'蜡笔小新',age:'5'},'callback')
.then(res => {
console.log(res);
})
})
</script>
</body>
</html>
后端代码
const Koa = require('koa')
const app = new Koa()
const main = (ctx,next)=>{
const {name,age,cb} = ctx.query
const userInfo = `${name}今年${age}岁`
const str = `${cb}(${JSON.stringify(userInfo)})` // 'callback('蜡笔小新今年5岁')'
ctx.body = str
}
app.use(main)
app.listen(3000,()=>{
console.log('项目已启动');
})
代码解析
首先在前端页面上放上一个button按钮,用来控制前端向后端获取数据,再定义一个 jsonp 函数,传入三个参数,第一个参数 url 是请求数据的地址;第二个参数 params 是前端想要传给后端的数据;第三个参数 cb 是一个函数名,后端响应前端时该函数被调用从而前端能够获取后端传来的数据。
当 button 按钮被点击时,jsonp函数被调用,前端利用 script.src 向后端发起请求的同时拼接了想要传给后端的数据。
后端对接收到的数据进行处理,并将函数调用以字符串的形式('callback('蜡笔小新今年5岁')')返回给前端,即前端获取到的 src 为函数调用的字符串形式。
window[cb] = (data) => {
resolve(data)
}
// 相当于在window上定义了一个函数:
// window.callbak = (data) => {
// resolve(data)
// }
当浏览器加载script标签的src属性时,虽然拿到的是一个字符串,但是浏览器也会将这个字符串执行,就相当于会把 callback 函数执行掉,src 请求回来的资源会被挂在window上面去执行,所以在window上定义的函数会被调用,前端就会打印出后端返回的数据了。
运行结果
后端打印了前端传过来的内容,前端也拿到了后端返回的内容,所以跨域问题被解决。当然,JSONP 方法也存在弊端,因为 src 属性只能支持 get 请求,所以JSONP也只能适用于 get 请求,同时,前端传给后端的数据会在 url 地址后面显示出来,故安全性不高。