一、关于函数式
这是前端非常重要的概念,参考函数式编程思想
二、循环和递归
循环函数
函数式中,如果需要循环处理某些逻辑,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/await 和 for 循环
const asyncOperations = [asyncOp1, asyncOp2, asyncOp3];
(async () => {
for (let i = 0; i < asyncOperations.length; i++) {
const result = await asyncOperations[i]();
console.log(result);
}
})();
4、使用 reduce 和 async/await
reduce是非常有用的方法,在实践中应该多使用
const asyncOperations = [asyncOp1, asyncOp2, asyncOp3];
(async () => {
await asyncOperations.reduce((promiseChain, currentOp) =>
promiseChain.then(() => currentOp()), Promise.resolve());
})();
无序执行
1、Promise.all 和 map
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);
});
}