函数式中的循环和递归

198 阅读5分钟

一、关于函数式

这是前端非常重要的概念,参考函数式编程思想

二、循环和递归

循环函数

函数式中,如果需要循环处理某些逻辑,js对应的结构和方法,比如for循环,while循环,do-while循环,以及一些针对数据类型的遍历方法:for in,for of,数组原型上的遍历方法:forEach,map,reduce,every,some。

基本的循环结构

1. for 循环

最基本的循环结构,适用于已知迭代次数的情况。

for (let i = 0; i < array.length; i++) {
  console.log(array[i]);
}

2. while 循环

当迭代次数未知,需要在运行时决定是否继续循环时使用。

let i = 0;
while (i < array.length) {
  console.log(array[i]);
  i++;
}

3. do...while 循环

至少执行一次循环体,然后根据条件判断是否继续循环。

let i = 0;
do {
  console.log(array[i]);
  i++;
} while (i < array.length);

以上循环结构,和for in, for of都可以通过break提前退出循环

递归函数

执行一个函数,返回的是自身的调用,就会形成递归调用,因此便可以在函数体内执行需要循环的逻辑,使用递归需要设置结束递归的条件

// 最简单的递归
function fn() {
  return fn()
}
// 函数式里基于循环逻辑的递归
const fn = () => {
  let count = 0
  return function fn1() {
    // 需要循环的逻辑
    count ++
    // 循环结束条件
    if (count = 3) {
      console.log('结束调用')
    }
    return fn1()
  }
}

循环结构和递归都可以用来循环执行你希望的逻辑

三、循环和递归的应用实践

实现reduce函数

Array.prototype.reduce = function(callback,initialValue) {
  // 检查调用类型不能为空
  if(this == null) {
    throw new TypeError('Array.prototype.myReduce called on null or undefined');
  }
  const arr = Object(this)
  if (typeof callback !== 'function') {
    throw new TypeError(callbakc + 'is not a function')
  }
  const len = arr.length
  let accumulator
  let startIndex = 0
  // 处理初始值
  if (arguments.length >= 2) {
    accumulator = initialValue
  } else {
    accumulator = arr[startIndex]
  }
  // 遍历数组循环执行callback, 并将callback的结果给下一次执行callback的累加器的结果
  for (let i = startIndex; i < len; i ++) {
    if (arr.hasOwnProperty(i)) {
      accumulator = callback(accumulator,arr[i],i,arr)
    }
  }
  return accumulator
}
// 测试
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.myReduce((acc, curr) => acc + curr, 0);
console.log(sum); // 输出: 15

const product = numbers.myReduce((acc, curr) => acc * curr);
console.log(product); // 输出: 120

实现compose

compose 用于将多个函数组合成一个单一的函数,它的参数是多个函数。主要思想是从右到左依次应用这些函数,即最右边的函数最先执行,然后将结果传递给下一个函数,依此类推。

const composedFunction = compose(f, g, h);
// 等价于
const composedFunction = (x) => f(g(h(x)));
// 循环
function compose(...fns) {
  return function(result) {
    for (let i = fns.length - 1; i >= 0; i--) {
      result = fns[i].call(this, result);
    }
    return result;
  };
}
// 递归
const compose = function(...args) {
  let length = args.length
  let count = length - 1
  let result
  return function f1 (...arg1) {
    // 递归结束条件
    if (count <= 0) {
      return result
    }
    result = args[count].apply(this, args1)
    count--
    return f1.call(null, result)
  }
}
// 利用reduce思路
const compose = (...args) => {
  // reduceFunc也是返回function
  const reduceFunc = (f,g) => {
    return (...arg) => g.call(this, f.apply(this, arg)) 
  }
  // return function
  return args.reverse().reduce(reduceFunc, args.shift())
}

四、循环异步操作

有序执行

1、for...await...of

const asyncOperations = [asyncOp1, asyncOp2, asyncOp3];

(async () => {
  for await (const op of asyncOperations) {
    const result = await op();
    console.log(result);
  }
})();

2、递归

const asyncOperations = [asyncOp1, asyncOp2, asyncOp3];

async function processAsyncOps(index = 0) {
  if (index < asyncOperations.length) {
    const result = await asyncOperations[index]();
    console.log(result);
    await processAsyncOps(index + 1);
  }
}

processAsyncOps();

3、async/awaitfor 循环

const asyncOperations = [asyncOp1, asyncOp2, asyncOp3];

(async () => {
  for (let i = 0; i < asyncOperations.length; i++) {
    const result = await asyncOperations[i]();
    console.log(result);
  }
})();

4、使用 reduceasync/await

reduce是非常有用的方法,在实践中应该多使用

const asyncOperations = [asyncOp1, asyncOp2, asyncOp3];

(async () => {
  await asyncOperations.reduce((promiseChain, currentOp) => 
    promiseChain.then(() => currentOp()), Promise.resolve());
})();

无序执行

1、Promise.allmap

const asyncOperations = [asyncOp1, asyncOp2, asyncOp3];

(async () => {
  try {
    const results = await Promise.all(asyncOperations.map(op => op()));
    console.log(results); // 包含所有结果的数组
  } catch (error) {
    console.error('至少一个操作失败:', error);
  }
})();

五、异步执行队列控制

实现一个可以控制执行数量的异步队列:

同时发起多个请求,但是限制只能执行3个,只有当3个当中有执行完了,才从等待队列中取出下一个执行。

首先从基本点拆解,同时可以借助现实中的场景加以分析理解。

假设,我们不需要排队立即执行:

// asyncFn是一个异步执行函数
function carryTask(asyncFn) {
  return asyncFn()
}

按照要求,在执行回调函数之前,我们应该是要处理排队情况控制等一系列问题,最后才是执行,执行后返回结果。

结合一个现实中排队做体检的例子分析:

假设检查室最多只能同时检查3人,如检查室不满3人,可以直接进去;否则要排队,只有出来一个,才能下一个进去。

基于这个场景,可以抽象:

声明一个数组,用来维护等待队列

声明一个计数器,用来记录检查室正在检查的人数,进去一个计数加一,出来一个计数减一

定义一个控制器来根据计数器决定是进去还是排队

定义一个执行器用于处理检查后的结果

定义一个检查室空闲时的后续处理

// limit最大限制
function carryTask(limit) {
  // 等待队列
  const waitQueue = [];
  // 正在执行的数量
  let count = 0;

  function taskControl(taskFn, callback) {
    if (count < limit) {
      // 如果检查室没到最大限制,直接执行
      executeNext(taskFn, callback);
    } else {
      waitQueue.push([taskFn, callback]);
    }
  }

  async function executeNext(taskFn, callback) {
    if (taskFn) {
      try {
        // 检查完处理结果
        const res = await carry(taskFn);
        callback(res);
      } catch (error) {
        console.error('Task error:', error);
      }
    }
  }

  function carry(fn) {
    return new Promise((resolve, reject) => {
      count++;
      fn()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          count--;
          processQueue();
        });
    });
  }

  // 后续处理
  function processQueue() {
    if (waitQueue.length > 0 && count < limit) {
      const [nextTask, nextCallback] = waitQueue.shift();
      executeNext(nextTask, nextCallback);
    }
  }
  
  return (taskFn, callback) => {
    taskControl(taskFn, callback);
  };
}

// 假设我们有一堆人要体检
const tasks = [
  () => {
    return new Promise((resolve)=>{
      console.log('开始执行1')
      setTimeout(()=> {
        resolve('结束执行1')
      },1)
    })
  },
  () => {
    return new Promise((resolve)=>{
      console.log('开始执行2')
      setTimeout(()=> {
        resolve('结束执行2')
      },2)
    })
  },
  () => {
    return new Promise((resolve)=>{
      console.log('开始执行3')
      setTimeout(()=> {
        resolve('结束执行3')
      },3)
    })
  },
  () => {
    return new Promise((resolve)=>{
      console.log('开始执行4')
      setTimeout(()=> {
        resolve('结束执行4')
      },4)
    })
  },
  () => {
    return new Promise((resolve)=>{
      console.log('开始执行5')
      setTimeout(()=> {
        resolve('结束执行5')
      },5)
    })
  },
];

// 利用柯里化固定最大限制参数
const carryTaskWithLimit_3 = carryTask(3);
for (let i = 0; i < tasks.length; i++) {
  // 传入要执行的异步操作以及异步执行后的回调
  carryTaskWithLimit_3(tasks[i], (res) => {
    console.log(res);
  });
}