jsonp的原理与实现

    |     2020年3月31日   |   html/css/js, web前端技术   |     0 条评论   |    1024

JSONP 是一种跨域通信手段。原理很简单:利用 script 标签的 src 不受同源策略限制,把前端回调名传给服务器,服务器再返回一段「调用该函数」的脚本。

因为走的是 script 加载,所以 JSONP 只支持 GET。下面按流程、最小实现和更可靠的封装把这件事写清楚。来源:https://segmentfault.com/a/1190000007665361#articleHeader1。

一、概述与流程

  • 用 script 的 src 跨域拉一段 JS。
  • 前端把回调函数名当作参数传给服务器,服务器注入参数后再返回,完成「服务端向客户端传数据」。
  • 只支持 GET。

先设定一个 script 标签,callback 指定函数名:

<script src="http://jsonp.js?callback=xxx"></script>

远程服务端调用指定函数并传入参数,把 fn(response) 传回客户端。PHP 侧常见写法如下(原文把 json_encode 写进了字符串里,这里改成真正拼接):

$callback = !empty($_GET['callback']) ? $_GET['callback'] : 'callback';
echo $callback.'('.json_encode($data).')';

客户端收到返回的 JS 脚本后开始解析,执行 fn(response)。

步骤 谁做 结果
1 前端 插入 script,src 带 callback 名
2 服务端 返回 callback(json)
3 浏览器 当脚本执行,数据进回调

二、简单实现

最小实现就是拼接 URL,再动态往 head 里塞一个 script:

function jsonp(req){
    var script = document.createElement('script');
    var url = req.url + '?callback=' + req.callback.name;
    script.src = url;
    document.getElementsByTagName('head')[0].appendChild(script); 
}

前端调用示例:

function hello(res){
    alert('hello ' + res.data);
}
jsonp({
    url : '',
    callback : hello 
});

Node 服务端把查询参数里的 callback 名和 JSON 拼成一段函数调用:

var http = require('http');
var urllib = require('url');

var port = 8080;
var data = {'data':'world'};

http.createServer(function(req,res){
    var params = urllib.parse(req.url,true);
    if(params.query.callback){
        console.log(params.query.callback);
        //jsonp
        var str = params.query.callback + '(' + JSON.stringify(data) + ')';
        res.end(str);
    } else {
        res.end();
    }
    
}).listen(port,function(){
    console.log('jsonp server is on');
});

这个实现虽短,但有两处不足:回调必须是全局函数(应尽量少暴露全局方法);还缺少参数校验,接口容易被乱调。

三、更可靠的封装

下面这份实现用自增 id 生成一次性全局函数名,请求结束后删节点、删全局函数,并处理 onerror:

(function (global) {
   var id = 0,
    container = document.getElementsByTagName("head")[0];

    function jsonp(options) {
      if(!options || !options.url) return;
    
        var scriptNode = document.createElement("script"),
            data = options.data || {},
            url = options.url,
            callback = options.callback,
            fnName = "jsonp" + id++;
    
        // 添加回调函数
        data["callback"] = fnName;
    
        // 拼接url
        var params = [];
        for (var key in data) {
            params.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
        }
        url = url.indexOf("?") > 0 ? (url + "&") : (url + "?");
        url += params.join("&");
        scriptNode.src = url;
    
        // 传递的是一个匿名的回调函数,要执行的话,暴露为一个全局方法
        global[fnName] = function (ret) {
            callback && callback(ret);
            container.removeChild(scriptNode);
            delete global[fnName];
        }
    
        // 出错处理
        scriptNode.onerror = function () {
            callback && callback({error:"error"});
            container.removeChild(scriptNode);
            global[fnName] && delete global[fnName];
        }
    
        scriptNode.type = "text/javascript";
        container.appendChild(scriptNode)
    }
    
    global.jsonp = jsonp;

})(this);

使用示例:

jsonp({    
    url : "www.example.com",   
    data : {id : 1},    
    callback : function (ret){    
     console.log(ret);    }
});

一句话总结:JSONP 靠 script.src 跨域,服务端返回 callback(json);封装时用一次性全局函数名、拼参、用完即删,并补上 onerror。

转载请注明来源:jsonp的原理与实现
本文链接地址:https://ai.zhousir.top/?p=2523
回复 取消