Ajax - 手写JSONP跨域实现及原理详解

JSONP实现原理

jsonp,其实就是单纯为了实现跨域请求而创造的一个欺骗(trick)。

虽然,因为同源策略的影响,不能通过XMLHttpRequest请求不同域上的数据(Cross -origin reads)。但是,在页面上引入不同域上的js脚本文件却是可以的(Cross -origin embedding)。因此,在js文件载入完毕之后,触发回调,可以将需要的data作为参数传入
注意,实现方式(需前后端配合)

优点

兼容性好(兼容低版本IE)

缺点

JSONP只支持GET请求,XMLHttpRequest相对于JSONP有着更好的错误处理机制。

实现

1.服务端采用的是Node,服务端处理请求方法如下

router.get('/', function(req, res, next) {
    console.log('收到客户端的请求:', req.query);
    // 传回到客户端的数据
    let data = JSON.stringify({
        'status':200,
        'result':{
            'name':'柳成荫',
            'site':'123456'
        }
    });
    // 获取方法名称 - 这是客户端传过来的方法名参数
    // 因为这个方法名必须是客户端有的,必须要客户端告诉服务端是哪个方法
    let methodName = req.query.callback;
    let methodStr = methodName + '(' + data + ')';
    // 快速结束没有任何数据的响应,客户端会执行这个方法,从而获取到服务端返回的数据
    res.end(methodStr)
});

2.封装JSONP

(function (w) {
        /**
         * jsonp的实现
         * @param {Object}option
         */
        function jsonp(option) {
            // 把success函数挂载在全局的getDate函数上
            w.getData = option.success;
            // 处理url,拼接参数 - 回调方法是getData
            option.url = option.url + '?callback=getData';
            // 创建script标签,并插入body
            let scriptEle = document.createElement('script');
            scriptEle.src = option.url;
            document.body.appendChild(scriptEle);
        }
        // 全局挂载一个jsonp函数
        w.jsonp = jsonp;
    })(window);

    /**
     * 把对象转换成拼接字符串
     * 把形如
       data:{
          "sex":"男",
          "name":"九月"
        }
       转换成sex=男&name=九月
     * @param paramObj 对象参数
     * @param words
     * @returns {string} 字符串
     */
    function getStrWithObject(paramObj,words){
        let resArr = [];
        // 1.转换对象
        for(let key in paramObj){
            let str = key + '=' + paramObj[key];
            resArr.push(str);
        }
        resArr.push(words);
        // 3.数组转换成字符串
        return resArr.join("&");
    }

看似代码没有任何问题。但是,我们测试一下,多次调用jsonp方法。

jsonp({
        url:'http://localhost:3000/',
        data:{
            "sex":"男",
            "name":"九月"
        },
        success:function (data) {
            console.log(data);
            alert(1);
        }
    });

    jsonp({
        url:'http://localhost:3000/',
        data:{
            "sex":"男",
            "name":"九月"
        },
        success:function (data) {
            console.log(data);
            alert(2);
        }
    });

结果会怎么样?的确,还是会执行2次,但是每次执行的都是第二个jsonp方法。这是为什么?

问题出现在这里,多次调用,会发生函数覆盖。

解决方案就是让每一次调用函数名不一致,在JS里有很多方法,比如通过随机数、时间戳之类的。这里采用的是随机数,修改如下。

// 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题
// 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象
let callBackName = 'lcy' + Math.random().toString().substr(2) 
+ Math.random().toString().substr(2);
// 把success函数挂载在全局的getDate函数上
w[callBackName] = option.success;
// 处理url,拼接参数
option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);

 好,现在看似完美解决。然而,我们发现,每次调用jsonp方法,都会在body里插入一个script标签。我们并不希望在调用jsonp之后,添加这样一个标签,我们需要在调用完之后,将其移除。

怎么做呢?我们在将success函数挂载在全局时,我们在success外层再套个函数。在这个函数里调用success方法之后,即已经请求到数据之后,我们就去把这个script标签给它移除就行了。修改如下

// 1.函数挂载在全局
w[callBackName] = function(data){
    option.success(data);
    // 删除script标签
    document.body.removeChild(scriptEle);
};

整个过程就是这样,以下是完整代码。

 (function (w) {
        /**
         * jsonp的实现
         * @param {Object}option
         */
        function jsonp(option) {
            // 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题
            // 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象
            let callBackName = 'lcy' + Math.random().toString().substr(2) + Math.random().toString().substr(2);
            // 1.函数挂载在全局
            w[callBackName] = function(data){
                option.success(data);
                // 删除script标签
                document.body.removeChild(scriptEle);
            };
            // 2.处理url
            option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);
            // 3.创建script标签插入body
            let scriptEle = document.createElement('script');
            scriptEle.src = option.url;
            document.body.appendChild(scriptEle);
        }
        w.jsonp = jsonp;
    })(window);

    /**
     * 把对象转换成拼接字符串
     * @param paramObj 对象参数
     * @returns {string} 字符串
     */
    function getStrWithObject(paramObj,words){
        let resArr = [];
        // 1.转换对象
        for(let key in paramObj){
            let str = key + '=' + paramObj[key];
            resArr.push(str);
        }
        resArr.push(words);
        // 3.数组转换成字符串
        return resArr.join("&");
    }
发布了59 篇原创文章 · 获赞 13 · 访问量 2512

猜你喜欢

转载自blog.csdn.net/qq_40885085/article/details/103605097