是時候徹底搞清jsonp了!
jsonp主要是利用了腳本不受 同源策略 限制的"BUG",算是一種簡單的hack寫法,主要分3個步驟:
1.前端請求一個腳本,注意這里是 腳本 而不是ajax。通常是動態(tài)創(chuàng)建一個script標(biāo)簽,文件的地址就是異步地址,但是會帶一個callback參數(shù)(也可以和后端商量約定)https://xxx.com/api/getsth?callback=__jsonp1234,callback參數(shù)的值是唯一的,用來區(qū)分不同的請求。
//ps:如果不需要接收response,比如埋點統(tǒng)計,甚至可以創(chuàng)建一個image
var target = document.getElementsByTagName('script')[0] || document.head;
var script;
script = document.createElement('script');
script.src = url;//這里的url實際就是異步地址
target.parentNode.insertBefore(script, target);
2.后端需要配合前端做下兼容:
//假設(shè)要返回的數(shù)據(jù)是:{name:'wyz',age:18}
var data = {name:'wyz',age:18};
if(req.query.callback){
//當(dāng)發(fā)現(xiàn)前端傳了callback參數(shù)
//實際輸出:__jsonp1234({name:'wyz',age:18})
res.send(`${req.query.callback}(${data})`)
}else{
//沒傳callback正常返回即可
res.json(data)
}
3.頁面加載完成這個腳本后,相當(dāng)于在創(chuàng)建script標(biāo)簽的位置調(diào)用了__jsonp1234({name:'wyz',age:18})方法。如果在請求之前,已經(jīng)創(chuàng)建好了全局方法window.__jsonp1234 = function(data){console.log(data)}呢?
<html>
<head>
</head>
<body>
<!--...-->
<!--這個是動態(tài)創(chuàng)建的-->
<script src="https://xxx.com/api/getsth?callback=__jsonp1234"></script>
<!--加載完成后,相當(dāng)于:-->
<!-- <script>
__jsonp1234({name:'wyz',age:18})
</script> -->
<script>
var target = document.getElementsByTagName('script')[0] || document.head;
var script;
script = document.createElement('script');
script.src = "https://xxx.com/api/getsth?callback=__jsonp1234";
target.parentNode.insertBefore(script, target);
window.__jsonp1234 = function (data) {
console.log(data)//當(dāng)請求完成,調(diào)用了__jsonp1234方法,這里就會打印出來
}
</script>
</body>
</html>
是不是so easy?趁熱打鐵,附上一個jsonp的實現(xiàn)源碼:
/**
* Module dependencies
*/
var debug = require('debug')('jsonp');//這個debug插件晚點有空研究一下
/**
* Module exports.
*/
module.exports = jsonp;
/**
* Callback index.
*/
var count = 0;
/**
* Noop function.
*/
function noop(){}
/**
* JSONP handler
*
* Options:
* - param {String} qs parameter (`callback`)
* - prefix {String} qs parameter (`__jp`)
* - name {String} qs parameter (`prefix` + incr)
* - timeout {Number} how long after a timeout error is emitted (`60000`)
*
* @param {String} url
* @param {Object|Function} optional options / callback
* @param {Function} optional callback
*/
function jsonp(url, opts, fn){
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
if (!opts) opts = {};
var prefix = opts.prefix || '__jp';
// use the callback name that was passed if one was provided.
// otherwise generate a unique name by incrementing our counter.
var id = opts.name || (prefix + (count++));
var param = opts.param || 'callback';
var timeout = null != opts.timeout ? opts.timeout : 60000;
var enc = encodeURIComponent;
var target = document.getElementsByTagName('script')[0] || document.head;
var script;
var timer;
if (timeout) {
timer = setTimeout(function(){
cleanup();
if (fn) fn(new Error('Timeout'));
}, timeout);
}
function cleanup(){
if (script.parentNode) script.parentNode.removeChild(script);
window[id] = noop;
if (timer) clearTimeout(timer);
}
function cancel(){
if (window[id]) {
cleanup();
}
}
window[id] = function(data){
debug('jsonp got', data);
cleanup();
if (fn) fn(null, data);
};
// add qs component
url += (~url.indexOf('?') ? '&' : '?') + param + '=' + enc(id);
url = url.replace('?&', '?');
debug('jsonp req "%s"', url);
// create script
script = document.createElement('script');
script.src = url;
target.parentNode.insertBefore(script, target);
return cancel;
}