闭包缓存功能程序理解分析
偷空看一本讲Javascript的书,里面有一个关于用闭包记录函数缓存的程序。第一次看过去发现绕得很,本来想大致在头脑中留个印象就直接放过去,等以后要用再回头理解,但后来觉得还是认真消化一下比较好,于是稍微分析了一下,在此记录理解过程。
程序选自《Secrets of the JavaScript Ninja》示例代码5.14
Function.prototype.memoized=function (key) {
this._values=this._values||{};
return this._values[key]!=undefined?
this._values[key]:
this._values[key]=this.apply(this,arguments);
}
Function.prototype.memoize=function () {
var fn=this;
return function () {
return fn.memoized.apply(fn,arguments);
}
}
var isPrime=(function(num) {
var prime=num!=1;
for(var i=2;i<num;i++){
if(num%i==0){
prime=false;
break;
}
}
return prime;
}).memoize();
assert(isPrime(17),'17 is prime');
这段代码主要目的是通过闭包记录一个函数的属性,并不需要用户手动调用memoized函数实现自动记录,自动读取。
首先在调用memoize开始执行所有的函数。
var isPrime=(function(num) {
var prime=num!=1;
for(var i=2;i<num;i++){
if(num%i==0){
prime=false;
break;
}
}
return prime;
}).memoize();
memoize被定义为所有函数的通用字段,它返回一个函数,这个函数被赋值给了isPrime,这个函数没有形参,此处并没有调用memoize返回的函数,在通用函数memoize中创建了一个闭包,这个闭包里保存了fn即时变量,个人理解即时变量是执行时保存的当时的值,会在闭包里永久保存下来一个状态值,这个状态值不会因为程序运行到其他地方引用发生了变化而变化,比如这里fn保存了程序运行到这里时的this引用,当时this为function(num){}这个方法,然后这个方法被fn记录下来了,于是在闭包中存储下来了,即使后来执行return的方法时this发生了改变,那也不会影响fn是引用这个function这件事:
Function.prototype.memoize=function () {
var fn=this;
return function () {
return fn.memoized.apply(fn,arguments);
}
}
接下来到执行断言assert语句时才会执行memoize返回的那个方法,其中assert断言函数细节参照该书前几章节,当程序执行isPrime(17)这条语句时,实际上执行的是如下方法:
function () {
return fn.memoized.apply(fn,arguments);
}
而其中的arguments只有一个元素,这里是isPrime调用的参数17,注意这里isPrime函数其实并没有形参,但是由于arguments自动获取参数列表,才会在执行时自动获取参数17;fn为闭包中保存好的之前的function(num){…}函数,完整内容如下:
function(num) {
var prime=num!=1;
for(var i=2;i<num;i++){
if(num%i==0){
prime=false;
break;
}
}
return prime;
}
虽然这时候this已经不是这个函数了,此时的this(上下文)是window,但是由于在闭包中之前已经保存好了一个“副本”(为了便于理解,个人理解为副本),所以fn仍然是之前保存好的函数引用,然后执行fn.memoized.apply(fn,arguments)这条语句,其中memoized是程序中定义的另一个所有函数的通用字段,它也返回一个函数,它的作用就是利用闭包记录一些函数计算结果并且存储在函数本身中,通过使用apply而不是直接调用memoized函数可以指定函数中的上下文,将上下文直接设置为fn也就是isPrime这样可以使计算结果绑定在isPrime函数中,其中 return fn.memoized.apply(fn,arguments);这条语句可以直接用return fn.apply(fn,arguments);替代结果一样,但是不能缓存计算结果了。