【JS】函数柯里化(currying)及reduce使用

柯里化
定义

是一种编程思想,把接收多个参数的函数变换成接收单一参数的函数,嵌套返回直到所有参数都被使用并返回最终结果,即将函数从调用方式:f(a,b,c)变换成调用方式:f(a)(b)(c)的过程,柯里化不会调用函数,只是对函数进行转换

特性

参数复用:公共的参数已经通过柯化预置了。

延迟执行:柯里化时只是返回一个预置参数的新函数,并没有立刻执行,实际上在满足条件后才会执行

好处较复杂的场景中,有简洁代码,可读性高的优点
应用具体如下目录

目录

柯里化思想实现

柯里化计算——使用全局变量

柯里化计算——使用闭包

柯里化计算——类型判断生成器函数

柯里化实际应用——axios中固定参数

展开嵌套数组

展开多层嵌套数组

求数组中,值的总和

求对象数组中,值的总和

统计对象中,值出现的次数

按照属性值,对对象进行分组

使用展开语法,和initialValue连接,包含在对象数组中的数组

数组去重,可以用Array.from(new Set(arr))更高效,reduce实现

按顺序执行promise

函数组合实现管道

稀疏数组中,会跳过缺失元素,但不会跳过undefined


柯里化思想实现

// 柯里化函数
function carry() {
    // 获取第一个参数,即函数
    // arguments为类数组对象,即有length属性的对象
    let fn = arguments[0];
    // 获取剩余参数,即将类数组对象转为数组;
    // 稀疏数组使用slice,返回也是稀疏数组
    let args = Array.prototype.slice.call(arguments, 1);
    // 判断参数是否已经全部传入
    // fn.length 获取函数的参数长度
    if (args.length === fn.length) {
        // 如果已经全部传入参数,则直接执行函数
        return fn.apply(this, args)
    }

    // 返回一个函数,该函数接收剩余参数
    function _curry() {
        // 将传入的参数与之前传入的参数合并
        args.push(...arguments);
        // 判断参数是否已经全部传入
        if (args.length === fn.length) {
            return fn.apply(this, args)
        }
        // 递归调用 curry 函数,继续接收参数
        return _curry
    }
    return _curry
}

function add(a, b, c) {
    return a + b + c
}

console.log(carry(add, 1, 2, 3))  // 6 
console.log(carry(add, 1)(2, 3))  // 6
console.log(carry(add, 1)(2)(3))  // 6
console.log(carry(add)(1, 2, 3))  // 6

一道很经典的面试题:

// add(1)(2)(3)
// add(1, 2, 3)(4)
// add(1)(2)(3)(4)(5)
// 根据以上代码,实现一个add函数,要求使用函数柯里化
function add() {
    // 将传入的所有参数转换为数组
    let args = [...arguments]

    // 定义一个函数,用于接收新的参数,并返回一个新的函数
    function calculator() {
        // 将传入的新参数转换为数组
        args.push(...arguments)
        return calculator
    }

    // 在calculator函数中定义toString方法,用于返回计算结果
    calculator.toString = function () {
        // 将数组中的所有参数相加并返回结果
        return args.reduce((a, b) => a + b)
    }
    // 返回calculator函数,以便可以继续调用add函数进行计算
    return calculator
}

console.log(add(1)(2)(3).toString())
console.log(add(1, 2, 3)(4).toString())
console.log(add(1)(2)(3)(4)(5).toString())

 例如:

// 调整函数 sum
function sum(num1, num2) {
  return num1 + num2
}
​
// 改写为 可以实现如下效果
console.log(sum(1)(2))

变成:

function sum(num1) {
  return function (num2) {
    return num1 + num2
  }
}

比如:

function sum(a, b, c, d, e) {
  return a + b + c + d + e
}
// 改写函数sum实现:参数传递到5个即可实现累加
// sum(1)(2)(3)(4)(5)
// sum(1)(2,3)(4)(5)
// sum(1)(2,3,4)(5)
// sum(1)(2,3)(4,5)

柯里化计算——使用全局变量

  • 接收不定长参数
  • 存储已传递的参数
  • 判断长度:满足5,即累加;不满足5,继续返回函数本身
  • 返回新的函数,根据已经记录的参数长度判断,继续调用函数本身,还是累加
let nums = []
function currySum(...args) {
  nums.push(...args)
  if (nums.length >= 5) {
    return nums.reduce((prev, curv) => prev + curv, 0)
  } else {
    return currySum
  }
}

柯里化计算——使用闭包

function sumMaker(length) {
  let nums = []
  function inner(...args) {
    nums.push(...args)
    if (nums.length >= length) {
      return nums.reduce((prev, curv) => prev + curv, 0)
    } else {
      return inner
    }
  }
  return inner
}

// 支持5个累加
const sum5 = sumMaker(5)
// 支持7个累加
const sum7 = sumMaker(7)
sum7(1,2,3)(4,5,6,7)

柯里化计算——类型判断生成器函数

  • 定义函数,接受需要判断的类型名;
  • 内部返回一个新的函数;
  • 新函数接受需要判断的具体的值;
  • 新函数内部根据外层函数传入的类型,以及传入的值进行判断并返回结果
// 有如下4个函数
function isUndefined(thing) {
  return typeof thing === 'undefined'
}
function isNumber(thing) {
  return typeof thing === 'number'
}
function isString(thing) {
  return typeof thing === 'string'
}
function isFunction(thing) {
  return typeof thing === 'function'
}
​
// 改为通过 typeOfTest 生成:
const typeOfTest = (type) => (thing) => typeof thing === type

const isUndefined = typeOfTest('undefined')
const isNumber = typeOfTest('number')
const isString = typeOfTest('string')
const isFunction = typeOfTest('function')
​
// 可以通过 isUndefined,isNumber,isString,isFunction 来判断类型:
​
isUndefined(undefined) // true
isNumber('123') // false
isString('memeda') // true
isFunction(() => { }) // true

柯里化实际应用——axios中固定参数


 // 项目开发中不少请求的 请求方法 是相同的,比如
axios({
  url: 'url',
  method: 'get'
})
axios({
  url: 'url',
  method: 'get',
  params: {
    // 
  }
})
axios({
  url: 'url',
  method: 'post',
  data: ''
})
axios({
  url: 'url',
  method: 'post',
  data: '',
  headers: {
​
  }
})
​
// 固定请求参数,请求方法固定,其他参数从外部传递进来
// 需求: 实现方法requestWithMethod 支持如下调用
requestWithMethod('get')({
  url: '',
  params: {},
  headers: {}
})
requestWithMethod('post')({
  url: '',
  headers: {},
  data: {}
})

实现如下:

function requestWithMethod(method) {
  return (config) => {
    return axios({
      method,
      ...config
    })
  }
}

展开嵌套数组

const flattened = [
  [0, 1],
  [2, 3],
  [4, 5],
].reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
// flattened 的值是 [0, 1, 2, 3, 4, 5]

展开多层嵌套数组

const  flattenArray = (arr) => {
  return arr.reduce((acc, val) => {
    if (Array.isArray(val)) {
      // 如果当前值是数组,则递归调用flattenArray
      return acc.concat(flattenArray(val));
    }
    // 否则,将当前值加入结果数组
    return acc.concat(val);
  }, []);
}

// 使用示例
const nestedArray = [1, [2, [3, [4]], 5]];
const flatArray = flattenArray(nestedArray);
console.log(flatArray); // 输出: [1, 2, 3, 4, 5]

求数组中,值的总和

const objects = [1, 2, 3]
const sum = objects.reduce((accumulator, currentValue) => accumulator + currentValue, 0)
console.log(sum) // 6

求对象数组中,值的总和

const objects = [{ x: 1 }, { x: 2 }, { x: 3 }];
const sum = objects.reduce(
  (accumulator, currentValue) => accumulator + currentValue.x,
  0,
);
console.log(sum); // 6

统计对象中,值出现的次数

const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];

const countedNames = names.reduce((allNames, name) => {
  const currCount = allNames[name] ?? 0;
  return {
    ...allNames,
    [name]: currCount + 1,
  };
}, {});
// countedNames 的值是:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

按照属性值,对对象进行分组

const people = [
  { name: "Alice", age: 21 },
  { name: "Max", age: 20 },
  { name: "Jane", age: 20 },
];

function groupBy(objectArray, property) {
  return objectArray.reduce((acc, obj) => {
    const key = obj[property];
    const curGroup = acc[key] ?? [];

    return { ...acc, [key]: [...curGroup, obj] };
  }, {});
}

const groupedPeople = groupBy(people, "age");
console.log(groupedPeople);
// {
//   20: [
//     { name: 'Max', age: 20 },
//     { name: 'Jane', age: 20 }
//   ],
//   21: [{ name: 'Alice', age: 21 }]
// }

使用展开语法,和initialValue连接,包含在对象数组中的数组

const friends = [
  {
    name: "Anna",
    books: ["Bible", "Harry Potter"],
    age: 21,
  },
  {
    name: "Bob",
    books: ["War and peace", "Romeo and Juliet"],
    age: 26,
  },
  {
    name: "Alice",
    books: ["The Lord of the Rings", "The Shining"],
    age: 18,
  },
];

// allbooks——列表,其中包含所有朋友的书籍和 initialValue 中包含的附加列表
const allbooks = friends.reduce(
  (accumulator, currentValue) => [...accumulator, ...currentValue.books],
  ["Alphabet"],
);
console.log(allbooks);
// [
//   'Alphabet', 'Bible', 'Harry Potter', 'War and peace',
//   'Romeo and Juliet', 'The Lord of the Rings',
//   'The Shining'
// ]

数组去重,可以用Array.from(new Set(arr))更高效,reduce实现

const myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const myArrayWithNoDuplicates = myArray.reduce((accumulator, currentValue) => {
  if (!accumulator.includes(currentValue)) {
    return [...accumulator, currentValue];
  }
  return accumulator;
}, []);

console.log(myArrayWithNoDuplicates);

按顺序执行promise

/**
 * 链接一系列 Promise 处理程序。
 *
 * @param {array} arr——一个 Promise 处理程序列表,每个处理程序接收前一个处理程序解决的结果并返回另一个 Promise。
 * @param {*} input——开始调用 Promise 链的初始值
 * @return {Object}——由一系列 Promise 链接而成的 Promise
 */
function runPromiseInSequence(arr, input) {
  return arr.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(input),
  );
}

// Promise 函数 1
function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5);
  });
}

// Promise 函数 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// 函数 3——将由 `.then()` 包装在已解决的 Promise 中
function f3(a) {
  return a * 3;
}

// Promise 函数 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4);
  });
}

const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10).then(console.log); // 1200

函数组合实现管道

// 组合使用的构建块
const double = (x) => 2 * x;
const triple = (x) => 3 * x;
const quadruple = (x) => 4 * x;

// 函数组合,实现管道功能
const pipe =
  (...functions) =>
  (initialValue) =>
    functions.reduce((acc, fn) => fn(acc), initialValue);

// 组合的函数,实现特定值的乘法
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);

// 用例
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240

稀疏数组中,会跳过缺失元素,但不会跳过undefined

console.log([1, 2, , 4].reduce((a, b) => a + b)); // 7
console.log([1, 2, undefined, 4].reduce((a, b) => a + b)); // NaN
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值