# 11. jsonp的原理是什么?

尽管浏览器有同源策略,但是<script>标签的src属性不会被同源策略所约束,可以获取任意服务器上的脚本并执行。jsonp通过插入script标签的方式来实现跨域,参数只能通过url传入,仅能支持get请求。

  1. 原理: jsonp 是用来解决跨域获取数据的一种解决方案,具体是通过动态创建 script 标签,然后通过标签的 src 属性获取 js 文件中的 js 脚本,该脚本的内容是一个函数调用,参数就是服务器返回的数据,为了处理这些返回的数据,需要事先在页面定义好回调函数,本质上使用的并不是 ajax 技术

  2. 优缺点:

  • jsonp 优点:

    • 完美解决在测试或者开发中获取不同域下的数据, 用户传递一个 callback 参数给服务端,然后服务端返回数据时会将这个 callback 参数作为函数名来包裹住 JSON 数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。简单来说数据的格式没有发生很大变化
  • jsonp 缺点:

      1. jsonp 只支持 get 请求而不支持 post 请求, 也即是说如果想传给后台一个 json 格式的数据, 此时问题就来了, 浏览器会报一个 http 状态码 415 错误, 告诉你请求格式不正确, 这让我很蛋疼(在登录注册中需要给后台传一大串数据), 如果都用参数的形式拼接在 url 后面的话不太现实, 后台取值也会显得繁琐,
      1. 在登录模块中需要用到 session 来判断当前用户的登录状态, 这时候由于是跨域的原因, 前后台的取到的 session 是不一样的, 那么就不能就行 session 来判断.
      1. 由于 jsonp 存在安全性问题(不知 qq 空间的跨域是怎么解决的, 还是另有高招?),后来考虑到上面的一系列问题, 采用的是后台进行设置允许跨域请求(但还是存在缺陷的, 实质上还是跨域, 如上面说的 session 问题). Header set Access-Control-Allow-Origin *为了防止 XSS 攻击我们的服务器, 我们可以限制域,比如 Access-Control-Allow-Origin: http://blog.csdn.net

# 实现原理:

  • Step1: 创建 callback 方法
  • Step2: 插入 script 标签
  • Step3: 后台接受到请求,解析前端传过去的 callback 方法,返回该方法的调用,并且数据作为参数传入该方法
  • Step4: 前端执行服务端返回的方法调用

# jsonp源码实现

function jsonp({url, params, callback}) {
    return new Promise((resolve, reject) => {
        //创建script标签
        let script = document.createElement('script');
        //将回调函数挂在 window 上
        window[callback] = function(data) {
            resolve(data);
            //代码执行后,删除插入的script标签
            document.body.removeChild(script);
        }
        //回调函数加在请求地址上
        params = {...params, callback} //wb=b&callback=show
        let arrs = [];
        for(let key in params) {
            arrs.push(`${key}=${params[key]}`);
        }
        script.src = `${url}?${arrs.join('&')}`;
        document.body.appendChild(script);
    });
}

# 使用:

function show(data) {
    console.log(data);
}
jsonp({
    url: 'http://localhost:3000/show',
    params: {
        //code
    },
    callback: 'show'
}).then(data => {
    console.log(data);
});

# 服务端代码(node):

// express启动一个后台服务
let express = require('express');
let app = express();

app.get('/show', (req, res) => {
    let {callback} = req.query; //获取传来的callback函数名,callback是key
    res.send(`${callback}('Hello!')`);
});
app.listen(3000);