【小哥哥, 跨域要不要了解下】JSONP

系列文章:

打算纯前端做一个接口测试工具, 直到遇到

这个报错, 触碰到了知识盲区了, 怎么办???

还好, 有谷哥和度娘. 原来是跨域 随手整理了一下常用的跨域方式处理方案, 这里马上分享给大家 ?

ps: 为了保证前后端编码的一致性, 本系列文章中涉及部分后端内容. 后端统一使用原生 nodejs 来搞, 请奔走相告.

准备工作

为了托管我们的静态页面, 我们需要一个可以提供服务器环境的插件, 这里推荐 live-server, 通过命令 npm i -g live-server 安装即可. 该插件支持html文件热更新. 那用户体验简直飞起. 一键启动, 只需要在需要托管的目录执行 live-server . 即可.

ps: live-server 依赖 nodejs, 没有安装的小伙伴, 请参照这篇文章安装 nodejs.

AJAX 访问接口跨域解决方案

首先, 更正几个常见的错误认识:

  1. 同源策略是浏览器的行为, 和 js 关系不大.
  2. 所谓跨域是指请求发起方页面所在的 url 与访问的 api 存在协议, 域名, 端口中任意一个不同即视为跨域. 并不单单是指域名.
  3. 跨域这个东西, 日常工作中并不是很常用. 你想, 谁会闲的没事儿干总是请求人家别人的 api 去.

jsonp

可能有小伙伴会说. 圈圈, 你扯淡, 既然浏览器有跨域限制. 为什么我司项目从 bootcdn, 引入的 jquery 依然跑在信息高速路上, 没有任何低头的意思?

hhh, ?. 这个质疑提的好. 浏览器同源策略禁止的是 ajax 请求. 然鹅, jquery 是一个 js 文件. 不受该策略的限制.

我尼玛, 那到底是啥限制啥不限制嘛???

根据 MDN (自备梯子), 对于浏览器的同源策略的解释, 不受限制的外域资源加载情况有以下几种:

  • script
  • link
  • img
  • video
  • object embed applet
  • font-face 有的浏览器允许, 有的禁止
  • frame

那么问题来了, 挖掘机学校..., 不好意思走错片场了. 既然有这么多方式可以绕过浏览器同源策略的限制. 那么, 是不是我们可以做一点事情呢 ^_^

答: 是的 ?.

那还不抓紧搞起来?

我们使用第一个特例 script 一步一步实现跨域访问 (jsonp).

  • 首先, 创建本次文章的项目目录
    目录中, be 代表是后端项目, fe 代表前端项目. jsonp 目录说明我们是用 jsonp 的方式实现跨域.
  • 在项目根目录下执行 live-server ./fe/jsonp/ 启动前端 web 容器
  • 编辑 ./fe/jsonp/ 目录下的 index.html 文件. 代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>jsonp 实现跨域</title>
</head>
<body>
    <h3>jsonp 实现跨域</h3>
</body>
</html>
复制代码
  • 浏览器访问 localhost:8080浏览器如下图说明前端 web 容器部署成功.

  • 编写后端代码, 编写 be/jsonp/index.js 文件, 文件内容如下

var http = require('http');
var PORT = 8888

// 创建一个 http 服务
var server = http.createServer(function(request, response) {
    response.end('hello world')
})

// 启动服务, 监听端口
server.listen(PORT, function() {
    console.log('服务启动成功, 正在监听: ', PORT)
})
复制代码
  • 编写完成后命令行执行 node ./be/jsonp/index.js 命令行中出现

    说明后端程序启动成功.此时可以通过浏览器访问 localhost:8888获得 hello world

  • 下来, 我们在前端的 index.html 中尝试通过 ajax 请求 http://localhost:8888/ 来获取返回数据, 添加如下代码, 添加以后代码

<script>
    var xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://localhost:8888/')
    xhr.send()
</script>
复制代码

回到浏览器, 查看页面控制台, 熟悉的错误出现了. Access to XMLHttpRequest at http://localhost:8888/ from origin http://127.0.0.1:8080 has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 这个错误说明了, 我们是不能通过 ajax 的方式从 http://127.0.0.1:8080 访问 http://localhost:8888/ 的.

既然不能通过 ajax 实现跨域的访问, 同时 mdn 又说 script 标签不受同源策略的限制. 那么, 我们尝试一下用 script 标签引入 http://localhost:8888/ 试试?

此时的代码, 网络请求没有问题. 知识报了 js 文件不合法的问题. 如果我们把接口返回的数据调整为规范的 js 是不是, 嗯哼???

干起, 修改后端代码, 返回的内容由 hello world 改为 console.log('hello world'), 修改后的代码(修改完后端代码以后切记重启服务哈 ^_^)

不得了, 不得了, 返回的结果不紧没有报错, 甚至可以执行. 我们从后端返回的 hellow world 成功的答应到了控制台了.

试想一下, 如果我们通过 js 文件里定义一个变量用于存放后端返回给前端的数据, 前端插入一个 script 标签, 把后端返回的变量定义执行一把. 那样定义的变量岂不是就可以在全局可以获取到后端定义的变量了. 赶紧试一把 ?

首先修改后端代码, 只需要调整一行.(修改完后端代码以后切记重启服务哈 ^_^)

response.end("var aaaa = {name: 'quanquan', friend: 'guiling'}");
复制代码

其次调整前端代码

<script>
    // 第一次因为还没有引入外部 js 所以打印 undefined
    console.log(window.aaaa)
    // 1 秒后, 外部 js 加载完成, 能打印出后端返回的变量定义
    setTimeout(() => {console.log(window.aaaa)}, 1e3)
</script>
<script src="http://localhost:8888/"></script>
复制代码

当前代码, 通过这种方式, 我们能够成功的获取到后端返回的数据. 但是, 接口这个东西时快时慢. 写个定时器轮询? 有点不够 666, 肿么办?

======================== 思考 5 分钟 ========================

======================== 5 分钟已过 ========================

既然, 写在 script 标签上的内容是可以直接执行的. 那么, 如果我们把变量的定义改写成一个函数的执行可不可以呢 ^_^, 试试?

后端(修改完后端代码以后切记重启服务哈 ^_^)

response.end("aaaa({name: 'quanquan', friend: 'guiling'})");
复制代码

前端

<script>
    // 由于后端返回的内容即将调用函数 aaaa, 那我们就预先定义一个呗, 这东西就叫回调函数
    function aaaa(param) {
        console.log('后端返回的参数是: ', param)
    }
</script>
<script src="http://localhost:8888/"></script>
复制代码

结果

此时代码, 目前为止, 我们已经彻底解决了跨域的问题. 很靠谱有木有? 当然木有. 这个玩意儿只是说明了 jsonp 的原理, 并没有实用性. 下一步, 我们做一点封装. 让我们的代码更健壮 ??

最后, 修改一把代码

前端

// 创建 Jsonp 类
// 初始化时传入两个参数, url 是接口的url
// cb 是对于接口返回的参数的处理
function Jsonp(url, cb) {
    this.callbackName = 'jsonp_' + Date.now()
    this.cb = cb
    this.url = url
    this.init()
}

// 初始化方法 用于拼接 url
Jsonp.prototype.init = function() {
    if(~this.url.indexOf('?')) {
        this.url = this.url + '&callback=' + this.callbackName
    } else {
        this.url = this.url + '?callback=' + this.callbackName
    }
    this.createCallback()
    this.createScript()
}

// 创建 script 标签, src 取接口请求的url
Jsonp.prototype.createScript = function() {
    var script = document.createElement('script')
    script.src = this.url
    script.onload = function() {
        this.remove()
        // 删除 window 下定义的无用方法
        delete window[this.callbackName]
    }
    document.body.appendChild(script)
}

// 绑定回调函数
Jsonp.prototype.createCallback = function() {
    window[this.callbackName] = this.cb
}

// 创建 jsonp 实例, 并指定回调函数
new Jsonp('http://localhost:8888/', function(data) {
    console.log(data)
})
复制代码

后端(修改完后端代码以后切记重启服务哈 ^_^)

const http = require('http');
// 新引入了 url 模块, 主要用于解析请求参数
const url = require('url');

const PORT = 8888;

// 创建一个 http 服务
const server = http.createServer((request, response) => {
  // 获取前端请求数据
  const queryObj = url.parse(request.url, true).query;
  // 这里把前端传来的 callback 字段作为后端返回的回调函数的函数名称
  response.end(`${queryObj.callback}({name: 'quanquan', friend: 'guiling'})`);
});

// 启动服务, 监听端口
server.listen(PORT, () => {
  console.log('服务启动成功, 正在监听: ', PORT);
});
复制代码

目前代码, 至此我们已经能够顺利的获取跨域资源了. ??.

下集预告: jsonp 是一种传统的跨域解决方案, 关于这种方式的优缺点, 请度娘, 下一节, 我们一起学习相对比较现代一点的跨域解决方案. CORS, See You

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值