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