18-3 函数一等公民 /高阶函数 / 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());

 

posted @ 2022-04-18 18:09  猎奇游渔  阅读(165)  评论(0编辑  收藏  举报