什么是函数缓存?

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

为什么需要做函数缓存?

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

函数缓存的实现原理是什么?

JavaScript 中的缓存的概念主要建立在两个概念之上,它们分别是 :

  • 闭包
  • 高阶函数

    闭包

    闭包是函数和声明该函数的词法环境的组合。 闭包的作用:闭包是javascript的一大难点,也是它的特色。很多高级应用都要依靠闭包来实现。闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在函数调用后被自动清除。我觉得对于闭包的作用一个很好的理解就是:既想重用变量,又不想变量受到全局污染。下面看一个小例子: ```javascript var object = { fn: function (){

    1. let result = new Array()
    2. for(var i =0; i < 10; i++) {
    3. result[i] = function (num) {
    4. return num;
    5. }(i)
    6. }
    7. return result

    } }

console.log(object.fn()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  1. 原理:由于for每执行一次,就执行一次匿名函数,每一次执行有自己的执行环境,有着自己的作用域。<br />在JavaScript中,所有函数都能访问它们上一层的作用域。所以在父函数里定义子函数,子函数能访问父函数的变量。子函数就相当于闭包。<br />如果把计数器`i`定义在父函数内,然后在子函数调用计数器`i`,再在父函数外面执行子函数进行计数。那么这个计数器只能通过嵌套函数访问到,并且每次计数都不会重置(因为我们执行的是子函数,只有执行父函数时该计数器才会重置)。
  2. ES5只有函数作用域和全局作用域, 这带来很多不合理的场景。比如:内层变量可能会覆盖外层变量;用来计数的循环变量泄露为全局变量。此时可能会需要用到闭包。不过ES6新增了let const 关键字,有独特的块级作用域,因此,块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式不再必要了。
  3. <a name="TrcME"></a>
  4. ### 高阶函数
  5. JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。说白了就是一个返回函数的函数
  6. 下面咱们来看一个高阶函数和闭包结合的小例子:
  7. ```javascript
  8. var add = function() {
  9. var num = 0
  10. return function(a) {
  11. return num = num + a
  12. }
  13. }
  14. add()(1) // 1
  15. add()(2) // 2复制代码

注意:这里的两个add()(1)和add()(2)不会互相影响,可以理解为每次运行add函数后返回的都是不同的匿名函数,就是每次add运行后return的function其实都是不同的,所以运行结果也是不会影响的。主要是利用闭包来保持着作用域。
如果换一种写法,比如:

  1. var add = function() {
  2. var num = 0
  3. return function(a) {
  4. return num = num + a
  5. }
  6. }
  7. var adder = add()
  8. adder(1); // 1
  9. adder(2); // 3

这样的话就会在之前运算结果基础上继续运算,意思就是这两个 adder 运行的时候都是调用的同一个 num。

JS实现函数缓存

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

  1. const memorize = function(fn) {
  2. const cache = {} // 存储缓存数据的对象
  3. return function(...args) { // 这里用到数组的扩展运算符
  4. const _args = JSON.stringify(args) // 将参数作为cache的key
  5. return cache[_args] || (cache[_args] = fn.apply(fn, args))// 如果已经缓存过,直接取值。否则重新计算并且缓存
  6. //return cache[_args] || (cache[_args] = fn(...args)) 这样写也可以
  7. //因为args是个数组,所以用aplly的方式去给fn传参。或者也可以使用扩展运算符去传参,写成fn(...args),效果是一样的
  8. }
  9. }
  10. const add = function(a, b) {
  11. console.log('开始缓存')
  12. return a + b
  13. }
  14. const adder = memorize(add)

调用的方式很简单,直接给sum传入参数就行:这里连续调用三次,咱们来看一下输出结果:

  1. console.log(adder(2, 6)) // 输出结果: 开始缓存 8 // cache: { '[2, 6]': 8 }
  2. console.log(adder(2, 6)) // 输出结果: 8 //cache: { '[2, 6]': 8 }
  3. console.log(adder(10, 10)) // 输出结果: 开始缓存 20 // cache: { '[2, 6]': 8, '[10, 10]': 20 }

可见:只有第一次会输出‘开始缓存’, 之后只要参数想同,每次取值都会在缓存里取。这里需要注意一下cache不可以是Map数据结构,因为Map的键是使用===比较的,[1]!==[1],因此即使传入相同的对象或者数组,那么还是被存为不同的键。

作者:谁动了我的橘子

链接:https://juejin.im/post/6844903885379731464
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。