乐趣区

关于javascript:jsonp-原理详解及-jsonppro-源码解析

什么是 JSONP

JSONP(JSON with Padding)是材料格局 JSON 的一种“应用模式”,能够让网页从别的网域获取材料。

因为浏览器同源策略,一般来说位于 server1.a.com 的网页无奈与 server2.a.com 的服务器沟通,而 HTML 的 <script> 元素是一个例外。利用 <script> 元素的这个凋谢策略,网页能够失去从其余起源动静产生的 JSON 材料,而这种应用模式就是所谓的 JSONP。用 JSONP 抓到的数据并不是 JSON,而是任意的 JavaScript,用 JavaScript 解释器执行而不是用 JSON 解析器解析。

JSONP 实现原理

jsonp 概念咱们理解了,那么如何实现呢?

咱们要获取一段 JSON 数据没有跨域问题,咱们能够通过 xhr GET 形式, 假如申请地址是以后域下 /apis/data?id=123,以后域名是 example.com;
返回数据是

{
    name=peng,
    age=18
}

当咱们获取的数据不在同域的状况,比方上边的例子申请域名改成 example2.com,页面所应用的域名还是 example.com,应用 JSONP 的形式去跨域。

  1. 依据下面的原理简介,首先咱们在全局生命一个函数。

    window.jsonp1 = function(data) {console.log(data)
    }
  2. 动静的往 head 标签中插入 script 标签

    const head = document.getElementByTagName('head')[0]
    // 获取页面的 head 标签
    
    const script = document.createElement('script')
    // 创立 script 标签
    
    script.src = 'https://example2.com/apis/data?id=123&callback=jsonp1'
    // 给 script 标签赋值 src 地址
    
    head.appendChild(script)
    // 最初插入 script 标签 
  3. 最初须要 script 标签返回的内容是一个办法调用并传入参数是要返回的内容。

    jsonp1({
     name=peng,
     age=18
    })

以上就对 JSONP 原理进行一个繁难的实现。

真正实现一个 JSONP 网络申请库

以上对 JSONP 原理和实现有了初步理解,如果咱们要在日常我的项目中应用,那就须要会封装一个残缺的 JSONP 网络申请库。

上面咱们对 jsonp-pro 网络申请库做一个源码剖析,从中理解并学习如何封装一个 JSONP 网络申请库。

先来看看申请的通用办法 method 库

// 查看类型的办法,用于对办法传入类型的限度
/**
 * object check method
 *
 * @param {*} item variable will be check
 * @param {string} type target type. Type value is 'String'|'Number'|'Boolean'|'Undefined'|'Null'|'Object'|'Function'|'Array'|'Date'|'RegExp'
 * @return {boolean} true mean pass, false not pass
 */
function typeCheck(item, type) {
  // 应用 Object.prototype.toString.call 办法,因为这个办法获取类型最全
  const itemType = Object.prototype.toString.call(item);

  // 拼接后果来做判断
  let targetType = `[object ${type}]`;
  if (itemType === targetType) {return true;} else {return false;}
}

// 获取随机数字型字符串,应用工夫戳 + 随机数拼接保障每次活的的字符串没有反复的
function randNum() {
  // get random number
  const oT = new Date().getTime().toString();
  const num = Math.ceil(Math.random() * 10000000000);
  const randStr = num.toString();
  return oT + randStr;
}

export {typeCheck, randNum};

主文件,次要办法

import {typeCheck, randNum} from './methods';

// 传参的解释阐明,十分具体。这里不做过多解释
/**
 * Param info
 * @param {string} url url path to get data, It support url include data.
 * @param {Object=} options all options look down
 * @param {(Object | string)=} options.data this data is data to send. If is Object, Object will become a string eg. "?key1=value1&key2=value2" . If is string, String will add to at the end of url string.
 * @param {Function=} options.success get data success callback function.
 * @param {Function=} options.error get data error callback function.
 * @param {Function=} options.loaded when data loaded callback function.
 * @param {string=} options.callback custom callback key string , default 'callback'.
 * @param {string=} options.callbackName callback value string.
 * @param {boolean} options.noCallback no callback key and value. If true no these params. Default false have these params
 * @param {string=} options.charset charset value set, Default not set any.
 * @param {number=} options.timeoutTime timeout time set. Unit ms. Default 60000
 * @param {Function=} options.timeout timeout callback. When timeout run this function.
 * When you only set timeoutTime and not set timeout. Timeout methods is useless.
 */
export default function(url, options) {

  // 获取 head 节点,并创立 scrpit 节点
  const oHead = document.querySelector('head'),
    script = document.createElement('script');
  
  // 申明变量,并给局部值增加默认值
  let timer, // 用于工夫定时器
    dataStr = '', // 用于存传输的 query
    callback = 'callback', // 和上边的参数一个含意
    callbackName = `callback_${randNum()}`, // 和上边的参数一个含意
    noCallback = false, // 和上边的参数一个含意
    timeoutTime = 60000, // 和上边的参数一个含意
    loaded, // 和上边的参数一个含意
    success; // 和上边的参数一个含意

  const endMethods = []; // 存储最初要执行回调函数队列

  // 如果没有 url 参数抛出异样
  if (!url) {throw new ReferenceError('No url ! Url is necessary !');
  }

  // 对 url 参数进行类型查看
  if (!typeCheck(url, 'String')) {throw new TypeError('Url must be string !');
  }

  // 对所有参数进行解决的办法对象,命名与参数 key 放弃始终不便后续调用
  const methods = {data() {
      // data 参数解决办法
      const data = options.data;
      if (typeCheck(data, 'Object')) {
        // 如果是对象类型将对象转换成 query 字符串并赋值给下面申明过的变量
        for (let item in data) {dataStr += `${item}=${data[item]}&`;
        }
      } else if (typeCheck(data, 'String')) {
        // 如果是字符串类型,间接赋值给上边变量
        dataStr = data + '&';
      } else {
        // 其余状况抛出类型谬误
        throw new TypeError('data must be object or string !');
      }
    },
    success() {
      // 对胜利参数办法进行解决
      // 将胜利办法赋值给上边的变量
      success = options.success;

      // 进行类型查看,异样抛出谬误
      if (!typeCheck(success, 'Function'))
        throw new TypeError('param success must be function !');
    },
    error() {
      // 对异样参数办法进行解决
      // 进行类型查看,异样抛出谬误
      if (!typeCheck(options.error, 'Function')) {throw new TypeError('param success must be function !');
      }
      // 类型查看通过,script 标签增加异样事件回调
      script.addEventListener('error', options.error);
    },
    loaded() {
      // 将加载实现办法进行解决
      // 将加载实现办法赋值给上边变量
      loaded = options.loaded;

      // 进行类型查看,异样抛出谬误
      if (!typeCheck(loaded, 'Function')) {throw new TypeError('param loaded must be function !');
      }
    },
    callback() {
      // 将 callback 参数进行解决
      callback = options.callback;

      // 进行类型查看,异样抛出谬误
      if (!typeCheck(callback, 'String')) {throw new TypeError('param callback must be string !');
      }
    },
    callbackName() {
      // 将 callbackName 参数进行解决
      callbackName = options.callbackName;

      // 进行类型查看,异样抛出谬误
      if (!typeCheck(callbackName, 'String')) {throw new TypeError('param callbackName must be string !');
      }
    },
    noCallback() {
      // 将 noCallback 参数进行解决
      noCallback = options.noCallback;

      // 进行类型查看,异样抛出谬误
      if (!typeCheck(noCallback, 'Boolean')) {throw new TypeError('param noCallback must be boolean !');
      }
    },
    charset() {
      // 将 charse 参数进行解决
      const charset = options.charset;
      if (typeCheck(charset, 'String')) {
        // 设置 script 标签 charset,浏览器个别默认是 UTF8,如果有非凡的须要手动设置
        script.charset = charset;
      } else {
      // 进行类型查看,异样抛出谬误
        throw new TypeError('param charset must be string !');
      }
    },
    timeoutTime() {
      // 将 timeoutTime 参数进行解决
      timeoutTime = options.timeoutTime;

      // 进行类型查看,异样抛出谬误
      if (!typeCheck(timeoutTime, 'Number')) {throw new TypeError('param timeoutTime must be number !');
      }
    },
    timeout() {
      // 将 timeout 办法进行解决 
      // 进行类型查看,异样抛出谬误
      if (!typeCheck(options.timeout, 'Function')) {throw new TypeError('param timeout must be function !');
      }
      function timeout() {function outTime() {
          // 移除无用的 script 节点
          script.parentNode.removeChild(script);

          // 删除命名在全局的办法
          window.hasOwnProperty(callbackName) && delete window[callbackName];

          // 革除定时器
          clearTimeout(timer);

          // 执行超时函数
          options.timeout();}
        
        // 设置超时函数
        timer = setTimeout(outTime, timeoutTime);
      }

      endMethods.push(timeout); // 超时函数放在队列中最初执行
    }
  };
  
  // 遍历选项执行对应的办法
  for (let item in options) {methods[item]();}

  // 执行最初要执行的队列
  endMethods.forEach(item => {item();
  });
  
  // 如果没有回调,并且申请 query 不为空的状况。兼容是否有问号状况
  // warn url include data
  if (noCallback && dataStr != '') {url.indexOf('?') == -1
      ? (url += `?${dataStr.slice(0, -1)}`)
      : (url += `&${dataStr.slice(0, -1)}`);
  }

  // 有回调且兼容有无问号状况
  if (!noCallback) {
    // 增加全局办法
    window[callbackName] = data => {
      // 有胜利回调则执行,并且将参数传入
      success && success(data);

      // 移除 script 标签
      oHead.removeChild(script);

      // 移除全局办法
      delete window[callbackName];
    };
    url.indexOf('?') == -1
      ? (url += `?${dataStr}${callback}=${callbackName}`)
      : (url += `&${dataStr}${callback}=${callbackName}`);
  }

  // 对 url 编码
  url = encodeURI(url);
  
  // 给 script 标签增加加载实现回调
  function loadLis() {
    // 移除加载实现办法
    script.removeEventListener('load', loadLis);

    // 参数中有回调则执行回调
    loaded && loaded();

    // 革除定时器
    clearTimeout(timer);
  }
  

  // 增加加载实现办法
  script.addEventListener('load', loadLis);
  
  // 将 url 赋值给 script 标签
  script.src = url;

  // 最初将 script 标签插入
  oHead.appendChild(script);
}

以上就实现实现了一个残缺的 JSONP 网络申请库。

jsonp-pro
github: https://github.com/peng/jsonp…
npm: https://www.npmjs.com/package…

退出移动版