跨域原理(一) JSONP

跨域与同源策略:

什么是跨域?

跨域 :跨域访问问题指的是在客户端浏览器中,由于同源策略的限制,不允许从一个源直接访问另一个源的资源。当浏览器发起一个跨域请求时,会被浏览器拦截,并阻止数据的传输。

如上图,只有当协议域名端口都一致才叫同源。

同源策略 :为了保护用户的隐私和数据安全,浏览器就会通过实施同源策略 来限制不同源 之间的直接通信。但也有一些情况不受同源策略的限制,分别是: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属性里面,再发送给后端。

实现跨域的代码过程

  1. 借助script标签的src属性不受同源策略的影响,来发送请求。
  2. 给后端携带一个参数 callback, 并在前端定义 callback 函数体。
  3. 后端返回 callback 的调用形式并将要响应的值作为 callback 的实参。
  4. 当浏览器接收到响应后, 就会触发全局的 callback 函数从而让 callback 以参数的形式 接受到后端的响应。

缺陷:需要后端配合, 只能发get请求。

用白话来说,JSONP这种方法实现跨域的代码过程就是:前端先给后端一个参数,后端再把这个参数写成调用的样子,然后再把后端想要返回给前端的数据作为实参塞到这个调用体里来,最后整体返回给前端。

相关推荐
web150850966412 小时前
【React&前端】大屏适配解决方案&从框架结构到实现(超详细)(附代码)
前端·react.js·前端框架
理想不理想v2 小时前
前端项目性能优化(详细)
前端·性能优化
CodeToGym2 小时前
使用 Vite 和 Redux Toolkit 创建 React 项目
前端·javascript·react.js·redux
Cachel wood3 小时前
Vue.js前端框架教程8:Vue消息提示ElMessage和ElMessageBox
linux·前端·javascript·vue.js·前端框架·ecmascript
桃园码工4 小时前
4_使用 HTML5 Canvas API (3) --[HTML5 API 学习之旅]
前端·html5·canvas
桃园码工4 小时前
9_HTML5 SVG (5) --[HTML5 API 学习之旅]
前端·html5·svg
人才程序员5 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面
m0_548514775 小时前
前端三大主流框架:React、Vue、Angular
前端·vue.js·react.js
m0_748232395 小时前
单页面应用 (SPA):现代 Web 开发的全新视角
前端
Zhu_S W5 小时前
Java web的发展历史
面试·职场和发展