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