📖
blog
  • README
  • JavaScript
    • 元素的宽高位置信息梳理
    • dom-align 源码浅析
    • Event Loop
    • 函数实参为对象时的陷阱
    • export 与 utils 方法书写规范
    • 手写 Promise 及相关代码理解
    • call,apply,bind 等函数的模拟实现
    • JavaScript继承
    • JavaScript 数据类型与类型判断
    • for..of 和 for..in 的区别
    • 写给自己看的 next 函数
    • JS 可选链与双问号
    • mouseenter 与 mouseover 事件的区别
    • Immutable相关知识
  • CSS
    • 不简单的 z-index
    • 两列布局,三列布局
    • CSS 居中方案整理
    • CSS 像素,设备像素,2倍图梳理
    • iconfont 的使用
  • Node JS
    • 实现简易的 express
  • React 核心知识点整理
    • 高阶组件
    • React 事件处理
    • React Hooks
    • React Context
  • React 状态管理
    • Redux 基础概念
    • Redux 中间件和异步操作
    • Redux Saga
    • Redux 只能有一个 store 对象嘛
  • React 开发实践
    • Ant Design Menu 组件的使用与深入
    • 讲讲吸顶效果与 react sticky
    • 基于 express,搭建 react 的开发环境
    • 通过 antd input 组件分析受控与非受控组件
    • DebounceClick 组件
    • react component Align 组件分析
    • React Portal 之事件冒泡
    • React Transition Group 源码浅析
    • React.cloneElement 父组件向子组件注入 props
    • 一次 Align 组件的问题记录
    • 如何知道子组件的类型
    • React Router 源码简单分析
    • React Redux 源码简单分析
  • Vue.js
    • Vue.js 概览
    • scoped 样式中的 deep
  • TypeScript 语法
    • 基础类型
    • 变量声明
    • 接口
    • 类
    • 函数
    • 泛型
    • 枚举
    • 类型推论
    • 类型兼容性
    • 高级类型
    • Symbol
    • 迭代器和生成器
    • 模块
    • 命名空间
    • JSX
  • 玩转 webpack
    • 第一章: webpack 与构建发展简史
    • 第二章:webpack基础用法
    • 第三章:webpack进阶用法
    • 第四章:编写可维护的 webpack 构建配置
    • 第五章:webpack构建速度和体积优化策略
    • 第六章:通过源代码掌握webpack打包原理
    • 第七章:编写Loader和插件
  • webpack 实践
    • 如何配置 output.library
  • 测试
    • 初识代码测试
    • Jest 中 如何测试 setTimeout
    • Jest Enzyme React 测试实践记录
  • WEB 开发,过往工作沉淀
    • Web安全(DVWA)
    • 内存泄露与事件移除的必要性
    • url to pdf api 与 服务部署踩坑记录
    • 前端调试指南
    • Markdown 转 email
    • github travis ci 自动部署
    • 浏览器缓存知识梳理
    • WEB 系统登录相关知识梳理
    • 将-Axios-请求参数和返回值进行格式化
    • source-map与源码调试
    • HTTPS
    • 使用 rollup 打造自己的 npm 包 (全流程)
    • father-build 是如何工作的
  • 书籍
    • 图解 HTTP 协议
    • 编写可维护的 JavaScript
    • 鸟哥的 Linux 私房菜
    • JavaScript Promise迷你书
  • Linux
    • vimtutor
    • CURL 使用指南
  • Nginx
    • 一次 nginx 分享
  • Git
    • Git Commit Message 须知
    • .gitignore 模板
    • git tag标签
  • 摄影
    • 摄影基础知识
    • 手机摄影从小白到大师
  • 翻译
    • log4js
    • log4js-node
    • 介绍GitLab上的CI/CD
    • 为GitLab Pages创建并调整GitLab CI/CD
    • 关于 rel=noopener
    • AngularJS 团队 Git 提交信息约定
    • JSON Schema
  • Lifehack
    • 20 个 Google 搜索 Tips 来高效使用 Google
    • 37 个高级 Google 搜索 Tips
Powered by GitBook
On this page
  • 前言
  • 需求
  • 1. 自己的理解过程
  • 1.1 正确分析
  • 1.2 易错
  • 2. 更多方法
  • 2.1 递归法
  • 2.2 方法二
  • 2.3 Promise 的方法
  • 3. compose 函数
  • 3.1 示例
  • 参考资料

Was this helpful?

  1. JavaScript

写给自己看的 next 函数

Previousfor..of 和 for..in 的区别NextJS 可选链与双问号

Last updated 4 years ago

Was this helpful?

前言

最早在看 redux 源码的时候,有个 applyMiddleware 函数,没能看明白(现在连它能干嘛的都忘了,衰)。只隐约记得,能够把一些 middleware 传入其中,然后它依次处理,顺序的执行一些逻辑(真忘了是干嘛的了)。

再如 express 中,某个中间件的使用都是 app.use(middleware), 而其内部具体干了什么还是不太清楚,最终呈现的效果是,当请求来的时候,依次的执行中间件,直到最后返回。

那这两个知识盲点给我的感觉是,都有种 "某个数组里存着函数,而里面的函数会依次执行" 的感觉。之后,偶然看到这个,说实话,觉得是能解决我的疑惑的,但还是绕不过来,理不清楚。

这次抽空强行理解了一下,赶紧留住思路,免得跑了~

需求

想象我们有这样的一个数组:

const fun1 = (next) => {
  setTimeout(() => {
    console.log(1);
    next();
  }, 3000);
}

const fun2 = (next) => {
  setTimeout(() => {
    console.log(2);
    next();
  }, 2000);
}

const fun3 = (next) => {
  setTimeout(() => {
    console.log(3);
    next();
  }, 1000);
}

const funArr = [
  fun1,
  fun2,
  fun3
];


// 实现 run 方法,让数组中的函数依次执行  
function run() {

}

1. 自己的理解过程

1.1 正确分析

关键是 next 这个参数,我们需要的就是在 next 执行的时候,能够使得下一个函数执行,那么假如下一个函数就是 next, 会怎么样?

先只有两个函数:

fun1(fun2)

当 fun1 执行完之后,fun2 作为 next 执行,看起来没有问题。

但是!,当 fun2 执行的时候,fun2 的参数是 undefined,也就是说 fun2 的next 是 undefined ,那执行到 fun2 中的 next() 的时候就会报错。

那我们就想,给 fun2 传一个空函数:

fun1(fun2(() => {}))

这样也会有问题,就是 fun2(() => {}) 这是一个函数调用,当它作为参数的时候,首先会被计算出来。同时 fun2(() => {})的返回值是 undefined, 又相当于 fun1 的参数是 undefined, 又会报错。

那么我们需要的就是,fun2 自身接受一个空函数,但是又不可以直接以 fun2() 的形式传给 fun1 ,那么我们考虑把 fun2 包裹在函数里:

fun1(() => fun2(() => {}))

那这样就顺利的实现了功能。

那如果有三个函数的时候,我们自然可以推出以下的式子:

fun1(() => fun2(() => fun3(() => {})))

是的,但是,当函数个数不一定时,我们怎么来凑出这个式子呢?

先直接上 reduce 吧(就是这么没有道理):

先看看 reduce 的结构:

arr.reduce((prev, curr) => {
    // code
}, initValue)

prev 为 initValue, 之后是 code 的返回值. curr 为当前迭代到的元素:

回到我们的例子: 当一个函数时:

fun1(() => {})

当两个函数时:

fun1(() => fun2(() => {}))

当三个函数时:

fun1(() => fun2(() => fun3(() => {})))

套用 reduce, 那就是:

function run(arr) {
    return arr.reduce((prev,curr) => {
        // 我们知道 curr 会依次为,fun1, fun2, fun3
        // 那 curr 为 fun1 时,我们为了凑出 fun1(() => {})
        // 因此让 prev 为 () => {}
        return () => curr(prev)
    }, () => {})
}

// 最终调用
run(funArr)()  // 因为 run 函数会返回一个函数

检验一下其余的: curr 为 fun1 时,run 函数最终返回:

() => fun1(() => {})     // prev

curr 为 fun2 时,prev 为 () => fun1(() => {}), run 函数最终返回:

() => fun2(() => fun1(() => {}))   // prev

curr 为 fun3 时,prev 为 () => fun2(() => fun1(() => {})), run 函数最终返回:

() => fun3(() => fun2(() => fun1(() => {})))

好像像那么回事,但是顺序好像反了,会先执行 fun3 而不是 fun1, 那我们换成 reduceRight:

function run(arr) {
    return arr.reduceRight((prev,curr) => {
        return () => curr(prev)
    }, () => {})
}
// 最终调用
run(funArr)()  // 因为 run 函数会返回一个函数

那结果应该是:

() => fun1(() => fun2(() => fun3(() => {})))

最终实现了需求

1.2 易错

在理解 1.1 上述过程中,我尝试了以下的代码:

function run(arr) {
    arr.reduce((prev,curr) => {
        return curr(prev)
    }, () => {})
}

以上代码(在runJS)会输出 3,2,1 于是,我以为我改成 reduceRight 就可以了,但是改成 reduceRight 之后,依旧是输出 3,2,1. 那是为什么呢?

先转换以上代码:

curr 为 fun1 时,prev 为 () => {}, 此时结果为:

fun1(() => {})   // prev

curr 为 fun2 时,prev 为 fun1(() => {}), 此时结果为:

fun2(fun1(() => {}))   // prev

curr 为 fun3 时,prev 为 fun2(fun1(() => {})), 此时结果为:

fun3(fun2(fun1(() => {})))

注意:

以上代码其实是会报错的!!!,如一开始分析的那样, fun1 会首先执行,返回值为 undefined, 当它作为 fun2 的参数时,其实是报错了,但是 runJS 居然没有报错。

以上的代码为什么还能输出结果呢?

因为在不报错的情况下,以上代码相当于:

fun1()
fun2()
fun3()

即相当于三个函数同时执行,因此最终的输出结果,在 setTimtout 的时间不同的情况下,只和时间有关。

因此也告诉我们,我们的这种场景, reduce 里面必须要返回函数

2. 更多方法

2.1 递归法

function run(arr) {
  if(arr.length === 0) {
    return;
  }
  arr[0](() => run(arr.slice(1)))
}

其中 next 是个函数,相当于 () => run(arr.slice(1)), 每次 next 函数执行,都会执行数组的下一项。

2.2 方法二

function run(arr){
  const trigger = () => {
    if (arr.length === 0) {
      return;
    }
    arr.shift()();
  };
  arr = arr.map(fun => () => fun(trigger));
  trigger();
};

即 next 的功能就是执行数组的下一项,因此我们先定义 trigger 作为 next. 然后我们通过 map 函数,给每个函数外面包一层,当函数执行的时候,主动给它们传入 trigger 这个函数.

2.3 Promise 的方法

function run(arr) {
  const trigger = () => {
    if (arr.length === 0) {
      return;
    }
    arr.shift()();
  };
  arr = arr.map(fun => () => new Promise((resolve) => {
    fun(resolve);
  }).then(trigger));
  trigger();
};

想一下,next 和 Promise 中的 resolve 是不是有点类似,就都是当执行的时候,会触发后续代码的执行。

于是我们同样构造 trigger 函数,但这次,我们利用 Promise#resolve 之后,执行 .then 代码的特性,来触发 trigger 个人觉得这种方法和 2.2 其实是一样的,而且添加了 Promise, 增加了执行流程,不推荐

3. compose 函数

函数式编程中有个 compose 函数,这个函数组合几个函数,形成一个新的函数:

const compose = ([f1, f2,f3]) => value => f1(f2(f3(value)));

即 compose 返回一个新的函数,该函数会被传入的函数,从右到左依次执行,同时右边函数的结果会作为左边函数的参数。

function compose(funcs) {
  if (funcs.length === 0) {
    return arg => arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

我们来看 reduce 的部分, 拆开是这样的:

funcs.reduce((prev,curr) => {
    return (...args) => {
        prev(curr(...args))
    }
})

当 reduce 没有第二个参数 initValue 时,prev 即为 funcs[0], curr 即为 funcs[1]。

因此,当只有两个函数时, prev 为 fun1, curr 为 fun2, 最终返回的函数是:

(...args) => {              // prev
    fun1(fun2(...args))
}

当有三个函数时: prev 为:

(...args) => {
    fun1(fun2(...args))
}

curr 为 fun3, 那么最终的结果就是:

(...args) => {
    (
        (...innerArgs) => {
            fun1(fun2(...innerArgs))
        }
    )
    (fun3(...args))
}

// 注意
以下是一个函数声明:
(
    (...innerArgs) => {
        fun1(fun2(...innerArgs))
    }
)

而 fun3(...args) 是上面函数的参数,因此最终替换过来就相当于:  

(...args) => {
    fun1(fun2(fun3(...args)))
}

3.1 示例

function add1(number) {
    return number + 1
}

function multiply2(number) {
    return number * 2
}

function minus3(number) {
    return number - 3
}

const newFun = compose([add1,multiply2,minus3])
newFun(6)   // 输出 7 
// 7 = ((6 - 3) * 2) + 1

参考资料

题外话,之后找到了 runjs 的 github,发现居然不开源,也发现一个有意思的讨论:

语雀:

知乎:

分享
issue
参考
来聊一道面试题吧
Node.js 的中间件里面的 next() 是干什么的?