前端系统课程 - 33. JSONP 是什么鬼

数据库

  • 能长久存储数据的仓库,就是数据库,文件系统也可以看作一种”数据库“。

  • 网页上有些数据不能写死,需要从数据库读取后再显示到页面上;而这些数据前端修改后,还要通知后端作出相同修改,这时便涉及到了前后端数据通信问题。

前后端通信

  • 使用 <form> 标签组成的 form 表单可以向后台提交数据,而且可以设置提交方式、提交地址以及提交成功页面的打开位置等等。

  • 使用 form 表单提交数据请求,后端接收到请求后对数据库进行相应的操作,这是最初前后端通信的方式;这种方式每次完成操作后,前端需要的到后端反馈信息后,再刷新页面才能看到最新的数据。

  • 后来的开发者将后端的反馈信息引导至一个内嵌的 iframe 窗口,这样可以让反馈信息在当前页面显示了,一定程度上提升了用户体验,但是最新的数据,还是得刷新整个页面后才能获取到。

  • 在 HTML 中,有些标签具备不需要用户操作就可以发送请求的功能(<a> 标签是需要用户操作的),例如 <link><script><img> 等标签,但它们只能发送 GET 请求,无法发送 POST 请求。

  • <img><script> 等标签都可以监听其 onloadonerror 事件,来判断加载成功和失败,图片验证虽然需要返回图片文件(光返回 Content-Type: image/xxx 是不行的),判断繁琐,但是总算可以向服务器提交数据了;当请求成功后,前端可以自动为用户刷新页面,减少用户的操作,也可以在收到成功反馈后,直接修改页面数据,这样在不刷新的情况下也能看到数据变化。

  • <script> 标签也可以发送请求,也不必返回图片,但是必须将它放到页面当中才可以,因为 <script> 中的内容需要解析并且使用的是 src 属性引用的外部文件,才会发送请求去获取这个文件。由于每次发送请求都会动态地创建一个 <script> 标签,所以每次请求成功后,文档中都会出现一个 <script> 标签;这个插入到页面中的 <script> 标签如果含有脚本代码,就会被执行;利用这些特点,可以不必监听 onload 事件,直接在后端返回的文件中,写一段代码,后端操作数据成功并将这个文件返回到前端,页面就会执行这段代码的内容了,这段代码可以做很多事,比如可以直接将页面的相应数据修改;页面中增加的 <script> 标签在添加并加载完成后,便删除它,这样在请求操作完成后,文档中也不会有冗余的”副产品”了。前面描述的这种方案是 AJAX 出现之前,局部更新页面内容的常见方案,称为“SRJ(Server Rendered JavaScript)”方案。

什么是 JSONP

  • 由于 <script> 标签发送的请求是不受域名限制的,也就是说,它可以跨域访问。利用这个特性,不同的网站(域名)之间也可以进行通信。

  • 如果不同的域名之间通信时,后端的返回的代码中写的是对具体页面数据的操作(数据耦合度高),那么就需要后端对页面中的细节非常了解,这显然是非常困难的;要对这些数据进行解耦,可以让后端在操作成功后,返回的数据中调用指定的方法,传入指定的参数;再进一步,前后端约定指定的查询参数,前端将要运行的方法名通过查询参数的形式放在 src 属性的地址中,传送给后端,后端截取约定的名称,传入参数后返回给前端,这样就顺利解耦了。

  • 前面的方法中,后端可以通过给指定的方法传参来向前端传送数据,那么这个数据换成是 JSON 数据也没有问题,这就是 JSONP。

  • 在使用 JSONP 时,有一些约定俗成的命名:查询参数的名称为 callback,而回调函数的名称内要包含随机数,并且每次请求后都会删除,这样不会污染全局变量,也不会发生命名冲突;

  • 在 jQuery 中的 AJAX 对象也集成了 JSONP 请求方式(虽然 JSONP 和 AJAX 没有关系),例如:

    1
    2
    3
    4
    5
    $.ajax({
    url: 'http://www.test.com:8811/example',
    dataType: 'jsonp',
    success: response => console.log(response);
    });
  • JSONP 的原理总结:

    • 浏览器要发送请求时,动态地创建一个 <script> 标签,并将其 src 属性中的地址指向目标服务器,同时在地址中传入一个包含已在本地声明的回调函数名称的查询参数(一般为 callback),将这个 <script> 插入到文档中,触发一个 GET 请求;

    • 服务器收到请求后,根据查询参数中的回调函数名称,构造一个调用回调函数的响应,并将数据通过回调函数的参数传入后发送给浏览器;

    • 浏览器接收到服务器的响应,执行响应中带有参数的回调函数,之后便通过参数获取到了服务器返回的数据;示例代码:

  • 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let btn = document.querySelector('#btn'),
    fnName = `res${parseInt(Math.random() * 100000, 10)}`;

    window[fnName] = response => console.log(response);

    btn.addEventListener('click', () => {
    let script = document.createElement('script');
    script.src = `http://frank.com:8001?callback=${fnName}`;
    script.addEventListener('load', e => e.currentTarget.remove());
    script.addEventListener('error', (e) => {
    console.log('fail!');
    });

    document.body.appendChild(script);
    });
  • 由于 <script> 标签不具备发送 POST 请求的能力,所以 JSONP 方法不能发送 POST 请求;另外,JSONP 与 JSON 数据没多大关系,只是具备接收 JSON 数据的能力;而 JSONP 与 AJAX 也没有什么关系,只是被 jQuery 集成到了其 AJAX 对象中。