什么是 JSONP?

JSONP 利用了具有 src 属性的标签引用资源不受同源策略的影响的特性。通过 <script> 标签向服务器端发送请求时在 URL 中通过参数把一个回调函数的名称告知服务端,服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

JSONP 的优点是简单,可以跨域,老式浏览器支持好。

  1. JSONP 是通过 <script> 标签加载数据的方式去获取数据并且当做 JS 代码来执行。

  2. 需要提前在页面上声明一个函数,以备后台返回数据后执行。

JSON 与 JSONP 的区别

JSON 是一种数据交换格式,是 JavaScript 的一个子集。

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

JSONP 为什么不支持 POST ?

因为 JSONP 是通过动态创建 <script> 标签实现的, <script> 标签只能发送 GET 请求,无法发送 POST 请求。

用 form 表单提交请求

用form表单提交请求提交表单后会跳转到新页面,用户体验不好。

当然我们也可以使用 iframe ,不过 iframe 有点过时了。

<h5>您的账户余额是<span id="amount">&&&amount&&&</span></h5>
<form action="/pay" method="post">
    <input type="submit" value="付款">
</form>
<h5>您的账户余额是<span id="amount">&&&amount&&&</span></h5>
<form action="/pay" method="post" target="result">
    <input type="submit" value="付款">
</form>
<iframe name="result" src="about:blank" frameborder="0" height=200></iframe>

后端代码:

if(path === '/pay'){
    var amount = fs.readFileSync('./db','utf8');
    var newAmount = amount - 1;
    fs.writeFileSync('./db',newAmount);
    response.write('success');
    response.end();
}

局部刷新

方案一:用图片造 get 请求

button.addEventListener('click', (e)=>{
    let image = document.createElement('img')
    image.src = '/pay'
    image.onload = function(){
        alert('成功')
    }
    image.onerror = function(){
        alert('失败')
    }
})

方案二:用 script 造 get 请求

button.addEventListener('click', (e)=>{
    let script = document.createElement('script')
    script.src = '/pay'
    document.body.appendChild(script)
    script.onload = function(e){
        e.currentTarget.remove()
    }
    script.onerror = function(e){
        e.currentTarget.remove()
    }
})

//后端代码
...
if (path === '/pay'){
    let amount = fs.readFileSync('./db', 'utf8')
    amount -= 1
    fs.writeFileSync('./db', amount)
    response.setHeader('Content-Type', 'application/javascript')
    response.write('amount.innerText = ' + amount)
    response.end()
}
...

JSONP

方案3:JSONP

请求方:frank.com 的前端程序员(浏览器)
响应方:jack.com 的后端程序员(服务器)

请求方创建 script,src 指向响应方,同时传一个查询参数 ?callback=xxx
响应方根据查询参数callback,构造形如
xxx.call(undefined, '你要的数据')
xxx('你要的数据')
这样的响应
浏览器接收到响应,就会执行 xxx.call(undefined, '你要的数据')
那么请求方就知道了他要的数据
这就是 JSONP

读者可以自行对比以下两个 URL 的所返回的内容有何不同:

http://api.douban.com/v2/movie/in_theaters

http://api.douban.com/v2/movie/in_theaters?callback=helloWorld

button.addEventListener('click', (e)=>{
    let script = document.createElement('script')
    let functionName = 'frank'+ parseInt(Math.random()*10000000 ,10)
    window[functionName] = function(){  // 每次请求之前搞出一个随机的函数
        amount.innerText = amount.innerText - 0 - 1
    }
    script.src = '/pay?callback=' + functionName
    document.body.appendChild(script)
    script.onload = function(e){
        e.currentTarget.remove()
        delete window[functionName] // 请求完了就干掉这个随机函数
    }
    script.onerror = function(e){
        e.currentTarget.remove()
        delete window[functionName] // 请求完了就干掉这个随机函数
    }
})

//后端代码
...
if (path === '/pay'){
    let amount = fs.readFileSync('./db', 'utf8')
    amount -= 1
    fs.writeFileSync('./db', amount)
    let callbackName = query.callback
    response.setHeader('Content-Type', 'application/javascript')
    response.write(`
        ${callbackName}.call(undefined, 'success')
    `)
    response.end()
}
...

用 jQuery 实现 JSONP

$.ajax({
url: "http://jack.com:8002/pay",
dataType: "jsonp",
success: function( response ) {
    if(response === 'success'){
        amount.innerText = amount.innerText - 1
    }
}
})

$.jsonp()

注意:JSONP 和 Ajax 没有关系。

用代码实现 frank.com:8001 和 jack.com:8002 之间的 JSONP 请求

frank.com:8001 的前端代码:

https://github.com/mokunshao/JSONP-EXAMPLE/blob/master/index.html

jack.com:8002 的后端代码:

https://github.com/mokunshao/JSONP-EXAMPLE/blob/master/server.js#L29