`
Spiritualize_彭韬
  • 浏览: 24727 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

一个简单的JavaScript函数式编程教程

 
阅读更多
涉及到了基于事件的并发机制和函数式编程。仔细想想,应该与JavaScript本身的特性不无关系:

基于事件(Event-Based)的Node.js的正是并发中很典型的一个模型
函数式编程使其天然支持回调,从而非常适合异步/事件机制
函数式编程特性使其非常适合DSL的编写
会后的第二天,我在项目代码里忽然想要将一个聚合模型用函数式编程的方式重写一下,结果发现思路竟然与NoSQL依稀有些联系,进一步发现自己很多不足。

下面这个例子来自于实际项目中的场景,不过Domain做了切换,但是丝毫不影响阅读和理解背后的机制。

一个书签应用
设想有这样一个应用:用户可以看到一个订阅的RSS的列表。列表中的每一项(称为一个Feed),包含一个id

,一个文章的标题title和一个文章的链接url。

数据模型看起来是这样的:

var feeds = [
    {
        'id': 1,
        'url': 'http://abruzzi.github.com/2015/03/list-comprehension-in-python/',
        'title': 'Python中的 list comprehension 以及 generator'
    },
    {
        'id': 2,
        'url': 'http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/',
        'title': '使用inotify/fswatch构建自动监控脚本'
    },
    {
        'id': 3,
        'url': 'http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/',
        'title': '使用underscore.js构建前端应用'
    }
];
当这个简单应用没有任何用户相关的信息时,模型非常简单。但是很快,应用需要从单机版扩展到Web版,也就是说,我们引入了用户的概念。每个用户都能看到一个这样的列表。另外,用户还可以收藏Feed。当然,收藏之后,用户还可以查看收藏的Feed列表。

由于每个用户可以收藏多个Feed,而每个Feed也可以被多个用户收藏,因此它们之间的多对多关系如上图所示。可能你还会想到诸如:

$ curl http://localhost:9999/user/1/feeds
来获取用户1

的所有feed等,但是这些都不重要,真正的问题是,当你拿到了所有Feed之后,在UI上,需要为每个Feed填加一个属性makred。这个属性用来标示该feed是否已经被收藏了。对应到界面上,可能是一枚黄色的星星,或者一个红色的心。

服务器端聚合
由于关系型数据库的限制,你需要在服务器端做一次聚合,比如将feed对象包装一下,生成一个FeedWrapper

之类的对象:

public class FeedWrapper {
    private Feed feed;
    private boolean marked;

    public boolean isMarked() {
        return marked;
    }

    public void setMarked(boolean marked) {
        this.marked = marked;
    }

    public FeedWrapper(Feed feed, boolean marked) {
        this.feed = feed;
        this.marked = marked;
    }
}
然后定义一个FeedService

之类的服务对象:

public ArrayList wrapFeed(List markedFeeds, List feeds) {
    return newArrayList(transform(feeds, new Function() {
        @Override
        public FeedWrapper apply(Feed feed) {
            if (markedFeeds.contains(feed)) {
                return new FeedWrapper(feed, true);
            } else {
                return new FeedWrapper(feed, false);
            }
        }
    }));
}
好吧,这也算是一个还凑合的实现,但是静态强类型的Java做这个事儿有点勉强,而且一旦发生新的变化(几乎肯定会发生),我们还是把这部分逻辑放在JavaScript中,来看看它是如何简化这一个过程的。

客户端聚合
快要说到主题了,这篇文章我们会使用lodash

作为函数式编程的库来简化代码的编写。由于JavaScript是一个动态弱类型的语言,我们可以随时为一个对象添加属性,这样一个简单的map操作就可以完成上边的Java对应的代码了:

_.map(feeds, function(item) {
    return _.extend(item, {marked: isMarked(item.id)});
});
其中函数isMarked

会做这样一件事儿:

var userMarkedIds = [1, 2];
function isMarked(id) {
    return _.includes(userMarkedIds, id);
}
即查看传入的参数是否在一个列表userMarkedIds

,这个列表可能由下列的请求来获得:

$ curl http://localhost:9999/user/1/marked-feed-ids
之所有只获取id是为了减少网络传输的数据大小,当然你也可以将全部的/marked-feeds

都请求到,然后在本地做_.pluck(feeds, 'id')来抽取所有的id属性。

嗯,代码是精简了许多。但是如果仅仅能做到这一步的话,也没有多大的好处嘛。现在需求又有了变化,我们需要在另一个页面上展示当前用户的收藏夹(用以展示用户所有收藏的feed)。作为程序员

,我们可不愿意重新写一套界面,如果能复用同一套逻辑当然最好了。

比如对于上面这个列表,我们已经有了对应的模板:

{{#each feeds}}
{{#if this.marked}} {{else}} {{/if}}
{{this.title}}

{{/each}}
事实上,这段代码在收藏夹页面上完全可以复用,我们只需要把所有的marked

属性都设置为true就行了!简单,很快我们就可以写出对应的代码:

_.map(feeds, function(item) {
    return _.extend(item, {marked: true});
});
漂亮!而且重要的是,它还可以如正常工作!但是作为程序员,你很快就发现了两处代码的相似性:

_.map(feeds, function(item) {
    return _.extend(item, {marked: isMarked(item.id)});
});

_.map(feeds, function(item) {
    return _.extend(item, {marked: true});
});
消除重复是一个有追求的程序员的基本素养,不过要消除这两处貌似有点困难:位于marked:

后边的,一个是函数调用,另一个是值!如果要简化,我们不得不做一个匿名函数,然后以回调的方式来简化:

function wrapFeeds(feeds, predicate) {
    return _.map(feeds, function(item) {
        return _.extend(item, {marked: predicate(item.id)});
    });
}
对于feed列表,我们要调用:

wrapFeeds(feeds, isMarked);
而对于收藏夹,则需要传入一个匿名函数:

wrapFeeds(feeds, function(item) {return true});
在lodash

中,这样的匿名函数可以用_.wrap来简化:

wrapFeeds(feeds, _.wrap(true));
好了,目前来看,简化的还不错,代码缩减了,而且也好读了一些(当然前提是你已经熟悉了函数式编程的读法)。

更进一步
如果仔细审视isMarked

函数,会发现它对外部的依赖不是很漂亮(而且这个外部依赖是从网络异步请求来的),也就是说,我们需要在请求到markedIds的地方才能定义isMarked函数,这样就把函数定义绑定到了一个固定的地方,如果该函数的逻辑比较复杂,那么势必会影响代码的可维护性(或者更糟糕的是,多出维护)。

要将这部分代码隔离出去,我们需要将ids

作为参数传递出去,并得到一个可以当做谓词(判断一个id是否在列表中的谓词)的函数。

简而言之,我们需要:

var predicate = createFunc(ids);
wrapFeeds(feeds, predicate);
这里的createFunc

函数接受一个列表作为参数,并返回了一个谓词函数。而这个谓词函数就是上边说的isMarked。这个神奇的过程被称为柯里化currying,或者偏函数partial。在lodash中,这个很容易实现:

function isMarkedIn(ids) {
    return _.partial(_.includes, ids);
}
这个函数会将ids

保存起来,当被调用时,它会被展开为:_.includes(ids, )。只不过这个会在实际迭代的时候才传入:

$('/marked-feed-ids').done(function(ids) {
    var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids));
    console.log(wrappedFeeds);
});
这样我们的代码就被简化成了:

$('/marked-feed-ids').done(function(ids) {
    var wrappedFeeds = wrapFeeds(feeds, isMarkedIn(ids));
    var markedFeeds = wrapFeeds(feeds, _.wrap(true));

    allFeedList.html(template({feeds: wrappedFeeds}));
    markedFeedList.html(template({feeds: markedFeeds}));
});
分享到:
评论

相关推荐

    一个简单的JavaScript函数式编程教程JAVA语言

    一个简单的JavaScript函数式编程教程

    一个简单的JavaScript函数式编程教程.docx

    一个简单的JavaScript函数式编程教程.docx

    一本关于JavaScript中函数式编程的书

    一本关于JavaScript中函数式编程的书

    JavaScript函数式编程

    JavaScript函数式编程 电子教程

    functional-programming:javascript函数式编程指南

    javascript函数式编程指南。 写这个文章的原因在于函数式编程的思想非常先进,其天生的可预测性(也可以说是可测试), 更细粒度的代码(逻辑)重用,以及天生支持并行等特点, 已经被也业内越来越多的人认可。由于其很高...

    frisby:JavaScript函数式编程库

    动机我记得我有着迷函数式编程我看了车间后由凯尔·辛普森和由Brian Lonsdorf,都托管在 。 这些讲习班的每个剪辑之后,我都惊呆了。 看到我可以写出整洁,不易出错的代码,并且用更少的代码来实现与其他编程范例...

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

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

    javaScript入门教程.pdf

    **函数式编程:**JavaScript 支持函数式编程,包括高阶函数、闭包和箭头函数。 **事件驱动:**JavaScript 是一种事件驱动的语言,可以响应用户交互(如点击、鼠标移动等)和系统事件(如页面加载、计时器到期等)。

    purescript-from-scratch:这是一个全面而实用的教程,可让人们学习Purescript,而无需任何函数式编程方面的经验

    从零开始的Purescript 全面而实用的教程,使人们无需具备任何函数式编程经验即可学习Purescript。为什么选择Purescript Purescript太神奇了! 这是一种非常强大的语言,可以编译为Javascript,并为您提供构建超级,...

    JavaScript语言教程.docx

    虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式。 [1] JavaScript在1995年由...

    JavaScript与HTML实用教程

    第一章、JavaScipt概述 第二章、编写一个简单的脚本 第三章、数据类型、变量、表达式和if结构 第四章、函数与对象 第五章、事件 第六章、交互式窗体 第七章、循环 第八章、框架、文档和窗口...

    mostly-adequate-guide.pdf

    函数式编程的概念里充满着诸如函数演算、代数、范畴论一类的术语。这些装逼的词汇一下就把人给忽悠瘸了。而本书的内容却生动而活泼。 要是所有的编程书都能像这本一样有趣该多好。这是一本会让你手不释卷,乐在其中...

    learn-reasonml-workshop:通过 24 个练习学习 ReasonML。 无需先前的函数式编程知识

    语法使 ReasonML 很容易开始使用,但除非您已经知道一种类型化的函数式语言,否则由于不熟悉静态类型和函数式编程,您很快就会遇到绊脚石。 不要怕! 完成这些练习并学习所有基础知识 - 定义和使用函数,理解递归、...

    JavaScript与HTML实用教程.part2

    第一章、JavaScipt概述 第二章、编写一个简单的脚本 第三章、数据类型、变量、表达式和if结构 第四章、函数与对象 第五章、事件 第六章、交互式窗体 第七章、循环 第八章、框架、文档和窗口...

    JavaScript与HTML实用教程.part4.rar

    第一章、JavaScipt概述 第二章、编写一个简单的脚本 第三章、数据类型、变量、表达式和if结构 第四章、函数与对象 第五章、事件 第六章、交互式窗体 第七章、循环 第八章、框架、文档和窗口...

    JavaScript与HTML实用教程.part3.rar

    第一章、JavaScipt概述 第二章、编写一个简单的脚本 第三章、数据类型、变量、表达式和if结构 第四章、函数与对象 第五章、事件 第六章、交互式窗体 第七章、循环 第八章、框架、文档和窗口...

    JavaScript与HTML实用教程.part5.rar

    第一章、JavaScipt概述 第二章、编写一个简单的脚本 第三章、数据类型、变量、表达式和if结构 第四章、函数与对象 第五章、事件 第六章、交互式窗体 第七章、循环 第八章、框架、文档和窗口...

    Y分钟学习X种语言

    函数式编程语言 Web 语言 秘教语言 浏览器IDE 提升级别 动态语言 厌烦了长时间的编译、渴望一种轻量级的脚本环境?动态语言一定会让你喜欢。 尝试Lua语言 Lua是一种轻量级的动态编程语言,对协程(coroutine)有着很好...

    LiveAndLearn-[removed]记录学习Javascript的100天,教程来源于Asabeneh的30-Days-Of-JavaScript

    JavaScript 是一种基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。 JavaScript 的标准是 ECMAScript 。截至 2012 年,所有的现代浏览器都完整的支持 ECMAScript 5.1,...

Global site tag (gtag.js) - Google Analytics