函数定义(掌握)
函数使用 function 关键字来定义,它可以用在函数定义表达式或者函数声明语句里。在两种形式中,函数定义都从 function 关键字开始,其后跟随这些组成部分:
- 函数名称标识符。函数名称是函数声明语句必需的部分。它的用途就像变量的名字,新定义的函数对象会赋值给这个变量。对函数定义表达式来说,这个名字是可选的:如果存在,该名字只存在于函数体中,并指代该函数对象本身。
- 一对圆括号,其中包含由 0 个或者多个用逗号隔开的标识符组成的列表。这些标识符是函数的参数名称,它们就像函数体中的局部变量一样。
- 一对花括号,其中包含 0 条或多条 JavaScript 语句。这些语句构成了函数体:一旦调用函数,就会执行这些语句。
函数声明语句“被提前”到外部脚本或外部函数作用域的顶部,所以以这种方式声明的函数,可以被在它定义之前出现的代码所调用。
不过,以表达式定义的函数就另当别论了,为了调用一个函数,必须要能引用它,而要使用一个以表达式方式定义的函数之前,必须把它赋值给一个变量。变量的声明提前了,但给变量赋值是不会提前的,所以,以表达式方式定义的函数在定义之前无法调用。
函数调用(掌握)
构成函数主体的 JavaScript 代码在定义之时并不会执行,只有调用该函数时,它们才会执行。有 4 种方式来调用 JavaScript 函数:
- 作为函数
- 作为方法
- 作为构造函数
- 通过它们的 cal1()和 apply()方法间接调用
函数调用
printprops({ x: 1 });
var total = distance(o, 0, 2, 1) + distance(2, 1, 3, 5);
var probability = factorial(5) / factorial(13);
2
3
对于普通的函数调用,函数的返回值成为调用表达式的值。如果该函数返回是因为解释器到达结尾,返回值就是 undefined。如果函数返回是因为解释器执行到一条 return 语句,返回值就是 return 之后的表达式的值,如果 return 语句没有值,则返回 undefined。
根据 ECMAScript3 和非严格的 ECMAScript5 对函数调用的规定,调用上下文(this 的值)是全局对象。然而,在严格模式下,调用上下文则是 undefined。
以函数形式调用的函数通常不使用 this 关键字。不过,“this”可以用来判断当前是否是严格模式。
/定义并调用一个函数来确定当前脚本运行时是否为严格模式
var strict=(function(){ return !this;}());
2
方法调用
对方法调用的参数和返回值的处理,和上面所描述的普通函数调用完全一致。但是,方法调用和函数调用有一个重要的区别,即:调用上下文。下面是一个具体的例子:
var calculator={//对象直接量
operand1:1,
operand2:1,
add:function(){
//注意this关键字的用法,this指代当前对象
this.result=this.operand1+this.operandz;
};
calculator.add();//这个方法调用计算1+1的结果
calculator.result //=>2
2
3
4
5
6
7
8
9
需要注意的是,this 是一个关键字,不是变量,也不是属性名。JavaScript 的语法不允许给 this 赋值。
和变量不同,关键字 this 没有作用域的限制,嵌套的函数不会从调用它的函数中继承 this。如果嵌套函数作为方法调用,其 this 的值指向调用它的对象。如果嵌套函数作为函数调用,其 this 值不是全局对象(非严格模式下)就是 undefined(严格模式下)。很多人误以为调用嵌套函数时 this 会指向调用外层函数的上下文。如果你想访问这个外部函数的 this 值,需要将 this 的值保存在一个变量里,这个变量和内部函数都同在一个作用域内。通常使用变量 self 来保存 this。
构造函数调用
如果函数或者方法调用之前带有关键宇 new,构造函数调用和普通的函数调用以及方法调用在实参处理、调用上下文和返回值方面都有不同。
构造函数调用创建一个新的空对象,这个对象继承自构造函数的 prototype 属性。构造函数试图初始化这个新创建的对象,并将这个对象用做其调用上下文,因此构造函数可以使用 this 关键字来引用这个新创建的对象。注意,尽管构造函数看起来像一个方法调用,它依然会使用这个新对象作为调用上下文。也就是说,在表达式 new o.m()中,调用上下文并不是 o。
构造函数通常不使用 return 关键宇,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值。然而如果构造函数显式地使用 return 语句返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用 return 语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果。
间接调用
JavaScript 中的函数也是对象,和其他 JavaScript 对象没什么两样,函数对象也可以包含方法。其中的两个方法 ca11()和 apply()可以用来间接地调用函数。两个方法都允许显式指定调用所需的 this 值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。ca11()方法使用它自有的实参列表作为函数的实参,apply()方法则要求以数组的形式传入参数。
函数的实参和形参(掌握)
可选形参
当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为 undefined 值。因此在调用函数时形参是否可选以及是否可以省略应当保持较好的适应性。为了做到这一点,应当给省略的参数赋一个合理的默认值。
a = a || null;
需要注意的是,当用这种可选实参来实现函数时,需要将可选实参放在实参列表的最后。那些调用你的函数的程序员是没办法省略第一个实参并传入第二个实参的,它必须将 undefined 作为第一个实参显式传入。同样注意在函数定义中使用注释/*optional*/来强调形参是可选的。
可变长的实参列表:实参对象
当调用函数的时候传入的实参个数超过函数定义时的形参个数时,没有办法直接获得未命名值的引用。参数对象解决了这个问题。在函数体内,标识符 arguments 是指向实参对象的引用,实参对象是一个类数组对象,这样可以通过数字下标就能访问传入函数的实参值,而不用非要通过名字来得到实参。
function max(/*...*/){
var max=Number.NEGATIVE_INFINITY;
//遍历实参,查找并记住最天值
for(var i=0;i< arguments.length;i++)
if(arguments[i]>max)max =arguments[i];
//返回最大值
return max;
}
var largest =max(1,10,100,2,3,1000,4,5,10000,6);//=>10000
2
3
4
5
6
7
8
9
通过实参名字来修改实参值的话,通过 arguments[]数组也可以获取到更改后的值,在 ECMAScript 5 中移除了实参对象的这个特殊特性。在严格模式下还有一点(和非严格模式下相比的)不同,在非严格模式中,函数里的 arguments 仅仅是一个标识符,在严格模式中,它变成了一个保留字。严格模式中的函数无法使用 arguments 作为形参名或局部变量名,也不能给 arguments 赋值。
callee 和 caller 属性
除了数组元素,实参对象还定义了 callee 和 caller 属性。在 ECMAScript 5 严格模式中,对这两个属性的读写操作都会产生一个类型错误。而在非严格模式下,ECMAScript 标准规范规定 callee 属性指代当前正在执行的函数。caller 是非标准的,但大多数浏览器都实现了这个属性,它指代调用当前正在执行的函数的函数。通过 caller 属性可以访问调用栈。callee 属性在某些时候会非常有用,比如在匿名函数中通过 callee 来递归地调用自身。
var factorial = function(x) {
if (x <= 1) return 1;
return x * arguments.callee(x - 1);
};
2
3
4
将对象属性用做实参
当一个函数包含超过三个形参时,对于程序员来说,要记住调用函数中实参的正确顺序实在让人头疼。最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要了。定义函数的时候,传入的实参都写入一个单独的对象之中,在调用的时候传入一个对象,对象中的名/值对是真正需要的实参数据。
作为值的函数
在 JavaScript 中,函数不仅是一种语法,也是值,也就是说,可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传人另外一个函数等。
自定义函数属性
JavaScript 中的函数并不是原始值,而是一种特殊的对象,也就是说,函数可以拥有属性。当函数需要一个“静态”变量来在调用时保持某个值不变,最方便的方式就是给函数定义属性,而不是定义全局变量,显然定义全局变量会让命名空间变得更加杂乱无章。
//计算阶乘,并将结果缓存至函数的属性中
function factorial(n){
if(isFinite(n)8& n>o 8& n==Math.round(n)){//有限的正整数
if(!(n in factorial))//如果没有缓存结果
factorial[n]=n* factorial(n-1);//计算结果并缓存之
return factorial[n];//返回缓存结果
}
else return NaN;//如果输入有误
}
factorial[1]=1;//初始化缓存以保存这种基本情况
2
3
4
5
6
7
8
9
10
作为命名空间的函数(掌握)
比如,假设你写了一段 JavaScript 模块代码,假定这段代码定义了一个用以存储中间计算结果的变量。这样问题就来了,当模块代码放到不同的程序中运行时,你无法得知这个变量是否已经创建了,如果已经存在这个变量,那么将会和代码发生冲突。解决办法当然是将代码放入一个函数内,然后调用这个函数。这样全局变量就变成了函数内的局部变量。
(function(){//mymodule()匿名的函数表达式
//模块代码
}());//结束函数定义并立即调用它
2
3
闭包(掌握)
和其他大多数现代编程语言一样,JavaScript 也采用词法作用域(lexical scoping),也就是说,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
为了实现这种词法作用域,JavaScript 函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。
var scope="global scope";//全局变量
function checkscope(){
var scope="local scope";//局部变量
function f(){ return scope;}//在作用域中返回这个值
return f();
}
checkscope()//=>"local scope"
2
3
4
5
6
7
回想一下词法作用域的基本规则:JavaScript 函数的执行用到了作用域链,这个作用域链是函数定义的时候创建的。嵌套的函数 f()定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管在何时何地执行函数 f(),这种绑定在执行 f()时依然有效。因此最后一行代码返回“local scope”,而不是“global scope”。
实现闭包
我们将作用域链描述为一个对象列表,不是绑定的栈。每次调用 JavaScript 函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。
当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉。
如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。但如果这些嵌套的函数对象在外部函数中保存下来,那么它们也会和所指向的变量绑定对象一样当做垃圾回收。
但是如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收。
var uniqueInteger=(function(){//定义函数并立即调用
var counter=0;//函数的私有状态
return function(){ return counter++;};
}());
2
3
4
function counter(){
var n=0;
return{
count:function(){return n++;},
reset:function(){n=o;}
};
}
var c=counter(),d=counter();//创建两个计数器
d.count()//l=>0:它们互不干扰
c.reset()//reset()和count()方法共享状态
C.count()//=>0:因为我们重置了c
d.count()//=>1:而没有重置d
2
3
4
5
6
7
8
9
10
11
12
每次调用 counter()都会创建一个新的作用域链和一个新的私有变量。因此,如果调用 counter()两次,则会得到两个计数器对象,而且彼此包含不同的私有变量,调用其中一个计数器对象的 count()或 reset()不会影响到另外一个对象。
function counter(n){//函数参数n是一个私有变量
return{
//属性getter方法返回并给私有计数器var递增1
get count(){return n++;},
//属性setter不允许n递减
set count(m){
if(m>=n)n=m;
else throw Error("count can only be set to a larger value");
}
};
}
var c=counter(1000);
c.count//=>1000
c.count//=>1001
c.count=2000
c.count//=>2000
c.count=2000//=>Error!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
需要注意的是,这个版本的 counter()函数并未声明局部变量,而只是使用参数 n 来保存私有状态,属性存取器方法可以访问 n。这样的话,调用 counter()的函数就可以指定私有变量的初始值了。
//这个函数返回一个总是返回v的函数
function constfunc(v){return function(){return v;};}
//创建一个数组用来存储常数函数
var funcs=[];
for(var i=o;i<10;i++)funcs[i]=constfunc(i);
//在第5个位置的元素所表示的函数返回值为5
funcs[5]()//=>5
2
3
4
5
6
7
这段代码利用循环创建了很多个闭包,当写类似这种代码的时候往往会犯一个错误:那就是试图将循环代码移入定义这个闭包的函数之内,看一下这段代码:
//返回一个函数组成的数组,它们的返回值是0~9
function constfuncs(){
var funcs=[];
for(vari=0;i<10;i++)
funcs[i]=function(){ return i;};
return funcs;
}
var funcs=constfuncs();
funcs[5]()//返回值是10
2
3
4
5
6
7
8
9
书写闭包的时候还需注意一件事情,this 是 JavaScript 的关键字,而不是变量。正如之前讨论的,每个函数调用都包含一个 this 值,如果闭包在外部函数里是无法访问 this 的,除非外部函数将 this 转存为一个变量:
var self=this;//将this保存至一个变量中,以便嵌套的函数能够访问它
函数属性、方法和构造函数(掌握)
length 属性
在函数体里,arguments.length 表示传入函数的实参的个数。而函数本身的 1ength 属性则有着不同含义。函数的 1ength 属性是只读属性,它代表函数实参的数量,这里的参数指的是“形参”而非“实参”,也就是在函数定义时给出的实参个数,通常也是在函数调用时期望传入函数的实参个数。
prototype 属性
每一个函数都包含一个 prototype 属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”(prototype object)。每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。
call()方法和 apply()方法
function classof(o) {
if (o === null) return 'Null';
if ((o = a = undefined)) return 'Undefined';
return Object.prototype.tostring.call(o).slice(8, -1);
}
2
3
4
5
在 ECMAScript5 的严格模式中,ca11()和 apply()的第一个实参都会变为 this 的值,哪怕传入的实参是原始值甚至是 null 或 undefined。在 ECMAScript3 和非严格模式中,传入的 null 和 undefined 都会被全局对象代替,而其他原始值则会被相应的包装对象(wrapper object)所替代。apply()方法和 cal1()类似,但传入实参的形式和 cal1()有所不同,它的实参都放入一个数组当中:需要注意的是,传入 apply()的参数数组可以是类数组对象也可以是真实数组。
f.apply(o, [1, 2]);
bind()方法
bind()是在 ECMAScript5 中新增的方法,但在 ECMAScript3 中可以轻易模拟 bind()。从名字就可以看出,这个方法的主要作用就是将函数绑定至某个对象。当在函数 f()上调用 bind()方法并传入一个对象 0 作为参数,这个方法将返回一个新的函数。(以函数调用的方式)调用新的函数将会把原始的函数 f()当做。的方法来调用。传入新函数的任何实参都将传入原始函数,比如:
function f(y){ return this.x+y;}//这个是待绑定的函数
var o={x:1};/将要绑定的对象
var g=f.bind(o);//通过调用g(x)来调用o.f(x)
g(2)//=>3
2
3
4
ECMAScript 5 中的 bind()方法不仅仅是将函数绑定至一个对象,它还附带一些其他应用:除了第一个实参之外,传入 bind()的实参也会绑定至 this,这个附带的应用是一种常见的函数式编程技术,有时也被称为“柯里化”(currying)。
var sum=function(x,y){ return x+y};//返回两个实参的和值
//创建一个类似sum的新函数,但this的值绑定到nu11
//并且第一个参数绑定到1,这个新的函数期望只传入一个实参
var succ=sum.bind(null,1);
succ(2)//=>3:x绑定到1,并传入2作为实参y
function f(y,z){ return this.x+y+z};//另外一个做累加计算的函数
var g=f.bind({x:1},2);//绑定this和y
g(3)//=>6:this.x绑定到1,y绑定到2,z绑定到3
2
3
4
5
6
7
8
ECMAScript3 版本的 Function.bind()方法
if(!Function.prototype.bind){
Function.prototype.bind=function(o/*,args*/){
//将this和arguments的值保存至变量中
//以便在后面嵌套的函数中可以使用它们
var self=this,boundArgs=arguments;
//bind()方法的返回值是一个函数
return function(){
//创建一个实参列表,将传入bind()的第二个及后续的实参都传入这个函数
var args=[],i;
for(i=1;i< boundArgs.length;i++)args.push(boundArgs[i]);
for(i=o;i<arguments.length;i++)args.push(arguments[i]);
//现在将self作为o的方法来调用,传入这些实参
return self.apply(o,args);
};
};
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ECMAScript5 定义的 bind()方法也有一些特性是上述 ECMAScript3 代码无法模拟的。首先,真正的 bind()方法返回一个函数对象,这个函数对象的 length 属性是绑定函数的形参个数减去绑定实参的个数(1ength 的值不能小于零)。
再者,ECMAScript5 的 bind()方法可以顺带用做构造函数。如果 bind()返回的函数用做构造函数,将忽略传入 bind()的 this,原始函数就会以构造函数的形式调用,其实参也已经绑定。
由 bind()方法所返回的函数并不包含 prototype 属性(普通函数固有的 prot otype 属性是不能删除的),并且将这些绑定的函数用做构造函数时所创建的对象从原始的未绑定的构造函数中继承 prototype。同样,在使用 instanceof 运算符时,绑定构造函数和未绑定构造函数并无两样。
toString()方法
和所有的 JavaScript 对象一样,函数也有 toString()方法,ECMAScript 规范规定这个方法返回一个字符串,这个字符串和函数声明语句的语法相关。实际上,大多数(非全部)的 toString()方法的实现都返回函数的完整源码。内置函数往往返回一个类似 “[native code]”的字符串作为函数体。
Function()构造函数
不管是通过函数定义语句还是函数直接量表达式,函数的定义都要使用 function 关字。但函数还可以通过 Function()构造函数来定义,比如:
var f = new Function('x', 'y', 'return x*y;');
Function()构造函数可以传入任意数量的字符串实参,最后一个实参所表示的文本就是函数体;它可以包含任意的 JavaScript 语句,每两条语句之间用分号分隔。传入构造函数的其他所有的实参字符串是指定函数的形参名字的字符串。如果定义的函数不包含任何参数,只须给构造函数简单地传入一个字符串——函数体即可。
关于 Function()构造函数有几点需要特别注意:
- Function()构造函数允许 JavaScript 在运行时动态地创建并编译函数。
- 每次调用 Function()构造函数都会解析函数体,并创建新的函数对象。如果是在一个循环或者多次调用的函数中执行这个构造函数,执行效率会受影响。相比之下,循环中的嵌套函数和函数定义表达式则不会每次执行时都重新编译。
- 最后一点,也是关于 Function()构造函数非常重要的一点,就是它所创建的函数并不是使用词法作用域,相反,函数体代码的编译总是会在顶层函数执行,正如下面代码所示:
var scope="global";function constructFunction(){
var scope="local";
return new Function("return scope");//无法捕获局部作用域
}
//这一行代码返回global,因为通过Function()构造函数
//所返回的函数使用的不是局部作用域
constructFunction()();//=>"global"
2
3
4
5
6
7
Function()构造函数在实际编程过程中很少会用到。
函数式编程(掌握)
使用函数处理数组
自定义 map()和 reduce()函数
//对于每个数组元素调用函数f(),并返回一个结果数组
//如果Array.prototype.map定义了的话,就使用这个方法
var map=Array.prototype.map
?function(a,f){ return a.map(f);}//如果已经存在map()方法,就直接使用它
:function(a,f){//否则,自己实现一个
var results=[];
for(var i=0,len=a.length;i< len;i++){
if(i in a)results[i]=f.call(null,a[i],i,a);
return results;
};
2
3
4
5
6
7
8
9
10
//使用函数f()和可选的初始值将数组a减至一个值
//如果Array.prototype.reduce存在的话,就使用这个方法
var reduce=Array.prototype.reduce
?function(a,f,initial){//如果reduce()方法存在的话
if(arguments.1ength>2)
return a.reduce(f,initial);//如果传人了一个初始值
else return a.reduce(f);//否则没有初始值
}
:function(a,f,initial){//这个算法来自ES5规范
var i=0,len=a.length,accumulator;
//以特定的初始值开始,否则第一个值取自a
if(arguments.1ength>2)accumulator=initial;
else{//找到数组中第一个已定义的索引
if(len==o)throw TypeError();
while(i<len){
if(i in a){
accumulator =a[i++];
break;
}
else i++;
}
if(i==1en)throw TypeError();
}
//对于数组中剩下的元素依次调用f()
while(i<1en){
if(i in a)
accumulator=f.call(undefined,accumulator,a[i],i,a);
i++;
}
return accumulator;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
高阶函数
所谓高阶函数(higher-order function)就是操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数,这里是一个更常见的例子,它接收两个函数 f()和 g(),并返回一个新的函数用以计算 f(g())::
//返回一个新的可以计算f(g(...))的函数
//返回的函数h()将它所有的实参传入g(),然后将g()的返回值传入f()
//调用f()和g()时的this值和调用h()时的this值是同一个this
function compose(f,g){
return function(){
//需要给f()传人一个参数,所以使用f()的cal1()方法
//需要给g()传入很多参数,所以使用g()的apply()方法
return f.call(this,g.apply(this,arguments));
};
}
var square=function(x){ return x*x;};
var sum=function(x,y){ return x+y;};
var squareofsum=compose(square,sum);
squareofsum(2,3)//=>25
2
3
4
5
6
7
8
9
10
11
12
13
14
//这个高阶函数返回一个新的函数,这个新函数将它的实参传入f()
//并返回f的返回值的逻辑非
function not(f){
return function(){/返回一个新的函数
var result=f.apply(this,arguments);//调用f()
return !result;//对结果求反
};
var even=function(x){//判断a是否为偶数的函数
return x%2===0;
};
var odd =not(even);//一个新函数,所做的事情和even()相反
[1,1,3,5,5].every(odd);//=>true:每个元素都是奇数
2
3
4
5
6
7
8
9
10
11
12
不完全函数
作者在本节讨论的是一种函数变换技巧,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数(partial function),每次函数调用叫做不完全调用(partial application),这种函数变换的特点是每次调用都返回一个函数,直到得到最终运行结果为止,举一个简单的例子,将对函数 f(1,2,3,4,5,6)的调用修改为等价的 f(1,2)(3,4)(5,6),后者包含三次调用,和每次调用相关的函数就是“不完全函数”。
记忆
定义了一个阶乘函数,它可以将上次的计算结果缓存起来。在函数式编程当中,这种缓存技巧叫做“记忆”(memorization)。下面的代码展示了一个高阶函数,memorize()接收一个函数作为实参,并返回带有记忆能力的函数。
需要注意的是,记忆只是一种编程技巧,本质上是牺牲算法的空间复杂度以换取更优的时间复杂度,在客户端 JavaScript 中代码的执行时间复杂度往往成为瓶颈,因此在大多数场景下,这种牺牲空间换取时间的做法以提升程序执行效率的做法是非常可取的。
//返回f()的带有记忆功能的版本
//只有当f()的实参的字符串表示都不相同时它才会工作
function memorize(f){
var cache={};//将值保存在闭包内
return function(){
//将实参转换为字符串形式,并将其用做缓存的键
var key=arguments.length +Array.prototype.join.call(arguments,",");
if(key in cache) return cache[key];
else return cache[key]=f.apply(this,arguments);
};
}
2
3
4
5
6
7
8
9
10
11
memorize()函数创建一个新的对象,这个对象被当做缓存(的宿主)并赋值给一个局部变量,因此对于返回的函数来说它是私有的(在闭包中)。所返回的函数将它的实参数组转换成字符串,并将字符串用做缓存对象的属性名。如果在缓存中存在这个值,则直接返回它。
否则,就调用既定的函数对实参进行计算,将计算结果缓存起来并返回,下面的代码展示了如何使用 memorize():
//返回两个整数的最大公约数
//使用欧几里德算法:http://en.wikipedia.org/wiki/Euclidean_algorithm
function gcd(a,b){//这里省略对ab的类型检查
var t;//临时变量用来存储交换数值
if(a<b)t=b,b=a,a=t;//确保a>=b
while(bl=0)t=b,b=ab,a=t;//这是求最大公约数的欧几里德算法
return a;
}
var gcdmemo=memorize(gcd);
gcdmemo(85,187)//=>17
//注意,当我们写一个递归函数时,往往需要实现记忆功能
//我们更希望调用实现了记忆功能的递归函数,而不是原递归函数
var factorial=memorize(function(n){
return(n<=1)?1:n*factorial(n-1);
});
factorial(5)//=>120.对于4~1的值也有缓存
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16