基础知识css
函数式编程简介html
说到函数式编程,人们的第一印象每每是其学院派,晦涩难懂,大概只有那些蓬头散发,不修边幅,甚至有些神经质的大学教授们才会用的编程方式。这可能在历史上的某个阶段的确如此,可是近来函数式编程已经在实际应用中发挥着巨大做用了,而更有愈来愈多的语言不断的加入诸如 闭包,匿名函数等的支持,从某种程度上来说,函数式编程正在逐步“同化”命令式编程。前端
函数式编程思想的源头能够追溯到 20 世纪 30 年代,数学家阿隆左 . 丘奇在进行一项关于问题的可计算性的研究,也就是后来的 lambda 演算。lambda 演算的本质为 一切皆函数,函数能够做为另一个函数的输出或者 / 和输入,一系列的函数使用最终会造成一个表达式链,这个表达式链能够最终求得一个值,而这个过程,即为计算的本质。node
然而,这种思想在当时的硬件基础上很难实现,历史最终选择了同丘奇的 lambda 理论平行的另外一种数学理论:图灵机做为计算理论,而采起另外一位科学家冯 . 诺依曼的计算机结构,并最终被实现为硬件。因为第一台计算机即为冯 . 诺依曼的程序存储结构,所以运行在此平台的程序也继承了这种基因,程序设计语言如 C/Pascal 等都在必定程度上依赖于此体系。jquery
到了 20 世纪 50 年代,一位 MIT 的教授 John McCarthy 在冯 . 诺依曼体系的机器上成功的实现了 lambda 理论,取名为 LISP(LISt Processor), 至此函数式编程语言便开始活跃于计算机科学领域。web
函数式编程语言特性ajax
在函数式编程语言中,函数是第一类的对象,也就是说,函数 不依赖于任何其余的对象而能够独立存在,而在面向对象的语言中,函数 ( 方法 ) 是依附于对象的,属于对象的一部分。这一点 j 决定了函数在函数式语言中的一些特别的性质,好比做为传出 / 传入参数,做为一个普通的变量等。shell
区别于命令式编程语言,函数式编程语言具备一些专用的概念,咱们分别进行讨论:编程
匿名函数json
在函数式编程语言中,函数是能够没有名字的,匿名函数一般表示:“能够完成某件事的一块代码”。这种表达在不少场合是有用的,由于咱们有时须要用函数完成某件事,可是这个函数可能只是临时性的,那就没有理由专门为其生成一个顶层的函数对象。好比:
function map(array, func){ var res = []; for ( var i = 0, len = array.length; i < len; i++){ res.push(func(array[i])); } return res; } var mapped = map([1, 3, 5, 7, 8], function (n){ return n = n + 1; }); print(mapped); 运行这段代码,将会打印: 2,4,6,8,9// 对数组 [1,3,5,7,8] 中每个元素加 1 |
注意 map 函数的调用,map 的第二个参数为一个函数,这个函数对 map 的第一个参数 ( 数组 ) 中的每个都有做用,可是对于 map 以外的代码可能没有任何意义,所以,咱们无需为其专门定义一个函数,匿名函数已经足够。
柯里化
柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。这句话有点绕口,咱们能够经过例子来帮助理解:
function adder(num){ return function (x){ return num + x; } } var add5 = adder(5); var add6 = adder(6); print(add5(1)); print(add6(1)); |
结果为:
6
7
比较有意思的是:函数 adder 接受一个参数,并返回一个函数,这个返回的函数能够被预期的那样被调用。变量 add5 保持着 adder(5) 返回的函数,这个函数能够接受一个参数,并返回参数与 5 的和。
柯里化在 DOM 的回调中很是有用,咱们将在下面的小节中看到。
高阶函数
高阶函数即为对函数的进一步抽象,事实上,咱们在匿名函数小节提到的 map 函数即为一种高阶函数,在不少的函数式编程语言中均有此函数。map(array, func) 的表达式已经代表,将 func 函数做用于 array 中的每个元素,最终返回一个新的 array,应该注意的是,map 对 array 和 func 的实现是没有任何预先的假设的,所以称之为“高阶”函数:
function map(array, func){ var res = []; for ( var i = 0, len = array.length; i < len; i++){ res.push(func(array[i])); } return res; } var mapped = map([1, 3, 5, 7, 8], function (n){ return n = n + 1; }); print(mapped); var mapped2 = map(["one", "two", "three", "four"], function (item){ return "("+item+")"; }); print(mapped2); |
将会打印以下结果:
2,4,6,8,9 (one),(two),(three),(four)// 为数组中的每一个字符串加上括号 |
mapped 和 mapped2 均调用了 map,可是获得了大相径庭的结果,由于 map 的参数自己已经进行了一次抽象,map 函数作的是第二次抽象,高阶的“阶”能够理解为抽象的层次。
JavaScript 是一门被误解甚深的语言,因为早期的 Web 开发中,充满了大量的 copy-paste 代码,所以平时能够见到的 JavaScript 代码质量多半不高,并且 JavaScript 代码老是很飞动的不断闪烁的 gif 广告,限制网页内容的复制等联系在一块儿的,所以包括 Web 开发者在内的不少人根本不肯意去学习 JavaScript。
这种情形在 Ajax 复兴时获得了完全的扭转,Google Map,Gmail 等 Ajax 应用的出现令人们惊叹:原来 JavaScript 还能够作这样的事!很快,大量优秀的 JavaScript/Ajax 框架不断出现,好比 Dojo,Prototype,jQuery,ExtJS 等等。这些代码在给页面带来绚丽的效果的同时,也让开发者看到函数式语言代码的优雅。
在 JavaScript 中,函数自己为一种特殊对象,属于顶层对象,不依赖于任何其余的对象而存在,所以能够将函数做为传出 / 传入参数,能够存储在变量中,以及一切其余对象能够作的事情 ( 由于函数就是对象 )。
JavaScript 被称为有着 C 语法的 LISP,LISP 代码的一个显著的特色是大量的括号以及前置的函数名,好比:
(+ 1 3 4 5 6 7) |
加号在 LISP 中为一个函数,这条表达式的意思为将加号后边的全部数字加起来,并将值返回,JavaScript 能够定义一样的求和函数:
function sum(){ var res = 0; for ( var i = 0, len = arguments.length; i < len; i++){ res += parseInt(arguments[i]); } return res; } print(sum(1,2,3)); print(sum(1,2,3,4,6,7,8)); |
运行此段代码,获得以下结果:
6 31 |
若是要彻底模拟函数式编码的风格,咱们能够定义一些诸如:
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 factorial(n){ if (n == 1){ return 1; } else { return factorial(n - 1) * n; } } // 更接近“函数式”编程风格的代码 function factorial(n){ if (equal(n, 1)){ return 1; } else { return mul(n, factorial(dec(n))); } } |
闭包是一个颇有趣的主题,当在一个函数 outter 内部定义另外一个函数 inner,而 inner 又引用了 outter 做用域内的变量,在 outter 以外使用 inner 函数,则造成了闭包。描述起来虽然比较复杂,在实际编程中却常常无心的使用了闭包特性。
function outter(){ var n = 0; return function (){ return n++; } } var o1 = outter(); o1();//n == 0 o1();//n == 1 o1();//n == 2 var o2 = outter(); o2();//n == 0 o2();//n == 1 |
匿名函数 function(){return n++;} 中包含对 outter 的局部变量 n 的引用,所以当 outter 返回时,n 的值被保留 ( 不会被垃圾回收机制回收 ),持续调用 o1(),将会改变 n 的值。而 o2 的值并不会随着 o1() 被调用而改变,第一次调用 o2 会获得 n==0 的结果,用面向对象的术语来讲,就是 o1 和 o2 为不一样的 实例,互不干涉。
总的来讲,闭包很简单,不是吗?可是,闭包能够带来不少好处,好比咱们在 Web 开发中常常用到的:
var con = $("div#con"); setTimeout( function (){ con.css({background:"gray"}); }, 2000); |
上边的代码使用了 jQuery 的选择器,找到 id 为 con 的 div 元素,注册计时器,当两秒中以后,将该 div 的背景色设置为灰色。这个代码片断的神奇之处在于,在调用了 setTimeout 函数以后,con 依旧被保持在函数内部,当两秒钟以后,id 为 con 的 div 元素的背景色确实获得了改变。应该注意的是,setTimeout 在调用以后已经返回了,可是 con 没有被释放,这是由于 con 引用了全局做用域里的变量 con。
使用闭包可使咱们的代码更加简洁,关于闭包的更详细论述能够在参考信息中找到。因为闭包的特殊性,在使用闭包时必定要当心,咱们再来看一个容易使人困惑的例子:
var outter = []; function clouseTest () { var array = ["one", "two", "three", "four"]; for ( var i = 0; i < array.length;i++){ var x = {}; x.no = i; x.text = array[i]; x.invoke = function (){ print(i); } outter.push(x); } } |
上边的代码片断很简单,将多个这样的 JavaScript 对象存入 outter 数组:
{ no : Number, text : String, invoke : function (){ // 打印本身的 no 字段 } } |
咱们来运行这段代码:
clouseTest();// 调用这个函数,向 outter 数组中添加对象 for ( var i = 0, len = outter.length; i < len; i++){ outter[i].invoke(); } |
出乎意料的是,这段代码将打印:
4 4 4 4 |
而不是 1,2,3,4 这样的序列。让咱们来看看发生了什么事,每个内部变量 x 都填写了本身的 no,text,invoke 字段,可是 invoke 却老是打印最后一个 i。原来,咱们为 invoke 注册的函数为:
function invoke(){ print(i); } |
每个 invoke 均是如此,当调用 outter[i].invoke 时,i 的值才会被去到,因为 i 是闭包中的局部变量,for 循环最后退出时的值为 4,所以调用 outter 中的每一个元素都会获得 4。所以,咱们须要对这个函数进行一些改造:
var outter = []; function clouseTest2(){ var array = ["one", "two", "three", "four"]; for ( var i = 0; i < array.length;i++){ var x = {}; x.no = i; x.text = array[i]; x.invoke = function (no){ return function (){ print(no); } }(i); outter.push(x); } } |
经过将函数 柯里化,咱们此次为 outter 的每一个元素注册的实际上是这样的函数:
//x == 0 x.invoke = function (){print(0);} //x == 1 x.invoke = function (){print(1);} //x == 2 x.invoke = function (){print(2);} //x == 3 x.invoke = function (){print(3);} |
这样,就能够获得正确的结果了。
好了,理论知识已经够多了,咱们下面来看看现实世界中的 JavaScript 函数式编程。有不少人为使 JavaScript 具备面向对象风格而作出了不少努力 (JavaScript 自己具备 可编程性),事实上,面向对象并不是必须,使用函数式编程或者二者混合使用可使代码更加优美,简洁。
jQuery 是一个很是优秀 JavaScript/Ajax 框架,小巧,灵活,具备插件机制,事实上,jQuery 的插件很是丰富,从表达验证,客户端图像处理,UI,动画等等。而 jQuery 最大的特色正如其宣称的那样,改变了人们编写 JavaScript 代码的风格。
优雅的 jQuery
有经验的前端开发工程师会发现,平时作的最多的工做有必定的模式:选择一些 DOM 元素,而后将一些规则做用在这些元素上,好比修改样式表,注册事件处理器等。所以 jQuery 实现了完美的 CSS 选择器,并提供跨浏览器的支持:
var cons = $("div.note");// 找出全部具备 note 类的 div var con = $("div#con");// 找出 id 为 con 的 div 元素 var links = $("a");// 找出页面上全部的连接元素 |
固然,jQuery 的选择器规则很是丰富,这里要说的是:用 jQuery 选择器选择出来的 jQuery 对象本质上是一个 List,正如 LISP 语言那样,全部的函数都是基于 List 的。
有了这个 List,咱们能够作这样的动做:
cons.each( function (index){ $( this ).click( function (){ //do something with the node }); }); |
想当与对 cons 这个 List中的全部元素使用 map( 还记得咱们前面提到的 map 吗? ),操做结果仍然为一个 List。咱们能够任意的扩大 / 缩小这个列表,好比:
cons.find("span.title");// 在 div.note 中进行更细的筛选 cons.add("div.warn");// 将 div.note 和 div.warn 合并起来 cons.slice(0, 5);// 获取 cons 的一个子集 |
如今咱们来看一个小例子,假设有这样一个页面:
<div class="note"> <span class="title">Hello, world</span> </div> <div class="note"> <span class="title">345</span> </div> <div class="note"> <span class="title">Hello, world</span> </div> <div class="note"> <span class="title">67</span> </div> <div class="note"> <span class="title">483</span> </div> |
效果以下:
咱们经过 jQuery 对包装集进行一次过滤,jQuery 的过滤函数可使得选择出来的列表对象只保留符合条件的,在这个例子中,咱们保留这样的 div,当且仅当这个 div 中包含一个类名为 title 的 span,而且这个 span 的内容为数字:
var cons = $("div.note").hide();// 选择 note 类的 div, 并隐藏 cons.filter( function (){ return $( this ).find("span.title").html().match(/^\d+$/); }).show(); |
效果以下图所示:
咱们再来看看 jQuery 中对数组的操做 ( 本质上来说,JavaScript 中的数组跟 List 是很相似的 ),好比咱们在前面的例子中提到的 map 函数,过滤器等:
var mapped = $.map([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function (n){ return n + 1; }); var greped = $.grep([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function (n){ return n % 2 == 0; }); |
mapped 将被赋值为 :
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11] |
而 greped 则为:
[2, 4, 6, 8, 10] |
咱们再来看一个更接近实际的例子:
function update(item){ return function (text){ $("div#"+item).html(text); } } 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/op=1", update("content1")); refresh("action.do/op=2", update("content2")); refresh("action.do/op=3", update("content3")); |
首先声明一个柯里化的函数 update,这个函数会将传入的参数做为选择器的 id,并更新这个 div 的内容 (innerHTML)。而后声明一个函数 refresh,refresh 接受两个参数,第一个参数为服务器端的 url,第二个参数为一个回调函数,当服务器端成功返回时,调用该函数。
而后咱们陆续调用三次 refresh,每次的 url 和 id 都不一样,这样能够将 content1,content2,conetent3 的内容经过异步方式更新。这种模式在实际的编程中至关有效,由于关于如何与服务器通讯,以及若是选取页面内容的部分被很好的抽象成函数,如今咱们须要作的就是将 url 和 id 传递给 refresh,便可完成须要的动做。函数式编程在很大程度上下降了这个过程的复杂性,这正是咱们选择使用该思想的最终缘由。
实际的应用中,不会囿于函数式或者面向对象,一般是二者混合使用,事实上,不少主流的面向对象语言都在不断的完善本身,好比加入一些函数式编程语言的特征等,JavaScript 中,这二者获得了良好的结合,代码不但能够很是简单,优美,并且更易于调试。
文中仅仅提到 jQuery 特征的一小部分,若是感兴趣,则能够在参考资料中找到更多的连接,jQuery 很是的流行,所以你能够找到不少论述如何使用它的文章。