`
abruzzi
  • 浏览: 452746 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

JavaScript内核系列 第9章 函数式的Javascript

阅读更多

 

第九章 函数式的Javascript

要说JavaScript和其他较为常用的语言最大的不同是什么,那无疑就是JavaScript是函数式的语言,函数式语言的特点如下:

函数为第一等的元素,即人们常说的一等公民。就是说,在函数式编程中,函数是不依赖于其他对象而独立存在的(对比与Java,函数必须依赖对象,方法是对象的方法)

函数可以保持自己内部的数据,函数的运算对外部无副作用(修改了外部的全局变量的状态等),关于函数可以保持自己内部的数据这一特性,称之为闭包。我们可以来看一个简单的例子:

 

var outter = function(){
    var x = 0;
    return function(){
       return x++;
    }
}
 
var a = outter();
print(a());
print(a());
 
var b = outter();
print(b());
print(b());

 

运行结果为:

0
1
0
1

 

变量a通过闭包引用outter的一个内部变量,每次调用a()就会改变此内部变量,应该注意的是,当调用a时,函数outter已经返回了,但是内部变量x的值仍然被保持。而变量b也引用了outter,但是是一个不同的闭包,所以b开始引用的x值不会随着a()被调用而改变,两者有不同的实例,这就相当于面向对象中的不同实例拥有不同的私有属性,互不干涉。

由于JavaScript支持函数式编程,我们随后会发现JavaScript许多优美而强大的能力,这些能力得力于以下主题:匿名函数,高阶函数,闭包及柯里化等。熟悉命令式语言的开发人员可能对此感到陌生,但是使用lisp, scheme等函数式语言的开发人员则觉得非常亲切。

9.1匿名函数

匿名函数在函数式编程语言中,术语成为lambda表达式。顾名思义,匿名函数就是没有名字的函数,这个是与日常开发中使用的语言有很大不同的,比如在C/Java中,函数和方法必须有名字才可以被调用。在JavaScript中,函数可以没有名字,而且这一个特点有着非凡的意义:

 

function func(){
    //do something
}
 
var func = function(){
    //do something
}

 

这两个语句的意义是一样的,它们都表示,为全局对象添加一个属性func,属性func的值为一个函数对象,而这个函数对象是匿名的。匿名函数的用途非常广泛,在JavaScript代码中,我们经常可以看到这样的代码:

 

var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});
print(mapped);

 

应该注意的是,map这个函数的参数是一个匿名函数,你不需要显式的声明一个函数,然后将其作为参数传入,你只需要临时声明一个匿名的函数,这个函数被使用之后就别释放了。在高阶函数这一节中更可以看到这一点。

9.2高阶函数

通常,以一个或多个函数为参数的函数称之为高阶函数。高阶函数在命令式编程语言中有对应的实现,比如C语言中的函数指针,Java中的匿名类等,但是这些实现相对于命令式编程语言的其他概念,显得更为复杂。

9.2.1 JavaScript中的高阶函数

         Lisp中,对列表有一个map操作,map接受一个函数作为参数,map对列表中的所有元素应用该函数,最后返回处理后的列表(有的实现则会修改原列表),我们在这一小节中分别用JavaScript/C/Java来对map操作进行实现,并对这些实现方式进行对比:

 

Array.prototype.map = function(func /*, obj */){
    var len = this.length;
    //check the argument
    if(typeof func != "function"){
       throw new Error("argument should be a function!");
    }
   
    var res = [];
    var obj = arguments[1];
    for(var i = 0; i < len; i++){
       //func.call(), apply the func to this[i]
       res[i] = func.call(obj, this[i], i, this);
    }
   
    return res;
}

 

 

我们对JavaScript的原生对象Array的原型进行扩展,函数map接受一个函数作为参数,然后对数组的每一个元素都应用该函数,最后返回一个新的数组,而不影响原数组。由于map函数接受的是一个函数作为参数,因此map是一个高阶函数。我们进行测试如下:

 

function double(x){
    return x * 2;
}
 
[1, 2, 3, 4, 5].map(double);//return [2, 4, 6, 8, 10]

 

应该注意的是double是一个函数。根据上一节中提到的匿名函数,我们可以为map传递一个匿名函数:

 

var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});
print(mapped);

 

这个示例的代码与上例的作用是一样的,不过我们不需要显式的定义一个double函数,只需要为map函数传递一个“可以将传入参数乘2并返回”的代码块即可。再来看一个例子:

 

[
    {id : "item1"},
    {id : "item2"},
    {id : "item3"}
].map(function(current){
    print(current.id);
});

将会打印:

 

item1
item2
item3

 

 

也就是说,这个map的作用是将传入的参数(处理器)应用在数组中的每个元素上,而不关注数组元素的数据类型,数组的长度,以及处理函数的具体内容。

9.2.2 C语言中的高阶函数

C语言中的函数指针,很容易实现一个高阶函数。我们还以map为例,说明在C语言中如何实现:

 

//prototype of function
void map(int* array, int length, int (*func)(int));

 

 

map函数的第三个参数为一个函数指针,接受一个整型的参数,返回一个整型参数,我们来看看其实现:

 

//implement of function map
void map(int* array, int length, int (*func)(int)){
    int i = 0;
    for(i = 0; i < length; i++){
       array[i] = func(array[i]);
    }
}

 

 

我们在这里实现两个小函数,分别计算传入参数的乘2的值,和乘3的值,然后进行测试:

 

int twice(int num) { return num * 2; }
int triple(int num){ return num * 3; }
 
//function main
int main(int argc, char** argv){
    int array[5] = {1, 2, 3, 4, 5};
    int i = 0;
    int len = 5;
 
    //print the orignal array
    printArray(array, len);
 
    //mapped by twice
    map(array, len, twice);
    printArray(array, len);
 
    //mapped by twice, then triple
    map(array, len, triple);
    printArray(array, len);
 
    return 0;
}

 

运行结果如下:

 

1 2 3 4 5
2 4 6 8 10
6 12 18 24 30

 

 

应该注意的是map的使用方法,如map(array, len, twice)中,最后的参数为twice,而twice为一个函数。因为C语言中,函数的定义不能嵌套,因此不能采用诸如JavaScript中的匿名函数那样的简洁写法。

         虽然在C语言中可以通过函数指针的方式来实现高阶函数,但是随着高阶函数的“阶”的增高,指针层次势必要跟着变得很复杂,那样会增加代码的复杂度,而且由于C语言是强类型的,因此在数据类型方面必然有很大的限制。

9.2.3 Java中的高阶函数

Java中的匿名类,事实上可以理解成一个教笨重的闭包(可执行单元),我们可以通过Java的匿名类来实现上述的map操作,首先,我们需要一个对函数的抽象:

 

    interface Function{
       int execute(int x);
    }

 

我们假设Function接口中有一个方法execute,接受一个整型参数,返回一个整型参数,然后我们在类List中,实现map操作:

 

    private int[] array;
   
    public List(int[] array){
       this.array = array;
    }
   
    public void map(Function func){
       for(int i = 0, len = this.array.length; i < len; i++){
           this.array[i] = func.execute(this.array[i]); 
       }
    }

 

map接受一个实现了Function接口的类的实例,并调用这个对象上的execute方法来处理数组中的每一个元素。我们这里直接修改了私有成员array,而并没有创建一个新的数组。好了,我们来做个测试:

 

    public static void main(String[] args){
       List list = new List(new int[]{1, 2, 3, 4, 5});
       list.print();
       list.map(new Function(){
           public int execute(int x){
              return x * 2;
           }
       });
       list.print();
      
       list.map(new Function(){
           public int execute(int x){
              return x * 3;
           }
       });
       list.print();
    }

 

 

同前边的两个例子一样,这个程序会打印:

 

1 2 3 4 5
2 4 6 8 10
6 12 18 24 30

 

 

灰色背景色的部分即为创建一个匿名类,从而实现高阶函数。很明显,我们需要传递给map的是一个可以执行execute方法的代码。而由于Java是命令式的编程语言,函数并非第一位的,函数必须依赖于对象,附属于对象,因此我们不得不创建一个匿名类来包装这个execute方法。而在JavaScript中,我们只需要传递函数本身即可,这样完全合法,而且代码更容易被人理解。

 

 

9.3闭包与柯里化

闭包和柯里化都是JavaScript经常用到而且比较高级的技巧,所有的函数式编程语言都支持这两个概念,因此,我们想要充分发挥出JavaScript中的函数式编程特征,就需要深入的了解这两个概念,我们在第七章中详细的讨论了闭包及其特征,闭包事实上更是柯里化所不可缺少的基础。

9.3.1柯里化的概念

闭包的我们之前已经接触到,先说说柯里化。柯里化就是预先将函数的某些参数传入,得到一个简单的函数,但是预先传入的参数被保存在闭包中,因此会有一些奇特的特性。比如:

 

var adder = function(num){
    return function(y){
       return num + y;  
    }
}
 
var inc = adder(1);
var dec = adder(-1);

 

这里的inc/dec两个变量事实上是两个新的函数,可以通过括号来调用,比如下例中的用法:

 

//inc, dec现在是两个新的函数,作用是将传入的参数值(+/-)1
print(inc(99));//100
print(dec(101));//100
 
print(adder(100)(2));//102
print(adder(2)(100));//102

 

9.3.2柯里化的应用

根据柯里化的特性,我们可以写出更有意思的代码,比如在前端开发中经常会遇到这样的情况,当请求从服务端返回后,我们需要更新一些特定的页面元素,也就是局部刷新的概念。使用局部刷新非常简单,但是代码很容易写成一团乱麻。而如果使用柯里化,则可以很大程度上美化我们的代码,使之更容易维护。我们来看一个例子:

 

//update会返回一个函数,这个函数可以设置id属性为item的web元素的内容
function update(item){
    return function(text){
       $("div#"+item).html(text);
    }
}
 
//Ajax请求,当成功是调用参数callback
function refresh(url, callback){
    var params = {
       type : "echo",
       data : ""
    };
 
    $.ajax({
       type:"post",
       url:url,
       cache:false,
       async:true,
       dataType:"json",
       data:params,
      
       //当异步请求成功时调用
       success: function(data, status){
           callback(data);
       },
      
       //当请求出现错误时调用
       error: function(err){
           alert("error : "+err);
       }
    });
}
 
refresh("action.do?target=news", update("newsPanel"));
refresh("action.do?target=articles", update("articlePanel"));
refresh("action.do?target=pictures", update("picturePanel"));

 

 

其中,update函数即为柯里化的一个实例,它会返回一个函数,即:

 

update("newsPanel") = function(text){
    $("div#newsPanel").html(text);
}

由于update(“newsPanel”)的返回值为一个函数,需要的参数为一个字符串,因此在refreshAjax调用中,当success时,会给callback传入服务器端返回的数据信息,从而实现newsPanel面板的刷新,其他的文章面板articlePanel,图片面板picturePanel的刷新均采取这种方式,这样,代码的可读性,可维护性均得到了提高。

9.4一些例子

9.4.1函数式编程风格

通常来讲,函数式编程的谓词(关系运算符,如大于,小于,等于的判断等),以及运算(如加减乘数等)都会以函数的形式出现,比如:

 

a > b

 

通常表示为:

 

gt(a, b)//great than

 

因此,可以首先对这些常见的操作进行一些包装,以便于我们的代码更具有“函数式”风格:

 

function abs(x){ return x>0?x:-x;}
function add(a, b){ return a+b; }
function sub(a, b){ return a-b; }
function mul(a, b){ return a*b; }
function div(a, b){ return a/b; }
function rem(a, b){ return a%b; }
function inc(x){ return x + 1; }
function dec(x){ return x - 1; }
function equal(a, b){ return a==b; }
function great(a, b){ return a>b; }
function less(a, b){ return a<b; }
function negative(x){ return x<0; }
function positive(x){ return x>0; }
function sin(x){ return Math.sin(x); }
function cos(x){ return Math.cos(x); }

 

如果我们之前的编码风格是这样:

// n*(n-1)*(n-2)*...*3*2*1
function factorial(n){
    if(n == 1){
        return 1;
    }else{
        return n * factorial(n - 1);
    }
}

 

 

在函数式风格下,就应该是这样了:

 

function factorial(n){
    if(equal(n, 1)){
        return 1;
    }else{
        return mul(n, factorial(dec(n)));
    }
}

 

函数式编程的特点当然不在于编码风格的转变,而是由更深层次的意义。比如,下面是另外一个版本的阶乘实现:

 

/*
 *  product <- counter * product
 *  counter <- counter + 1
 * */
 
function factorial(n){
    function fact_iter(product, counter, max){
        if(great(counter, max)){
            return product;
        }else{
            fact_iter(mul(counter, product), inc(counter), max);
        }
    }
 
    return fact_iter(1, 1, n);
}

 

 

虽然代码中已经没有诸如+/-/*//之类的操作符,也没有>,<,==,之类的谓词,但是,这个函数仍然算不上具有函数式编程风格,我们可以改进一下:

function factorial(n){
    return (function factiter(product, counter, max){
       if(great(counter, max)){
           return product;
       }else{
           return factiter(mul(counter, product), inc(counter), max);
       }
    })(1, 1, n);
}
 
factorial(10);

 

通过一个立即运行的函数factiter,将外部的n传递进去,并立即参与计算,最终返回运算结果。

9.4.2 Y-结合子

提到递归,函数式语言中还有一个很有意思的主题,即:如果一个函数是匿名函数,能不能进行递归操作呢?如何可以,怎么做?我们还是来看阶乘的例子:

 

function factorial(x){
    return x == 0 ? 1 : x * factorial(x-1);  
}

 

factorial函数中,如果x值为0,则返回1,否则递归调用factorial,参数为x1,最后当x等于0时进行规约,最终得到函数值(事实上,命令式程序语言中的递归的概念最早即来源于函数式编程中)。现在考虑:将factorial定义为一个匿名函数,那么在函数内部,在代码x*factorial(x-1)的地方,这个factorial用什么来替代呢?

lambda演算的先驱们,天才的发明了一个神奇的函数,成为Y-结合子。使用Y-结合子,可以做到对匿名函数使用递归。关于Y-结合子的发现及推导过程的讨论已经超出了本部分的范围,有兴趣的读者可以参考附录中的资料。我们来看看这个神奇的Y-结合子:

 

var Y = function(f) {
  return (function(g) {
    return g(g);
  })(function(h) {
    return function() {
      return f(h(h)).apply(null, arguments);
    };
  });
};

 

我们来看看如何运用Y-结合子,依旧是阶乘这个例子:

 

var factorial = Y(function(func){
    return function(x){
       return x == 0 ? 1 : x * func(x-1);
    }
});
 
factorial(10);

 

或者:

 

Y(function(func){
    return function(x){
       return x == 0 ? 1 : x * func(x-1);
    }
})(10);

 

 

不要被上边提到的Y-结合子的表达式吓到,事实上,在JavaScript中,我们有一种简单的方法来实现Y-结合子:

 

    var fact = function(x){
       return x == 0 : 1 : x * arguments.callee(x-1);
    }
   
    fact(10);

 

或者:

 

    (function(x){
       return x == 0 ? 1 : x * arguments.callee(x-1);
    })(10);//3628800

 

其中,arguments.callee表示函数的调用者,因此省去了很多复杂的步骤。

9.4.3其他实例

下面的代码则颇有些“开发智力”之功效:

 

//函数的不动点
function fixedPoint(fx, first){
    var tolerance = 0.00001;
    function closeEnough(x, y){return less( abs( sub(x, y) ), tolerance)};
    function Try(guess){//try 是javascript中的关键字,因此这个函数名为大写
        var next = fx(guess);
        //print(next+" "+guess);
        if(closeEnough(guess, next)){
            return next;
        }else{
            return Try(next);
        }
    };
    return Try(first);
}
 

 

 

// 数层嵌套函数,
function sqrt(x){
    return fixedPoint(
        function(y){
            return function(a, b){ return div(add(a, b),2);}(y, div(x, y));
        },
        1.0);
}
 
print(sqrt(100));

 

 

fiexedPoint求函数的不动点,而sqrt计算数值的平方根。这些例子来源于《计算机程序的构造和解释》,其中列举了大量的计算实例,不过该书使用的是scheme语言,在本书中,例子均被翻译为JavaScript

 

 附:由于作者本身水平有限,文中难免有纰漏错误等,或者语言本身有不妥当之处,欢迎及时 指正,提出建议,参与讨论,谢谢大家!

分享到:
评论
25 楼 superobin 2011-01-07  
abruzzi 写道
weiqingfei 写道
个人感觉,只要涉及到递归的地方,都尽量写成尾递归的形式比较好,容易养成良好的习惯。


嗯,如果递归层次深的话,不使用尾递归估计很快就抛掉了。

如果不出于性能考虑一般递归我都写成异步的比如
setTimeout(arguments.callee)

如果需要变动参数
var _this = this,self = arguments.callee,new args = [xxx,xxx,xxx];
setTimeout(function() {
self.apply(_this,args);
})

这样保证不会stackoverflow
24 楼 xiaoliang330 2011-01-06  
一门新的语言  看的太快没实际项目来支持 看到后面确实就迷糊了 

  回过来再看几遍了 
23 楼 scottxp 2010-05-20  
这就是MIT淘沙子的第一步,跨不过这个门槛的都回家蹲着去。

相比中国的计算机科班教育就太宽容了。
22 楼 abruzzi 2010-05-19  
tufly 写道
大概一共会有多少章?下一章什么时候出啊?好期待。

呵呵,在第一篇的时候我已经说了,这个系列一共九章,这就是最后一章了。你可以关注我的blog,可能后边还会涉及JavaScript的一些应用,但是已经不属于“内核”,也就是核心概念的范畴了。
21 楼 tufly 2010-05-18  
大概一共会有多少章?下一章什么时候出啊?好期待。
20 楼 abruzzi 2010-05-18  
这种把整个文章复制一遍作为回复的方式想要表达的是什么意思?我遇到好几次了,是不是意味着什么特殊的意义,我out了?
19 楼 samtt 2010-05-18  
<div class="quote_title">abruzzi 写道</div>
<div class="quote_div">
<p> </p>
<h2>
<a name="_Toc260749016"><span>第九章</span> </a><span><span>函数式的</span><span lang="EN-US">Javascript</span></span> </h2>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span>要说</span><span lang="EN-US">JavaScript</span><span>和其他较为常用的语言最大的不同是什么,那无疑就是</span><span lang="EN-US">JavaScript</span><span>是函数式的语言,函数式语言的特点如下:</span></p>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span>函数为第一等的元素,即人们常说的一等公民。就是说,在函数式编程中,函数是不依赖于其他对象而独立存在的</span><span lang="EN-US">(</span><span>对比与</span><span lang="EN-US">Java</span><span>,函数必须依赖对象,方法是对象的方法</span><span lang="EN-US">)</span><span>。</span></p>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span>函数可以保持自己内部的数据,函数的运算对外部无副作用</span><span lang="EN-US">(</span><span>修改了外部的全局变量的状态等</span><span lang="EN-US">)</span><span>,关于函数可以保持自己内部的数据这一特性,称之为闭包。我们可以来看一个简单的例子:</span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><strong><span style="font-size: 10.0pt;" lang="EN-US"> </span></strong></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #0000c8; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>var outter = function(){ var x = 0; return function(){ return x++; } } var a = outter(); print(a()); print(a()); var b = outter(); print(b()); print(b());
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>运行结果为:</span></p>
<p class="MsoNormal"><span style="font-size: small; font-family: 'Courier New';"></span></p>
<div class="quote_div">0<br>1<br>0<br>1</div>
<p> </p>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span>变量</span><span lang="EN-US">a</span><span>通过闭包引用</span><span lang="EN-US">outter</span><span>的一个内部变量,每次调用</span><span lang="EN-US">a()</span><span>就会改变此内部变量,应该注意的是,当调用</span><span lang="EN-US">a</span><span>时,函数</span><span lang="EN-US">outter</span><span>已经返回了,但是内部变量</span><span lang="EN-US">x</span><span>的值仍然被保持。而变量</span><span lang="EN-US">b</span><span>也引用了</span><span lang="EN-US">outter</span><span>,但是是一个不同的闭包,所以</span><span lang="EN-US">b</span><span>开始引用的</span><span lang="EN-US">x</span><span>值不会随着</span><span lang="EN-US">a()</span><span>被调用而改变,两者有不同的实例,这就相当于面向对象中的不同实例拥有不同的私有属性,互不干涉。</span></p>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span>由于</span><span lang="EN-US">JavaScript</span><span>支持函数式编程,我们随后会发现</span><span lang="EN-US">JavaScript</span><span>许多优美而强大的能力,这些能力得力于以下主题:匿名函数,高阶函数,闭包及柯里化等。熟悉命令式语言的开发人员可能对此感到陌生,但是使用</span><span lang="EN-US">lisp, scheme</span><span>等函数式语言的开发人员则觉得非常亲切。</span></p>
<h3>
<a name="_Toc260749017"><span lang="EN-US">9.1</span></a><span><span>匿名函数</span></span> </h3>
<p class="MsoNormal"><span>匿名函数在函数式编程语言中,术语成为</span><span lang="EN-US">lambda</span><span>表达式。顾名思义,匿名函数就是没有名字的函数,这个是与日常开发中使用的语言有很大不同的,比如在</span><span lang="EN-US">C/Java</span><span>中,函数和方法必须有名字才可以被调用。在</span><span lang="EN-US">JavaScript</span><span>中,函数可以没有名字,而且这一个特点有着非凡的意义:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>function func(){ //do something } var func = function(){ //do something }
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>这两个语句的意义是一样的,它们都表示,为全局对象添加一个属性</span><span lang="EN-US">func</span><span>,属性</span><span lang="EN-US">func</span><span>的值为一个函数对象,而这个函数对象是匿名的。匿名函数的用途非常广泛,在</span><span lang="EN-US">JavaScript</span><span>代码中,我们经常可以看到这样的代码:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2}); print(mapped);
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>应该注意的是,</span><span lang="EN-US">map</span><span>这个函数的参数是一个匿名函数,你不需要显式的声明一个函数,然后将其作为参数传入,你只需要临时声明一个匿名的函数,这个函数被使用之后就别释放了。在高阶函数这一节中更可以看到这一点。</span></p>
<h3>
<a name="_Toc260749018"><span lang="EN-US">9.2</span></a><span><span>高阶函数</span></span> </h3>
<p class="MsoNormal"><span>通常,以一个或多个函数为参数的函数称之为高阶函数。高阶函数在命令式编程语言中有对应的实现,比如</span><span lang="EN-US">C</span><span>语言中的函数指针,</span><span lang="EN-US">Java</span><span>中的匿名类等,但是这些实现相对于命令式编程语言的其他概念,显得更为复杂。</span></p>
<h4>
<a name="_Toc260749019"><span lang="EN-US">9.2.1 JavaScript</span></a><span><span>中的高阶函数</span></span> </h4>
<p class="MsoNormal"><span lang="EN-US"><span>         </span>Lisp</span><span>中,对列表有一个</span><span lang="EN-US">map</span><span>操作,</span><span lang="EN-US">map</span><span>接受一个函数作为参数,</span><span lang="EN-US">map</span><span>对列表中的所有元素应用该函数,最后返回处理后的列表</span><span lang="EN-US">(</span><span>有的实现则会修改原列表</span><span lang="EN-US">)</span><span>,我们在这一小节中分别用</span><span lang="EN-US">JavaScript/C/Java</span><span>来对</span><span lang="EN-US">map</span><span>操作进行实现,并对这些实现方式进行对比:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
Array.prototype.map = function(func /*, obj */){ var len = this.length; //check the argument if(typeof func != "function"){ throw new Error("argument should be a function!"); } var res = []; var obj = arguments[1]; for(var i = 0; i &lt; len; i++){ //func.call(), apply the func to this[i] res[i] = func.call(obj, this[i], i, this); } return res; }
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>我们对</span><span lang="EN-US">JavaScript</span><span>的原生对象</span><span lang="EN-US">Array</span><span>的原型进行扩展,函数</span><span lang="EN-US">map</span><span>接受一个函数作为参数,然后对数组的每一个元素都应用该函数,最后返回一个新的数组,而不影响原数组。由于</span><span lang="EN-US">map</span><span>函数接受的是一个函数作为参数,因此</span><span lang="EN-US">map</span><span>是一个高阶函数。我们进行测试如下:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>function double(x){ return x * 2; } [1, 2, 3, 4, 5].map(double);//return [2, 4, 6, 8, 10]
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>应该注意的是</span><span lang="EN-US">double</span><span>是一个函数。根据上一节中提到的匿名函数,我们可以为</span><span lang="EN-US">map</span><span>传递一个匿名函数:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2}); print(mapped);
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>这个示例的代码与上例的作用是一样的,不过我们不需要显式的定义一个</span><span lang="EN-US">double</span><span>函数,只需要为</span><span lang="EN-US">map</span><span>函数传递一个“可以将传入参数乘</span><span lang="EN-US">2</span><span>并返回”的代码块即可。再来看一个例子:</span></p>
<p class="MsoNormal"> </p>
[ {id : "item1"}, {id : "item2"}, {id : "item3"} ].map(function(current){ print(current.id); });
<pre></pre>
<p class="MsoNormal"><span>将会打印:</span></p>
<p class="MsoNormal"> </p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
<div class="quote_div">item1<br>item2<br>item3</div>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>也就是说,这个</span><span lang="EN-US">map</span><span>的作用是将传入的参数</span><span lang="EN-US">(</span><span>处理器</span><span lang="EN-US">)</span><span>应用在数组中的每个元素上,而不关注数组元素的数据类型,数组的长度,以及处理函数的具体内容。</span></p>
<h4>
<a name="_Toc260749020"><span lang="EN-US">9.2.2 C</span></a><span><span>语言中的高阶函数</span></span> </h4>
<p class="MsoNormal"><span lang="EN-US">C</span><span>语言中的函数指针,很容易实现一个高阶函数。我们还以</span><span lang="EN-US">map</span><span>为例,说明在</span><span lang="EN-US">C</span><span>语言中如何实现:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #3f7f5f; font-family: 'Courier New';"></span></p>
//prototype of function void map(int* array, int length, int (*func)(int));
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span lang="EN-US">map</span><span>函数的第三个参数为一个函数指针,接受一个整型的参数,返回一个整型参数,我们来看看其实现:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #3f7f5f; font-family: 'Courier New';"></span></p>
//implement of function map void map(int* array, int length, int (*func)(int)){ int i = 0; for(i = 0; i &lt; length; i++){ array[i] = func(array[i]); } }
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>我们在这里实现两个小函数,分别计算传入参数的乘</span><span lang="EN-US">2</span><span>的值,和乘</span><span lang="EN-US">3</span><span>的值,然后进行测试:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>int twice(int num) { return num * 2; } int triple(int num){ return num * 3; } //function main int main(int argc, char** argv){ int array[5] = {1, 2, 3, 4, 5}; int i = 0; int len = 5; //print the orignal array printArray(array, len); //mapped by twice map(array, len, twice); printArray(array, len); //mapped by twice, then triple map(array, len, triple); printArray(array, len); return 0; }
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>运行结果如下:</span></p>
<p class="MsoNormal"> </p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
<div class="quote_div">1 2 3 4 5<br>2 4 6 8 10<br>6 12 18 24 30</div>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>应该注意的是</span><span lang="EN-US">map</span><span>的使用方法,如</span><span lang="EN-US">map(array, len, twice)</span><span>中,最后的参数为</span><span lang="EN-US">twice</span><span>,而</span><span lang="EN-US">twice</span><span>为一个函数。因为</span><span lang="EN-US">C</span><span>语言中,函数的定义不能嵌套,因此不能采用诸如</span><span lang="EN-US">JavaScript</span><span>中的匿名函数那样的简洁写法。</span></p>
<p class="MsoNormal"><span lang="EN-US"><span>         </span></span><span>虽然在</span><span lang="EN-US">C</span><span>语言中可以通过函数指针的方式来实现高阶函数,但是随着高阶函数的“阶”的增高,指针层次势必要跟着变得很复杂,那样会增加代码的复杂度,而且由于</span><span lang="EN-US">C</span><span>语言是强类型的,因此在数据类型方面必然有很大的限制。</span></p>
<h4>
<a name="_Toc260749021"><span lang="EN-US">9.2.3 Java</span></a><span><span>中的高阶函数</span></span> </h4>
<p class="MsoNormal"><span lang="EN-US">Java</span><span>中的匿名类,事实上可以理解成一个教笨重的闭包</span><span lang="EN-US">(</span><span>可执行单元</span><span lang="EN-US">)</span><span>,我们可以通过</span><span lang="EN-US">Java</span><span>的匿名类来实现上述的</span><span lang="EN-US">map</span><span>操作,首先,我们需要一个对函数的抽象:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
interface Function{ int execute(int x); }
<pre></pre>
<p> </p>
<p class="MsoNormal"><span>我们假设</span><span lang="EN-US">Function</span><span>接口中有一个方法</span><span lang="EN-US">execute</span><span>,接受一个整型参数,返回一个整型参数,然后我们在类</span><span lang="EN-US">List</span><span>中,实现</span><span lang="EN-US">map</span><span>操作:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
private int[] array; public List(int[] array){ this.array = array; } public void map(Function func){ for(int i = 0, len = this.array.length; i &lt; len; i++){ this.array[i] = func.execute(this.array[i]); } }
<pre></pre>
<p> </p>
<p class="MsoNormal"><span lang="EN-US">map</span><span>接受一个实现了</span><span lang="EN-US">Function</span><span>接口的类的实例,并调用这个对象上的</span><span lang="EN-US">execute</span><span>方法来处理数组中的每一个元素。我们这里直接修改了私有成员</span><span lang="EN-US">array</span><span>,而并没有创建一个新的数组。好了,我们来做个测试:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
public static void main(String[] args){ List list = new List(new int[]{1, 2, 3, 4, 5}); list.print(); list.map(new Function(){ public int execute(int x){ return x * 2; } }); list.print(); list.map(new Function(){ public int execute(int x){ return x * 3; } }); list.print(); }
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>同前边的两个例子一样,这个程序会打印:</span></p>
<p class="MsoNormal"> </p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
<div class="quote_div">1 2 3 4 5<br>2 4 6 8 10<br>6 12 18 24 30</div>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>灰色背景色的部分即为创建一个匿名类,从而实现高阶函数。很明显,我们需要传递给</span><span lang="EN-US">map</span><span>的是一个可以执行</span><span lang="EN-US">execute</span><span>方法的代码。而由于</span><span lang="EN-US">Java</span><span>是命令式的编程语言,函数并非第一位的,函数必须依赖于对象,附属于对象,因此我们不得不创建一个匿名类来包装这个</span><span lang="EN-US">execute</span><span>方法。而在</span><span lang="EN-US">JavaScript</span><span>中,我们只需要传递函数本身即可,这样完全合法,而且代码更容易被人理解。</span></p>
<p class="MsoNormal"> </p>
<p class="MsoNormal"> </p>
<h3>
<a name="_Toc260749022"><span lang="EN-US">9.3</span></a><span><span>闭包与柯里化</span></span> </h3>
<p class="MsoNormal"><span>闭包和柯里化都是</span><span lang="EN-US">JavaScript</span><span>经常用到而且比较高级的技巧,所有的函数式编程语言都支持这两个概念,因此,我们想要充分发挥出</span><span lang="EN-US">JavaScript</span><span>中的函数式编程特征,就需要深入的了解这两个概念,我们在第七章中详细的讨论了闭包及其特征,闭包事实上更是柯里化所不可缺少的基础。</span></p>
<h4>
<a name="_Toc260749023"><span lang="EN-US">9.3.1</span></a><span><span>柯里化的概念</span></span> </h4>
<p class="MsoNormal"><span>闭包的我们之前已经接触到,先说说柯里化。柯里化就是预先将函数的某些参数传入,得到一个简单的函数,但是预先传入的参数被保存在闭包中,因此会有一些奇特的特性。比如:</span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><strong><span style="font-size: 10.0pt;" lang="EN-US"> </span></strong></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>var adder = function(num){ return function(y){ return num + y; } } var inc = adder(1); var dec = adder(-1);
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>这里的</span><span lang="EN-US">inc/dec</span><span>两个变量事实上是两个新的函数,可以通过括号来调用,比如下例中的用法:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #3f7f5f; font-family: 'Courier New';"></span></p>
//inc, dec现在是两个新的函数,作用是将传入的参数值(+/-)1 print(inc(99));//100 print(dec(101));//100 print(adder(100)(2));//102 print(adder(2)(100));//102
<pre></pre>
<p> </p>
<h4>
<a name="_Toc260749024"><span lang="EN-US">9.3.2</span></a><span><span>柯里化的应用</span></span> </h4>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span>根据柯里化的特性,我们可以写出更有意思的代码,比如在前端开发中经常会遇到这样的情况,当请求从服务端返回后,我们需要更新一些特定的页面元素,也就是局部刷新的概念。使用局部刷新非常简单,但是代码很容易写成一团乱麻。而如果使用柯里化,则可以很大程度上美化我们的代码,使之更容易维护。我们来看一个例子:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #3f7f5f; font-family: 'Courier New';"></span></p>
//update会返回一个函数,这个函数可以设置id属性为item的web元素的内容 function update(item){ return function(text){ $("div#"+item).html(text); } } //Ajax请求,当成功是调用参数callback function refresh(url, callback){ var params = { type : "echo", data : "" }; $.ajax({ type:"post", url:url, cache:false, async:true, dataType:"json", data:params, //当异步请求成功时调用 success: function(data, status){ callback(data); }, //当请求出现错误时调用 error: function(err){ alert("error : "+err); } }); } refresh("action.do?target=news", update("newsPanel")); refresh("action.do?target=articles", update("articlePanel")); refresh("action.do?target=pictures", update("picturePanel"));
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>其中,</span><span lang="EN-US">update</span><span>函数即为柯里化的一个实例,它会返回一个函数,即:</span></p>
<p class="MsoNormal"> </p>
update("newsPanel") = function(text){ $("div#newsPanel").html(text); }
<pre></pre>
<p class="MsoNormal"><span>由于</span><span lang="EN-US">update(“newsPanel”)</span><span>的返回值为一个函数,需要的参数为一个字符串,因此在</span><span lang="EN-US">refresh</span><span>的</span><span lang="EN-US">Ajax</span><span>调用中,当</span><span lang="EN-US">success</span><span>时,会给</span><span lang="EN-US">callback</span><span>传入服务器端返回的数据信息,从而实现</span><span lang="EN-US">newsPanel</span><span>面板的刷新,其他的文章面板</span><span lang="EN-US">articlePanel,</span><span>图片面板</span><span lang="EN-US">picturePanel</span><span>的刷新均采取这种方式,这样,代码的可读性,可维护性均得到了提高。</span></p>
<h3>
<a name="_Toc260749025"><span lang="EN-US">9.4</span></a><span><span>一些例子</span></span> </h3>
<h4>
<a name="_Toc260749026"><span lang="EN-US">9.4.1</span></a><span><span>函数式编程风格</span></span> </h4>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span>通常来讲,函数式编程的谓词</span><span lang="EN-US">(</span><span>关系运算符,如大于,小于,等于的判断等</span><span lang="EN-US">)</span><span>,以及运算</span><span lang="EN-US">(</span><span>如加减乘数等</span><span lang="EN-US">)</span><span>都会以函数的形式出现,比如:</span></p>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left; text-indent: 21.0pt;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
a &gt; b
<pre></pre>
<p> </p>
<p class="MsoNormal" style="text-align: left;" align="left"><span>通常表示为:</span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 10.0pt;" lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left; text-indent: 21.0pt;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
gt(a, b)//great than
<pre></pre>
<p> </p>
<p class="MsoNormal"><span>因此,可以首先对这些常见的操作进行一些包装,以便于我们的代码更具有“函数式”风格:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #0000c8; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>function abs(x){ return x&gt;0?x:-x;} function add(a, b){ return a+b; } function sub(a, b){ return a-b; } function mul(a, b){ return a*b; } function div(a, b){ return a/b; } function rem(a, b){ return a%b; } function inc(x){ return x + 1; } function dec(x){ return x - 1; } function equal(a, b){ return a==b; } function great(a, b){ return a&gt;b; } function less(a, b){ return a&lt;b; } function negative(x){ return x&lt;0; } function positive(x){ return x&gt;0; } function sin(x){ return Math.sin(x); } function cos(x){ return Math.cos(x); }
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>如果我们之前的编码风格是这样:</span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #808000; font-family: 'Courier New';"></span></p>
// n*(n-1)*(n-2)*...*3*2*1 function factorial(n){ if(n == 1){ return 1; }else{ return n * factorial(n - 1); } }
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>在函数式风格下,就应该是这样了:</span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><strong><span style="font-size: 10.0pt;" lang="EN-US"> </span></strong></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #0000c8; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>function factorial(n){ if(equal(n, 1)){ return 1; }else{ return mul(n, factorial(dec(n))); } }
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>函数式编程的特点当然不在于编码风格的转变,而是由更深层次的意义。比如,下面是另外一个版本的阶乘实现:</span></p>
<p class="MsoNormal"><span style="font-size: 10.0pt;" lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
/* * product &lt;- counter * product * counter &lt;- counter + 1 * */ function factorial(n){ function fact_iter(product, counter, max){ if(great(counter, max)){ return product; }else{ fact_iter(mul(counter, product), inc(counter), max); } } return fact_iter(1, 1, n); }
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>虽然代码中已经没有诸如</span><span lang="EN-US">+/-/*//</span><span>之类的操作符,也没有</span><span lang="EN-US">&gt;,&lt;,==,</span><span>之类的谓词,但是,这个函数仍然算不上具有函数式编程风格,我们可以改进一下:</span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>function factorial(n){ return (function factiter(product, counter, max){ if(great(counter, max)){ return product; }else{ return factiter(mul(counter, product), inc(counter), max); } })(1, 1, n); } factorial(10);
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>通过一个立即运行的函数</span><span lang="EN-US">factiter</span><span>,将外部的</span><span lang="EN-US">n</span><span>传递进去,并立即参与计算,最终返回运算结果。</span></p>
<h4>
<a name="_Toc260749027"><span lang="EN-US">9.4.2 Y-</span></a><span><span>结合子</span></span> </h4>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span>提到递归,函数式语言中还有一个很有意思的主题,即:如果一个函数是匿名函数,能不能进行递归操作呢?如何可以,怎么做?我们还是来看阶乘的例子:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>function factorial(x){ return x == 0 ? 1 : x * factorial(x-1); }
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span lang="EN-US">factorial</span><span>函数中,如果</span><span lang="EN-US">x</span><span>值为</span><span lang="EN-US">0</span><span>,则返回</span><span lang="EN-US">1</span><span>,否则递归调用</span><span lang="EN-US">factorial</span><span>,参数为</span><span lang="EN-US">x</span><span>减</span><span lang="EN-US">1</span><span>,最后当</span><span lang="EN-US">x</span><span>等于</span><span lang="EN-US">0</span><span>时进行规约,最终得到函数值</span><span lang="EN-US">(</span><span>事实上,命令式程序语言中的递归的概念最早即来源于函数式编程中</span><span lang="EN-US">)</span><span>。现在考虑:将</span><span lang="EN-US">factorial</span><span>定义为一个匿名函数,那么在函数内部,在代码</span><span lang="EN-US">x*factorial(x-1)</span><span>的地方,这个</span><span lang="EN-US">factorial</span><span>用什么来替代呢?</span></p>
<p class="MsoNormal" style="text-indent: 21.0pt;"><span lang="EN-US">lambda</span><span>演算的先驱们,天才的发明了一个神奇的函数,成为</span><span lang="EN-US">Y-</span><span>结合子。使用</span><span lang="EN-US">Y-</span><span>结合子,可以做到对匿名函数使用递归。关于</span><span lang="EN-US">Y-</span><span>结合子的发现及推导过程的讨论已经超出了本部分的范围,有兴趣的读者可以参考附录中的资料。我们来看看这个神奇的</span><span lang="EN-US">Y-</span><span>结合子:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>var Y = function(f) { return (function(g) { return g(g); })(function(h) { return function() { return f(h(h)).apply(null, arguments); }; }); };
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>我们来看看如何运用</span><span lang="EN-US">Y-</span><span>结合子,依旧是阶乘这个例子:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #7f0055; font-family: 'Courier New';"><span style="font-size: 13px;"><strong></strong></span></span></p>
<p><strong>var factorial = Y(function(func){ return function(x){ return x == 0 ? 1 : x * func(x-1); } }); factorial(10);
<pre></pre>
<br></strong></p>
<p> </p>
<p class="MsoNormal"><span>或者:</span></p>
<p class="MsoNormal"><span style="font-size: 10.0pt;" lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
Y(function(func){ return function(x){ return x == 0 ? 1 : x * func(x-1); } })(10);
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span>不要被上边提到的</span><span lang="EN-US">Y-</span><span>结合子的表达式吓到,事实上,在</span><span lang="EN-US">JavaScript</span><span>中,我们有一种简单的方法来实现</span><span lang="EN-US">Y-</span><span>结合子:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
var fact = function(x){ return x == 0 : 1 : x * arguments.callee(x-1); } fact(10);
<pre></pre>
<p> </p>
<p class="MsoNormal"><span>或者:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; font-family: 'Courier New';"></span></p>
(function(x){ return x == 0 ? 1 : x * arguments.callee(x-1); })(10);//3628800
<pre></pre>
<p> </p>
<p class="MsoNormal"><span>其中,</span><span lang="EN-US">arguments.callee</span><span>表示函数的调用者,因此省去了很多复杂的步骤。</span></p>
<h4>
<a name="_Toc260749028"><span lang="EN-US">9.4.3</span></a><span><span>其他实例</span></span> </h4>
<p class="MsoNormal"><span>下面的代码则颇有些“开发智力”之功效:</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span></span></p>
//函数的不动点 function fixedPoint(fx, first){ var tolerance = 0.00001; function closeEnough(x, y){return less( abs( sub(x, y) ), tolerance)}; function Try(guess){//try 是javascript中的关键字,因此这个函数名为大写 var next = fx(guess); //print(next+" "+guess); if(closeEnough(guess, next)){ return next; }else{ return Try(next); } }; return Try(first); }
<pre></pre>
 
<p> </p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 10.0pt;" lang="EN-US"> </span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: small; color: #808000; font-family: 'Courier New';"></span></p>
// 数层嵌套函数, function sqrt(x){ return fixedPoint( function(y){ return function(a, b){ return div(add(a, b),2);}(y, div(x, y)); }, 1.0); } print(sqrt(100));
<pre></pre>
<p> </p>
<p> </p>
<p class="MsoNormal"><span lang="EN-US">fiexedPoint</span><span>求函数的不动点,而</span><span lang="EN-US">sqrt</span><span>计算数值的平方根。这些例子来源于《计算机程序的构造和解释》,其中列举了大量的计算实例,不过该书使用的是</span><span lang="EN-US">scheme</span><span>语言,在本书中,例子均被翻译为</span><span lang="EN-US">JavaScript</span><span>。</span></p>
<p class="MsoNormal"> </p>
<p class="MsoNormal"><span style="line-height: 18px; font-family: Arial, sans-serif, Helvetica, Tahoma;"> <span><strong style="font-weight: bold;">附:由于作者本身水平有限,文中难免有纰漏错误等,或者语言本身有不妥当之处,欢迎及时 指正,提出建议,参与讨论,谢谢大家!</strong></span></span></p>
</div>
<p> </p>
18 楼 summerfeel 2010-05-18  
楼主好文!
js是一门混杂的语言,兼具面向对象和函数式语言的特性,合理的使用它的各项特性来解决问题才是我们需要关注的。
令人乍舌的是,《计算机程序的构造和解释》这样抽象的书竟被MIT当作计算机科学导论的教科书,真不知道他们计算机系的学生考试怎么通过的...
17 楼 liushilang 2010-05-18  
JAVASCRIPT的东西真牛,楼主好利害
16 楼 whiletrue 2010-05-17  
哈哈,回的太慢了
15 楼 whiletrue 2010-05-17  
dreampuf01 写道
arguments.callee表示函数的调用者
=======================
感觉应该是函数本身.也就是被调用者而不是调用者..

callee :返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文。

caller :返回一个对函数的引用,该函数调用了当前函数。这个才是调用者

14 楼 abruzzi 2010-05-17  
supperbbq 写道
平时我都是看贴必不回的,看了楼主的文章,发现js还真是高深,很多地方值得借鉴和学习,谢谢了

呵呵,我的表达能力还需要再多练习。事实上,我觉得JavaScript比我描述出来的更有意思,更深刻。不过我现在找不到合适的词语来描述。
13 楼 supperbbq 2010-05-17  
平时我都是看贴必不回的,看了楼主的文章,发现js还真是高深,很多地方值得借鉴和学习,谢谢了
12 楼 abruzzi 2010-05-16  
02221021 写道
好文章,投新手的太过分了

呵呵,可能有些人眼中,这就是已经熟知的知识了,闻道有先后。
11 楼 02221021 2010-05-15  
好文章,投新手的太过分了
10 楼 abruzzi 2010-05-15  
dreampuf01 写道
arguments.callee表示函数的调用者
=======================
感觉应该是函数本身.也就是被调用者而不是调用者..


嗯,谢谢指正,callee时候被调用者,caller是调用者
9 楼 wusuo513 2010-05-15  
看到后面有点迷糊了....
8 楼 dreampuf01 2010-05-15  
arguments.callee表示函数的调用者
=======================
感觉应该是函数本身.也就是被调用者而不是调用者..
7 楼 abruzzi 2010-05-14  
寻找出路的苍蝇 写道
其实吧LZ提到的这些JavaScript的特性平时不知不觉间经常使用,就是不知道原来它们还有这么酷的称呼,原来这就是函数式编程,原来这就是闭包,原来这就是柯里化。。。呵呵,以前还真不知道函数式编程与柯里化的概念


呵呵,我们平时可能接触这方面的东西的机会较少,西方的计算机科学专业都会开始诸如lisp之类的语言。就像我们在编程的时候,如果经验足够,会用到很多设计模式,但是并没有意识到,当看了关于设计模式方面的专著以后,才恍然,原来这就是事件监听器,原来这就是装饰者。
6 楼 寻找出路的苍蝇 2010-05-14  
其实吧LZ提到的这些JavaScript的特性平时不知不觉间经常使用,就是不知道原来它们还有这么酷的称呼,原来这就是函数式编程,原来这就是闭包,原来这就是柯里化。。。呵呵,以前还真不知道函数式编程与柯里化的概念

相关推荐

    JavaScript内核系列

    Mocha使用了C的语法,但是设计思想上主要从函数式语言Scheme那里取得了灵 感。当Netscape 2发布的时候,Mocha被改名为LiveScript,当时可能是想让LiveScript为WEB页面注入更多的活力。后来,考虑到这个脚本语言的推 ...

    python入门到高级全栈工程师培训 第3期 附课件代码

    第9章 01 Python开发系列课程概要 02 Python作业要求以及博客 03 编程语言介绍 04 Python种类介绍 05 Python安装以及环境变量的操作 06 Python初识以及变量 07 Python条件语句和基本数据类型 08 Python while循环...

    基于PHP的拓商企业服务网DT内核php版源码.zip

    9. **前端资源**:包括CSS样式表、JavaScript文件和图片等,可能采用现代前端框架如Bootstrap或Vue.js。 10. **文档**:项目可能会包含README文件、API文档或其他形式的说明,帮助开发者理解和使用源码。 为了深入...

    代码之美(中文完整版).pdf

    第9章 自顶向下的运算符优先级 9.1. JavaScript 9.2. 符号表 9.3. 语素 9.4. 优先级 9.5. 表达式 9.6. 中置运算符 9.7. 前置操作符 9.8. 赋值运算符 9.9. 常数 9.10. Scope 9.11. 语句 9.12. 函数 9.13. 数组和对象...

    javascript入门笔记

    Javascript Basic 1、Javascript 概述(了解) Javascript,简称为 JS,是一款能够运行在 JS解释器/引擎 中的脚本语言 JS解释器/引擎 是JS的运行环境: 1、独立安装的JS解释器 - NodeJS 2、嵌入在浏览器中的JS...

    基于PHP的八零学院内核网站系统php版源码.zip

    9. **错误处理与日志记录**:良好的错误处理和日志记录机制是保证系统稳定运行的关键,PHP有error_reporting和log函数等工具。 10. **安全性**:防止SQL注入、XSS攻击等,通过过滤输入、使用预编译语句、HTTP头部...

    基于PHP的在线网络电视直播内核完整PHP版v3.0源码.zip

    10. **响应式设计**:确保网站在不同设备(桌面、手机、平板)上都能正常显示和播放。 综上所述,基于PHP的在线网络电视直播内核完整PHP版v3.0源码项目涵盖了广泛的IT知识领域,不仅涉及PHP编程,还包含了流媒体...

    caps-driver:驱动程式

    驱动程式通常由设备制造商提供,但有时由社区开发者或其他第三方创建,特别是对于开源硬件或非标准设备。它们可以是内核模块,直接加载到操作系统内核中,也可以是用户空间程序,通过系统调用与内核通信。 标签...

    面试大全新-148P.docx

    - Function:函数是第一类公民,可以作为变量、参数和返回值。 - 继承:原型链继承、构造函数继承、组合继承和ES6的类继承。 - 闭包:理解作用域、作用域链和闭包的原理,以及它们在内存管理中的角色。 - 事件和...

    面试题总结.docx

    在JavaScript中,函数是一级公民,这意味着函数可以嵌套在其他函数内部,并且内部函数可以访问外部函数的局部变量。作用域决定了变量的可见性和生命周期,主要有全局作用域、局部作用域和块级作用域。 ### 十三、...

    开源应用程序架构 二(The Architecture of Open Source Applications 2)

    对于学习函数式编程语言Haskell的开发者来说,这是一篇必读的文章。 **6. Git** - **作者:** Susan Potter - **内容概览:** Git是一个分布式版本控制系统,被广泛应用于软件开发过程中。本章不仅解释了Git的基本...

    Url Prime Number Checker-crx插件

    JavaScript是一种广泛应用于Web开发的脚本语言,支持事件驱动和函数式编程。 4. **URL解析**:插件首先需要解析页面的URL,从中提取出数字。URL(统一资源定位符)包含协议、主机名、路径等部分,其中可能包含数字...

    阿里前端面试第三期.pdf

    31. 函数式编程理解: - 一种编程范式,强调使用函数来进行程序设计。 32. 尾调用及其好处: - 尾调用是函数执行的最后一个动作是调用另一个函数。 - 好处包括优化内存使用,因为可以重用栈帧。 33. Vue组件间...

    电脑编程有哪几个方向和板块

    - 特点:函数式编程、面向对象。 - **Perl**: - 用途:文本处理、系统管理。 - 特点:强大的文本处理能力。 - **Lua**: - 用途:嵌入式脚本、游戏开发。 - 特点:轻量级、易于嵌入。 - **MATLAB**: - 用途...

    5月最新大厂前端高频核心面试题.pdf

    9. data-属性用于存储页面的自定义数据,可以用于JavaScript的DOM操作,但不会影响页面的渲染。 10. HTML5语义化的理解是,使用HTML5提供的语义化标签来表示内容的结构,有助于搜索引擎优化和提供无障碍支持。 11....

    CSS3实现的动画效果

    CSS3 动画不仅提供了丰富的视觉体验,而且在性能上也有显著优势,因为它们是浏览器内核直接处理的,无需依赖JavaScript或者Flash。本篇文章将深入探讨CSS3实现的动画效果,以及如何确保它们在Google、Firefox和IE等...

    2021-2022计算机二级等级考试试题及答案No.1470.docx

    11. **JavaScript四舍五入**:在JavaScript中,使用`Math.round()`函数可以将数字四舍五入到最接近的整数。 12. **文本框滚动条**:在编程中,若要让文本框显示滚动条,需要设置`Multiline`属性为True,然后设置`...

    字节最新前端面试题.pdf

    这里的问题在于parseInt的第二个参数是解析的基数,通常map的索引(0, 1, 2...)会被传入,导致解析结果异常。 3. 防抖(Debounce)和节流(Throttle)的区别及其实现: - 防抖指的是在事件触发后,延迟执行动作,如果...

Global site tag (gtag.js) - Google Analytics