common/cache.mjs

(function() {

  const generateUUID = function() {
    // bits 12-15 of the time_hi_and_version field to 0010
    // bits 6-7 of the clock_seq_hi_and_reserved to 01
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random() * 16 | 0;
      var v = c == 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
  };

  /**
   * LRU缓存
   * FIFO队列,当插入新数据时,插到尾部,并检查是否超过队列长度,若是则删除头部数据;当访问旧数据时,会将其移动到队列尾部
   * 适合热点数据缓存,不适合偶发性、周期性批量操作
   */
  class LRUCache {
    maxCount = 0;
    lruQueue = [];

    constructor(maxCount) {
      this.maxCount = maxCount;
    }

    get(key) {
      const idx = this.lruQueue.findIndex(cache => cache.key === key);
      if (idx > -1) {
        const arr = this.lruQueue.splice(idx, 1);
        this.lruQueue.push(arr[0]);
        return arr[0].value;
      } else {
        return null;
      }
    }

    set(value) {
      const key = generateUUID();
      this.lruQueue.push({
        key,
        value,
      })
      if (this.lruQueue.length > this.maxCount) {
        this.lruQueue.shift();
      }
      return key;
    }

    print(ignore) {
      ignore && console.log('LRU Cache:')
      console.log(this.lruQueue.map(cache => cache));
    }
  }

  /**
   * 双队列缓存
   * 1.当插入新数据时,插入FIFO队列,若数据在FIFO队列中一直未被访问,则最终按FIFO规则淘汰;
   * 2.当数据在FIFO队列中被再次访问,则将数据移到LRU队列头部;
   * 3.当数据在LRU队列中中被再次访问,则将数据迁移到LRU队列头部,若数据在LRU队列中一直未被访问,则最终按LRU规则淘汰;
   * 根据使用频率和时间智能化淘汰,对批量突发图片的缓存污染有良好的防范能力。
   */

  // TODO:暂不支持根据缓存大小做限制
  class DoubleQueueCache{
    maxCount = 0;
    queue = [];
    LRUCache = null;
    // 存储数据在FIFO、LRU两队列的映射,key是FIFO队列的uuid,value是LRU队列的uuid
    hashMap = {};

    constructor(maxCount, LRUMaxCount) {
      this.maxCount = maxCount;
      this.LRUCache = new LRUCache(LRUMaxCount);
    }

    get(key) { 
      const idx = this.queue.findIndex(cache => cache.key === key);
      if (idx === -1) {
        const value = this.LRUCache.get(this.hashMap[key]);
        if (value !== null) {
          return value;
        } else {
          return null;
        }
      } else {
        const cache = this.queue.splice(idx, 1);
        const value = cache[0].value;
        Object.assign(this.hashMap, {
          [key]: this.LRUCache.set(value),
        });
        return value;
      }
    }

    set(value) { 
      const key = generateUUID();
      this.queue.push({
        key,
        value,
      })
      if (this.queue.length > this.maxCount) {
        this.queue.shift();
      }
      return key;
    }

    print() {
      console.group('Double Queue Cache:')
      console.log(this.queue.map(cache => cache.value));
      this.LRUCache.print(true);
      console.groupEnd();
    }
  }

  const CACHE = {
    LRUCache,
    DoubleQueueCache
  }

  if (typeof define === 'function') {
    define(function() {
      return CACHE;
    });
  }
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = CACHE;
  }
  // 是否处于window环境
  function isInWindow() {
    return 'undefined' !== typeof window && /^\[object (?:Window|DOMWindow|global)\]$/.test(toString.call(window));
  }
  if (isInWindow()) {
    for (let key in CACHE) {
      window[key] = CACHE[key];
    }
  }

})()