• 《JavaScript编程精解》读书笔记第五章:函数式编程


    5.1抽象:归根结底程序是要解决生活中的问题,但多数时候现实中的问题总是很复杂,而尽量降低程序复杂程度的办法就是进行抽象化处理。把许多实际的复杂关系抽象成更简单的逻辑运用到程序当中。这是我对编程中抽象的理解。函数式编程就是通过巧妙的函数组合来创建抽象。

    5.2高阶函数:简单点说高阶函数就是处理其他函数的函数,也就是函数的嵌套。js是面向函数的一门语言,在js的世界里任何东西都是值类型的,当然函数也不例外。它与其他语言(比如说C#)最明显的差异就是函数能够完全像值一样去生成,去传递。你可以把函数作为另一个函数的参数来使用,也可以在一个函数的内部再定义一个新的函数。当然你也许会想到C#中的lambda表达式,确实也能完成一些类似的功能,但js是纯动态的语言,处理函数是它的看家本领,C#中lambda,var的运用只是在一定程度上增加了动态性,灵活性上还是远不如js的。前面说的这些是我个人的一些理解,书中并没讲这些,书里面多是以实例的形式来展现函数编程的,下面我们用代码试试看(当然,代码也一定跟书上一样,有些是高仿的)。

    一个最简单的函数式编程的例子

    View Code
    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);

     在上面的例子中,函数就是值的特性一目了然。还有一个常用的高阶函数的类型就是修改函数,作用是修改了传入的函数的值,如下: 

    View Code
    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()函数,其实是一个算法的变体,而这个函数就是规约函数,下面是规约函数的一个例子:

    View Code
    //规约函数
    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的个数。

    View Code
    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,这样在很多情况下都可以判断跟加减运算一气呵成了,挺好。

    接下来是一个映射数组,这是一个与数组相关的基本算法。跟前面的规约函数一样可以处理数组中的每个数值,但是函数的返回值并不会被丢弃,而是重新构建一个函数。

    View Code
    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函数,但每次这样编写并调用显然很烦躁,我们可以这样干:

    View Code
    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

    View Code
    //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

    后记:这一章的内容虽然总量不大,但牵扯到算法的比较多,还有一部分自己没理解好的就没写。自己不理解的,真是写不出来,以后开始每天看点,慢慢的补充上。 

  • 相关阅读:
    《JAVA多线程编程核心技术》 笔记:第四章、Lock的使用
    服务器负载粗略估算
    spring事务传播性理解
    BlockingQueue 阻塞队列2
    六大原则
    mycat之schema.xml理解
    mycat分库读写分离原理
    sqlservere连接问题
    java代码添加mysql存储过程,触发器
    Amoeba+Mysql实现读写分离+java连接amoeba
  • 原文地址:https://www.cnblogs.com/zhangran/p/2788370.html
Copyright © 2020-2023  润新知