`
wolferill
  • 浏览: 58328 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

JavaScript的函数式编程特性

阅读更多

 

首先说说什么事“函数式编程”,借用维基百科的概念:

函数式编程是种编程范型,它将电脑运算视为函数的计算。函数式编程的重点是函数的定义而不是像命令式编程那样强调状态机(state machine)的实现。

也就是说,函数式编程只描述在程序输入上执行的操作,重点是捕捉 “是什么以及为什么”,而不是 “如何做”,我们只需知道一个函数能返回什么样的结果,然后将结果用于进一步的运算。

有一个容易误解的概念是——“函数式编程就是一堆函数”,这是错误的。并不是一个语言支持函数,这个语言就可以叫做 “ 函数式语言 ” 。函数式语言中的 “ 函数( funct ion ) ” 除了能被调用之外,还具有一些其它的性质:

  • 函数是运算元
  • 在函数内保存数据
  • 函数内的运算对函数外无副作用

 

说回JavaScript,其实JavaScript并不是一个函数式编程语言,只能说它的实现参照了些函数式编程的特性,是个“半函数式编程语言”。下面我就介绍下JavaScript中函数式编程的一些特性吧。

 

一、函数(Function)是一等公民

Function是JavaScript中最基础的模块,本身为一种特殊对象(Object),属于顶层对象,不依赖于任何其他的对象而可以独立存在,而在面向对象的语言中,Function是依附于对象的,属于对象的一部分。JavaScript中一切皆是对象,那Function自然也是对象,换个角度说,一切皆是可传入Function的值,连Function本身也不例外。

这样有什么好处?举一个排序的例子:


//Array对象的sort方法,需传入一比较函数
var myarray = [2,5,7,3];
var byAsc = function(x,y) { return x-y; };
var byDesc = function(x,y){ return y-x; };
myarray.sort(byDesc);
alert(myarray);    //7,5,3,2
myarray.sort(byAsc);
alert(myarray);    //2,3,5,7

甚至于我们根本不需要定义一个对外公开的Function(因为其他地方不会使用到),直接用一个匿名函数:


//对一个日期数组排序
dateArr.sort( function (x,y) {
    return x.date – y.date;
});

 

二、高阶函数

高阶函数即为对函数的进一步抽象,上面提到的sort既是JS引擎自身提供的一个高阶函数。sort传入的比较函数(byAsc, byDesc)是没有任何预先的假设的,sort是对整个排序方法的二阶抽象,因此称之为“高阶”函数。

下面再举一个高阶函数的例子,数组元素遍历:


Array.prototype.each = function(fun){
    var ret = [], len = this.length, i;
    for(i = 0; i < len ; i++){
        ret.push(fun(this[i],i));
    }
    return ret;
}
alert([1,2,3].each(function(x){return x+1;}));  //[2,3,4]
alert([1,2,3].each(function(x){return x*2;}));  //[2,4,6]

代码重用什么的好处我就不多说了,最重要一个好处就是写起来爽!

 

三、闭包

这个特性对于初学者来说可能还真不好了解,详情可以看看我之前写的《JavaScript的闭包与作用域链》。举个闭包的小例子:

//实现一个计数器
function counter(){
    var n = 0;
    return function(){ return ++n; }
}
//创建一个计数器
var count1 = counter();
count1();  //1
count1();  //2
count1();  //3
//再创建一个
var count2 = counter();
count2();  //1
count2();  //2

闭包的最大特性就是不需要通过传递变量的方式就可以从内层直接访问外层的环境,这为多重嵌套下的函数式程序带来了极大的便利性。

有了闭包就相当于我们可以在函数内保存数据了,这样有啥好处呢?

 

四、函数柯里化(Currying)

啥是柯里化?详见维基百科

在计算机科学中,柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

有了“闭包”这个利器之后,我们就可以享受“柯里化”这种酷爽编程体验了,话不多说,上例子:


//这是一个Ajax页面局部刷新的例子
 
//替换DOM中某个节点的html
function update(id){
    return function (data){
        $("div#"+id).html(data.text);
    }
}
 
//Ajax局部刷新
function refresh(url, callback){
    $.ajax({
        type:"get",
     url:url,
        dataType:"json",
     success:  function (data){
            callback(data);
    });
}
 
//刷新两个区域
refresh("friends.php", update("friendsDiv"));
refresh("newfeeds.php", update("feedsDiv"));

上面的update函数的原型本来应该是update(id, data),接收两个参数。柯里化之后则先接收id,确定刷新区域是哪里,返回接收余下参数的一个函数作为refresh函数的callback,等到ajax返回data结果之后,在传入callback,更新页面。或许有人会问,为何不直接把refresh设计成refresh(id, url)?这是因为update的方法可能有很多,可能还有update1,update2这样一系列方法,对返回结果的处理都不同,通过这种“柯里化”的方式可以使得refresh得到更高阶的抽象,更好的重用。

 

五、Memoization递归优化

递归是拖慢脚本运行速度的大敌之一,太多的递归会让浏览器变得越来越慢直到死掉或者莫名其妙的突然自动退出。有了闭包之后,我们就可以通过memoization技术来替代函数中太多的递归调用,提升JavaScript效率。

Memoization说白了,就是在函数中缓存下之前的运行结果,这样我们就不需要重新计算那些已经计算过的结果了。

举个熟悉的例子——斐波那契数列(兔子问题)

兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔都不死,那么一年以后可以繁殖多少对兔子?

这是个烦人的小学问题,答案我就不和大家纠结了——第n月的兔子数量为F(n),F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)。

写成javascript代码如下:


function fibonacci(n){
    return n<2 ? n : fibonacci(n-1) + fibonacci(n-2);
};

恩,看起来短小精悍,蛮顺眼的。不过如果你敢试试fibonacci(100)的话……那你的浏览器就“自挂东南枝”了……

为啥会这样子捏?我们给它加个计数器就知道了(节省时间就不演示了),我们调用fibonacci(10)的时候,它自己调用自己176次,总计177次,11的时候287次,12的时候465次……fibonacci(20)的时候21891次……这也太坑爹了!

其实fabonacci调用自身的时候,有很多计算结果都是已经得出的,太多无用的重复计算,最终使得调用栈溢出了。咋办捏?

不知你还记不记得,现在我们可以在函数内保存对象了!这就是我要说的Memoization方法。上代码:


//接收两个参数, 递归方法, 缓存对象
function memoizer(fun, cache) {
    cache = cache || {};
    //定义递归函数的壳,具体怎么递归是fun说的算
    var shell = function(arg) {
        //如果不在缓存中, 递归计算并放入缓存
        //否则直接返回缓存的结果
        if (! (arg in cache)) {
            //需要在fun中定义shell的递归的方法
            cache[arg] = fun(shell, arg);
        }
        return cache[arg];
    };
    return shell;
}

这是一个通用的方法,此时我们可以这样改写fabonacci:


var factorial = memoizer(function(shell, n){
    return n<2 ? n : shell(n-1) + shell(n-2);
});

这回,fabonacci(100)是淡定无压力呀——354224848179262000000,只调用了101次 [大笑] ,自豪感油然而生……

同理,什么阶乘运算之类的都可以用Memoization的方法解决,so easy!

 

六、函数式代码风格

在一些语言中,连续运算被认为是不良好的编程习惯。我们被要求运算出一个结果值,先放到中间变量中,然后拿中间变量继续参与运算。然而在函数式的语言中,连续运算是被推崇的方法(原因未明,个人没接触过纯函数式语言,理解不深)

在 JavaScr ipt 中,一种常见的情况就是连续赋值:

var a = b = c = d = 100;

还有我们常用的“短路”条件:||和&&(||用来提供变量的默认值,&&可避免从undefined中取值抛出异常),也是连续运算的体现。

再例如,三元表达式:


var objType = getFromInput();
var cls = ((objType == 'String') ? String :
    (objType == 'Array') ? Array :
    (objType == 'Number') ? Number :
    (objType == 'Boolean') ? Boolean :
    (objType == 'RegExp') ? RegExp :
    Object
);
var obj = new cls();

如果你要用if/else,switch,甚至于“多态”的手段去重写上面的方法,绝对是长长一坨……个人感觉这种代码风格还是很容易懂的,不见得比if/else差。

当然,不得不说的还有JS攻城师最喜欢的“链式调用”,例如jQuery的DOM操作:


//举个jQuery的栗子~
$('#item').width('100px')
        .height('100px').
        .css('padding','20px')
        .click(function(){
            alert('hello');
         });

其实原理就是在函数最后return this,即可接着之前的上下文环境继续调用函数,这样很爽吧!

 

七、函数内的运算对函数外无副作用

其实,这并不是JavaScript的一个特性,这是函数式语言应当达到的一种特性,在 JavaScr ipt 中这项特性只能通过开发人员的编程习惯来保证。

所谓对函数外无副作用,含义在于:

  • 函数使用入口参数进行运算,而不修改它(作为值参数而不是变量参数使用)
  • 在运算过程中不会修改函数外部的其它数据的值(例如全部变量)
  • 运算结束后通过函数返回向外部系统传值。

这样有啥好处呢?

没有函数修改过在其作用域之外的量并被其他函数使用(如类成员或全局变量)——这意味着函数求值的结果只是其返回值,而惟一影响其返回值的就是函数的参数。

如果一个函数式程序不如你期望地运行,调试是轻而易举的。因为函数式程序的 bug 不依赖于执行前与其无关的代码路径,你遇到的问题就总是可以再现。在单元测试中,你只需在意其参数,而不必考虑函数调用顺序,不用谨慎地设置外部状态。所有要做的就是传递代表了边际情况的参数。如果程序中的每个函数都通过了单元测试,你就对这个软件的质量有了相当的自信。

而命令式编程就不能这样乐观了,在 Java 或 C++ 中只检查函数的返回值还不够——我们还必须验证这个函数可能修改了的外部状态。

这种特性其实也是程序“高内聚,低耦合”的一种体现,在实际开发中应当尽量遵从。

 

我的博客:http://technicolor.flycoder.org/articles/793.html

1
1
分享到:
评论

相关推荐

    JavaScript函数式编程

    本书专门介绍JavaScript函数式编程的特性。 全书共9章,分别介绍了JavaScript函数式编程、一等函数与Applicative编程、变量的作用域和闭包、高阶函数、由函数构建函数、递归、纯度和不变性以及更改政策、基于流的...

    javaScript函数式编程

    本书专门介绍JavaScript函数式编程的特性。 全书共9章,分别介绍了JavaScript函数式编程、一等函数与Applicative编程、变量的作用域和闭包、高阶函数、由函数构建函数、递归、纯度和不变性以及更改政策、基于流的...

    JavaScript ES6函数式编程入门经典

    《JavaScriptES6函数式编程入门经典》使用JavaScriptES6带你学习函数式编程。你将学习柯里化、偏函数、高阶函数以及Monad等概念。  目前,编程语言已经将... ●了解ES6的函数式编程特性,例如扩展运算符和Generator

    用函数式编程技术编写优美的 JavaScript

    一些传统的编程语言,例如 C++ 和 JavaScript,引入了由函数式编程提供的一些构造和特性。在许多情 况下,JavaScript 的重复代码导致了一些拙劣的编码。如果使用函数式编程,就可以避免这些问题。此外,可以利用函数...

    JS 函数式编程指南

    这本书的主题是函数范式(functional paradigm),我们将使用 JavaScript 这个世界上最流行的函数式编程语言来讲述这一主题。有人可能会觉得选择 JavaScript 并不明智,因为当前的主流观点认为它是一门命令式...

    Functional-Programming-in-[removed]JavaScript 函数式编程的完整总结

    JavaScript 中的函数式编程 作者支持创建更多教育材料更多材料在本文中,我将尝试帮助您很好地理解 JavaScript 最常见的特性,函数式编程。 函数式编程允许您编写更短的代码、干净的代码,还可以解决传统方法可能...

    函数式JavaScript编程指南

    简介 你是否知道JavaScript其实也是一个函数式编程语言呢?本指南将教你如何利用JavaScript的函数式特性。 要求:你应当已经对JavaScript和DOM有了一个基本的了解。 写这篇指南的目的是因为关于JavaScript编程的...

    Javascript函数式编程语言

    JavaScript 是近年来非常受瞩目的一门编程语言,它既支持面向对象编程,也支持函数式编程。本文专门介绍JavaScript函数式编程的特性。

    浅谈javascript函数式编程

    你是否知道JavaScript其实也是一个函数式编程语言呢?本文将教你如何利用JavaScript的函数式特性。

    JavaScript征途

    JavaScript对象,数组,字符串,使用正则表达式操纵字符串,客户端,控制文档结构的模型,JavaScript事件驱动模型,CSS,Cookie,XML和JSON,Ajax,深入JavaScript面向对象编程,深入JavaScript函数式编程,深入...

    用函数式编程技术编写优美的 JavaScript_ibm

    一些传统的编程语言,例如 C++ 和 JavaScript,引入了由函数式编程提供的一些构造和特性。在许多情况下,JavaScript 的重复代码导致了一些拙劣的编码。如果使用函数式编程,就可以避免这些问题。此外,可以利用函数...

    《JavaScript语言精髓与编程实践》精选版--动态函数式语言精粹

    《JavaScript语言精髓与编程实践》这本书,最初的名字是叫《动态函数式语言精髓与编程实践》,这是作者写本书的原意。确切地说,作者并非是想讨论JavaScript作为一种语言工具的用法或特性,更多地是希望用一种简洁的...

    理解javascript函数式编程中的闭包(closure)

    闭包(closure)是函数式编程中的概念,出现于 20 世纪 60 年代,最早实现闭包的语言是 Scheme,它是 LISP 的一种方言。之后闭包特性被其他语言广泛吸纳。 闭包的严格定义是“由函数(环境)及其封闭的自由变量组成...

    《JavaScript征途》书稿实例

    数组,字符串,使用正则表达式操纵字符串,客户端,控制文档结构的模型,JavaScript事件驱动模型,CSS,Cookie,XML和JSON,Ajax,深入JavaScript面向对象编程,深入JavaScript函数式编程,深入JavaScript动态化编程...

    《JavaScript语言精髓与编程实践》精选版

    《JavaScript语言精髓与编程实践》这本书,最初的名字是叫《动态函数式语言精髓与编程实践》,这是作者写本书的原意。确切地说,作者并非是想讨论JavaScript作为一种语言工具的用法或特性,更多地是希望用一种简洁的...

    比较不错的函数式JavaScript编程指南教程

    你是否知道JavaScript其实也是一个函数式编程语言呢?本指南将教你如何利用JavaScript的函数式特性。 要求:你应当已经对JavaScript和DOM有了一个基本的了解。 写这篇指南的目的是因为关于JavaScript编程的资料太多...

    JavaScript面向对象编程

    javascript做为一个函数式语言,只把他 用于一些简单的前端数据输入验证以及实现一些简单的页面动态效果等,我们没能完全把握 动态语言的各种特性。而 ext中大量使用了 javascript的面向对象特性,要使用好 ext...

    JavaScript语言精髓与编程实践

    javascript作为一种混合式语言的各方面特性,包括过程式、面向对象、函数式和动态语言特性等,在动态函数式语言特性方面有着尤为细致的讲述。本书的主要努力之一,就是分解出这些语言原子,并重现将它们混合在一起的...

Global site tag (gtag.js) - Google Analytics