zoukankan      html  css  js  c++  java
  • 高阶函数

    1、什么样的函数是高阶函数?

      1)一个函数的参数是另一个函数(回调)

      2)一个函数返回另一个函数(拆分函数)

      如 function a(){return function(){}}

    2、常见的高阶函数:

      1)before:我们经常会遇到这种需求,就是一个核心功能上面需要衍生出一些个性化功能,这时候,我们需要先将核心功能抽离出来,在外面再增加方法

        解决方法:我们可以重写原型上的方法,加以扩展:

    如我们现在有一个核心方法,toSay
    function toSay(...args){
        console.log("say sth...",args)
    }
    现在我们可以在toSay的原型上增加一个before方法,这样子所有的toSay方法也都可以调用
    Function.prototype.before = function(beforeFn){
        // 1、箭头函数中,没有this指向,所以会向上级作用于查找,这里的this就是调用before方法的对象。
        // 2、箭头函数中,没有arguments参数,所以我们需要再return方法的时候,将参数获取,然后再箭头函数中传递使用。
        return (...args)=>{
            beforeFn();
            this(...args);
        }
    }
    let newSay = toSay.before(()=>{
        console.log("你好")
    })
    newSay("111") 
    // 你好
    // 说话,[111]

      上面这种将核心方法抽离出来,再在核心功能基础上封装新的方法的行为,可以理解为切片或者装饰。

      2)react中的事物:即事物的改变,可以在前面和后面同时增加方法。

    const perform=(anyFn,wrappers)=>{
        wrappers.forEach(fn=>{
            fn.init();
        });
         anyFn();
        wrappers.forEach(fn=>{
            fn.finish();
        });
    }
    perform(()=>{
        console.log("说话")
    },[
        {
            init:()=>{console.log("wrapper1...init")},
            finish:()=>{console.log("wrapper1...finish")}
        },
        {
            init:()=>{console.log("wrapper2...init")},
            finish:()=>{console.log("wrapper2...finish")}
        }
       ]
    )

      上面的例子中,perform函数接收两个参数,一个是需要执行的主体函数,一个是包裹的事物,在主体函数执行前,执行事物中的init方法,主体函数执行后,执行事物的finish方法。

      3)柯里化:将一个函数拆成多个函数

      比如说,我们现在需要判断一个对象的类型,最常用的方式是什么呢?

      首先想到的就是函数的toString()方法

      Object.prototype.toString().call(obj)

      那么如何自定义实现判断某对象是否是某种类型的函数呢?

      有了上面的toString方法,我们马上就能想到下面这种方法:

    const checkType = (obj, type)=>{
        return Object.prototype.toString().call(obj)==`[object ${type}]`  // 注意这里的`` 必须是反单引号(英文状态下键盘上1旁边的符号)
    }
    checkType('111','String')  // true

      上面这个函数实现了我们需要的功能,但是需要用户传入对象和对象类型,我们经常可能会输入错误的类型,如String类型,可能会被输入string,而我们还会觉得 是不是程序出问题了呢?

      所以,我们能不能将上面的方法优化一下?不用用户输入对象类型,只用告诉用户调用对应函数名称,就可以知道知道对象是不是制定的类型呢?

      我们将上面的函数修改一下,checkType1接收一个参数,type,然后返回一个函数,这个函数中,接收用户需要判断类型的对象。

      然后用户只需要调用对应的isString或者是isNumber即可判断对应的数据类型。如下:

    const checkType1 = (type)=>{
      // 这里还涉及到了闭包的知识,即当前函数返回值,可以在任意作用域执行
    return (obj)=>{ return Object.prototype.toString.call(obj)==`[object ${type}]` } } const isString = checkType1("String") const isNumber = checkType1("Number") isString("111") isNumber(111)

      上面的方法看起来比前面的要好了一点,用户调用相对方便了一点点,但是我们的程序写的就相对啰嗦了,如果有很多种类型需要判断的话,意味着我们需要多次调用checkType1方法,传入不同的类型参数,并定义多个不同的变量去接收。

      所以我们继续对上面的方法进一步优化:

    const checkType1 = (type)=>{
        return (obj)=>{
             return Object.prototype.toString.call(obj)==`[object ${type}]`
        }
    }
    
    let types = ["isString","isNumber","isBoolean"]
    let utils = {};
    types.forEach(t=>{
        utils["is"+t] = checkType1(t);
    });
    
    // 用户在调用时,使用utils.isString('111')这种方式即可

      上面的方法种,我们在前面方法的基础上,做了一点改变,我们将所有类型通过一个数组储存起来,然后通过循环这个数组,分别给每个类型调用checkType1方法进行校验,并将校验的返回结果通过对象utils存储起来

      后面用户想要判断某种对象的时候,只需要使用utils.isType即可。

      这种做法的好处在于,不用再去分别手写对每种类型的判断,只需要将类型添加到数组中即可。

      例如,现在有如下代码:

    const add = (a,b,c,d)=>{
        return a+b+c+d
    }
    add(1,2,3,4)
    // 现在不想一起把参数传递完毕,希望分多次传递参数,比如,我们希望 add(1)(2)(3,4) 这样子传参执行,要如何实现?
    // 这是一个典型的柯里化应用
    const curring = (fn,arr=[])=>{
      // fn 的参数个数,fn.length
      const len = fn.length;
      return (...args)=>{
        // 这里的args就是每次传递的参数,用arr存储起来
        arr = arr.concat(args);
        // 如果传递的参数个数和计算函数的参数个数相等,则开始计算,否则继续调用curring函数,收集参数。
        if(len==arr.length){
          return fn(...arr);
        }else{
          return curring(fn, arr);
        }
      }
    }
    let newAdd = curring(add);
    newAdd(1)(2)(3,4)

     4)after函数:在做完一件事情后,执行某个函数。如调用一个函数3次后,通知另一个函数执行

    1) 先定义一个希望三次后执行的函数
    const fn= ()=>{
        console.log("三次后执行我...")
    }
    
    2) 定义一个函数,接受两个参数,一个times,一个需要执行的函数
    const after= (times,fn)=>{
        return ()=>{
            if(--times==0){
                fn()
            }
        }
    }
    
    let test = after(3, fn)
    test();
    test();
    test()'// 执行fn()

    3、高阶函数的应用

    前面列举了好几种高阶函数,具体有哪些情况下会使用高阶函数呢?

    如我们常见的并发问题,发布订阅,还有常说的观察者模式,都是使用的高阶函数。

    1)并发问题:如我们需要同时去读取两个文件内容,并在两个文件读取完毕后,将读取的内容打印出来。这里使用node的fs模块

    最丑的代码是这样的
    const fs = require("fs");
    let info = {};
    fs.readFile("name.txt","utf8",(err,data)=>{
        if(err){
            console.log(err);
            return;
        }
        info["name"] = data;
        fs.readFile("age.txt","utf8",(err,data)=>{
            if(err){
                console.log(err);
                return;
            }
            info["age"] = data;
        }
        console.log(info)
    }
    上面代码,如果我们有多个文件需要读取的话,需要像金字塔一样,堆很远...这显然不是我们想要的结果,那么怎么样写的比较优雅一点呢?
    
    上面我们讲了after函数,我们在读取完两个文件后,输出文件内容,和after函数的定义是不是很像?其实这种写法也很繁琐,但是不会像上面的方法一样一层层的堆叠了
    const after = (times, fn)=>{
        return ()=>{
            if(--times==0){
                fn()
            }
        }
    }
    const out=after(2,()=>{
        console.log(info)
    })
    
    fs.readFile("name.txt","utf8",(err, data)=>{
        if(err){
            console.log(err);
            return;
        }
        info["name"] = data;
        out();
    })
    fs.readFile("age.txt","utf8",(err, data)=>{
        if(err){
            console.log(err);
            return;
        }
        info["age"] = data;
        out() 
    })
     
  • 相关阅读:
    Django model 常用方法记录
    程序员的注意事项
    硬件天使的使用
    你是否应该成为一名全栈工程师?
    web技术
    6个处理上面代码异味的重构方法(手法)
    git 命定
    ie console报错
    apache 省略index.php访问
    myisam和innodb的区别
  • 原文地址:https://www.cnblogs.com/fiona-zhong/p/11401614.html
Copyright © 2011-2022 走看看