• 183 函数一等公民 /高阶函数 / js 闭包的深刻理解(实例分解:如何实现无限累加的一个函数)


    函数的地位:一等公民

    在编程语言中,一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量。

    例如,字符串在几乎所有编程语言中都是一等公民,字符串可以做为函数参数,字符串可以作为函数返回值,字符串也可以赋值给变量。

    对于 JavaScript 来说,函数可以赋值给变量,也可以作为函数参数,还可以作为函数返回值,因此 JavaScript 中函数是一等公民。

    在 JavaScript 语言中,函数可以保存到变量、作为参数传递、作为返回值返回。在JavaScript里函数可以出入任何场所,这一点在很多其他语言(比如 Java、C# 等)中是不太容易做到,正是因为如此,使用 JavaScript 可以非常轻松的实现高阶函数。

    什么是高阶函数?

      - 参数是函数,或者返回值是函数即为高阶函数

    高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。

    看图:此时fn和func就是高阶函数,函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最经典的就是作为回调函数。

    使用高阶函数的意义

      • 高阶函数是用来抽象通用的问题
      • 抽象可以帮我们屏蔽细节,只需要关注与我们的目标

     下面我们来看一个例子:实现一个add方法,执行add(1)(2)(3)(4),求传入参数的和。

    1. 耦合度高:具体实现求和的方法和组织参数冗余在了一起,并且在没没有传入参数时还要再执行一次
    function curring() {
      var total = 0;
      let params = [].slice.call(arguments)[0];
      total += params;
      return function fn() {
        let params = [].slice.call(arguments)[0];
        if (params) {
          total += params;
        }
        if (arguments.length) {
          return fn;
        }
        return total;
      };
    }
    let res = curring(1)(2)(3)();
    console.log(res);

    2. 方式1:高阶函数 -> 在没没有传入参数时还要再执行一次

    把求和的业务方法和组织入参的通用方法解耦

    // 业务方法
    function toolFn() {
      let arr = Array.from(arguments);
      return arr.reduce((p, c) => {
        return p + c;
      });
    }
    // 通用方法,收集参数
    function curring1(toolFn) {
      let params = [];
      return function fn(...args) {
        params.push(...args);
        if (args.length) {
          return fn;
        } else {
          return toolFn.apply(null, params);
        }
      };
    }
    let computed = curring1(toolFn);
    let res1 = computed(1)(2)(3)();
    console.log(res1, "方式1");

    3.方式2: 高阶函数 -> 业务方法入参个数对应curring执行次数,这样就不用像上面那样再多执行一次了

    // 业务方法
    function sum(x, y, z) {
      return x + y + z;
    }
    // 柯里化函数(轮子);组织入参
    function curring2(func) {
      let params = [];
      return function fn(...args) {
        params.push(...args);
        if (params.length < func.length) {
          return fn;
        } else {
          return func.call(null, ...params);
        }
      };
    }
    let add = curring2(sum);
    let res2 = add(1)(2)(3);
    console.log(res2, "方式2");

    4.方式3: 高阶函数 -> 更简洁和灵动的写法,对于每次执行的入参可以是多个,但与业务方法入参数的总个数要保持一致,我认为很nice

    const curry = (fn, ...args) =>
      // 函数的参数个数可以直接通过函数数的.length属性来访问
      args.length >= fn.length // 这个判断很关键!!!
        ? // 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
          fn(...args)
        : /**
           * 传入的参数小于原始函数fn的参数个数时
           * 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数
           */
          (..._args) => curry(fn, ...args, ..._args);
    
    function total(x, y, z) {
      return x + y + z;
    }
    const addFn = curry(total);
    console.log(addFn(1, 2, 3, 4), "方式3");
    console.log(addFn(1)(2)(3), "方式3");
    console.log(addFn(1, 2)(3), "方式3");
    console.log(addFn(1)(2, 3), "方式3");

    上面我们写的要么是需要多执行一次,要么是执行次数是要与业务方法的入参是一致的,下面我们实现一个无限执行累加求和的函数

    js 闭包的深刻理解(实例分解:如何实现无限累加的一个函数)

    js闭包,递归函数,es6结构解析,js高阶函数,拓展运算符等。

    题目:实现一个无限累加的js函数

    add(1)(2)(3) // 6
    add(1, 2, 3)//6 add(2, 3)(2)//7 add(1)(2)(3)(4) //10 add(2)(4, 1)(2) //9

    首先,胖子也是一口一口吃出来的,我们一步步拆解

      // add(1)
      function add(a) {
        return a;
      }
    
      // add(1)(2)
      function add(a) {
        return (b) => {
          a = a + b;
          return a;
        };
      }
      // add(1)(2)(3)
      function add(a) {
        return (b) => {
          a = a + b;
          return (c) => {
            a = a + c;
            return a;
          };
        };
      }

    如果分开来看,我们都可以单独实现他,都很简单,那我们怎么一个函数来实现呢?

    那我们来分析一下,分析下上面的三个函数实现,是否得出一些规律呢,add函数后面每增加一个“(num)”,实现中都会多一层“ return function(){} ”。

      function add(a) {
        var num = (b) => {
          a = a + b;
          return num;
        };
        return num;
      }

    看是不是和上面的执行规律一致了,每次调用都会返回一个函数来接受下次的传值,这样你会发现,每次返回的都是函数,可是我想要返回的是值,而不是无限的返回函数。
    那我们就来尝试来解决一下

      function add(a) {
        var num = (b) => {
          if (b) {.  // 当后面传入有参数时,继续返回函数
            a = a + b;
            return num;
          } else {   // //当后面出入无参数时,返回值
            return a;
          }
        };
        return num;
      }
    
      console.log(add(1)(2)(3)()); //6

    显然:我们期望的是add(1)(2)(3)直接就能得到值了,而不需要再去调用一次。

    我们这样改造:

      function add2(a) {
        var num = (b) => {
          a = a + b;
          return num;
        };
        num.toString = function () {
          console.log("我自动被触发了?结果是:", a);
          return a;
        };
        num.xxx = function () {
          return a;
        };
        return num;
      }
    
      console.log(add2(1)(2)(3)); // 是num函数,需要手动调用改写的toString()方法
      console.log(add2(1)(2)(3).toString()); // 6
      console.log(add2(1)(2)(3).xxx()); // 6 -> 既然是手动触发,那么可以调用num函数自定义的xxx属性,返回结果。之所以写toString()是alert会自动调用
      //其实console.log()也能触发toString方法,但是返回不出去,不知道为啥?
      alert(add2(1)(2)(3)); // 6 -> alert会自动调用toString()方法,进而触发了改写的num函数的toString方法

    传送门:alert和console.log的区别

    小示例说明:

        var f = function(name) {
            var age = 18;
            f.toString = function() {
                return `${name} : ${age}`
            }
            return f
        }
    
        alert(f('张三'))
        console.log(f('李四'));

     不定参,多参写法:

      function sum(...a) {
        const s = (...b) => sum(...[...a, ...b]); //参数合并
    
        let result = a.reduce((x, y) => {
          //当前阶段求值
          return x + y;
        });
        s.toString = function () {
          // 虽说console.log不能直接拿到返回出去的result,但是确实在console的时候确实
          // 自动执行了toString方法,所以我们在这里得到了result,赋值给别的变量也同样算是
          // 拿到了求和的结果
          console.log("自动调用了??", result);
          //函数出口
          return result;
        };
    
        return s;
      }
    
      console.log(sum(3, 10)(1)(2)(10)); // 输入函数s
      alert(sum(3, 10)(1)(2)(10, 1, 3, 8, 2)); //直接输出40
      let r = sum(3, 10)(1)(2)(10).toString();
      console.log(r, "手动调用toString方法"); // 26

     方法与主函数解耦:

    (1)执行一个run方法后得到计算结果:

    function curring3(fn) {
        // var total = 0;
        var arr = []
        var innerFn = (...args) => {
            arr = [...args]
            return innerFn.bind(this, ...arr)
        }
    
        innerFn.run = function() {
            return fn.call(this, ...arr)
        }
        return innerFn
    }
    
    function sum3(...args) {
        return args.reduce((x, y) => (x + y))
    }
    
    var res3 = curring3(sum3)
    res3(1, 2, 3, 4, 5)(6)(1)(2, 3)
    console.log(res3.run());

     (2)借用alert自动调用了toString()方法

        function curring3(fn) {
            let result = 0;
            var innerFn = (...args) => {
                let f = (..._args) => {
                    result = fn(...args, ..._args)
                    return innerFn(...args, ..._args)
                }
                f.toString = function() {
                    return result
                }
                return f
            }
            return innerFn
        }
    
        function sum3(...args) {
            return args.reduce((x, y) => (x + y))
        }
    
        var res3 = curring3(sum3)
        let result = res3(1, 2, 3, 4, 5)(6)(1)(2)(3, 3)
        alert(result);
        console.log(result.toString());
  • 相关阅读:
    JavaScript 基础知识 4
    JavaScript 基础知识 5 函数
    JavaScript 基础知识 3
    JavaScript 基础知识 2
    JavaScript 基础知识 1
    JavaScript 一
    HTML <a>等元素
    HTML CSS
    HTML <head> 元素
    HTML <meta> 标签
  • 原文地址:https://www.cnblogs.com/haoqiyouyu/p/16160976.html
Copyright © 2020-2023  润新知