这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
1. 什么是闭包
我们首先来看一个闭包的例子:
function foo() {
var a = 2
function bar() {
console.log(a)
}
return bar
}
var baz = foo()
baz() // 输出: 2
foo
函数传递出了一个函数bar
,传递出来的bar
被赋值给baz
并调用,虽然这时baz
是在foo
作用域外执行的,但baz
在调用的时候可以访问到前面的bar
函数所在的foo
的内部作用域。- 由于
bar
声明在foo
函数内部,bar
拥有涵盖foo
内部作用域的闭包,使得foo
的内部作用域一直存活不被回收。一般来说,函数在执行完后其整个内部作用域都会被销毁,因为JavaScript
的GC
(Garbage Collection)垃圾回收机制会自动回收不再使用的内存空间。但是闭包会阻止某些GC
,比如本例中foo()
执行完,因为返回的bar
函数依然持有其所在作用域的引用,所以其内部作用域不会被回收。 - 注意: 如果不是必须使用闭包,那么尽量避免创建它,因为闭包在处理速度和内存消耗方面对性能具有负面影响。
2. 利用闭包实现结果缓存(备忘模式)
function add(a) {
return a + 1;
}
- 多次运行
add()
时,每次得到的结果都是重新计算得到的,如果是开销很大的计算操作的话就比较消耗性能了,这里可以对已经计算过的输入做一个缓存。 - 所以这里可以利用闭包的特点来实现一个简单的缓存,在函数内部用一个对象存储输入的参数,如果下次再输入相同的参数,那就比较一下对象的属性,如果有缓存,就直接把值从这个对象里面取出来。
/* 备忘函数 */
function memorize(fn) {
var cache = {}
return function() {
var args = Array.prototype.slice.call(arguments)
var key = JSON.stringify(args)
return cache[key] || (cache[key] = fn.apply(fn, args))
}
}
/* 复杂计算函数 */
function add(a) {
return a + 1
}
var adder = memorize(add)
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(2) // 输出: 3 当前: cache: { '[1]': 2, '[2]': 3 }
使用 ES6
的方式会更优雅一些:
/* 备忘函数 */
function memorize(fn) {
const cache = {}
return function(...args) {
const key = JSON.stringify(args)
return cache[key] || (cache[key] = fn.apply(fn, args))
}
}
/* 复杂计算函数 */
function add(a) {
return a + 1
}
const adder = memorize(add)
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(2) // 输出: 3 当前: cache: { '[1]': 2, '[2]': 3 }
稍微解释一下:
- 备忘函数中用
JSON.stringify
把传给adder
函数的参数序列化成字符串,把它当做cache
的索引,将add
函数运行的结果当做索引的值传递给cache
,这样adder
运行的时候如果传递的参数之前传递过,那么就返回缓存好的计算结果,不用再计算了,如果传递的参数没计算过,则计算并缓存fn.apply(fn, args)
,再返回计算的结果。 - 当然这里的实现如果要实际应用的话,还需要继续改进一下,比如:
- 缓存不可以永远扩张下去,这样太耗费内存资源,我们可以只缓存最新传入的
n
个; - 在浏览器中使用的时候,我们可以借助浏览器的持久化手段,来进行缓存的持久化,比如
cookie
、localStorage
等; - 这里的复杂计算函数可以是过去的某个状态,比如对某个目标的操作,这样把过去的状态缓存起来,方便地进行状态回退。
- 复杂计算函数也可以是一个返回时间比较慢的异步操作,这样如果把结果缓存起来,下次就可以直接从本地获取,而不是重新进行异步请求。
注意: cache
不可以是 Map
,因为 Map
的键是使用 ===
比较的,因此当传入引用类型值作为键时,虽然它们看上去是相等的,但实际并不是,比如 [1]!==[1]
,所以还是被存为不同的键。
// X 错误示范
function memorize(fn) {
const cache = new Map()
return function(...args) {
return cache.get(args) || cache.set(args, fn.apply(fn, args)).get(args)
}
}
function add(a) {
return a + 1
}
const adder = memorize(add)
adder(1) // 2 cache: { [ 1 ] => 2 }
adder(1) // 2 cache: { [ 1 ] => 2, [ 1 ] => 2 }
adder(2) // 3 cache: { [ 1 ] => 2, [ 1 ] => 2, [ 2 ] => 3 }
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!