Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 编码专题

    • 00.React-目录
    • 10.React-组件-组件封装
    • 11.React-hooks-自定义钩子
    • 21.函数技巧 - 函数式编程之 compose 函数
      • 0. 前言
      • 1. 如何实现传统的 compose 函数?
      • 2. 使用递归实现 compose 函数
      • 3. 传统 compose 函数的特点及后续改进
    • 22.函数技巧-tapable钩子
    • 23.函数技巧-koa源码
    • 30.Promise系列-目录
    • 31.Promise-深入Promise原理
    • 32.Promise-指令式Promise改造
    • 32.promise-梳理 Promise 错误处理
    • 34.promise-竞态问题
    • 35.Promise-如何处理异步数组
  • 深入浅出 Vite

  • 快速上手 API

  • 深入浅出Babel

  • 深入浅出 React

  • 百问掘金
  • 编码专题
wangjiasheng
2023-09-30
目录

21.函数技巧 - 函数式编程之 compose 函数

# 0. 前言

# 1. 如何实现传统的 compose 函数?

例如,假设我们有三个函数 task1、task2、task3 ,compose 函数可以定义如下:

const compose = (task1, task2, task3) => (...args) =>
  task1(task2(task3(...args)));
1
2

其中,args 是传入 compose 函数的参数,最终传递给 task3 函数执行,task3 函数的返回结果再传递给 task2 。类似的,task2 的执行结果再传递给队首。

JS 实现:

const tasks = [step1, step2, step3, step4]

// 定义实现:
const compose = (...args) => step1(step2(setp3(step4(...args))));

// 优雅实现
const compose = (...funcs) => {
  return funcs.reduce((preFun,curFun) => (...args) => preFun(curFun(...args))
}

// 如何调用
compose(tasks)(); // 注:这里需执行下
1
2
3
4
5
6
7
8
9
10
11
12

对于 reduce 实现的 compose 函数不是很好理解,因为正常我们使用 reduce 返回的通常是一个值,而这里返回的是一个函数,于是存在一个函数递归的逻辑。

整个 reduce 是一个递归的过程,自顶向下构建如下函数:

[task1] → (...args) => task1(...args);
[task1,task2] → (...args) => task1(task2(...args));
[task1,task2,task3] → (...args) => task1(task2(task3(...args)));
1
2
3

递归逻辑: (...args) => pre(cur(...args))

整个公式很优美,但是比较难思考,下面换个角度来思考这道题,并且尝试将这种编程思想泛化。

# 2. 使用递归实现 compose 函数

首先比较难实现的是两个点:

  1. 如何实现 step4 → step3 → step2 → step1,即列表的从后往前的执行。
  2. 如何将参数透传到最后一个函数。

如果数组的执行次序是,从前往后非常好实现,将数组翻转后操作就比较方便。

这里提供两种处理第一个数据和剩余数组数据的写法。

写法一:arr.slice(1) + reduce(()=>{}, arrp[0])

/* 立即执行写法 */
const compose_immediate1 = (...funcs) => {
  /* 翻转数组 */
  funcs.reverse();

  /* 处理初始值 */
  funcs.slice(1).reduce((pre, cur) => {
    pre = cur(pre);
    return pre;
  }, funcs[0]());
};
compose_immediate1(a, b, c);
1
2
3
4
5
6
7
8
9
10
11
12

写法二:

const compose_immediate2 = (...funcs) => {
  /* 翻转数组 */
  funcs.reverse();

  /* 处理初始值 */
  const [first, ...otherFuns] = funcs;
  otherFuns.reduce((pre, cur) => {
    pre = cur(pre);
    return pre;
  }, first());
};
compose_immediate2(a, b, c);
1
2
3
4
5
6
7
8
9
10
11
12

如果不考虑数组翻转,实现起来需要使用 递归 方案,构造一个包裹函数 wrap_fun ,入参接受 callback 函数(即,高阶函数),该高阶函数用于执行下一个任务函数。

const tasks = [step1, step2];

const step1 = () => console.log("step1");
        ↓
const wrap_step1 = (next) => {
  next(); // callback 为下一个任务函数
  step1();
}

// 此时设置 next = step2 即可
wrap_step1(step2)
1
2
3
4
5
6
7
8
9
10
11

当 step2 为最后一个执行函数时,next 传递 step2 即可,若 step2 不为最后一个,next 需要传递 step2 的包裹函数 wrap_step2 ,内置了下一层的包裹函数。

举例 step 存在四个任务时,有如下逻辑:

// 利用箭头函数,延迟代码执行, wrap_fun 为包裹函数
// args 为 compose 入参
const step4 = (...args) => console.log(4,args);
      ↓
const wrap_step4 = (...args) => step4(...args);
      ↓
const step3 = (res4) => console.log(3,res4);
      ↓
const wrap_step3 = (...args) => {
  // 需调用执行 step4 函数
  const res = wrap_step4(...args);
  // step3 默认函数
  step3(res);
}
      ↓
const step2 = () => console.log(2);
      ↓
const wrap_step2 = (...args) => {
  // 需调用执行 step3 函数
  const res = wrap_step3(...args);
  // 运行自己的函数
  step2(res);
}
      ↓
const step1 = () => console.log(1);
      ↓
const wrap_step1 = (...args) => {
  // 需调用执行 step2 函数
  const res = wrap_step2(args);
  // 运行自己的函数
  step1(res);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

基于这个思路,构造 wrapFun 的写法,编写递归逻辑。

function compose_iterable(...tasks: any[]) {
  return (...args) => {
    function wrapFun(i: number) {
      // 最后一个任务,直接调用 args 返回函数值
      if (i === tasks.length - 1) return tasks[i](...args);
      return tasks[i](wrapFun(i + 1));
    }
    return wrapFun(0);
  };
}
compose_iterable(a, b, c)("test1");
1
2
3
4
5
6
7
8
9
10
11

有的时候,我们希望 compose 不直接调用,可以将 wrapFun 返回一个箭头函数,主动控制调用时机:

function compose_iterable_delay(...tasks: any[]) {
  return (...args) => {
    function wrapFun(i: number) {
      if (i === tasks.length - 1) return () => tasks[i](...args);
      return () => tasks[i](wrapFun(i + 1)());
    }
    return wrapFun(0);
  };
}
compose_iterable(a, b, c)("test1")();
1
2
3
4
5
6
7
8
9
10

# 3. 传统 compose 函数的特点及后续改进

传统的 compose 函数执行特点如下:

  1. 参数传递:第一个入参支持多元(接受多个参数),后面的函数呈现柯里化特性(接受一个参数)。
  2. 执行次序:task3 → task2 → task1
  3. 同步函数:所有 task 要求是同步的。

总结:compose 函数为一个高阶函数,它接受多个函数作为参数,并返回一个新的函数。

希望实现的 compose 的特点:

  1. 支持同步 compose 和 异步 compose 的特性。
  2. 执行次序:根据队列排序 顺序 执行。
  3. 支持多种处理模式,并且使用 Class 取代原有的 Funciton 写法。
编辑 (opens new window)
#compose函数
上次更新: 2023/10/02, 17:10:00
11.React-hooks-自定义钩子
22.函数技巧-tapable钩子

← 11.React-hooks-自定义钩子 22.函数技巧-tapable钩子→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式