今天看啥  ›  专栏  ›  plz叫我红领巾

进阶篇(一)JS如何实现函数缓存

plz叫我红领巾  · 掘金  ·  · 2019-07-08 17:25
阅读 148

进阶篇(一)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每执行一次,就执行一次匿名函数,每一次执行有自己的执行环境,有着自己的作用域,所以用这函数里面一个变量来记录一下实时的i的值,这个n是不会随着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实现函数缓存

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

  let memoize = function (fn) {
    let toStr = Object.prototype.toString
    let isCallable = function (fn) {
      return typeof fn === 'function' || toStr.call(fn) === '[object Function]'
    }
    if (!isCallable(fn)) {
      throw new TypeError('memoize: when provided, the first argument must be a function')
    }
    let cache = {}
    return function() {
      if (arguments.length < 1) {
        throw new TypeError('memoize: arguments cannot be null or undefined')
      }
      let args = Array.prototype.slice.call(arguments)
      let str = args.join('-')
      cache[str] = cache[str] || fn.apply(fn, arguments)
      return cache[str]
    }
  }

  let sum = memoize(function (a, b) {
    console.log('开始相加')
    return a + b
  })复制代码

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

sum(2,6)    // 开始相加 8
sum(2,6)    // 8
sum(2,6)    // 8复制代码

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

5.结尾

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





原文地址:访问原文地址
快照地址: 访问文章快照