阅读 68

【前端实践系列之十五】浏览器标签页通信库:lsbridge源码解读

这是我参与更文挑战的第22天,活动详情查看: 更文挑战 !

👽 概论

众所周知,受限于浏览器的安全策略,同一浏览器的不同标签页之间是没法直接进行通信的。间接实现通信的最简单可靠的桥梁便是LocalStroage。

LocalStroage是浏览器中实现本地存储的一种方案,它与SessionStroage最大的区别便在于其不受标签页隔离限制,只要是同源状态下便可共享存储内容。

👽 基本思路

实现的基本思路也很简单:在全局添加监听localStroage的监听器,当目标页面对localStoage内的值发生改变时做出相应。

原理很简单,具体的实现我们来分析下借由此原理实现的lsbridge库的源码,看看还有没有更多值得学习的地方。

👽 lsbridge源码解读

//lsbridge https://github.com/krasimir/lsbridge

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.lsbridge = factory();
  }
})(this, function () {
  var api = {};

  /**
   * --测试lsbridge兼容性--
   * 但此步必要性不是很强,
   * 目前不兼容lsbridge的浏览器几乎没有
   */
  api.isLSAvailable = (function () {
    var mod = '_';
    try {
      localStorage.setItem(mod, mod);
      localStorage.removeItem(mod);
      return true;
    } catch (e) {
      return false;
    }
  })();

  if (api.isLSAvailable) {
  var interval = 100, //获取订阅消息的间隔(每100ms获取一次)事实上该库就是通过不断的访问localstroge来进行监听的。
    intervalForRemoval = 200, //清除间隔
    ls = localStorage,
    listeners = {}, //监听器(所有订阅的消息都会在这个对象上)
    isLoopStarted = false,
    buffer = {}; //缓存器(起数据中转作用)

  /**
   *
   * @description 此方法就是定期监听localStroge的关键方法。
   * 该方法会从监听器上取出各个命名空间对应的值,将其置入缓存器,
   * 然后对数据执行相应的自定义方法,最后再清空缓存器
   */
  var loop = function () {
    console.log('buffer: ', buffer);
    for (var namespace in listeners) {
      var data = ls.getItem(namespace);

      if (data && buffer[namespace] && buffer[namespace].indexOf(data) === -1) {
        buffer[namespace].push(data);
        try {
          var parsed = JSON.parse(data);
          if (parsed) data = parsed;
        } catch (e) {}

        for (var i = 0; i < listeners[namespace].length; i++) {
          listeners[namespace][i](data);
        }

        if (!ls.getItem(namespace + '-removeit')) {
          ls.setItem(namespace + '-removeit', '1');
          (function (n) {
            setTimeout(function () {
              ls.removeItem(n);
              ls.removeItem(n + '-removeit');
              buffer[namespace] = [];
            }, intervalForRemoval);
          })(namespace);
        }
      } else if (!data) {
        buffer[namespace] = [];
      }
    }

    console.log('buffer: ', buffer);
    setTimeout(loop, interval);
    return true;
  };

  /**
   * @description 调用此方法向目标命名空间发布消息
   * @param {*} namespace 目标命名空间(可以看做localstorage中键值对的key)
   * @param {*} data 消息内容(可以看做localstorage中键值对的value)
   * @supplement 该方法有对函数或者对象形式的消息内容做特殊处理,然后将其存入localstroge的
   * 相应命名空间中
   */
  api.send = function (namespace, data) {
    var raw = '';
    if (typeof data === 'function') {
      data = data();
    }
    if (typeof data === 'object') {
      raw = JSON.stringify(data);
    } else {
      raw = data;
    }
    ls.setItem(namespace, raw);
  };

  /**
   * @description 调用此方法订阅目标命名空间
   * @param {*} namespace 目标命名空间
   * @param {*} cb 对应待执行的自定义方法
   * @supplement //目标命名空间存在后(不存在时会在监听器和缓存器上同时新建相应命名空间)。
   * 会将对应的执行方法存入监听器中,同时开启定时器
   */
  api.subscribe = function (namespace, cb) {
    if (!listeners[namespace]) {
      listeners[namespace] = [];
      buffer[namespace] = [];
    }

    listeners[namespace].push(cb);

    if (!isLoopStarted) {
      isLoopStarted = loop();
    }
  };

  /**
   * @description 调用此方法取消订阅
   * @param {*} namespace 目标命名空间
   * @supplement 取消订阅时释放监听器与缓存器
   */
  api.unsubscribe = function (namespace) {
    if (listeners[namespace]) {
      listeners[namespace] = [];
    }
    if (buffer[namespace]) {
      buffer[namespace] = [];
    }
  };

  api.getBuffer = function () {
    return buffer;
  };
  } else {
    api.send = api.subscribe = function () {
      throw new Error('localStorage not supported.');
    };
  }

  return api;
});

复制代码
文章分类
前端
文章标签