前言: 看到玉伯的聊聊jsonp的p,引发了另一种loader方式来跨域的方法,他把它叫做JSONM协议,原理和seajs相似,都是动态加载script,加载完成后执行callback,
同时还不用考虑回调函数名,都指定为define,服务端可以静态存储例如(define({name:"alice",age:21}))。如果数据是静态的,还可以放在cdn上。原文issue连接
以及一篇原理分析,写得很赞 连接。
所以我就斗胆按照自己的理解,自己写了一个简易的用loader的思想来完成jsonp。
原理概述:动态加载script,利用onload事件执行回调函数,在ie9/10中 用onreadystatechange。可是在ie6-8中不支持,在seajs里看到了绝招,利用readyState为interactive判断正在执行的脚本,脚本的src和传入时的callback建立唯一连接,进行查找,执行回调函数。
那么怎么获取json数据呢? 我用JSONPData这个里存储获取到的_jsondata(私有变量,提供接口访问)。在非ie中就简单了,直接callback.call(this,JSONPData.getJsonData()).
我实现的这个有什么限制呢?
在ie6-8中,因为用的是src和callback唯一对应,假若几个请求的src相同,callback只能得到第一次设置的函数,解决方法,如果回调函数不同,就用不同的src请求喽,加个无意义的参数就ok啦。
var currentAddingScript; var JSONP = (function() { var _head = document.getElementsByTagName("head")[0]; var _baseElement = document.getElementsByTagName("base")[0]; var _helper = { encodeURL: function(url) { var param = url.split("?")[1]; var url = url.split("?")[0]; var params = param.split("&"); var parLen = params.length; for (var i = 0; i < parLen; i++) { var paItem = params[i].split("="); url += url.indexOf("?") >= 0 ? "&" : "?"; url += encodeURIComponent(paItem[0]) + "=" + encodeURIComponent(paItem[1]); } return url; }, addLoad: function(node, callback) { node.onload = node.readystatechange = function() { // Ensure only run once in IE9 node.onload = node.readystatechange = null; if (!this.readyState || node.readyState == "complete" || node.readyState == "loaded") { callback.call(this, JSONPData.getJsonData()); this.parentNode.removeChild(this); } } } }; return { start: function(url, func) { var encUrl = url;//存储的是未经过编码的,以便与对应的callback创建关联 //创建script结点 var script = document.createElement("script"); if (encUrl.indexOf("?") >= 0) { url = encUrl + "&callback=JSONP.callback"; script.src = _helper.encodeURL(url); } else { url = encUrl + "?callback=JSONP.callback"; script.src = _helper.encodeURL(url); } script.charset = "UTF-8"; //绑定加载完成事件 _helper.addLoad(script, func); // For some cache cases in IE 6-8, the script executes IMMEDIATELY after // the end of the insert execution, so use `currentlyAddingScript` to // hold current node, currentAddingScript = script; _baseElement ? _head.insertBefore(script, _baseElement) : _head.appendChild(script); //for ie6-9 关联script 和callback var data = {}; data[url] = func; JSONPData.save(data); }, getInteractiveScript: function() { //ie6-9 获取正在执行的脚本 if (currentAddingScript) { return currentAddingScript; } var scripts = document.getElementsByTagName('script'); for (var i = 0; i < scripts.length; i++) { var script = scripts[i]; if (script.readyState === 'interactive') { return script; } } return null; }, callback: function(data) { // if ie6-9 if (document.attachEvent) { var script = JSONP.getInteractiveScript(); var callback = null; if (script) { callback = JSONPData.findCallback(script.src); if (typeof callback == "function") { callback(data); } } else { console.log("fail"); } } // 设置返回的json数据,供调用 JSONPData.setJsonData(data); } } })(); var JSONPData = (function() { var _dataStorage = []; var _jsonData; // 每个数据项是data {url: [callbackFunc]} return { save: function(data) { //检查data的url若相同,则回调函数不变 for (var i in data) { if (!_dataStorage.hasOwnProperty(i)) { _dataStorage.push(data); } } }, findCallback: function(url) { var len = _dataStorage.length; for (var i = 0; i < len; i++) { if (_dataStorage[i][url]) { return _dataStorage[i][url]; } } return null; }, setJsonData: function(data) { _jsonData = data ? data : null; }, getJsonData: function() { return _jsonData; } } })();
update分割线
==========================================================================================
我用Ie的仿真模式,测试ie7和ie8时,发现第一次点击按钮触发请求不能返回数据,请先看重要的html部分
<button id="button">发送三个jsonp请求</button>
绑定button点击事件
var button = document.getElementById("button"); button.onclick = function() { JSONP.start("http://localhost:8088/data.php", function(data) { console.log(data); console.log("yes1"); }); JSONP.start("http://localhost:8088/data.php?cc=ff", function(data) { console.log(data); console.log("yes2"); }); JSONP.start("http://localhost:8088/data.php?ur=f", function(data) { console.log(data); console.log("yes3"); }); }
在ie7,8下我点一下后不出现结果,
通过断点调试,代码问题出在了这段上,我先把创建的script结点添加到dom结点中,之后再存储url和callback,导致在立即执行函数时,数据还未存储,无法找到关联的callback,所以没有成功调用,解决方案是,存储数据放在插入节点之前,就完美啦!
再看运行结果:
新代码:就只贴了需要做小改动得JSONP返回的start函数代码:
start: function(url, func) {
var encUrl = url;
//创建script结点
var script = document.createElement("script");
if (encUrl.indexOf("?") >= 0) {
url = encUrl + "&callback=JSONP.callback";
script.src = _helper.encodeURL(url);
} else {
url = encUrl + "?callback=JSONP.callback";
script.src = _helper.encodeURL(url);
}
script.charset = "UTF-8";
//for ie6-9 关联script 和callback
var data = {};
data[url] = func;
JSONPData.save(data);
//绑定加载完成事件
_helper.addLoad(script, func);
// For some cache cases in IE 6-8, the script executes IMMEDIATELY after
// the end of the insert execution, so use `currentlyAddingScript` to
// hold current node,
currentAddingScript = script;
_baseElement ? _head.insertBefore(script, _baseElement) : _head.appendChild(script);
},