jsonp的原理与实现

1.概述

动态创建script标签,因为script标签是没有同源策略限制,可以跨域的

jsonp是一种跨域通信的手段,它的原理其实很简单:

首先是利用script标签的src属性来实现跨域。

通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客户端通信。

由于使用script标签的src属性,因此只支持get方法

2、实现流程

1)、设定一个script标签

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

2)、callback定义了一个函数名,而远程服务端通过调用指定的函数并传入参数来实现传递参数,将fn(response)传递回客户端

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

3)、客户端接收到返回的js脚本,开始解析和执行fn(response)

3、jsonp简单实现

一个简单的jsonp实现,其实就是拼接url,然后将动态添加一个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);

}

前端js示例

function hello(res){

    alert('hello ' + res.data);

}

jsonp({

    url : '',

    callback : hello

});

服务器端代码

var http = require('http');

var urllib = require('url');

var port = 8080;

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

http.createServer(function(req,res){

//url.parse()第二个参数为true,query属性会生成一个对象,如果为false,则返回url对象上的query属性会是一个未解析,未解码的字符串,默认为false

    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');

});

然而,这个实现虽然简单,但有一些不足的地方:

我们传递的回调必须是一个全局方法我们都知道要尽量减少全局的方法

需要加入一些参数校验,确保接口可以正常执行。
4、 可靠的jsonp实现

(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(promise封装)

  • JSONP以前研究过,最近又有点忘了,写篇本文mark一下,旨在理解记住JSONP的原理及其实现。代码实现用到es6语法,使用promise来封装JSONP方法,本地测试用的自己node搭的服务器,具体代码就不贴了。
  • 一句话阐述下JSONP原理:动态生成一个JavaScript标签,其src由接口url、请求参数、callback函数名拼接而成,利用js标签没有跨域限制的特性实现跨域请求。 
  • 有几点需要注意:1.callback函数要绑定在window对象上
        2.服务端返回数据有特定格式要求:callback函数名+'('+JSON.stringify(返回数据) +')'
        3.不支持post,因为js标签本身就是一个get请求
  • 具体代码如下,最后一段是调用函数的示例,这个函数将返回一个promise对象,获取到数据时状态为resolve

   const jsonp = function (url, data) {

      return new Promise((resolve, reject) => {

        // 初始化url

        let dataString = url.indexOf('?') === -1 ? '?' : '&'

        let callbackName = `jsonpCB_${Date.now()}`

        url += `${dataString}callback=${callbackName}`

        if (data) {

         // 有请求参数,依次添加到url

          for (let k in data) {

            url += `&${k}=${data[k]}`

          }

        }

        let jsNode = document.createElement('script')

        jsNode.src = url

        // 触发callback,触发后删除js标签和绑定在window上的callback

        window[callbackName] = result => {

          delete window[callbackName]

          document.body.removeChild(jsNode)

          if (result) {

            resolve(result)

          } else {

            reject('没有返回数据')

          }

        }

        // js加载异常的情况

        jsNode.addEventListener('error', () => {

          delete window[callbackName]

          document.body.removeChild(jsNode)

          reject('JavaScript资源加载失败')

        }, false)

        // 添加js节点到document上时,开始请求

        document.body.appendChild(jsNode)

      })

    }

 jsonp('http://192.168.0.103:8081/jsonp', {a: 1, b: 'heiheihei'})

    .then(result => { console.log(result) }).catch(err => { console.error(err) })

总结

  • 个人感觉JSONP用的情况还是比较少吧,如果已经是需要服务端配合来进行跨域的情况,为什么不直接用CORS呢

promise封装ajax

const axios = function(options){

    let promise = new promise(( resolve , reject )=>{

        var xhr = null;

        if(window.XMLHttpRequest){//兼容处理

           xhr = new XMLHttpRequest();

       }else{

           xhr = new ActiveXObject("Microsoft.XMLHTTP");   

       }

        var data = "";

        for (var key in options.data) {//数据处理

            data += "&" + key + "=" + options.data[key]

        }

       if (options.method == "get") {

            let url = options.url + "?" + data.slice(1);

            xhr.open(options.method, url);

            xhr.send();

        } else if (options.method == "post") {

              xhr.open(options.method, options.url);

              xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");

              xhr.send(data);

        }

        xhr.onreadystatechange = function () {

      let timer = null;

      let timeout = options.timeout?options.timeout:5000  //等待响应的时间

       if(xhr.readyState == 4 && xhr.status == 200){

            let res = JSON.parse(xhr.responseText);

            clearTimeout(timer);

          resolve(res);

      }

            timer = setTimeout(()=>{

                clearTimeout(timer);

                reject(xhr.status);

            },timeout)

            

        }

    })

    return promise;

}

例子:

axios({

        method:"get",

        url:"./data.json",

        data:{

            id:10

        }

    }).then((res)=>{

        console.log(res)

    },(e)=>{

        console.log(e);

    })

 

你可能感兴趣的:(javascript)