美文网首页JavaScript
函数式编程入门系列一

函数式编程入门系列一

作者: Eastboat | 来源:发表于2019-11-20 00:27 被阅读0次

    快速掌握一个知识体系的秘诀就是:抓住概念并理清概念之间的关系

    函数

    f(x) = Y;
    //函数f以x为参数,返回输出Y
    
    /*
        1.函数必须总是接受一个参数
        2.函数必须总是返回一个值
        3.函数应该依据接收到的参数,例如x,而不是外部环信运行
        4.对于一个给定的参数x,只会输出唯一的y
    */
    

    函数是一段可以通过其名称被调用的代码,他可以传递参数并返回值

    var simple = (a) => {retrun a}
    

    方法是一段必须通过其名称及其关联对象的名称被调用的代码

    var obj = { simple:(a)=>{retrun a}};
    
    obj.ssimple(5) //用名称及其关联对象调用
    

    引用透明性

    所有的函数对于相同的输入都将返回相同的值,这一属性称之为引用透明性

    //函数只是根据传入的参数i进行操作,函数内部并没有全局引入
    
    //var i=1;
    var identity = i => {
      return i;
    };
    

    替换模型

    sum(3, 4) + identity(5);
    // 直接替换函数的结果,主要因为identity函数的内部逻辑不依赖于其他全局变量
    
    sum(3, 4) + 5;
    

    引用透明性在并发代码和可缓存代码中发挥着至关重要的作用

    命令式,声明式与抽象

    函数式编程主张声明式编程和编写抽象的代码

    命令式语句

    //任务: 打印数组的元素
    //过程:我们告诉编译器,获取数组长度,循环数组,用下标索引获取每一个元素
    //总结: 命令式编程主张告诉编译器'如何做'
    var arr=[1,2,3,4,5]
    for (var i=0;i<arr.length;i++){
      console.log(i)
    }
    

    声明式语句

    //命令式编程主张告诉编译器'做什么
    var arr=[1,2,3,4,5];
    arr.forEach((v,i,a)=>console.log(v)) //打印1,2,3,4,5
    //使用了一个处理"如何做"的抽象函数,所以开发者只需要关系手头的任务就可以了
    

    纯函数

    对给定的输入返回相同的输出的函数

    var double = (value) => value*2; 
    //输入2总是返回4,纯函数遵守引用透明性,所以我们可以将用数字4替换 double(2)
    

    纯函数产生可测试的代码

    纯函数不应该依赖任何外部变量,也不应该改变任何外部变量
    

    并发代码

    纯函数总是允许我们并发的执行代码,因为纯函数不会改变他的环境

    js没有多线程来执行并发,如果有一段node环境中的服务端代码需要并发执行函数,又该怎么办?

    let golbal = 10;
    let func1 = (input) => {
        global = 5
    }
    let func2 = () => {
        if (golbal === 10) {
            console.log("业务逻辑")
        }
    }
    

    如果并发执行,func1在func2之前就执行,由于两个函数都依赖于全局变量,并发执行这些函数就会引起不良的影响

    //改为纯函数
    
    let func1 = (input,global) => {
        //处理输入,改变global值
        global = 5
    }
    let func2 = (global) => {
        if (golbal === 10) {
            console.log("业务逻辑")
        }
    }
    
    

    可缓存

    纯函数总是为给定的输入返回相同的输出,为什么要通过多次的输入来反复调用此函数呢?不能用函数的上一个结果代替函数调用吗?

    var longRunningFunction=(ip)=>{
        //耗时任务的函数,给定什么返回什么
    }
    var longRunningFnBookKeeper={2:3,4:5}
    
    //检查key是否在记账对象中
    //如果在返回结果,否则更新记账对象
    
    longRunningFnBookKeeper.hasOwnProperty(ip) ?
    longRunningFnBookKeeper[ip] :
    longRunningFnBookKeeper[ip]=longRunningFunction(ip)
    
    

    管道与组合

    UNIX哲学,用组合或管道完成复杂的任务

    纯函数是数学函数

    js函数基础

    1. return语句是可选的
    2. 作用域
    3. 函数参数
    4. export和import
      函数式的方法处理循环问题
    const forEach = (array, fn) => {
        let i;
        for (i = 0; i < array.length; i++) {
            fn(array[i])
        }
    }
    const arr=['a','b','v','e','r'];
    forEach(arr,(data)=>console.log(data))
    

    高阶函数

    接受另一个函数作为其参数的函数,称之为高阶函数(HOC - Higher-Order Function)

    理解数据

    当一门语言允许函数作为任何其他数据类型使用时,函数被称为一等公民

    函数可以赋值给变量,作为参数传递,也可以被其他函数返回

    js数据类型

    Number,String,Boolean,Undefined,Null,Object,Symbol

    存储函数

    let fn = () => {}  //fn就是一个指向函数数据类型的变量
    
    typeof fn // 'function'
    
    fn()  //既然fn是函数的引用就可以这么调用
    

    传递函数

    var tellType = (arg) =>{
      console.log(typeof arg);
    }
    
    let data=1;
    tellType(data)  //number
    
    let dataFn=()=>{
      console.log('function')
    }
    tellType(dataFn) // function
    
    //如果传入的参数是函数就执行
    
    var tellType = (arg) =>{
      if(typeof arg === 'function'){
        arg()
      }else{
        console.log(typeof arg);
      }
    }
    

    返回函数

    let crazy = () => { return String }
    console.log(crazy()) //[Function: String]  只是返回了一个指向String函数的函数引用
    
    let string=crazy();
    console.log(string(123)) //'123'
    

    抽象和高阶函数

    高阶函数是接受函数作为参数,或者返回函数作为输出的函数

    此函数抽象出了遍历数组的问题,使用forEach函数的用户就不用理解内部是如何实现遍历的

    const forEach = (array, fn) => {
        let i;
        for (i = 0; i < array.length; i++) {
            fn(array[i])
        }
    }
    const arr=['a','b','v','e','r'];
    forEach(arr,(data)=>console.log(data))
    

    遍历一个js对象步骤:
    1.遍历给定对象的所有key
    2.识别key是否属于该对象本身
    3.如果步骤2为true,则获取key的值

    //forEach,forEachObject都是高阶函数,使开发者专注于任务
    //通过传递相应的函数,而抽象出遍历的部分
    const forEachObject = (obj, fn) => {
        for (var property in obj) {
            if (obj.hasOwnProperty(property)) {
                fn(property, obj[property]) //以key和value作为参数调用
            }
        }
    }
    
    let person={name:'cc',age:18};
    forEachObject(person,(k,v)=>console.log(v,k))
    

    以抽象的方式实现对控制流程的处理

    const forEach = (array, fn) => {
        let i;
        for (i = 0; i < array.length; i++) {
            fn(array[i])
        }
    }
    const unless=(predicate,fn)=>{
        if(!predicate) fn()
    }
    
    forEach([1,2,3,4,5,6,7],(number)=>{
        unless((number%2),()=>{
            console.log(number,'is even')
        })
    })
    /*
    2 'is even'
    4 'is even'
    6 'is even'
    */
    

    times高阶函数:接受一个数字,并根据调用者提供的次数调用传入的函数

    const times=(times,fn)=>{
        for(var i=0;i<times;i++){
            fn(i)
        }
    }
    const unless=(predicate,fn)=>{
        if(!predicate) fn()
    }
    times(100,function(n){
        unless(n%2,function(){
            console.log(n,'is even')
        })
    })
    

    真实的高阶函数

    从简单的高阶函数逐步进入复杂的高阶函数

    every函数

    经常需要检查数字的内容是否为一个数字,自定义对象或者其他类型.
    通常要编写循环来解决,现在我们将这些抽象到一个every函数中,接受两个参数:一个数据和一个函数
    使用传入的函数检查数组的所有元素是否为true

    const every = (arr, fn) => {
        let result = true;
        for (let i = 0; i < arr.length; i++) {
            //fn需要返回一个布尔值,然后使用&&运算符确保所有的数组内容遵循fn给出的条件
            result = result && fn(arr[i])
        }
        return result;
    }
    // isNaN作为fn传入,检查给定的数字是否为NaN
    console.log(every([NaN,NaN,NaN],isNaN));// true 
    console.log(every([1,2,1,'1'],isNaN)) //false
    console.log(every([1,2,1],isNaN)) //false
    
    

    some函数

    
    const some=(arr,fn)=>{
        let result=false;
        for(const value of arr){
            result=result||fn(value)
        }
        return result
    }
    
    console.log(some([NaN,NaN,NaN],isNaN));// true 
    console.log(some([1,NaN,1,'1'],isNaN)) //true
    console.log(some([1,NaN,1],isNaN)) //true
    
    
    

    sort函数

    compareFunction为可选,如果未提供,元素将被转换为字符串并按照Unicode编码点顺序排序

    sort函数设计的非常灵活,以致于我们可以排序任何javascript数据,sort函数灵活的原因要归功于高级函数的本质

    
    var people = [{
        firstname: 'aaFirstName',
        lastname: "ccLastName",
    
    },{
        firstname: "ccLastName",
        lastname: "aaFirstName",
    },{
        firstname: "bbFirstName",
        lastname: "bbLastName",
    }]
    
    //compareFunction代入
    // people.sort((a, b) => {
    //     return (a.firstname < b.firstname) ? -1 : (a.firstname > b.firstname) ? 1 : 0
    // })
    
    //设计一个函数,不以函数为参数,单是会返回一个compareFunction函数
    const sortBy = (property) => {
        return (a, b) => {
            var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
            return result
        }
    }
    console.log(people.sort(sortBy('firstname')))
    /*
    [ { firstname: 'aaFirstName', lastname: 'ccLastName' },
      { firstname: 'bbFirstName', lastname: 'bbLastName' },
      { firstname: 'ccLastName', lastname: 'aaFirstName' } ]
    */
    console.log(people.sort(sortBy('lastname')))
    
    /*
    [ { firstname: 'ccLastName', lastname: 'aaFirstName' },
      { firstname: 'bbFirstName', lastname: 'bbLastName' },
      { firstname: 'aaFirstName', lastname: 'ccLastName' } ]
    */
    

    闭包和高阶函数

    闭包如此强大的原因就是在于他对作用域链的访问

     /*
           闭包就是一个内部函数,闭包有三个可以访问的作用域
              1.在它自身声明之内声明的变量
              2.对全局变量的访问
              3.对外部函数变量的访问
          */
    let global='global'
    function outer(){
      let outer='outer'
      function inner(){
          let a=5;
          console.log(5) 
          console.log(global) //全局变量的访问
          console.log(outer)  //可访问外部函数的变量
      } 
    
      inner()// 打印5  说明可访问自身声明之内声明的变量
    }
    
    
    
    
    

    上下文

    闭包可以记住自己的上下文

    let fn=(arg)=>{
      let outer='outer'
      let innerFn=()=>{
        console.log(outer);
        console.log(arg)
      }
      return innerFn
    }
    
    let resFn=fn(10);
    resFn() //打印outer,10
    

    1.调用fn函数时,js引擎将返回的innerFn视为一个闭包,并相应的设置了它的作用域.
    2.闭包有三个作用域层级,在返回innerFn时都被设置了,返回函数的引用保存在resFn中.所以通过作用域链被调用时就记住了arg和outer

    tab函数

    //tab函数接受一个value并返回一个包含value的闭包函数,该函数将被执行
    const tap = (value) =>
        (fn) => (
            typeof (fn) === 'function' && fn(value),
            console.log(value)
        )
    
    //(exp1,exp2)表示执行两个参数并返回第二个表达式的结果
    
    tap('fun')((data)=>{console.log('value is',data)})
    

    假设在遍历一个来自服务器的数组,并且发现数据错了,我们想调试一下,看看数据究竟包含了什么

    //抽象,高阶函数
    const forEach = (array, fn) => {
        let i;
        for (i = 0; i < array.length; i++) {
            fn(array[i])
        }
    }
    
    const tap = (value) =>
        (fn) => (
            typeof (fn) === 'function' && fn(value)
        )
    
    let resData=[1,2,3,'b'];
    forEach(resData,(item)=>{
        tap(item)(()=>{
            console.log(item)
        })
    })
    

    unary函数

    map实现数组加倍

    
    var arr=[1,2,3,4];
    const res=arr.map((v,i,a)=>{
        return v*v
    })
    
    console.log(res)
    

    假设我们要把字符串数组解析为整数数组.(以下做法不成功)
    parseInt接受两个参数 string和radix, map的index值会作为值radix传给parseInt

    ['2','3','5'].map(parseInt)
    // [2, NaN, NaN]
    

    所以我们需要实现unary函数,他的任务就是接受一个给定的多参数函数,并把它转换为一个只接受一个参数的函数

    const unary = (fn) =>
        fn.length === 1 ? fn : (arg) => fn(arg)
    
    
    var strArr=['1','2','3'];
    var data=strArr.map(unary(parseInt));
    console.log(data) //[1,2,3]
    

    以下是特别的高阶函数,开发者控制函数被调用的次数

    once函数

    有时候只想运行一次给定的函数,比如初始化第一次支付,或者发起第一次银行请求等

    const once = (fn) => {
        let done = false;
        //返回的函数会形成一个覆盖他的闭包作用域,所以返回的函数会访问并检查done是否为true
        return function () {
            return done ? undefined : (
                //done为ture就阻止下一次执行
                (done = true), fn.apply(this, arguments)
            )
        }
    }
    
    var func=once(()=>{
        console.log("我只执行这一次")
    })
    func()
    func() //undefined
    
    

    memoized函数

    我们知道纯函数只依赖它的参数运行,不依赖外部环境,纯函数结果完全依赖它的参数

    // 计算给定数字的阶乘
    var factorial = n => {
      if (n === 0) {
        return 1;
      }
      //递归
      return n * factorial(n - 1);
    };
    
    console.log(factorial(3)) //3*2*1=6
    

    问题出现:为什么不能为每一个输入存储结果呢?为了计算3的阶乘,就需要计算2的阶乘,为什么不能重用函数中的计算结果呢?

    
    const memozied = fn => {
      const lookupTable = {}; //存在返回函数的闭包上下文中
      //接受一个参数并检查是否存在lookupTable中
      //如果在,返回对应的值,否则使用心得输入作为key
      return arg => lookupTable[arg] || (lookupTable[arg] = fn(arg));
    };
    
    let fastFactorial = memozied(n => {
      if (n === 0) {
        return 1;
      }
      return n * fastFactorial(n - 1);
    });
    
    console.log(fastFactorial(5)); //120
    
    /*
        lookupTable:{
            0:1,
            1:1,
            2:2,
            3:6,
            4:24,
            5:120
        }
     */
    console.log(fastFactorial(3)); //6  直接从上面的lookupTable中返回
    
    
    

    相关文章

      网友评论

        本文标题:函数式编程入门系列一

        本文链接:https://www.haomeiwen.com/subject/cmdnictx.html