同源策略是为了保证服务器的数据安全而设立的一种安全策略。
其简单解释就是:相同协议、相同域名、相同端口的情况下,数据通信才能成功,否则就会因为跨域问题而报错。

当然,在开发环境下,我们可以通过 nodeJS 搭建一个微服务,再利用 http-proxy-middleware 对 ajax 请求进行转发来实现跨域,参见,或者利用 webpack 自带的跨域代理配置也可以解决。

线上我们使用 nginx 搭建一个代理服务,解决线上跨域是刚刚的了。

但是本文今天说的是 jsonp。
没错,在 webpack 出现之前,jsonp 可以说是大名鼎鼎了。

jsonp 跨域原理:src 属性不受到同源策略的限制。

一个最最简单的 jsonp 请求如下:

http://digi.duodiangame.com/digMineral/interfaces/task/reportDailySignin.do?callback=cb_callback&user_id=AAA

你会看到这个 url 返回了一个函数的调用 cb_callback({"Error":"用户数据错误"});

如果当前页面有个叫做 ‘cb_callback’ 的函数,那么这个函数就会执行,参数就是后台的返回值。

我们封装一个简单的 jsonp 来解释 jsonp 应该如何实现

function jsonp (url,data,fn){
    if(!url)
        throw new Error('url is necessary')
    const callback = 'CALLBACK' + Math.random().toString().substr(9,18)
    // const callback = 'cb_callback'
    const JSONP = document.createElement('script')
          JSONP.setAttribute('type','text/javascript')

    const headEle = document.getElementsByTagName('head')[0]

    let ret = '';
    if(data){
        if(typeof data === 'string')
            ret = '&' + data;
        else if(typeof data === 'object') {
            for(let key in data)
                ret += '&' + key + '=' + encodeURIComponent(data[key]);
        }
        ret += '&_time=' + Date.now();
    }
    JSONP.src = `${url}?callback=${callback}${ret}`;

    window[callback] = function(r){
      fn && fn(r)
      headEle.removeChild(JSONP)
      delete window[callback]
    }

    headEle.appendChild(JSONP)
}

然后需要后台返回一段 function 的调用

<?php
 
$data = ".......";
$callback = $_GET['callback'];
echo $callback.'('.json_encode($data).')';
exit;
 
?>

当 jsonp 和 axios 碰撞的时候,会发出什么样的火花呢?最重要的改变是我们可以通过 promise 来替代回调参数了

axios.jsonp = (url,data)=>{
    if(!url)
        throw new Error('url is necessary')
    const callback = 'CALLBACK' + Math.random().toString().substr(9,18)
    const JSONP = document.createElement('script')
          JSONP.setAttribute('type','text/javascript')

    const headEle = document.getElementsByTagName('head')[0]

    let ret = '';
    if(data){
        if(typeof data === 'string')
            ret = '&' + data;
        else if(typeof data === 'object') {
            for(let key in data)
                ret += '&' + key + '=' + encodeURIComponent(data[key]);
        }
        ret += '&_time=' + Date.now();
    }
    JSONP.src = `${url}?callback=${callback}${ret}`;
    return new Promise( (resolve,reject) => {
        window[callback] = r => {
          resolve(r)
          headEle.removeChild(JSONP)
          delete window[callback]
        }
        headEle.appendChild(JSONP)
    })
    
}

调用方式也发生了变化:

axios.jsonp(url, params)  
     .then(res => console.log(res))        
     .catch(err => console.log(err))