阅读 4720

JS如何实现函数缓存

1.什么是缓存?

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

2.为什么需要做函数缓存?

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

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

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

  • 闭包
  • 高阶函数

闭包

闭包是函数和声明该函数的词法环境的组合。

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

var object = {
    fn: function (){
        let result = new Array()
        for(var i =0; i < 10; i++) {
            result[i] = function (num) {
                return num;
            }(i)
        }
        return result
    }
}
          
console.log(object.fn());
//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

复制代码

原理:由于for每执行一次,就执行一次匿名函数,每一次执行有自己的执行环境,有着自己的作用域,在JavaScript中,所有函数都能访问它们上一层的作用域。所以在父函数里定义子函数,子函数能访问父函数的变量。子函数就相当于闭包。如果把计数器i定义在父函数内,然后在子函数调用计数器i,再在父函数外面执行子函数进行计数。那么这个计数器只能通过嵌套函数访问到,并且每次计数都不会重置(因为我们执行的是子函数,只有执行父函数时该计数器才会重置)。

ES5只有函数作用域和全局作用域, 这带来很多不合理的场景。比如:内层变量可能会覆盖外层变量;用来计数的循环变量泄露为全局变量。此时可能会需要用到闭包。不过ES6新增了let const 关键字,有独特的块级作用域,因此,块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式不再必要了。

高阶函数

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

下面咱们来看一个高阶函数和闭包结合的小例子:

var add = function() {
    var num = 0
    return function(a) {
        return num = num + a
    }
}
add()(1)      // 1
add()(2)      // 2复制代码

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

如果换一种写法,比如:

var add = function() {
    var num = 0
    return function(a) {
        return num = num + a
    }
}
var adder = add()
adder(1); // 1
adder(2); // 3复制代码

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

4.JS实现函数缓存

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

  const memorize = function(fn) {
    const cache = {}       // 存储缓存数据的对象
    return function(...args) {        // 这里用到数组的扩展运算符
      const _args = JSON.stringify(args)    // 将参数作为cache的key
      return cache[_args] || (cache[_args] = fn.apply(fn, args))  // 如果已经缓存过,直接取值。否则重新计算并且缓存
    }
  }
  const add = function(a, b) {
    console.log('开始缓存')
    return a + b
  }

  const adder = memorize(add)

复制代码

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

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

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

5.结尾

函数式编程越来越流行了,也越来越重要!作为新手,我们也需要多学多看,多实践。看完本篇之后,希望大家能亲自实现一下,正所谓眼过千遍不如手过一遍!如果此文能帮助到大家,希望各位看官手动点赞~ 天啦~听说点赞的朋友都脱单了???


文章分类
前端
文章标签