手记

你真的懂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 >您的余额是<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.=function(e){
                e.currentTarget.remove()
            } //当script标签加载完毕之后,删除script标签
           script.=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标签里的内容,返回后会执行,且比事件先执行
    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 >您的余额是<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.=function(e){
                e.currentTarget.remove()
            } 
           script.=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。

    这样的回答,满分!~



作者:宣泽彬
链接:https://www.jianshu.com/p/771181b1e437


0人推荐
随时随地看视频
慕课网APP