跨域与同源策略:
什么是跨域?
跨域 :跨域访问问题指的是在客户端浏览器中,由于同源策略的限制,不允许从一个源直接访问另一个源的资源。当浏览器发起一个跨域请求时,会被浏览器拦截,并阻止数据的传输。
如上图,只有当协议 ,域名 、端口都一致才叫同源。
同源策略 :为了保护用户的隐私和数据安全,浏览器就会通过实施同源策略 来限制不同源 之间的直接通信。但也有一些情况不受同源策略的限制,分别是:Img标签下的 、Link标签下的 、Script标签下的(至于为什么它们不受限,请往下看)。
本篇我们就来一步一步的深入剖析JSONP这种方法之所以能实现跨域的原理,以及代码实操。
JSONP实现跨域的原理?
现在我们知道了同源策略的存在,也明白了不同源之间的数据传输是受到限制的,但是神奇的一幕发生了,我居然能直接在页面上展示出一张不同源的图片!
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img src="https://img2.baidu.com/it/u=446981511,947966320&fm=253&fmt=auto&app
=120&f=JPEG?w=750&h=500" alt="">
// 这是一张百度上随便找来的图片
</body>
</html>
js
const http = require('http')
http.createServer(function(req, res) {
res.end('hello world')
}).listen(3000) // 项目运行在 3000 端口
要知道,要想展示出百度的这张图片,就势必得从我们本机的端口发出请求,也就势必的会造成跨域。但正是因为img标签不受同源策略的限制,所以它才能够正常的展示出来。
其实最开始的同源策略被打造的极其严格,严格到任何资源只要是不同源的就无法请求到,给当时的第三方图片展示带来了极大的麻烦。但是在项目中加载第三方图片的需求应该属于合理的,于是同源策略在后面又修改了一下,把img标签 排除在外,也就是放入白名单了。link标签 ,Script标签同理。
于是,利用img标签不受同源策略的限制的原理,我们用来实现JSPON的跨域。
JSPON跨域的实现
现在我们知道,直接发送ajax请求会被同源策略拦截,于是我们将ajax请求塞在src属性里面,再发送给后端,这样前端就能正常发送请求给后端,后端也能返回数据。
但是相比于正常发送 ajax 请求给后端,这种方式有一个很大的区别,那就是不能直接操作后端返回来的数据。
所以我们现在就得在原有的基础上进行封装,使得我们能够操作后端返回的数据。
那么,我们第一步的思路 就是创建一个jsonp
函数,每次调用这个函数就会向后端发送请求,随后就可以通过then
对后端返回的响应体进行操作。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function jsonp(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script'); // 创建一个script标签
script.src = url; // 设置script标签的src属性
document.body.appendChild(script); // 将script标签添加到页面中
})
}
jsonp('http://localhost:3000').then(res => {
console.log(res);
})
</script>
</body>
</html>
以上第11,12、13行的代码,先是创建一个script标签,再设置script标签的src属性,最后将script标签添加到页面中:
要想将浏览器请求到的数据为我们所用,关键在于我们接下来要向后端传递的这个我们命名为callback
的参数。 我们又在以上的基础上做出修改,在设置script标签的src属性过程中将callback
参数拼接上去,传递给后端。
html
<body>
<script>
function jsonp(url, cb) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
window[cb] = function (data) {
}
script.src = `${url}?cb=${cb}`; // 拼接callback参数
document.body.appendChild(script);
})
}
jsonp('http://localhost:3000', 'callback').then(res => { //传递callback参数给后端
console.log(res)
})
</script>
</body>
注意修改后的第6行,我们在全局(window)上定义了一个名为cb(callback)的一个函数体,至于它起到的作用,我们很快会讲到。
现在既然我们前端将callback参数传递给了后端,现在后端自然要接收这个参数,并且,别忘记这么做的最终目的是为了凭借这个参数,我们能将后端的数据能够返回给前端。
于是我们将后端代码修改成这样:
js
const http = require('http')
http.createServer(function(req, res) {
const query = new URL(req.url, `http://${req.headers.host}`).searchParams
// 拿到前端传递的路径和地址
if(query.get('cb')) {
const cb = query.get('cb') // 'callback'
const data = 'hellow world' // 我们想向前端返回的数据
const result = `${cb}("${data}")` // 拼接数据:'callback('hellow world')'
res.end(result) // 返回给前端
}
// res.end('hello world')
}).listen(3000) // 项目运行在 3000 端口
现在重点来了:
在第4行,我们通过定义query来获取前端传递的路径与地址,因此在第8行我们就能通过query.get('cb')
获取到前端地址传递过来的参数callback,假设我们在第9行定义我们想要向前端返回hellow world
,只需要使用${cb}(${data})
将我们想要向前端返回的信息拼接上去即可!
好了,这时候后端返回callback的调用 ,我们提前在全局定义的函数体就能起作用了,现在后端返回的hellow world
就是实参,window[cb] = function (data)
中的data就是形参。
那么现在我们只需添加上resolve(data)
,14行的.then中就能得到我们想要的数据!
js
<body>
<script>
function jsonp(url, cb) {
return new Promise((resolve, reject) => {
const script = document.createElement('script'); // 创建一个script标签
window[cb] = function (data) { // 全局定义function callback(){}
resolve(data);
}
script.src = `${url}?cb=${cb}`; // 设置script标签的src属性
document.body.appendChild(script); // 将script标签添加到页面中
})
}
jsonp('http://localhost:3000', 'callback').then(res => {
console.log(res) // 输出后端返回的数据
})
</script>
</body>
随着"hollow world"在控制台出现的那一刻,艺术已成!实现跨域的整个过程可谓是精妙绝伦!
最后
JSONP跨域原理: 利用img标签不受同源策略的限制,于是我们就可以通过将ajax请求塞在src属性里面,再发送给后端。
实现跨域的代码过程:
- 借助script标签的src属性不受同源策略的影响,来发送请求。
- 给后端携带一个参数 callback, 并在前端定义 callback 函数体。
- 后端返回 callback 的调用形式并将要响应的值作为 callback 的实参。
- 当浏览器接收到响应后, 就会触发全局的 callback 函数从而让 callback 以参数的形式 接受到后端的响应。
缺陷:需要后端配合, 只能发get请求。
用白话来说,JSONP这种方法实现跨域的代码过程就是:前端先给后端一个参数,后端再把这个参数写成调用的样子,然后再把后端想要返回给前端的数据作为实参塞到这个调用体里来,最后整体返回给前端。