《JavaScript编程精解》读书笔记-第五章:函数式编程
5.1抽象:归根结底程序是要解决生活中的问题,但多数时候现实中的问题总是很复杂,而尽量降低程序复杂程度的办法就是进行抽象化处理。把许多实际的复杂关系抽象成更简单的逻辑运用到程序当中。这是我对编程中抽象的理解。函数式编程就是通过巧妙的函数组合来创建抽象。
5.2高阶函数:简单点说高阶函数就是处理其他函数的函数,也就是函数的嵌套。js是面向函数的一门语言,在js的世界里任何东西都是值类型的,当然函数也不例外。它与其他语言(比如说C#)最明显的差异就是函数能够完全像值一样去生成,去传递。你可以把函数作为另一个函数的参数来使用,也可以在一个函数的内部再定义一个新的函数。当然你也许会想到C#中的lambda表达式,确实也能完成一些类似的功能,但js是纯动态的语言,处理函数是它的看家本领,C#中lambda,var的运用只是在一定程度上增加了动态性,灵活性上还是远不如js的。前面说的这些是我个人的一些理解,书中并没讲这些,书里面多是以实例的形式来展现函数编程的,下面我们用代码试试看(当然,代码也一定跟书上一样,有些是高仿的)。
一个最简单的函数式编程的例子
function calculate(calMethod,numA,numB) { return calMethod(numA,numB); } function add(num1,num2) { return num1+num2; } function multiply(num1,num2) { return num1*num2; } var result1 = calculate(add,1,2); var result2 = calculate(multiply,1,2); alert("result1:"+result1+",result2:"+result2);
在上面的例子中,函数就是值的特性一目了然。还有一个常用的高阶函数的类型就是修改函数,作用是修改了传入的函数的值,如下:
function initMethod(method1) { return function(num1) { return method1(num1); } } function changedMethod(num1) { return num1*num1; } var testMethod = initMethod(changedMethod); var result = testMethod(2); alert(result);
上面这个例子让我想起最近asp.net的mvc中的依赖注入,把带有具体功能的一个对象通过C#中构造函数传到当前类中,当前类中的功能其实是靠传过来的对象实现的。代理模式应该也是这样的。
我们常见的sum()函数,其实是一个算法的变体,而这个函数就是规约函数,下面是规约函数的一个例子:
//规约函数 function reduce(combine,base,array){ forEach(array,function(element){ base = combine(base,element); }); return base; } function add(a,b){ return a+b; } function forEach(array,action){ for(var i = 0; i< array.length; i++){ action(array[i]); } } function sum(numbers){ return reduce(add,0,numbers); } alert(sum([1,2,3]));
reduce函数通过重复的调用一个函数,将一个数组里面的所有的值都加到一个基础数据base上。这样就能对数组里的所有值按照一定规则计算,这里是相加,改变combine参数来具体实现你自己想要的运算。另外注意的是函数作为参数传递时放在第一位置,这是惯例,至于具体原因,书中声明后面会讲到。
到此为止我稍微总结了一下函数内部读取函数的情况:
1.条用外部的函数(最起码的)。
2.能够读取作为参数传进来的函数。
3.能够在当前函数内部定义函数,并调用该函数。
4.能够读取该函数自身(递归)。
再看一个函数,目的是接受一个数组,返回数组里面值为0的个数。
function reduce(combine,base,array){ forEach(array,function(element){ base = combine(base,element); }); return base; } function forEach(array,action){ for(var i = 0; i< array.length; i++){ action(array[i]); } } function countZeros(array){ function counter(total,element){ return total + (0 === element ? 1 : 0); //下面注释掉的这一行是一个替代方案 //return total + (0 === element); } return reduce(counter,0,array); } alert(countZeros([0,1,0,0,0,0,1,1,1,1])); /*帅!,能把true转成整型啊,true就是1,false就是0. var a = 1 + true; alert(a); */ /* var a = 1+ false; alert(a); */
上面这个例子中return total + (0 === element ? 1 : 0)用的十分巧妙,但我尝试了一种新的实现方法return total + (0===element),经过实验我发现其实bool与number相加的时候true会变成1,false会变成0,这样在很多情况下都可以判断跟加减运算一气呵成了,挺好。
接下来是一个映射数组,这是一个与数组相关的基本算法。跟前面的规约函数一样可以处理数组中的每个数值,但是函数的返回值并不会被丢弃,而是重新构建一个函数。
function forEach(array,action){ for(var i = 0; i< array.length; i++){ action(array[i]); } } //映射数组 function map(func,array){ var result = []; forEach(array,function(element){ result.push(func(element)); }); return result; } alert(map(Math.round,[1.1,3.3,2.2]));
5.4其他函数技巧:在用高阶函数的时候,js操作符都不是函数,就像前面的例子,我们需要定义一个add函数,但每次这样编写并调用显然很烦躁,我们可以这样干:
var op = { "+":function(a,b){return a+b;}, "==":function(a,b){return a==b;}, "===":function(a,b){return a===b;}, "!":function(a){return !a;} /*等等,可以任意添加自己常用的操作*/ } //下面的方式来完成求和 reduce(op["+"],0,[1,2,3,4,5,6]);
备注:javascript常用函数
1.call() via
//call()函数的用法一 function Class1() { this.name = "class1"; this.showNam = function() { alert(this.name); } } function Class2() { this.name = "class2"; } var c1 = new Class1(); var c2 = new Class2(); //call函数使得c1中的方法能够在c2这个对象上执行 c1.showNam.call(c2); //result:class2 //call()函数的用法二 function Class1() { this.showTxt = function(txt) { alert(txt); } } function Class2() { //在Class2中调用Class1.call,就是把Class1中的对象覆盖当前对象,以此来完成继承。 Class1.call(this); } var c2 = new Class2(); c2.showTxt("cc");
2.与call()相对应的还有一个apply()方法。关于两者的差异请看:http://www.cnblogs.com/fighting_cp/archive/2010/09/20/1831844.html
后记:这一章的内容虽然总量不大,但牵扯到算法的比较多,还有一部分自己没理解好的就没写。自己不理解的,真是写不出来,以后开始每天看点,慢慢的补充上。