是时候用闭包实现一个缓存函数了

2,715 阅读2分钟

什么是缓存函数

所谓缓存函数就是将函数运算过的结果缓存起来,这种做法是典型的用内存去换取性能的手段,常用于缓存数据计算结果和缓存对象。缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理。

记忆函数的使用场景

在客户前端页面中,有些数据(比如一些后续不常改变的数据),可以在第一次请求的时候全部拿过来保存在 js 对象中,以后需要的时候就不用每次都去请求服务器了。对于那些大量使用类似下拉框的页面,这种方法可以极大地减少对服务器的访问。可以提供便利,减少查询次数和所消耗的时间。

函数缓存的实现原理

  1. 闭包(相关介绍可以查阅这篇文章
  2. 高阶函数

高阶函数

JavaScript 的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

我们可以利用高阶函数的思想来实现一个简单的缓存,在函数内部用一个对象存储输入的参数,如果下次再输入相同的参数,那就比较一下对象的属性,把值从这个对象里面取出来,不必再继续往运行,这样就极大的节省了客户端等待的时间。

  function serialize(obj) {
	if(obj == null) {
		return ''
	} else if(Array.isArray(obj)) {
		const items = obj.map((item) => serialize(item) )
		return `[${items.join(',')}]`
	} else if(isPlainObject(obj)) {
		const keys = Object.keys(obj)
		keys.sort()
		const items = keys.map((key) => `${key}=${serialize(obj[key])}`)
		return `{${items.join('&')}}`
	} else if(typeof obj === 'object' && typeof obj.valueOf === 'function') {
		return serialize(obj.valueOf())
	} else {
		return obj + ''
	}
}

function isPlainObject(obj) {
	let prototype = Object.getPrototypeOf(obj)
	return obj && typeof obj === 'object' && (prototype === null || prototype === Object.prototype)
}

function memorize(fn) {
      const results = {}
      return (...args) => {
          const key = serialize(args)
          if(!(key in results)) {
              console.log('新的缓存')
              results[key] = fn.apply(this, args)
          }
          console.log('缓存结果', results)
          return results[key]
      }
}

const add = (...args) => {
    return args.reduce((accValue, curValue, index) => accValue + curValue, 0)
}

const adder = memorize(add)

adder(1, 2, 3) // 新的缓存, 缓存结果 {[1,2,3]: 6}
adder(1, 2, 3) // 缓存结果 {[1,2,3]: 6}
adder(1, 2, 3, 4, 5) // 新的缓存, 缓存结果 {[1,2,3]: 6, [1,2,3,4,5]: 15}

我们在 memorize 里入参一个函数并返回了一个闭包函数,根据序列化比较这个函数参数是否有变动,判断是从闭包作用域上 results 取值还是重新执行入参函数。可以看到只要参数相同,每次取值都会在闭包作用域上缓存的 results 里取。