一直以为以前对jsonp道理和实现已经掌握了(src不受同源策略的限制,通过新建scrpit来加入dom,并绑定回调函数来实现跨域),但是最近被问到的时候还是有种一知半解,说不全,今天来总结一下jsonp的一些难点(由于jq对jsonp的封装导致了很多不一致的地方)
有很多内容摘参考了https://mp.weixin.qq.com/s/9I...

json

JSONP 是 JSON 的一种使用模式,可以解决主流浏览器的跨域数据访问问题。其原理是根据 XmlHttpRequest 对象受到同源策略的影响,而 <script> 标签元素却不受同源策略影响,可以加载跨域服务器上的脚本,网页可以从其他来源动态产生 JSON 资料。用 JSONP 获取的不是 JSON 数据,而是可以直接运行的 JavaScript 语句。

jsonp源码

jsonp({
    url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
    type: 'get',
    data:{
        wd: 'jsonp'
    },
    callback: 'cb',
    success: function (data) { 
        console.log(data) 
    }
});

function jsonp (options) {
    let url = options.url
    let data = options.data
    
    let oBody = document.getElementsByTagName('body')[0]
    let oScript = document.createElement('script')
    
    let callbackName = 'cb' + (~~(Math.random()*0xffffff)).toString(16)
    window[callbackName] = function (result) {
        options.success(result)        
    }
    data[options.callback] = callbackName
    
    oScript
        .setAttribute('src', url + '?' + format(data))
    oBody.append(oScript)
}
function format(data) {
    let str = ''
    for (var p in data) {
        str += encodeURIComponent(p) + '=' + encodeURIComponent(data[p]) + '&'
    }
    return str
}

我记得以前封装的时候一直不是很理解这个callback名字的道理,不是直接读取options的callback就行了吗,后来才知道这个是为了在默认情况下callbackName为了不重名,对其进行了改写,后端代码直接读取callbackName就行。

后端读取jsonp

app.get("https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su", function(req, res) {
    console.log("server accept: ", req.query.name, req.query.id)
    var data = "{" + "name:'" + req.query.name + " - server 3001 process'," + "id:'" + req.query.id + " - server 3001 process'" +"}"
    var callback = req.query.callback
    var jsonp = callback + '(' + data + ')'
    console.log(jsonp)
    res.send(jsonp)
    res.end()
})

这里对data数据进行了序列化,data 中字符串拼接,不能直接将 JSON 格式的 data 直接传给回调函数,否则会发生编译错误: parsererror Error: jsonpCallback was not called。
注意:jsonp返回的不是json数据,而是一段可以立即执行的js代码,所以不会像 ajax 的 XmlHttpRequest 那样可以监听不同事件对数据进行不同处理。由于jq的jsonp进行了封装,所以才看起来想ajax一样,有错误回调,其实jsonp的短板就是不能发post请求和不能注册success,error等监听函数

cors跨域

现在用得比较多的跨域是cors,以前的flash和iframe跨域都存在的问题,这是一种新规范。所谓同源策略的限制其实是跨域请求并非是浏览器限制了发起跨站请求,而是请求可以正常发起,到达服务器端,但是服务器返回的结果会被浏览器拦截。

原生封装cors

function createCORSRequest(method, url) {
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr) {

    // "withCredentials"属性是XMLHTTPRequest2中独有的
    xhr.open(method, url, true);

  } else if (typeof XDomainRequest != "undefined") {

    // 检测是否XDomainRequest可用
    xhr = new XDomainRequest();
    xhr.open(method, url);

  } else {

    // 看起来CORS根本不被支持
    xhr = null;

  }
  return xhr;
}

var xhr = createCORSRequest('GET', url);
if (!xhr) {
  throw new Error('CORS not supported');
}
// 绝大多数情况下,我们只需要和onload及onerror打交道,就像下面这样:

xhr.onload = function() {
 var responseText = xhr.responseText;
 console.log(responseText);
 // 继续其它代码
};

xhr.onerror = function() {
  console.log('There was an error!');
};

对比下元素ajax

function AJAX(json) {
    var url = json.url,
        method = json.method,
        flag = json.flag,
        data = json.data,
        callBack = json.callBack,
        xhr = null;
    if(window.XMLHttpRequest) {
        xhr = new window.XMLHttpRequest();
    }else {
        xhr = new ActiveXObject('Mircosoft.XMLHTTP');
    }            
    if(method == 'get') {
        url += '?' + data + new Date().getTime(); 
        xhr.open('get', url, flag);
    }else {
        xhr.open('post', url, flag);
    }
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            // 数据已经可用了
            callBack(xhr.responseText);
        }
    }
    if(method == 'get') {
        xhr.send();
    }else {
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urle');
        xhr.send(data);
    }    
}

服务器应答cors

app.post('/cors', function(req, res) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    res.header("X-Powered-By", ' 3.2.1')
    res.header("Content-Type", "application/json;charset=utf-8");
    var data = {
        name: req.body.name + ' - server 3001 cors process',
        id: req.body.id + ' - server 3001 cors process'
    }
    console.log(data)
    res.send(data)
    res.end()
})

哈,感谢你的观看,喜欢的话可以点赞,收藏~~~
如果有什么写错的地方,欢迎大家指出,一起学习进步~~~~


challen
140 声望9 粉丝

一个正在努力搬砖的码农