JSONP是什么?

以前是如何发送请求的?

有一些HTML标签能发送请求,比如<form>标签、<a>标签、<link>标签、<script>标签、<img>标签。

我们引入一个例子来说明:
例子
用户每点击一下付款按钮,页面和数据库中的余额减少1。

使用form表单

1
2
3
4
5
6
<body>
<h5>你的账户余额为<span id="amount">&&&amount&&&</span></h5>
<form action="/pay" method="post">
<input type="submit" value="付款1块钱">
</form>
</body>

form表单一旦提交就会自动刷新页面,进入pay页面,需要用户退回index页面,刷新页面才能看到结果。
而a标签也会刷新页面或打开新页面。
这样的用户体验很差,程序员就想办法怎么样才能发送请求并局部刷新页面。

使用form + iframe

1
2
3
4
5
6
7
<body>
<h5>你的账户余额为<span id="amount">&&&amount&&&</span></h5>
<form action="/pay" method="post" target="result">
<input type="submit" value="付款1块钱">
</form>
<iframe src="about:blank" frameborder="0" name="result" height="200"></iframe>
</body>

这种方法发送请求后不会自动跳转页面,但余额部分不会局部刷新,这也是过时的方法了。

动态创建图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<h5>你的账户余额为<span id="amount">&&&amount&&&</span></h5>
<button id="button">付款1块钱</button>
<script>
button.addEventListener('click', (e) =>{
let image = document.createElement('img')
image.src = '/pay'
image.onload = function () {
alert('打钱成功')
window.location.reload() //刷新页面
}
image.onerror = function () {
alert('打钱失败')
}
})
</script>
</body>

这样子发送请求虽然能成功(浏览器控制台中返回了200状态码),但实际上仍显示打钱失败,这是为什么呢?
因为后端代码中返回的不是真正的图片,被浏览器识别出来,导致image.onerror执行,如果使用的是真图片就会执行image.onload。所以后端必须返回一个图片。

  • 另外img标签还有一个弊端是只能发起GET请求不是发起POST请求。

动态创建script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<h5>你的账户余额为<span id="amount">&&&amount&&&</span></h5>
<button id="button">付款1块钱</button>
<script>
button.addEventListener('click', (e) =>{
let script = document.createElement('script')
script.src = '/pay'
document.body.appendChild(script)
script.onload = function(){
alert('success')
window.location.reload() //刷新页面
}
script.onerror = function(){
alert('fail')
}
})
</script>
</body>
  1. 动态创建img不用放入页面就能发起请求,而script必须要放入页面才能发起请求

  2. 因为这种方法会在页面中放入一个script,那么里面的内容就会执行,所以我们可以不用监听script.onload,直接在后端返回的script中写入我们要执行的代码。
    node.js:

    1
    2
    response.write(`alert('success')
    window.location.reload()`)
  3. 页面中会插入script,那么在请求成功后应该删除script (SRJ)

    1
    2
    3
    script.onload = function(e){
    e.currentTarget.remove()
    }
  4. script实际上依然存在于内存,只是在页面中消失了


原生JS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
button.addEventListener('click',(e)=>{
let script = document.createElement('script');
let functionName = 'allen'+parseInt(Math.random()*100000,10);
window[functionName] = function (result) {
if(result === 'success'){
amount.innerText = amount.innerText - 1
}else {
alert(`fail`)
}
};

script.src = 'http://jack.com:8002/pay?callback=' + functionName;
document.body.appendChild(script);
script.onload = function (e) {
e.currentTarget.remove();
delete window[functionName]
};
script.onerror = function (e) {
alert('fail');
e.currentTarget.remove();
delete window[functionName]
}
});

  • 这种方法可以跨域请求。
    比如能在xxx.com中请求zzz.com的JS,但因为script请求只能GET,很不安全,隐私相关的重要操作还是需要使用POST请求。

附server代码:

https://github.com/yuyuye958/git-nodejs/blob/master/server.js


所以JSONP到底是什么?

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

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

约定:

  1. callbackName -> callback / jQuery_callback
  2. xxx -> 随机数 如allen16516541644464()
  3. jQuery 用法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $.ajax({
    url: "http://jack.com:8002/pay",
    dataType: "jsonp",
    success: function( response ) {
    if(response === 'success') {
    amount.innerText = amount.innerText - 1
    }
    }
    })
  • 注意JSONP不是ajax

JSONP 为什么不支持 POST

  1. 因为 JSONP 是通过动态创建 script 实现的
  2. 创建 script 只能发送 get 请求