相信我们在工作或者是学习中,前后端的交互都会涉及到跨域,那什么是跨域,什么时候会发生跨域,跨域有哪些解决方式,今天我们就来聊一聊跨域以及跨域的一些解决方式。
什么是跨域?
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全措施。
什么是同源策略?
协议号 - 域名 - 端口号 ,只有当这三个条件同时满足相同时,我们就称之为符合同源策略,同源策略也可以看做是一个协议。
https:// www.baidu.com :8080 /test
协议号 域名 端口号 路径
通常我们导航的url都是由这四部分组成的。
什么情况下会发生跨域?
当我们前端向后端(服务器)发送接口请求,后端相应返回给前端的数据,在浏览器接收到时被跨域机制拦截下来了,浏览器为了安全问题一般都限制了跨域访问。
如何解决跨域?
首先我们要了解有三个h5里的标签允许加载跨域资源
1. <img src="XXX" >
2. <link href="XXX" > // 通常用于引入css文件
3. <script src="XXX" > // 通常用于引入js文件
我们接下来要讲的第一个解决跨域的方法其实就是利用了script标签的src属性。
JSONP
JSONP -- 利用script的src属性加载资源时不受同源策略的影响这一特性(需要前后端配合)
代码实现:
html:
<button id="btn">接口请求</button>
js:
<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="harvey"','age=20', 'cb:xxx']
script.src = `${url}?${arr.join('&')}` // http://localhost:3000?name="harvey
"&age=20&cb=xxx
document.body.appendChild(script)
// 后端会返回一个函数给前端
// script.src === 'callback()' === callback('harvey今年18岁')
window[cb] = (data) => {
resolve(data)
}
})
}
let btn = document.getElementById('btn')
btn.addEventListener('click',()=>{
jsonp('http://localhost:3000',{name: 'harvey',age: 18},'callback')
.then(res => {
console.log(res);
})
})
</script>
使用node来模拟的一个本地后端项目(其中安装了koa依赖)
app.js:
const Koa = require('koa')
const app = new Koa()
const main = (ctx,next) => {
// console.log(ctx.query);
const {name,age,cb} = ctx.query
const userInfo = `${name}今年${age}岁`
const str = `${cb}(${JSON.stringify(userInfo)})` // 'callback()'
//我们可以使用 JSON.stringify() 方法将 JavaScript 对象转换为字符串。
ctx.body = str
}
app.use(main)
app.listen(3000, () => {
console.log('项目已启动');
})
我们一起来分析JSONP是如何实现的,首先我们在JS中定义了一个函数,它接收三个参数,分别是接口的请求地址、可能携带的参数、以及一个回调函数,它返回的是一个Promise对象,方便我们后续使用.then进行后面的操作,我们先在js中创建了一个script标签,我们再对第二个参数进行结构,再直接给script标签的src属性上赋值,然后将这个script标签直接添加到html中,当我们点击事件之后向后端的 ' http://localhost:3000 ' 这个地址发送接口请求,携带的参数是 {name: 'harvey',age: 18},回调函数我们就放'callback'字符串,而我们后端将前端传递过来的数据进行一些操作时候,使得返回给前端的内容变成字符串,里面是回调函数的调用以及参数,即'cb(harvey今年18岁)'。前端接收到之后js中会将'cb(harvey今年18岁)'这一串字符串里面的内容执行,所以 window[cb] = (data) => { resolve(data) } 这个就会执行,相当于我们将cb(harvey今年18岁)这个执行结果作为参数data传给了window[cb],再将结果通过Promise对象的resolve出去了,那么调用JSONP的.then就会将执行结果输出在控制台。从而解决跨域问题。
其核心就在于将后端返回的结果加载在script的src属性上,从而跨域加载资源。
缺点:JSONP-- 只适用于get请求,因为script的src加载资源默认就是get请求,所以不支持post等其他类型的请求 get请求,会将请求的数据加载url后面。
Cors(Cross-Origin Resouce Sharing)
其实有一个koa/cors的依赖,只需要我们在后端npm i @koa/cors -save安装好这个依赖,并且在全局引入并使用即可,这样后端就开启了cros,允许跨域操作,那在学习阶段,我们直接来实现用原生node如何封装cros呢?
前端代码:
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
// 引入一个ajax,方便发接口请求
<button id="btn">获取数据</button>
js:
<script>
let btn = document.getElementById('btn')
btn.addEventListener('click',()=>{
$.ajax({
url: 'http://localhost:3000',
data: {
name: 'harvey'
},
headers: {
// 为了告诉后端,你返回的响应头的数据类型应该是xxx
'Content-Type': 'application/json; charset=utf-8'
},
method: 'get',
success(res) {
console.log(res);
}
})
})
</script>
node // app.js
const http = require('http') // 引入http模块
const server = http.createServer((req,res) => {
// 开启cros, 后端用于写响应头的设置
res.writeHead(200, {
// 允许跨域
"Access-Control-Allow-Origin": "*", // 配置同源白名单
"Access-Control-Allow-Methods": "GET, POST, OPTIONS,PUT",
// 不管向浏览器返回什么类型都可以
// "Access-Control-Allow-Headers": "Content-Type"
})
res.end('hello cros')
})
server.listen(3000, () => {
console.log('cros项目已启动');
})
前端部分就是一个简单的使用ajax来发起一个接口请求,请求类型为GET类型,后端我们直接使用node的http模块,来生成一个服务,关键在于配置白名单,以及你接口请求的类型,如果你接口请求的响应头中有数据类型要求,那也需要配置 "Access-Control-Allow-Headers": "Content-Type" ,res.end('hello cros')是向前端返回的内容,只要配置好以上三项,无论什么前端都可以向我电脑本地的3000端口发送接口请求。
cros只需要后端开启,就能实现跨域,但是只能用于开发阶段。
缺点:任何前端都可以向后端发接口请求。
node代理
原理:利用后端与后端进行数据交互时没有同源策略,所以我们创建一个自己的后端,我们的后端开启cros,然后将数据传输给我们的前端,这样我们就可以获取到其他后端的数据。
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
// 引入一个ajax,方便发接口请求
<button id="btn">获取数据</button>
js:
<script>
let btn = document.getElementById('btn')
btn.addEventListener('click',()=>{
$.ajax({
url: 'http://localhost:3001',
data: {
name: 'harvey'
},
headers: {
// 为了告诉后端,你返回的响应头的数据类型应该是xxx
'Content-Type': 'application/json; charset=utf-8'
},
method: 'get',
success(res) {
console.log(res);
}
})
})
</script>
其实与上一个方法的前端代码几乎一摸一样,只不过将申请的地址换成了我们自己写好的后端的地址。
后端:
// 别人的后端(我们要获取数据的后端,端口号为3000)
const Koa = require('koa')
const app = new Koa()
const main = (ctx,next) => {
console.log(ctx.query.name);
ctx.body = 'hello.word'
}
app.use(main)
app.listen(3000, () => {
console.log('项目已启动');
})
// 自己的后端
const http = require('http') // 引入http模块
const server = http.createServer((req,res) => {
// 开启cros, 后端用于写响应头的设置
res.writeHead(200, {
// 允许跨域
"Access-Control-Allow-Origin": "*", // 配置同源白名单
"Access-Control-Allow-Methods": "GET, POST, OPTIONS,PUT",
// 不管向浏览器返回什么类型都可以
"Access-Control-Allow-Headers": "Content-Type"
})
// 向别人的后端请求数据
http.request({
host: '127.0.0.1', // 域名
port: '3000', // 接口
path: '/', // 路径
method: 'GET'
}, proxyRes => {
// console.log(proxyRes);
proxyRes.on('data', result => {
res.end(result.toString())
})
}).end()
})
server.listen(3001, () => {
console.log('my项目已启动');
})
我们写的后端接收到我们需要的数据之后,再返回给我们的前端即可。
缺点:与cros方式一致任何前端都可以向我们的后端发接口请求,仅适用于开发阶段。
以上就是我对跨域以及跨域的一些解决办法的个人见解,当然解决跨域的方法当然不仅仅只有这三种,有任何疑问,都可在评论区沟通交流,感谢你的观看!