几十年来,函数式编程一直是计算机科学狂热者的至爱,因为数学的纯洁性和谜通常的本质, 它被埋藏在计算机实验室,只有数据学家和有但愿得到博士学位的人士使用。可是如今,它正经历一场复兴, 这要感谢一些现代语言好比Python,Julia,Ruby,Clojure以及——但不是最后一个——Javascript。 javascript
你是说Javascript?这个WEB脚本语言?没错!html
Javascript已经被证实是一项长期以来都没有消失的重要的技术。这主要是因为它扩展的一些框架和库而使其具备重生的能力, 好比backbone.js,jQuery,Dojo,underscore.js等等。这与Javascript函数式编程语言的真实身份直接相关。 对Javascript函数式编程的理解很重要,而且在至关长的一段时间会对各类水平的程序员颇有用。 java
为何呢?函数式编程很是强大、健壮而且优雅。它对于大型数据结构很是有用而且高效。 Javascript做为一个客户端脚本语言,在应对日益复杂的网站时,函数式地操做DOM、 组织API响应以及完成一些其它任务会很是有好处。 node
在这本书里,你将会学习用Javascript进行函数式编程所须要知道的一切:如何用函数式编程构建你的Javascript web应用, 如何解锁Javascript隐藏的力量,如何编写更强大的代码,而且因为程序更小,使得代码更容易维护,可以更快被下载, 而且花费更少的开支。你还会学到函数式编程的核心概念,以及如何将它们应用到Javascript, 还有将Javascript做为函数式语言时如何回避一些问题,如何在Javascript中混合使用函数式编程和面向对象编程。 程序员
不过在咱们开始前,先来作个实验。 web
也许快速举个例子是介绍Javascript函数式编程最好的方式。咱们将用Javascript完成一些任务—— 一个使用传统、原生的方法,另外一个使用函数式编程。而后咱们将会比较这两种方法。 数据库
为了追求真实感,咱们来作一个电子商务网站,一个邮购咖啡豆的公司。这个网站会销售好几种类型的咖啡, 有不一样的品质,固然也有不一样的价格。 编程
首先,咱们开始写程序。为了让这个例子接地气,咱们须要建立一些对象来保存数据。若是须要的话咱们能够从数据库里取值。 可是如今咱们假设他们是静态定义的: 数组
// create some objects to store the data. var columbian = {  name: 'columbian', basePrice: 5 }; var frenchRoast = { name: 'french roast', basePrice: 8 }; var decaf = { name: 'decaf', basePrice: 6 }; // 咱们将使用辅助函数计算价格 // 根据size打印到一个HTML的列表中 function printPrice(coffee, size) { if (size == 'small') { var price = coffee.basePrice + 2; } else if (size == 'medium') { var price = coffee.basePrice + 4; } else { var price = coffee.basePrice + 6; } // create the new html list item var node = document.createElement("li"); var label = coffee.name + ' ' + size; var textnode = document.createTextNode(label+' price: $'+price); node.appendChild(textnode); document.getElementById('products').appendChild(node); } // 如今咱们只需根据咖啡的各类价格和size的组合调用printPrice函数 printPrice(columbian, 'small'); printPrice(columbian, 'medium'); printPrice(columbian, 'large'); printPrice(frenchRoast, 'small'); printPrice(frenchRoast, 'medium'); printPrice(frenchRoast, 'large'); printPrice(decaf, 'small'); printPrice(decaf, 'medium'); printPrice(decaf, 'large');
如你所见,这个代码很是基础。若是如今有更多的咖啡种类而不仅是这三个改怎么办?若是有20个,甚至50个? 若是有更多的size呢?若是有有机和无机之分呢?这将会很快将代码量变得巨大无比! 浏览器
采用这种方法,咱们让机器去打印每一种咖啡类型和每个size。这就是采用这种命令式方法的基本问题。
命令式的代码一步一步地告诉电脑须要作什么来解决问题,相反,函数式编程追求用数学方式来描述问题, 其他的交给电脑来作。
经过更函数式一些的方式,一样的应用能够这样来写:
// 从接口中分解数据和逻辑 var printPrice = function(price, label) { var node = document.createElement("li"); var textnode = document.createTextNode(label+' price: $'+price); node.appendChild(textnode); document.getElementById('products 2').appendChild(node); } // 为每种咖啡建立函数对象 var columbian = function(){ this.name = 'columbian'; this.basePrice = 5; }; var frenchRoast = function(){ this.name = 'french roast'; this.basePrice = 8; }; var decaf = function(){ this.name = 'decaf'; this.basePrice = 6; }; // 为每种size经过字面量建立对象 var small = { getPrice: function(){return this.basePrice + 2}, getLabel: function(){return this.name + ' small'} }; var medium = { getPrice: function(){return this.basePrice + 4}, getLabel: function(){return this.name + ' medium'} }; var large = { getPrice: function(){return this.basePrice + 6}, getLabel: function(){return this.name + ' large'} }; // 将全部咖啡的种类和size放到数组里 var coffeeTypes = [columbian, frenchRoast, decaf]; var coffeeSizes = [small, medium, large]; // 建立由上面内容组成的新对象,并把它们放到一个新数组里 var coffees = coffeeTypes.reduce(function(previous, current) { var newCoffee = coffeeSizes.map(function(mixin) { // `plusmix`是函数式的minxin, 见第7章 var newCoffeeObj = plusMixin(current, mixin); return new newCoffeeObj(); }); return previous.concat(newCoffee); },[]); // 如今咱们已经定义了如何得到全部咖啡种类和size组合方式的价格,如今能够直接打印它们了 coffees.forEach(function(coffee){ printPrice(coffee.getPrice(),coffee.getLabel()); });
首先须要明确的是这个代码更加模块化了。如今新增一种size或者信新增一个咖啡种类就像下面的代码这样简单:
var peruvian = function(){ this.name = 'peruvian'; this.basePrice = 11; }; var extraLarge = { getPrice: function(){return this.basePrice + 10}, getLabel: function(){return this.name + ' extra large'} }; coffeeTypes.push(Peruvian); coffeeSizes.push(extraLarge);
咖啡对象的数组和size对象的数组混合(mix)到了一块儿,也就是他们的方法和成员变量被组合到了一起 ——经过一个叫“plusMinxin”的自定义函数(详见第七章)。这些咖啡类型的类(columbian, frenchRoast, decaf)包含了成员变量, 而这些size对象(small, medium, large)包含了获取名称和计算价格的方法。 ”混合”(minxing)这个动做经过一个map操做来起做用,也就是对数组中的每个成员执行一个纯函数并返回一个新的函数, 而后这些返回的函数被放到了一个reduce函数中被操做,reduce也是一个高阶函数,和map有些像, 只是reduce把数组里的全部元素处理后组合到了一个东西里面。最终,新的数组包含了全部可能的种类和size的组合, 这个数组经过forEach方法遍历,forEach也是一个高阶函数,它会让数组里面每个对象做为参数执行一遍回调函数。 在这个例子里,这个回调函数是一个匿名函数,它获取这些对象后,以对象的getPrice()和getLabel() 两个方法的返回值做为参数调用printPrice函数。
实际上,咱们可让这个例子更加函数式:去掉coffees变量,并将函数串到一块儿链式调用,这也是函数式编程的一个小技巧。
coffeeTypes.reduce(function(previous, current) { var newCoffee = coffeeSizes.map(function(mixin) { // `plusMixin` function for functional mixins, see Ch.7 var newCoffeeObj = plusMixin(current, mixin); return new newCoffeeObj(); }); return previous.concat(newCoffee); },[]).forEach(function(coffee) { printPrice(coffee.getPrice(),coffee.getLabel()); });
这样,控制流没有像命令式代码那样从头至尾的顺序进行。在函数式编程里,map函数和其它高阶函数代替了for和while循环, 只有少许关键的代码是在顺序执行。 这使得新接触的人在阅读这样范式的代码有些困难,可是一旦你可以欣赏它,你就会发现这根本没啥难的, 并且这样写看起来更好。
这个例子仅仅是刚开始展露Javascript中函数式编程能作什么。经过这本书,你将会看到更多函数式实现的强悍的例子。
首先,采用函数式风格的优势已经明确了。 其次,不要惧怕函数式编程。的确,它每每被认为是编程语言的纯逻辑形式,可是咱们不须要理解lambda演算也可以在平常任务中应用它。 实际上,经过把咱们的程序拆分红小的片断,它们变得更容易被理解、维护,也更加可靠。 map和reduce函数是Javascript中不太被知道的内建函数,然而咱们将要关注它们。
Javascript是一个脚本语言,可交互,易使用,不须要编译。咱们甚至不须要下载任何开发软件, 你最喜欢的浏览器就能够做为开发环境的解释器。
感兴趣吗?好,咱们开始!