JS 中如何实现函数缓存?函数缓存有哪些应用场景?
什么是缓存?
所谓函数缓存,就是将函数运算过的结果缓存起来,这种做法是典型的用内存去换取性能的手段,常用于缓存数据计算结果和缓存对象。缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理
为什么需要?
在前端页面中,有些数据(比如数据字典中的数据),可以在第一次请求的时候全部拿过来保存在 js 对象中,以后需要的时候就不用每次都去请求服务器了。对于那些大量使用数据字典来填充下拉框的页面,这种方法可以极大地减少对服务器的访问。简单点说,就是提供便利,减少查询次数和所消耗的时间。
JavaScript 中的缓存的概念主要建立在两个概念之上, 闭包 + 高阶函数
利用高阶函数的思想来实现一个简单的缓存,在函数内部用一个对象存储输入的参数,如果下次再输入相同的参数,那就比较一下对象的属性,把值从这个对象里面取出来,不必再继续往运行,这样就极大的节省了客户端等待的时间
JS 实现函数缓存
const memorize = function (fn) {
const cache = {} // 存储缓存数据的对象
return function (...args) {
// 这里用到数组的扩展运算符
// 将参数作为cache的key
const _args = JSON.stringify(args)
// 如果已经缓存过,直接取值。否则重新计算并且缓存
return cache[_args] || (cache[_args] = fn.apply(fn, args))
}
}
const add = function (a, b) {
console.log('开始缓存')
return a + b
}
const adder = memorize(add)
调用
- console.log(adder(2, 6)) // 输出结果: 开始缓存 8 // cache:
- console.log(adder(2, 6)) // 输出结果: 8 //cache:
- console.log(adder(10, 10)) // 输出结果: 开始缓存 20 // cache:
只有第一次会输出‘开始缓存’, 之后只要参数想同,每次取值都会在缓存里取。这里需要注意一下 cache 不可以是 Map 数据结构,因为 Map 的键是使用 === 比较的,[1]!==[1],因此即使传入相同的对象或者数组,那么还是被存为不同的键。
一、函数缓存是什么
函数缓存是:保存函数的运算结果。本质上就是用空间(缓存存储)换时间(计算过程),常用于缓存数据计算结果和缓存对象
示例代码如下:
const add = (a, b) => a + b
const calc = memoize(add) // 函数缓存
calc(10, 20) // 30
calc(10, 20) // 30 缓存
缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理。
二、如何实现
实现函数缓存主要依靠闭包、柯里化、高阶函数,这里再简单复习下:
闭包
闭包可以理解成,函数 + 函数体内可访问的变量总和
;(function () {
var a = 1
function add() {
const b = 2
let sum = b + a
console.log(sum) // 3
}
add()
})()
add
函数本身,以及其内部可访问的变量,即 a = 1
,这两个组合在⼀起就形成了闭包
柯里化
把接受多个参数的函数转换成接受一个单一参数的函数
// 非函数柯里化
var add = function (x, y) {
return x + y
}
add(3, 4) //7
// 函数柯里化
var add2 = function (x) {
//**返回函数**
return function (y) {
return x + y
}
}
add2(3)(4) //7
将一个二元函数拆分成两个一元函数
高阶函数
通过接收其他函数作为参数或返回其他函数的函数
function foo() {
var a = 2
function bar() {
console.log(a)
}
return bar
}
var baz = foo()
baz() //2
函数 foo
如何返回另一个函数 bar
,baz
现在持有对 foo
中定义的bar
函数的引用。由于闭包特性,a
的值能够得到
下面再看看如何实现函数缓存,实现原理也很简单,把参数和对应的结果数据存在一个对象中,调用时判断参数对应的数据是否存在,存在就返回对应的结果数据,否则就返回计算结果
如下所示
const memoize = function (func, content) {
let cache = Object.create(null)
content = content || this
return (...key) => {
if (!cache[key]) {
cache[key] = func.apply(content, key)
}
return cache[key]
}
}
调用方式也很简单
const calc = memoize(add)
const num1 = calc(100, 200)
const num2 = calc(100, 200) // 缓存得到的结果
过程分析:
- 在当前函数作用域定义了一个空对象,用于缓存运行结果
- 运用柯里化返回一个函数,返回的函数由于闭包特性,可以访问到
cache
- 然后判断输入参数是不是在
cache
的中。如果已经存在,直接返回cache
的内容,如果没有存在,使用函数func
对输入参数求值,然后把结果存储在cache
中
三、应用场景
虽然使用缓存效率是非常高的,但并不是所有场景都适用,因此千万不要极端的将所有函数都添加缓存
以下几种情况下,适合使用缓存:
- 对于昂贵的函数调用,执行复杂计算的函数
- 对于具有有限且高度重复输入范围的函数
- 对于具有重复输入值的递归函数
- 对于纯函数,即每次使用特定输入调用时返回相同输出的函数