# Web 运行时缓存

# 前言

Web 运行时缓存本质上就是用空间(缓存存储)换时间(跳过计算过程)。前端运行时缓存,可以通过声明一个全局变量存储,更好的是通过一个函数中获取。

# 编码基本缓存技术

# 基本示例

假设又一个获取天气的函数 getChanceOfRain,每次调用都要花 100ms 计算:

import { getChanceOfRain } from "magic-weather-calculator";
function showWeatherReport() {
  let result = getChanceOfRain(); // Let the magic happen
  console.log("The chance of rain tomorrow is:", result);
}

showWeatherReport(); // (!) Triggers the calculation
showWeatherReport(); // (!) Triggers the calculation
showWeatherReport(); // (!) Triggers the calculation

很显然这样太浪费计算资源了,当已经计算过一次天气后,就没有必要再算一次了,我们期望的是后续调用可以直接拿上一次结果的缓存,这样可以节省大量计算。因此我们可以做一个 memoizedGetChanceOfRain 函数缓存计算结果:

import { getChanceOfRain } from "magic-weather-calculator";
let isCalculated = false;
let lastResult;
// We added this function!
function memoizedGetChanceOfRain() {
  if (isCalculated) {
    // No need to calculate it again.
    return lastResult;
  }
  // Gotta calculate it for the first time.
  let result = getChanceOfRain();
  // Remember it for the next time.
  lastResult = result;
  isCalculated = true;
  return result;
}
function showWeatherReport() {
  // Use the memoized function instead of the original function.
  let result = memoizedGetChanceOfRain();
  console.log("The chance of rain tomorrow is:", result);
}

# 闭包 + 高阶函数

可以把缓存函数抽离出来,运行闭包和高阶函数:

function memoize(fn) {
  let isCalculated = false;
  let lastResult;
  return function memoizedFn() {
    // Return the generated function!
    if (isCalculated) {
      return lastResult;
    }
    let result = fn();
    lastResult = result;
    isCalculated = true;
    return result;
  };
}

这样生成新的缓存函数就很方便:

let memoizedGetChanceOfRain = memoize(getChanceOfRain);
let memoizedGetNextEarthquake = memoize(getNextEarthquake);
let memoizedGetCosmicRaysProbability = memoize(getCosmicRaysProbability);

# 实战应用

# 封装一个 LocalStorage 缓存类

// 先实现一个基础的StorageBase类,把getItem和setItem方法放在它的原型链上
function StorageBase() {}
StorageBase.prototype.getItem = function(key) {
  return localStorage.getItem(key);
};
StorageBase.prototype.setItem = function(key, value) {
  return localStorage.setItem(key, value);
};

// 以闭包的形式创建一个引用自由变量的构造函数
const Storage = (function() {
  let instance = null;
  return function() {
    // 判断自由变量是否为null
    if (!instance) {
      // 如果为null则new出唯一实例
      instance = new StorageBase();
    }
    return instance;
  };
})();

// 这里其实不用 new Storage 的形式调用,直接 Storage() 也会有一样的效果
const storage1 = new Storage();
const storage2 = new Storage();

上面这种缓存方式在设计模式中就是一个单例,前端的应用有全局建立一个模态框,vuex 的 install 函数。

# 前端 api 请求响应数据缓存

缓存指定服务的基本信息,后期减少请求次数。

import axios from "axios";
// 缓存指定服务的基本信息,后期减少请求次数
let serverLayerInfo = new Map();
// 拿到服务所有图层信息
/**
 * @description 拿到服务所有图层信息
 * @param {...Array} args 数组参数 顺序不能乱
 * @param {String} url 服务地址
 * @returns {Object}
 */
export const clearLayerToolsCacheData = () => {
  serverLayerInfo = null;
};
export const getServerAllLayerInfo = async (url) => {
  // 解析服务里的图层
  const infoUrl = url.trim() + "?f=pjson";
  let res = null;
  let status = true;
  if (serverLayerInfo && serverLayerInfo.has(url)) {
    res = serverLayerInfo.get(url);
  } else {
    serverLayerInfo = new Map();
    try {
      res = await axios.get(infoUrl);
      serverLayerInfo.set(url, res);
    } catch (err) {
      // 服务挂了
      // iview.Message.error("地图服务查询错误!");
      console.log("地图服务查询错误!");
      status = false;
    }
  }
  return {
    status: status,
    result: res,
  };
};
/**
 * @description: 通过名称解析服务里的对应的图层ID
 * @param {String} name 图层名称
 * @return: Number
 */
export const resolveServerLayerId = async (url, name) => {
  // 解析服务里的图层
  const { status, result: res } = await getServerAllLayerInfo(url);
  let localstatus = status;
  let layerId = void 0;
  if (localstatus) {
    const { layers } = res.data;
    if (!layers) {
      // 图层不存在
      console.log("地图服务查询结果里没有图层信息");
      localstatus = false;
    } else {
      const filterLayer = layers.filter((v) => v.name.trim() === name.trim());
      if (filterLayer.length > 0) {
        layerId = filterLayer[0].id;
        console.log(`${url}/${layerId}  ${name}`);
      } else {
        // 特定的图层不存在
        console.log(`${name}所对应的图层不存在`);
        localstatus = false;
      }
    }
  }
  return new Promise(async (resolve) => {
    if (localstatus) {
      resolve(layerId);
    } else {
      resolve(-1);
    }
  });
};

# 小结

运行时缓存虽好,但并不是所有场景都适用,比如以下两种情况不适合用缓存:

  • 不经常执行的函数。
  • 本身执行速度较快的函数。

# 参考资料