前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

JSONP原理及实现|博客系列

# JSONP原理及实现

# 基本原理

基本原理: 主要就是利用了 script 标签的src没有跨域限制来完成的。

# 执行过程

执行过程:

  • 前端定义一个解析函数(如: jsonpCallback = function (res) {})
  • 通过params的形式包装script标签的请求参数,并且声明执行函数(如cb=jsonpCallback)
  • 后端获取到前端声明的执行函数(jsonpCallback),并以带上参数且调用执行函数的方式传递给前端
  • 前端在script标签返回资源的时候就会去执行jsonpCallback并通过回调函数的方式拿到数据了。

# 优缺点

缺点:

  • 只能进行GET请求

优点:

  • 兼容性好,在一些古老的浏览器中都可以运行

# 案例分析

先来看看我们要实现一个什么效果:

在一个叫index.html的文件中有以下代码:

<script type='text/javascript'>
    window.jsonpCallback = function (res) {
        console.log(res)
    }
</script>
<script src='http://localhost:8080/api/jsonp?id=1&cb=jsonpCallback' type='text/javascript'></script>
@前端进阶之旅: 代码已经复制到剪贴板

然后我本地有一个文件server.js它会使用node提供一个服务,来模拟服务器。

并且定义一个接口/api/jsonp来查询id对应的数据。

当我打开index.html的时候就会加载script标签,并执行了此次跨域请求。

# 前期准备

  • 我在本地新建一个文件夹node-cors
  • 并在此目录下npm init,初始化package.json
  • 安装koa(node的一个轻量级框架)
  • 新建文件夹jsonp,并新建index.html和server.js,一个写前端代码,一个写后端
mkdir node-cors && cd node-cors
npm init
cnpm i --save-dev koa
mkdir jsonp && cd jsonp
touch index.html
touch server.js
@前端进阶之旅: 代码已经复制到剪贴板

# 后端代码

由于JSONP的实现需要前后端配合,先来写一下后端的实现:

(看不懂没关系,下面的前端简单实现会做解释)

const Koa = require('koa');
const app = new Koa();
const items = [{ id: 1, title: 'title1' }, { id: 2, title: 'title2' }]

app.use(async (ctx, next) => {
  if (ctx.path === '/api/jsonp') {
    const { cb, id } = ctx.query;
    const title = items.find(item => item.id == id)['title']
    ctx.body = `${cb}(${JSON.stringify({title})})`;
    return;
  }
})
console.log('listen 8080...')
app.listen(8080);
@前端进阶之旅: 代码已经复制到剪贴板

写完之后,保存。

并在jsonp这个文件夹下执行:

node server.js
@前端进阶之旅: 代码已经复制到剪贴板

来启动服务,可以看到编辑器的控制台中会打印出"listen 8080..."

# 前端简单实现

OK👌,后端已经实现了,现在让我们来看看前端最简单的一种实现方式,也就是写死一个script并发送请求:

index.html中:

<script type='text/javascript'>
    window.jsonpCallback = function (res) {
        console.log(res)
    }
</script>
<script src='http://localhost:8080/api/jsonp?id=1&cb=jsonpCallback' type='text/javascript'></script>
@前端进阶之旅: 代码已经复制到剪贴板

这两个script的意思是:

  • 第一个,创建一个jsonpCallback函数。但是它还没有被调用
  • 第二个,加载src中的资源,并等待请求的内容返回

整个过程就是:

  1. 当执行到第二个script的时候,由于请求了我们的8080端口,并且把id和cb这两个参数放到URL里。那么后台就可以拿到URL里的这两个参数。

  2. 也就是在后端代码中的const { id, cb } = ctx.query这里获取到了。

  3. 那么后端在拿到这两个参数之后,可能就会根据id来进行一些查询,当然,我这里只是模拟的查询,用了一个简单的find来进行一个查找。查找到id为1的那项并且取title。

  4. 第二个参数cb,拿到的就是"jsonpCallback"了,这里也就是告诉后端,前端那里是会有一个叫做jsonpCallback的函数来接收后端想要返回的数据,而后端你只需要在返回体中写入jsonpCallback()就可以了。

  5. 前端在得到了后端返回的内容jsonpCallback({"title":"title1"}),发现里面是一段执行函数的语句,因此就会去执行第一个script中的jsonpCallback方法了,并且又是带了参数的,所以此时浏览器控制台会打印出{ title: 'title1' }

以此来达到一个简单的跨域的效果。

其实你想想,如果我们把第二个script标签换成以下代码,是不是也能达到同样的效果呢?

<!-- <script src='http://localhost:8080/api/jsonp?id=1&cb=jsonpCallback' type='text/javascript'></script> -->
<script type="text/javascript">
    jsonpCallback({ title: 'title1' })
</script>
@前端进阶之旅: 代码已经复制到剪贴板

# jQuery中ajax实现

上面👆我们介绍了用script标签来实现,在jQuery的$.ajax()方法其实也提供了jsonp。

让我们一起来看看:

<script src="https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js"></script>
<script>
    $.ajax({
        url: "http://localhost:8080/api/jsonp",
        dataType: "jsonp",
        type: "get",
        data: {
            id: 1
        },
        jsonp: "cb",
        success: function (data) {
            console.log(data);
        }
    });
</script>
@前端进阶之旅: 代码已经复制到剪贴板

在success回调中同样可以拿到数据。

# 封装一个JSONP方法

(此章会一步一步教你如何封装一个比较完美的JSONP方法)

# 简易版

先看下我们要实现的功能

定义一个JSONP方法,它接收四个参数:

  • url
  • params
  • callbackKey:与后台约定的回调函数是用哪个字段(如cb)
  • callback:拿到数据之后执行的回调函数
<script>
    function JSONP({
        url,
        params = {},
        callbackKey = 'cb',
        callback
    }) {
        // 定义本地的一个callback的名称
        const callbackName = 'jsonpCallback';
        // 把这个名称加入到参数中: 'cb=jsonpCallback'
        params[callbackKey] = callbackName;
        //  把这个callback加入到window对象中,这样就能执行这个回调了
        window[callbackName] = callback;

        // 得到'id=1&cb=jsonpCallback'
        const paramString = Object.keys(params).map(key => {
            return `${key}=${params[key]}`
        }).join('&')
        // 创建 script 标签
        const script = document.createElement('script');
        script.setAttribute('src', `${url}?${paramString}`);
        document.body.appendChild(script);
    }
    JSONP({
        url: 'http://localhost:8080/api/jsonp',
        params: { id: 1 },
        callbackKey: 'cb',
        callback (res) {
            console.log(res)
        }
    })
</script>
@前端进阶之旅: 代码已经复制到剪贴板

这样写打开页面也可是可以看到效果的。

# 同时多个请求

上面我们虽然实现了JSONP,但有一个问题,那就是如果我同时多次调用JSONP:

JSONP({
    url: 'http://localhost:8080/api/jsonp',
    params: { id: 1 },
    callbackKey: 'cb',
    callback (res) {
        console.log(res) // No.1
    }
})
JSONP({
    url: 'http://localhost:8080/api/jsonp',
    params: { id: 2 },
    callbackKey: 'cb',
    callback (res) {
        console.log(res) // No.2
    }
})
@前端进阶之旅: 代码已经复制到剪贴板

可以看到这里我调用了两次JSONP,只是传递的参数不同。但是并不会按我们预期的在No.1和No.2中分别打印,而是都会在No.2中打印出结果。这是因为后面一个callback把JSONP里封装的第一个callback给覆盖了,它们都是共用的同一个callbackName,也就是jsonpCallback。如下所示:

两次结果都是从76行打印出来的。

所以我们得改造一下上面的JSONP方法:

  • 让callbackName是一个唯一的,可以使用递增
  • 不要把回调定义在window中这样会污染全局变量,可以把它扔到JSON.xxx中

OK👌,来看看改造之后的代码:

← HTTP相关总结从网络协议的角度聊一聊最近Github被大规模攻击事件 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合