你真的懂JSONP吗?

    JSONP是什么,在谷歌里面搜索JSONP,可以从维基百科里面得到它的定义:

JSONPJSON with Padding)是数据格式JSON的一种“使用模式”,可以让网页从别的网域要数据。

    可以看到,JSONP是一种可以从别的网域要数据的方法,这里所说的要数据,其实就是对服务器发送GET请求的意思。


    那么,在讲述如何利用JSONP发送GET请求之前,我们先来看看,在JSONP之前,大家是如何向服务器发送GET请求的。
    首先,说到发送请求,当然少不了form表单发送请求的方法。

<form action="#">
    <input type="submit">
</form>

    通过创建form标签,并在里面创建一个type为submit的input标签,就可以实现发送GET请求的功能了。当然了,使用form表单来发送请求的话,不仅可以发送GET请求,POST、PUT和DELETE请求也都可以发送,只要对form标签的method属性进行设置就行了。
    这样听起来,form表单好像“很万能”嘛,但是,form表单来发送请求,有一个很致命的缺点,那就是一旦提交请求,页面就会被刷新。


    那么,有没有什么好的办法来解决这种缺陷呢,有的,有两种办法:
    1.使用iframe标签

<form action="xxx" method="get" target="result">
  <input type="submit">
</form>
<iframe name="result" src="about:blank" frameborder="0" height=200></iframe>

    创建一个iframe标签,然后把form表单的target指向iframe的name,这样,每次在提交请求的时候,就不会刷新本页面了,而是刷新iframe里的页面,但是很明显的,这是一种相当不理想的办法,原因是页面有一个这样专门用来防止刷新的iframe很碍眼。
    2.所以就有了第二种方法,第二种方法就是:不用form表单提交请求。




    于是我们果断地,舍弃了form表单这种方法~
    那么,除了用form表单来发送请求,我们还有以下几种方法可以实现发送请求的功能:
    1.用a标签也可以发送get请求,但其问题和form表单一样,就是会刷新页面或新开页面。
    2.用img可以发送get请求,但是它的问题是得到的数据只能以图片的形式来展示。显然很多场景下这种办法并不符合需求。
    3.用 link 可以发送get请求,但是只能以CSS、favicon的形式展示。
    4.用 script 可以发送get请求,这种方法又称为SRJ方案,S为Server,R为Rendered,J为JavaScript。

    值得在这里说明的是,script标签是只能发送get请求的。
    具体实现的JS部分代码如下:

<body>
    <p style="color:red;">您的余额是<span id=amount>&&&amount&&&</span></p>
    <button id=button>付款</button>
    <script>
        $('#button').on('click',function(){
           let script=document.createElement('script') //创建script标签
           script.src='pay' //scipt标签的请求路径
           document.body.appendChild(script)
           script.onload=function(e){
                e.currentTarget.remove()
            } //当script标签加载完毕之后,删除script标签
           script.onerror=function(e){
                alert('fail'); 
                e.currentTarget.remove()
            }//当script标签请求失败之后,通知用户请求失败,并删除script标签
        })
    </script>
</body>

    服务器端部分代码:

  if(path === '/'){
    let string=fs.readFileSync('./index.html','utf8')
    let amount=fs.readFileSync('./db','utf8')
    string=string.replace('&&&amount&&&',amount)
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
  } else if (path === '/pay'){
    let amount=fs.readFileSync('./db','utf8')
    amount--
    fs.writeFileSync('./db',amount)  //改变数据库内账户余额数量
    response.statusCode = 200
    response.setHeader('Content-Type', 'application/javascript;charset=utf-8')
    response.write(`
      amount.innerText--
    `) //返回的script标签里的内容,返回后会执行,且比onload事件先执行
    response.end()
  }

    这里需要说明的是,db为通过文件系统自己创建的数据库,里面存有的是当前余额的数量。


    看到这里,或许你会觉得,咦,好像需求得到解决了,现在的方案听完善了吖感觉~
    是这样吗?NO!
    以上代码有什么问题呢,问题在于,后端程序员需要对前端的页面细节了得地十分清楚,存在着一个耦合的问题,为什么这么说呢,请看服务器端的代码,在response.write()这里,响应的内容是前端页面的代码细节,这就要求后端程序员对前端程序员所写的页面代码十分了解,这显然是不符合实际的。
    那要怎么改善呢?很简单,只要我们不返回细节,返回函数名来表示成功即可,然后在前端页面上定义这个成功时候执行的函数就可以了,具体服务器端的部分代码如下所示:

response.write(`
    callback.call(undefined,'success')
`)

    然后在前端页面中新建一个成功时候执行的callback函数:

window.callback=function(result) {
    alert('这里是前端代码')
    alert(`我得到的结果是${result}`)
}



    这里还有一个问题,那是不是后端程序员需要知道每个成功函数的函数名呢,其实并不需要,只要我们把这个函数名提交给服务器端就好了,所以下面我们进一步对其进行改进。
    以下为服务器端的部分代码:

response.setHeader('Content-Type', 'application/json;charset=utf-8')
response.write(`
    ${query.callback}.call(undefined,{
        "success":true,
        "left":${amount}
    })
`)

    前端页面的部分代码:

<body>
    <p style="color:red;">您的余额是<span id=amount>&&&amount&&&</span></p>
    <button id=button>付款</button>
    <script>
        $('#button').on('click',function(){
           let script=document.createElement('script') 
           script.src='/pay?callback=yyy' //函数名yyy作为查询参数被提交给服务器
           document.body.appendChild(script)
           script.onload=function(e){
                e.currentTarget.remove()
            } 
           script.onerror=function(e){
                alert('fail'); 
                e.currentTarget.remove()
            }
        })
        window.yyy=function(result){
            amount.innerText=result.left
        }
    </script>
</body>

    上面代码中,函数名yyy作为查询参数被提交给服务器,然后服务器端通过${query.callback}获取到了这个函数名,然后把调用这个函数名的代码作为响应发送回给客户端也就是前端页面,有人可能会发现,响应头response.setHeader中出现了改变,响应类型从javascript变成了json,没错,上面代码返回了一个JSON格式的代码片段:

{
    "success":true,
    "left":${amount}
}

    JSON两边的代码称为Padding,因此JSON+Padding=JSONP,这个就是JSONP这个名字的由来。
    JSON是一种语言,这种语言的详细语法就不在这里介绍了,大家可以通过在其官网(点击即可进入)上查看其语法,如果你学过JavaScript,那么这门语音的语法对你来说应该是通俗易懂的,因为JSON就是抄袭JavaScript的一门语言。
    客户端接收到了这个JSON格式的数据之后,会自动将其转换为可供JavaScript操作的对象供客户端操作,操作完成script标签自行销毁之后,整个请求响应的过程到这里就结束了。

说了那么多,我们来总结一下,什么是JSONP:

    1.请求方通过动态创建script标签,并使得script标签的src属性指向响应方即服务器端域名,同时传入一个查询参数“callback”作为请求成功之后页面要执行的函数名。
    2.响应方根据传入的查询参数“callback”来构造形如xxx.call(undefined,'你要的数据')这样的响应。而‘你要的数据’里面的语法是JSON这门语言的语法。
    3.浏览器接收到响应之后,就会执行响应里面的内容,并把这些JSON内容自动转换为可供JavaScript操作的对象,通过执行这些内容,请求方就会得到他想要的数据了。
    以上三个步骤合起来称为JSONP


    另外,这里提一下一个行业共识,就是传入的函数名一般不为yyy,而是一个随机数,你可以通过parseInt(Math.random()*1000000,10)来构造这个随机数。


    当你把以上我说的内容都搞清楚之后,代表你可以通过使用原生JS来实现JSONP了,那我在这里再说一下如何用jQuery来实现JSONP:

$.ajax({
    url:"http://127.0.0.1:8888/pay",
    dataType:"jsonp",
    success:function(response){
        if (response.success){
            amount.innerText=response.left
        }
    }
})

    注意注意:上述代码,和AJAX没有半毛钱关系!
    另外,再次强调,script标签是只能发送get请求的,因此JSONP也只能实现跨域发送get请求。


    以后当再有面试官问到你:请问JSONP为什么不支持post请求的呢?
    你可以拍拍胸膛告诉他:
    1.因为JSONP是通过动态创建script实现的。
    2.我们动态创建script的时候只能使用get,没有办法使用post。

    这样的回答,满分!~

    预告一下~下一次的文章将为大家讲述:就算你懂了JSONP,那你又懂AJAX吗?



本教程版权归宣泽彬所有,转载须说明来源

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

推荐阅读更多精彩内容