JSONP

JSONP

问:什么是 JSONP?

1. 问题引入

在日常开发中,前端程序员想要从后端请求一些数据,是如何操作的呢?如下图:

image

需求为:用户每点击一下“打钱”,上面的账户余额数量减1
如何实现?

首先,要修改动态数据必然是要请求数据库的数据,在此基础上进行修改,然后保存在数据库中,便于下一次使用。由此发现,在请求数据过程中必然是要请求外部链接。

然而在HTML标签中,可以发请求的标签屈指可数,只有<a href="">,<form action="">,<img src="">,<link rel="stylesheet" href="">,<script src=“”> 等。

我们的设想是,把数据请求包装成为一个 HTML 标签,然后让服务端对我发出的请求进行相应的操作,操作完成后返回给我一个操作成功或者失败的标志,同时修改页面中的数据为修改后的数据。

2.方案一:使用 <form>

<body>
<h3>您的余额为<span id="amount">&&&amount&&&</span></h3>
<form aciton="/pay" method="post">
    <input type="submit" value="付款">
</form>
</body>

考虑到 <form> 传输数据方式为 post 安全性较高, 然后用户在页面中操作的时候后台实现修改数据并告知用户操作成功与失败。

此方案看似可以,但是有个问题,后台每次都在新页面中返回个success 和 fail ,但当前页面的数据没有变化,需要用户点击后退再回到刚才的页面才可看到变化后的数据。

后台返回的标志在新页面中
点击后退后查看数据

我们发现,查看成功与失败的状态在两个不同的页面中,每一次用户点击“付款”都要回到原来的页面才能看到变化后数据,用户体验很差。

原因:在页面中使用<fomr>提交数据后一定会刷新页面

如何优化用户体验?
在页面中嵌入一个<iframe> 把数据放入 <iframe>中,这样刷新页面返回成功或者失败的时候只会刷新并显示在<iframe>中,而不会刷新当前页面,然后我们点击刷新后即可看到变化后的数据。

使用 iframe

显然,尽管优化后,用户体验依然不是很好。(ps:这是早期程序员开发网站时使用的方式)

3.方案二:使用 <img>

<body>
<h5>您的账户余额为<span id="amount">amount</h5>
<button id="button">打钱</button>
<script>
button.addEventListener('click',(e)={
  let image = document.creatElement('img')
  image.src =  '/pay'
  image.onload = function(){
    alert('success')
}
  image.onerror = function(){
    alert('fail')
    }
})
</script>
</body>
  
  //部分后端代码如下
  if(path == '/'){
    var string = fs.readFileSync('./index.html','utf8')
    response.setHeader('Content-Type', 'image/jpg; charset=utf-8')
    response.write(string)
    response.end()
  }
  

假设服务器允许该请求,在请求路径为/pay 时表示告诉服务器要请求数据,后台进行相对应的操作。

4.方案三:使用 <script>

<body>
    <h3>您的余额为<span id="amount">amount</span></h3>
    <button id="button">打钱</button>
    <script>
        button.addEventListener('click',(e)=>{
            let script = document.createElement('script')
            script.src = '/pay'
            document.body.appendChild(script)
        })
  </script>
</body>

细节:

  • 使用 <scirpt> 标签的传输 方式只能为 get ,没有 post 的方式
  • 创建 <script> 后必须得添加到 body 里面才能被执行,使用代码 document.body.appendChild(script)
  • 创建后的 <script> 会不断重复叠加,显得不好看。

改进:

    <script>
        button.addEventListener('click',(e)=>{
            let script = document.createElement('script')
            script.src = '/pay'
            document.body.appendChild(script)
        
          //当script执行完了以后就自动删除  
          script.onload = function(e){
                alert('success')
                e.currentTarget.remove();
            }
            script.onerror = function(e){
                alert('fail')
                e.currentTarget.remove();
            }
        })

    </script>

// 部分后端代码
if(path == '/'){
    var string = fs.readFileSync('./index.html','utf8')
    response.setHeader('Content-Type', 'text/html; charset=utf-8')
    response.write(string)
    response.end()
  }else if(path == '/pay'){
    var amount = fs.readFileSync('./db','utf8')
    var newAmount = amount - 1
    fs.writeFileSync('./db',newAmount)
    response.setHeader('Content-Type','application/javascript')
    response.statusCode = 200
    response.write(`amount.innerText = amountText - 1`)
    response.end
}else{
    response.statusCode = 404
    response.end()
}
  • 当script执行完了以后,无论成功或者失败,都自动删除
  • 验证方法? debugger

使用<script>请求另一个网站

思考:我们使用 jQuery 时,引用的是外部链接,然而外部链接 并不是我们自己写的,但是我们却可以使用它。而在上面我们发现,使用<script> 相比使用 <img>,<form> 都要方便的多,那么我们想,是否可以使用 <script> 请求另一个网站并且获取数据呢?

答案是可以的

假设有一个这样的网站 www.jack.com ,那么我们在请求的时候使用的地址应该为 script.src = 'http://www.jack.com/pay'

同样的,假设网站 www.jack.com 也允许访问,同时写好了对应地方后端代码提供相应的数据,那么,使用 <script>是可以访问这个网站并且请求到数据的。

但是,并不是每个网站的后端程序员都知道前端代码的请求操作去写类似的 response.write(amount.innerText = amountText - 1) ,事实上,依据代码分离的原则,前端和后端不应该有这样的代码耦合(即前后端的代码依赖程度高)

这时候怎么办呢?JSONP出现了!

为了实现代码分离,更好的进行解耦 ,我们这样设想:

<script> 中把要进行操作的代码写在一个函数中,然后在请求数据的时候加入这个请求函数名作为请求参数(函数名),例如:

window.yyy = function(result){
  if(result === 'success'){
    response.write(amount.innerText = amountText - 1)
  }else{
    alert('fail')
  }
}
script.src = 'http://www.jack.com/pay?callbackName=yyy

然后后端程序员看到这个函数名参数以后,把整理好的数据放入这个函数的第一个参数中返回给前端,然后就实现了上述操作。例如:

response.write(`${query.callbackName}.call(undefined,'success')`)

这就是 JSONP的实现原理!

具体为:返回的函数的第二个参数为一个对象

response.write(`${query.callbackName}.call(undefined,{
  "success":true,
  "left":${newAmount}
})`)
Image 3.png

如图:代码高亮部分为 JSON ,高亮 左侧部分为 左padding ,高亮右侧的反括号为 右padding ,由此组成了 JSONP

下面,我们给JSONP做个定义

JSONP

前台方面:浏览器发出请求,例 :jsmond.com

后台方面:服务器接受并响应请求,例: manager.com

1.请求方创建 script ,src 指向响应方,同时传一个查询参数 ?callbackName=yyy

2.响应方根据查询参数 callbackName ,构造形如

​ (1) yyy.call(undefined,"你要的数据")

​ (2) yyy('你要的数据')

​ 这样的响应

3.浏览器接收到响应后,就会执行 yyy.call(undefined,'你要的数据')

4.那么请求方就知道了他要的数据

约定:

  1. callbckName -> callback
  2. yyy - >随机数 frank123123123123()

这就是JSONP

<body>
    <h3>您的余额为<span id="amount">&&&amount&&&</span></h3>
    <button id="button">打钱</button>
    <script>
        button.addEventListener('onclick',(e)=>{
            let script = doucment.createElement('script')
            let functionName = 'frank'+parseInt(Math.random()*10000000,10)
            window[functionName] =  funciton(result){
                if(result === 'success'){
                    amount.innerText = amount.innerText - 1
                }else{}
            }
            script.src = 'http://www.jack.com/pay?callback='+functionName
            document.body.appendChild(script)
            script.onload = funciton(e){
                e.currentTarget.remove()
                delete window[functionName]
            }
            script.onerror = funciton(e){
                alert('fail')
                e.currentTarget.remove
                delete window[functionName]
            }
        })
    </script>
</body>

知道 以上原理以后,我们可以开始使用 jQuery

首先引用 jQuery,关于 JSONP 的API 和使用方法可以参考 jsonp-jquery

$.ajax({
  dataType: "json",
  url: "http://www.jack.com/pay",
  data: data,
  success: function(rewponse){
    if(response === 'success'){
      amount.innerText = amount.innerText - 1
    }
  }
});

注意:上面的 $.ajaxAJAX 没有半毛钱关系,它的本质还是上面我们说到的 动态<script>

  • 为什么JSONP不支持POST?

    1.因为JSONP是通过动态创建<script>实现的

    2.动态创建script 的时候只能用  get 而无法使用 post

参考链接:

JSONP--知乎

JSONP为什么不支持POST请求?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345