关于前端原生技术-Jsonp的理解与简述

news2024/11/19 7:46:24

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://blog.csdn.net/m0_69908381/article/details/134777717
出自【进步*于辰的博客】

在学习了Jsoup这个知识点之后,发觉js的这一特点真的很强大—— 动态解释 \color{brown}{动态解释} 动态解释
本文以实用性的角度对Jsonp进行阐述,故在有些方面不是很详细或“不到位”,推荐一篇博文【jsonp原理详解——终于搞清楚jsonp是啥了】(转发)。
参考笔记三,P47。

文章目录

  • 1、jsonp是什么?
  • 2、jsonp的原理
    • 2.1 基本思路
    • 2.2 扩展实现
    • 2.3 补充说明
  • 3、一个比较好的示例
  • 4、最后

1、jsonp是什么?

在学习jsonp之前,我们先来了解一个概念—— 同源策略 \color{green}{同源策略} 同源策略

“同源策略”是为了保证服务器的安全性,从而对客户端请求进行一定限制的规定,规定当客户端与服务端在协议、域名(IP地址)、端口都相同时才允许访问。

若有一种不同时仍进行访问,称之为 “跨域访问” \color{red}{“跨域访问”} 跨域访问

由于js文件的访问不受“同源策略”限制,故若将请求“模拟”成js请求(以引入js文件<script>的形式发送请求,服务端响应js脚本),便可解决“跨域”问题,这种“模拟”或“格式约定”就是jsonp,它是一种非正式的传输协议。
示例。

<script src="http://127.0.0.1:8081/j1.js"></script>

除了js请求外,凡是具有src属性的标签同样不受“跨域”限制。
示例。

<img src="http://127.0.0.1:8081/1.jpg">
<iframe src="http://127.0.0.1:8081/1.html"></iframe>

2、jsonp的原理

2.1 基本思路

从以上对jsonp的简述可以得出如下结论:

jsonp的原理是由客户端发出请求,服务端响应js脚本,再对js脚本进行“动态解释”。因此,客户端的请求方法任意(前提是客户端处理服务端响应的运行环境使用的是 “解释” \color{green}{“解释”} 解释)。
注意:这里说的是“请求方法”(如:<script>、ajax),不是“请求方式”(如:get、post)。

这个结论与上面简述有所不同,上面说的是“以引入js文件<script>的形式发送请求(下文简称“js请求”)”,而这里却是“任意请求方法”,为什么?而且这段总结看起来云里雾里,下面我会用一个个示例逐步进行说明。。。

大家注意一个关键词“动态解释”,什么意思呢?就是当服务端响应后,客户端对响应体进行解释,将其加入到当前上下文中,并同时执行。(这就是为何服务端响应必须是js脚本的原因)
可能不太好理解,举几个例子。

客户端仍是:

<script src="http://127.0.0.1:8081/j1.js"></script>

1、若服务端中j1.js的代码是:

console.log("1'm js脚本.")

则会在控制台打印1'm js脚本.

2、若j1.js的代码是:

var test = confirm('You need a js脚本?')
if(test)
    console.log('1'm js脚本.')
else
    console.log('I can't help it.')

则会弹出确认框,显示的消息是You need a js脚本?

3、若j1.js的代码是:

var script = document.querySelector('script')
console.log(script.src)

则会在控制台打印http://127.0.0.1:8081/j1.js

Y o u \color{red}{You} You a r e \color{red}{are} are O K 。 \color{red}{OK。} OK

客户端是通过<script>标签发送js请求(写死了。。。),我们改一下。

var script = document.createElement('script')
script.src = 'http://127.0.0.1:8081/j1.js'
document.body.append(script)

这种形式有一个名称—— “动态添加 j s 脚本” \color{blue}{“动态添加js脚本”} 动态添加js脚本

留言: \color{brown}{留言:} 留言:走到这一步,大家就知道jsonp是如何实现“跨域”了吧。。。

可是,如果仅仅是这样,并没有什么意义,因为我们要实现的是客户端与服务端之间的数据交互。

既然客户端都会对服务端响应的js脚本进行解释(动态解释),那么我们可以换一个思维。

服务端怎样能在js脚本中将数传递给客户端呢?一个典型的办法: 函数 \color{purple}{函数} 函数。也就是这样:
客户端代码。

var script = document.createElement('script')
script.src = 'http://127.0.0.1:8081/j1.js'
document.body.append(script)

var handlerRes = function(res) {
	console.log(res)
}

j1.js的代码。

handlerRes('I"m js脚本')// 函数调用语句
// 在客户端中,handlerRes()称为“回调函数”

这样就将字符串'I"m js脚本'传递给客户端了。

还不够,这只是实现了服务端 → 客户端的数据传递,若要实现客户端 → 服务端的数据传递,则需要借助request对象,这就需要在服务端搭建服务器。

这里服务器是通过Node.js中的express模块进行搭建,详述可查阅博文【JS服务端技术—Node.js知识点锦集】的第4项。

我们再改一下,也就是向服务器路由发送js请求。
客户端代码:

var script = document.createElement('script')
script.src = 'http://127.0.0.1:8081/g1?userid=1001'
document.body.append(script)

var handlerRes = function(res) {
	console.log(res)
}

服务器在j1.js中搭建,代码是:

const express = require('express')

const ser = express()
ser.get('/g1', (req, res) => {
    var id = req.query.userid
    console.log(id)// 打印:1001
})

ser.listen(8081, () => {
    console.log('create3')
})

这样客户端 → 服务器的数据传递就实现了,可是服务器怎么响应呢?响应需要使用res对象,难道是这样?

res.send(id)

没错,在F12的网络那里可以看到,响应是1001。的确成功响应了,可是没有意义(客户端没反应。。。或者说客户端处理不了这个1001)。怎么办呢?

前面提到,只要服务器响应js脚本,客户端就可以进行“动态解释”。那么我们就把这个1001放进js脚本中,也就是这样:

res.send('handlerRes(' + id +')')

这样响应体就是'handlerRes(1001)',解释后打印1001

到这一步,基本功能实现了。。。既实现了客户端与服务器的数据交互(请求-响应),又解决了“跨域”问题。

2.2 扩展实现

综上所述,jsonp解决“跨域”问题的代码是这样:
客户端代码。

var script = document.createElement('script')
script.src = 'http://127.0.0.1:8081/g1?userid=1001'
document.body.append(script)

var handlerRes = function(res) {
	console.log(res)
}

服务端代码。

const express = require('express')

const ser = express()
ser.get('/g1', (req, res) => {
    var id = req.query.userid
    res.send('handlerRes(' + id +')')
})

ser.listen(8081, () => {
    console.log('create3')
})

虽然功能实现了,不过代码的整体情况差强人意。例如:

  1. 每次客户端都需要动态添加<script>;(有点“冗余”)
  2. 服务器必须提前知道回调函数名是什么。

这样很不方便,我们可以再换个思维。。。在我们平时使用的技术中,哪一种跟上述这种情况类似?没错, a j a x \color{red}{ajax} ajax,于是客户端可以这样优化:

$.ajax({
    url: 'http://localhost:8081/g1?userid=1001',
    type: 'get',
    success: res => {
        console.log(res)
    }
})

var handlerRes = function(res) {
	console.log(res)
}

这样就出现了一个问题—— 跨域 \color{red}{跨域} 跨域。因为这只是一个普通的ajax异步请求,而不是js请求,自然受“同源策略”限制。

无妨,我们在服务器加上这一条代码:

res.set('access-control-allow-origin', '*')// 允许任意请求

这是解决“跨域”问题的另一种方法(在此不讨论),不过,这样不就直接解决了“跨域”问题,与jsonp有什么关系?没错,的确没有直接关系。我这样写是为了便于大家看到效果,继续看。。。

现在第一个问题解决了,第二个问题如何解决?我们可以给请求加一个参数funName,值为回调函数名。也就是这样:
客户端代码。

$.ajax({
    url: 'http://localhost:8081/g1?userid=1001&funName=handlerRes',
    type: 'get',
    success: res => {
        console.log(res)
    }
})

var handlerRes = function(res) {
	console.log(res)
}

服务端代码。

const express = require('express')

const ser = express()
ser.get('/g1', (req, res) => {
    res.set('access-control-allow-origin', '*')
    var id = req.query.userid
    var fn = req.query.funName
    res.send(fn + '(' + id +')')
})

ser.listen(8081, () => {
    console.log('create3')
})

这样处理后,响应结果同上,即'handlerRes(1001)'。既然响应体相同了,是不是效果也相同?完全不同。
因为上一种是js请求,客户端会对响应体中的js脚本进行“动态解释”,即'handlerRes(1001)'会被“解释”为回调函数的调用语句,因此打印1001;而ajax并不会对响应体进行处理,即会将'handlerRes(1001)'视为字符串直接打印(这样客户端就无法处理1001,所以还不行。。。)。

ajax与jsonp的区别在哪?就是是否会对响应体进行“动态解释”。也就是说,如果ajax也能实现“动态解释”,这个优化就成功了。。。

还真有。 \color{red}{还真有。} 还真有。由于ajax与jsonp这两种技术在调用方式和目的上都很相似,故jq把jsonp作为ajax的一种形式进行了封装,就是这样:

$.ajax({
    url: 'http://localhost:8081/g1?userid=1001',
    type: 'get',
    dataType: 'jsonp',
    jsonp: 'funName',
    jsonpCallback: 'handlerRes',
    success: res => {
        console.log(res)
    }
})

var handlerRes = function(res) {
	console.log(res)
}

可见,多了三个属性dataTypejsonpjsonpCallback,大家对ajax都很熟悉了,dataType是响应体的数据类型;后面两个大家可能没见过,它们的含义暂不多言(大家结合上文便可知是做什么的)。
同时将服务器中的“跨域”设置去掉,最终的结果是:
在这里插入图片描述
在这里插入图片描述
这样大家就明白了吧(请求行和响应体与上一个示例相同)。我们再看一下控制台:
在这里插入图片描述
??? \color{red}{???} ???怎么突然就既实现了“动态解释”,又解决了“跨域”问题?

这就是dataType: 'jsonp'的作用了,也就是jq在ajax上对jsonp的封装。即:

dataType: 'jsonp'设置使得客户端将响应体视为js脚本进行“动态解释”,从而解决“跨域”问题。

留言: \color{purple}{留言:} 留言:言至于此,大家都已经完全明白了在使用ajax请求时如何通过jsonp解决“跨域”问题了吧。。。

思考: \color{red}{思考:} 思考:

  1. 另外两个属性到底是做什么的?或者说有没有必要自定义?
  2. 为什么控制台打印了两次1001

2.3 补充说明

上面我留下了两个思考,我先回答第二个,因为:

  • 首先,客户端将响应的字符串'handlerRes(1001)'视为js脚本进行“动态解释”,结果为回调函数的调用语句,于是打印一个1001
  • 然后,设置了dataType: 'jsonp'的ajax请求可是说是js请求,但本质仍是ajax请求。成功的ajax请求自然会调用success(res),又打印一个1001

所以控制台打印了两个1001

旁白:我们IT人士普遍有一个“通病”——强迫症,或者说是“严谨”。

ajax请求既会调用success(res),也会调用回调函数(也就是有两个位置可以处理响应体),这就有点“不严谨”了。换言之,肯定有某种情况的配置可以做到只有一个位置。

这里唯一可以配置的地方就只有jsonpjsonpCallback这两个属性,那我们就详细了解一个这两个属性:

jsonp的思想就是将响应的js脚本进行“动态解释”,从而添加进上下文,并执行,故需要js脚本中包含回调函数的调用语句。js脚本由服务器提供,那么服务器就必须知道回调函数名是什么,这个名称自然由客户端提供,那么,客户端在请求时会将回调函数名以某种形式(添加参数)封装在请求中(请求行),例如:funName=handlerRes

  • jsonp:值为回调函数名对应的请求参数名,默认值为'callback'
  • jsonpCallback:值为回调函数名,默认值为由jq生成的随机字符串。

大致介绍是这样,效果如下:(在都不配置的情况下)
在这里插入图片描述
现在我们写一个完整的示例。

客户端代码。

$.ajax({
    url: 'http://localhost:8081/g1?userid=1001',
    type: 'get',
    dataType: 'jsonp',
    success: res => {
        console.log(res)
    }
})

服务器代码。

const express = require('express')

const ser = express()
ser.get('/g1', (req, res) => {
    var id = req.query.userid
    var fn = req.query.callback
    res.send(fn + '(' + id +')')
})

ser.listen(8081, () => {
    console.log('create3')
})

O f \color{red}{Of} Of C o u r s e !! \color{red}{Course!!} Course!!控制台只打印了一个1001,功能实现。

能不能再简化一点?这样:

ser.get('/g1', (req, res) => {
    var id = req.query.userid
    res.send('xx(' + id +')')
})

响应体是函数xx()的调用语句,就是忽略客户端发来的回调函数名。看一下控制台:
在这里插入图片描述
报错了,因为找不到回调函数xx(),这样客户端会认为这个ajax请求失败了,也就不会调用success()(ajax还有一个属性error(e),大家可以自行测试)。所以:

若想success(res)中的res能接收到响应数据(指服务器响应的js脚本中函数调用语句的实参),要求服务器响应的js脚本中的回调函数名必须与参数jsonpCallback的值相同。

也就是这样:

$.ajax({
    url: 'http://localhost:8081/g1?userid=1001',
    type: 'get',
    dataType: 'jsonp',
    jsonpCallback: 'xx',
    success: res => {
        console.log(res)
    }
})

或者都不指定(使用默认值),那么服务器响应的js脚本中的回调函数名与参数jsonpCallback的值都是jQuery37107813896465729704_1702010749715(随机字符串),故res可成功接收响应数据。

吐槽:研究了半天,转了这么一大弯,最后就只是在ajax那里添加一个属性dataType: 'jsonp'就解决了“跨域”问题,这不消遣人嘛。。。哈哈,虽同归,但殊途,我们这么研究过来就对jsonp有了一个全面的认识,并且对前端原生的底层逻辑有了更深的了解,而不是“套用式”学习。

3、一个比较好的示例

虽然上面已经可以实现“跨域访问”,但有点单调了(服务器的响应数据是1001,仅是一个数字)。我们来补充一下:

客户端代码。

$.ajax({
    url: 'http://localhost:8081/g1?userid=1',
    type: 'get',
    dataType: 'jsonp',
    success: res => {
        try {
            var user = JSON.parse(res)
            console.log(user)
            console.log('用户名:' + user.name)
        } catch(e) {
            console.log(res)
        }
    }
})

服务器代码。

const express = require('express')

const ser = express()

var users = [
    {
        id: 1001,
        name: '进步',
        pass: '2023'
    }, {
        id: 1002,
        name: '于辰',
        pass: '2021'
    }
]

ser.get('/g1', (req, res) => {
    var id = req.query.userid
    var fn = req.query.callback
    var result = users.find(item => {
        if (item.id == id) {
            var jsonstr = JSON.stringify(item)
            res.send(fn + "('"+ jsonstr + "')")
            return true
        }
    })
    if(!result)// 未找到
        res.send(fn + "('未找到')")
})

ser.listen(8081, () => {
    console.log('create3')
})

控制台:
在这里插入图片描述
如果userid1003,则:
在这里插入图片描述
这是不是大家期望的效果?

留言: \color{red}{留言:} 留言:至于为何要将对象(js对象 / json对象)转为json字符串再进行响应,这一点我暂未作研究,似乎与“数据在网络传输时格式必须是字符串”有关。当然,也是可以将jsonstr换成item,只是客户端无法处理。

4、最后

首先,相信本文能帮助到大家。
在本文中,我写了很多个示例,大家可能看得有点晕。。。无妨,大家不用注意示例中具体的业务或目的,主要留意“前后变化”即可,关键是技术的运用,示例本身无关紧要。
jsonp其实很简单,就是实现对服务器响应的js脚本的“动态解释”,剩下的就是对js上下文的操作了。
js是一种“弱类型”的编程语言,在某种程度上说“很灵活”或者说“解释功能很强大”,所以“用函数调用语句将响应数据带回客户端”只是其中一种方法,至于其他方法需要大家自行扩展了。(本人暂且只想到这一种较好的方法,因此以这种方法作为阐述jsonp的示例)

本文完结。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1294630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2、Redis变慢原因排查(下)

感觉Redis变慢了&#xff0c;这些可能的原因你查了没 &#xff1f;(下) Redis变慢排查的上一篇【感觉Redis变慢了&#xff0c;这些可能的原因你查了没 &#xff1f;(上)】&#xff0c;我们是基于Redis命令为入口&#xff0c;比如命令使用不得当&#xff0c;bigkey问题&#xf…

Vue3组件使用问题

Vue3组件学习 文章目录 Vue3组件学习一、Message 全局提示组件返回数据换行问题二、DatePicker 日期选择框组件限制选定年份问题 一、Message 全局提示组件返回数据换行问题 问题&#xff1a;使用中发现仅仅通过写入\n或<br/>&#xff0c;无法实现回车显示的结果。 解决…

网站高性能架构设计——web前端与池化

从公众号转载&#xff0c;关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、高性能浏览器访问 1.减少HTTP请求 HTTP协议是无状态的应用层协议&#xff0c;也就是说每次HTTP请求都需要建立通信链路、进行数据传输&#xf…

Swagger Array 逐步解密:带你简化开发工作

Swagger 允许开发者定义 API 的路径、请求参数、响应和其他相关信息&#xff0c;以便生成可读性较高的文档和自动生成客户端代码。而 Array &#xff08;数组&#xff09;是一种常见的数据结构&#xff0c;用于存储和组织多个相同类型的数据元素。数组可以有不同的维度和大小&a…

想要高速文件传输?这些Aspera替代方案等你来试

在现如今数字化的时代&#xff0c;文件传输已成为企业、组织以及个人日常工作中必不可少的一部分。但是&#xff0c;面对庞大的数据量和低效的传输速度&#xff0c;很多人会感到头疼和无奈。在这样的情况下&#xff0c;高速文件传输工具成为了一个热门话题。而aspera替代方案则…

React/Vue/Svelte 前端项目中开始使用TailwindCSS

背景 TailwindCSS 近年来在前端圈非常流行&#xff0c;它摆脱了原有的CSS限制&#xff0c;以灵活实用为卖点&#xff0c;用户通过各种class组合即可构建出漂亮的用户界面。对于初学者而言&#xff0c;可能需要一些上手成本&#xff0c;一旦掌握实用技巧后&#xff0c;Tailwind…

JetBrains Rider for Mac/win中文版- 跨平台.NET 开发的终极选择!

在.NET开发世界中&#xff0c;JetBrains Rider凭借卓越的性能和丰富的功能成为了开发者的首选。JetBrains Rider是一款跨平台.NET集成开发环境&#xff08;IDE&#xff09;&#xff0c;可在Windows和macOS上无缝运行&#xff0c;为.NET开发提供了卓越的工作体验。 JetBrains R…

C++新经典模板与泛型编程:用成员函数重载实现std::is_convertible

用成员函数重载实现is_convertible C标准库中提供的可变参类模板std::is_convertible&#xff0c;这个类模板的主要能力是判断能否从某个类型隐式地转换到另一个类型&#xff0c;返回的是一个布尔值true或false。例如&#xff0c;一般的从int转换成float或从float转换成int&am…

相控阵天线(十四):常规大阵列天线分布(椭圆阵列、三角阵列、矩形拼接阵列、栅格拼接阵列)

目录 简介椭圆阵列三角阵列子阵拼接的矩形阵列子阵拼接的圆形阵列圆形子阵拼接阵列子阵栅格拼接阵列 简介 前面的博客已经介绍过常见的平面阵有一些基本类型&#xff0c;本篇博客介绍一些实际工程中可能出现的阵列&#xff0c;包括椭圆阵列、子阵通过矩形拼接形成的矩形大阵列…

题目:分糖果(蓝桥OJ 2928)

题目描述&#xff1a; 解题思路&#xff1a; 本题采用贪心思想 图解 题解&#xff1a; #include<bits/stdc.h> using namespace std;const int N 1e6 9; char s[N];//写字符串数组的一种方法,像数组一样***int main() {int n, x;cin >> n >> x;for(int …

有哪些已经上线的vue商城项目?

前言 下面是一些商城的项目&#xff0c;需要练手的同学可以挑选一些来练&#xff0c;废话少说&#xff0c;让我们直接开始正题~~ 1、newbee-mall-vue3-app 是一个基于 Vue 3 和 TypeScript 的电商前端项目&#xff0c;它是 newbee-mall 项目的升级版。该项目包含了商品列表、…

EXP-00056: 遇到 ORACLE 错误 12154 ORA-12154: TNS: 无法解析指定的连接标识符

exp oas/oasoas filed:\daochu.dmp owner(s) 导出特定用户 //exp 用户名/密码数据库 filed:\daochu.dmp owner(用户名) 1.重启oracle监听 cmd 中输入 services.msc 找到服务&#xff1a;OracleOraDb10g_home1TNSListener 与 OracleServiceORCL。 把两个服务启动. 若未解决…

CRM系统选择技巧,什么样的CRM系统好用?

SaaS行业发展迅速&#xff0c;更多的企业逐渐选择CRM管理系统。打开搜索引擎&#xff0c;有非常多的结果。怎样在数十万个搜索结果中选择适合您的CRM系统&#xff1f;下面我们将聊聊&#xff0c;怎样选择CRM系统。 第一步&#xff1a;明确自身需求 重要性&#xff1a;每家企业…

机器学习与低代码开发:创新驱动的双剑合璧

引言 随着科技的日新月异&#xff0c;机器学习和低代码开发已经成为引领技术行业变革的两大重要趋势。机器学习通过模拟人类的学习方式&#xff0c;让计算机具备了自我学习和预测的能力&#xff0c;打破了传统计算机程序的局限性。而低代码开发则以简化软件开发过程为目标&…

leetcode 1466

leetcode 1466 如图 node 4 -> node 0 -> node 1 因为节点数是n, 边长数量是n-1。所以如果是从0出发的路线&#xff0c;都需要修改&#xff0c;反之&#xff0c;如果是通向0的节点&#xff0c;例如节点4&#xff0c;则把节点4当作父节点的节点&#xff0c;之间的路线的方…

土壤水分传感器土壤体积含水率含量监测仪器

产品概述 外型小巧轻便&#xff0c;便于携带和连接。 土壤水分传感器由电源模块、变送模块、漂零及温度补偿模块、数据处理模块等组成。传感器内置信号采样及放大、漂零及温度补偿功能&#xff0c;用户接口简洁、方便。 功能特点 ◆本传感器体积小巧化设计&#xff0c;测量…

妙手ERP功能更新:TikTok支持打印10×10面单、Ozon支持设置最低售价、超过90天的Shopee订单买家信息不再显示......

为了给卖家朋友带来更好的使用体验&#xff0c;更高效地运营跨境店铺&#xff0c;妙手ERP在上周优化了以下多项功能。 01、产品模块优化 全平台 - 自定义平台SKU时&#xff0c;连接符支持为空或可输入其他符号&#xff08;不支持输入数字、emoji、文字&#xff09; Ozon - 支…

modbus转profinet网关解决plc插槽号不够用的情况

PLC作为常用的控制设备之一&#xff0c;其插槽号有时会限制外部设备的连接数量。然而&#xff0c;通过使用modbus转profinet网关&#xff0c;可以解决这一问题。这种设备能够将modbus协议转换为profinet协议&#xff0c;实现PLC与更多外部设备的连接。 modbus转profinet网关还具…

【ET8】1.ET8入门-运行指南

主要学习网址 论坛地址为&#xff1a;https://et-framework.cn Git地址为&#xff1a;GitHub - egametang/ET: Unity3D Client And C# Server Framework 官方QQ群 : 474643097 项目检出 检出项目切换到release8.0分支 GitHub地址&#xff1a;GitHub - egametang/ET: Unity…

大数据毕业设计之前端02:架构布局和aside的设计

前言 上一篇主要讲了我学习前端的一个经历&#xff0c;以及为什么选择BuildAdmin作为深入前端学习的原因.同事也大致聊了一下学习前端需要使用哪些技术栈。 本篇文章来拆解一下BuildAdmin的前端代码结构&#xff0c;和布局实现的细节。 前端代码结构 必须先了解项目的结构&…