听说全部的函数式程序员都会写本身的函数库,函数式Javascript程序员也不例外。 随着现在开源代码分享平台如GitHab、Bower和NPM的涌现,对这些函数库进行分享、变得及补充变得愈来愈容易。 如今已经有不少Javascript的函数式变成苦,从小巧的工具集到庞大的模块库都有。 javascript
每个库都宣扬着本身的函数式编程风格。从一本正经的数学风格到灵活松散的非正式风格,每个库都不尽相同, 然而他们他们有一个共同的特色:都是经过抽象的Javascript函数式能力来增进代码的重用行、可读性和健壮性。 css
然而直到写这本书的时候,尚未一个函数库成为事实上的标准。有人可能会说underscore.js是, 不过在后面的章节你会看到,可能避免使用underscore.js是明智的。 html
Underscore在不少人眼里已经成为函数式Javascript库的标准。它成熟稳定, 其建立者Jeremy Ashkenas也是Backbone.js和Coffeescript的建立者。 Underscore其实是对Ruby的Enumerable模块的从新实现, 这也解释了为何Coffeescript也是受Ruby影响。 java
与jQuery类似,Underscore并不改变Javascript原生对象,而是用一个符号来定义本身的对象, 就是下划线(underscore)字符“_”。因此使用Underscore会是这个样子: node
var x = _.map([1,2,3], Math.sqrt); // Underscore的map函数 console.log(x.toString());
咱们已经见过Javascript数组原生的map()方法,它是这样用的:程序员
var x = [1,2,3].map(Math.sqrt);
不一样的是,用underscore时,数组对象和回调函数都是做为参数传入给underscore的map()方法(_.map)的, 而不是像数组原生的map()方法(Array.prototype.map)那样只需传递回调。 编程
不过underscore除了map()还有不少内建函数,他们都是很是好用的函数, 好比find()、invoke()、pluck()、sortBy()、groupBy()等等。 设计模式
var greetings = [{ origin: 'spanish', value: 'hola' }, { origin: 'english', value: 'hello' }]; console.log(_.pluck(greetings, 'value')); // 获取一个对象的属性. // 返回: ['hola', 'hello'] console.log(_.find(greetings, function(s) { return s.origin == 'spanish'; })); // 查找第一个回调函数返回真的元素 // 返回: {origin: 'spanish', value: 'hola'} greetings = greetings.concat(_.object(['origin', 'value'], ['french', 'bonjour'])); console.log(greetings); // _.object经过合并两个数组来创建一个对象 // 返回: [{origin: 'spanish', value: 'hola'}, //{origin: 'english', value: 'hello'}, //{origin: 'french', value: 'bonjour'}]
而且它还提供了链式调用方法数组
var g = _.chain(greetings) .sortBy(function(x) { return x.value.length }) .pluck('origin') .map(function(x) { return x.charAt(0).toUpperCase() + x.slice(1) }) .reduce(function(x, y) { return x + ' ' + y }, '') .value(); // 应用这些函数 // 返回: 'Spanish English French' console.log(g);
尽管underscore易于使用而且被社区改进,他仍是在遭受批评。underscore强迫你编写过于冗长的代码, 并鼓励你使用错误的模式。underscore的结构并不完美,甚至不够函数式! 数据结构
就在Brian Lonsdorf在YouTube上发表名为“嘿,underscore,你作错了”的讲话不久以后, underscore在发行的1.7.0版本中明确地阻止了咱们扩展函数,好比map()、reduce()和filter()等等。
_.prototype.map = function(obj, iterate, [context]) { if (Array.prototype.map && obj.map === Array.prototype.map) return obj.map(iterate, context); // ... };
在范畴论的形式里,map是一个同态函子接口(详见第五章《范畴轮》)。咱们应该可以把map定义为函子, 不管咱们是否须要这样。因此说underscore不是很函数式。
而且因为Javascript不具备内建的不可变数据,函数式库应该十分当心地避免辅助函数改变传入的对象。 下面展现了一个针对这个问题的例子。代码中你会了一个新的选线列表,其中有一个选择项被设为了默认项。 实际上原来的列表被修改了。
function getSelectedOptions(id, value) { options = document.querySelectorAll('#' + id + ' option'); var newOptions = _.map(options, function(opt) { if (opt.text == value) { opt.selected = true; opt.text += ' (this is the default)'; } else { opt.selected = false; } return opt; }); return newOptions; } var optionsHelp = getSelectedOptions('timezones', 'Chicago');
咱们应当插入一行“opt = opt.cloneNode()”,让回调函数对传入的列表中每个元素创建一份拷贝。 underscore的map()函数为了获得性能而破坏了函数式的风水。原生的Array.prototype.map()不要求这些, 由于它会创建一个拷贝,然而它没法做用于nodelist集合。
Underscore也许并无要追求函数式编程数学上的正确性,不过它也历来没有想要把Javascript扩展或者转变为一个纯函数语言。 它把本身定义为一个提供一大堆有用的函数式编程辅助函数的Javascript库。 也许它比那些伪造得看起来像函数式辅助函数的玩意儿要好些,不过它也不是一个严肃的函数式库。
那么有没有更好的库呢?一个创建在数学之上的库?
有时,真实世界比小说更离奇。
Fantasy Land是一个函数式基础库的集合,也是一份关于如何在Javascript中实现“代数结构”的规格。 更确切地说,Fantasy Land阐述了通常代数结构(简称代数)的互操做性:monads、monoids、setoids、 函子(functors)、链(chains)等等。这些名字可能听起来很吓人,不过他们只是一系列值、 一系列操做以及一些必需要遵照的规定。换句话说,他们只不过是对象。
下图展现了他们是如何工做的。每个代数是一个单独的Fantasy Land规格, 它可能依赖于另外一个须要实现的代数。
这里列出一些代数的规格:
这个列表还有不少内容
咱们不须要知道么一个代数的确切含义是什么,可是它的确颇有帮助,尤为是当你编写符合这些这些规则的本身的库的时候。 这不仅是抽象的玩意儿,它对一个叫作范畴论的高度抽象的东西的含义进行了归纳。第五章将对范畴论进行全面的解释。
Fantasy Land不仅告诉了咱们如何实现函数式编程,它还提供了一个Javascript的函数式模块集。 然而里面有不少不完整的东西,而且文档也很不完善。不过Fantasy Land不是对这个开源规格的惟一实现。 还有一个实现的库叫作Bilby.js。
Bilby是个啥?它可不是梦幻大陆(Fantasy Land)上的神话生物,而是地球上的介于老鼠和兔子之间的一种怪异而可爱的动物, 中文名是兔耳袋狸。尽管如此,bilby.js库听从Fantasy Land的规格。
实际上,bilby是一个严肃的函数库。如它的文档中所描述:严肃,意味着它应用范畴论来实现高度抽象代码; 函数式,意味着它可使程序引用透明。Wow,它还真够严肃的。文档的地址是 http://bilby.brianmckenna.org/。 它提供了如下内容:
目前,Bilby.js这个已经很成熟的的库符合了Fantasy Land关于代数结构的规格。 要写彻底函数式语言的代码,Bilby.js是一个优秀的资源。
咱们来看个例子
// bilby的环境是多元方法的不可变结构 var shapes1 = bilby.environment() // 定义方法 .method( 'area', // 方法的名称 function(a){return typeof(a) == 'rect'}, // 断言 function(a){return a.x * a.y} // 实现 ) // 定义属性,相似于定义一个方法,里面只有总返回true的断言 .property( 'name', // 名称 'shape'); // 函数 // 如今咱们能够把它重载 var shapes2 = shapes1 .method( 'area', function(a){return typeof(a) == 'circle'}, function(a){return a.r * a.r * Math.PI} ); var shapes3 = shapes2 .method( 'area', function(a){return typeof(a) == 'triangle'}, function(a){return a.height * a.base / 2} ); // 如今咱们能够像这样作点什么 var objs = [{type:'circle', r:5}, {type:'rect', x:2, y:3}]; var areas = objs.map(shapes3.area); // 或者这样 var totalArea = objs.map(shapes3.area).reduce(add);
这就是范畴论和特定多态的实践。再啰嗦一次:范畴论将会在第五章全面讲解。
事实上,Bilby和Fantasy Land真的让Javascript之上的函数式编程成为了可能。 尽管能够看到计算机科学发生着突飞猛进的变化,可是这个世界仍未准备好迎接Bilby和Fantasy Land 所推进的顽固的函数式编程风格。
也许在函数式Javascript的恐慌地带的如此壮丽的一个库并非咱们想要的。 毕竟咱们的出发点是寻找用于补充Javascript的函数式技术,而不是创建函数式编程信条。 如今让咱们把注意力转向另外一个新库:Lazy.js。
Lazy是一个实用的库,它更大程度上是沿着Underscore的路线,不过它有惰性求值策略。正因如此, Lazy让即刻解释的语言本不可能完成的函数式计算变成了可能。它还会显著提高性能。
Lazy库还很年轻,可是在它背后有旺盛的社区热度和强劲的动力。
Lazy的主意是,咱们可以迭代的全部东西都是一个序列。因为这个库用方法执行的前后来控制顺序, 不少很酷的事情就能够实现了:异步循环(并行编程)、无限序列、函数式响应式编程等等。
下面的例子展现了一下各类情形的代码:
// 得到一首歌歌词的前三行 var lyrics = "我徘徊在海之滨山之巅\n越此城镇越彼乡园\n ... // 若是没有惰性,整个歌词会先根据换行来分割 console.log(lyrics.split('\n').slice(0, 3)); // 有了惰性,能够只文本分割出来前三行 // 歌词甚至能够无限的长! console.log(Lazy(lyrics).split('\n').take(3));
// 前十个能被3整除的平方数 var oneTo1000 = Lazy.range(1, 1000).toArray(); var sequence = Lazy(oneTo1000) .map(function(x) { return x * x; }) .filter(function(x) { return x % 3 === 0; }) .take(10) .each(function(x) { console.log(x); });
// 对无限序列的异步循环 var asyncSequence = Lazy.generate(function(x) { return x++ }) .async(100) // 每两个元素间隔0.100秒 .take(20) // 只计算前20项 .each(function(e) { // 开始对序列进行循环 console.log(new Date().getMilliseconds() + ": " + e); });
更多例子参见第四章。
不过Lazy库的这个主意并不能保证它彻底的正确性。它还有一个前辈,Bacon.js,他们的工做方式差很少。
这是Bacon.js的logo:
这个大胡子牛仔是一个函数式响应式编程的库。函数式响应式编程的意思是: 函数式设计模式用于展现响应式的常常变化的值,好比鼠标在屏幕上的位置,或者公司股票的价格。 跟Lazy经过须要时才计算值而避免建立无限循环的序列的方法相同, Bacon能够避免实时计算随时在发生变化的值,直到须要这个值的最后一秒。
在Lazy中被称为序列的东西在Bacon中是事件流和属性,由于这样更适合于使用事件(onmouseover, onkeydown等等)以及响应属性(滚动位置、鼠标位置、toggle等等)。
Bacon.fromEventTarget(document.body, "click") .onValue(function() { alert("Bacon!") });
Bacon比Lazy稍老一些,可是它的功能集差很少是Lazy的一半,社区热度差很少。
Javascript函数式编程的库实在太多了,没法在本书中一一展现。咱们再来简单看几个吧。
$('#mydiv').fadeIn().css('left': 50).alert('hi!');关于这个的详细解释能够在第七章《Javascript的函数式和面向对象编程》中找到
$('li').css('left': function(index){return index*50});