前言
前段时间腾讯三面(没看清要求,好像那个岗也要了解后端知识比如Redis但是我不会,已挂),有一个前端知识把我问懵了:请讲一下js中的回调函数,回调函数是什么?
讲真,一直在用回调但是却压根没有想过,确实是我本身的不足。
正文
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数的时候,我们就说这是回调函数。
函数指针,也就是函数的地址,可以看做是指向函数的指针变量。
函数指针有两个用途:调用函数和做(别的)函数的参数!
官方对回调函数的定义是:作为参数传递给另一个函数并在其父函数执行完成后执行的函数。
可以明确的是:回调函数不是由该函数的实现方直接调用,而是在特定的事件/条件下由另外一方调用的,用于对该事件/条件进行响应。
这里不得不提到一个概念:回调队列(也有叫“消息队列”) —— js在运行时除了函数调用栈之外,还包含了一个待处理的回调队列。其中都是已经有了运行结果的异步任务,每一个任务都会关联一个回调函数。
回调队列遵循FIFO(先进先出)的原则,在js代码执行中会进行一些处理:
- 运行时,会从最先进入队列的任务开始,处理队列中的任务;
- 被处理的任务会被移出队列,该任务的运行结果会作为输入参数,并调用与之关联的函数,此时会产生一个函数调用栈;
- 函数会一直处理调用栈直到再次为空,然后 eventLoop 会去处理队列中的下一个任务
说起回调,就不得不提起“回调地狱” —— 为了达到某种效果而不断在函数中添加函数参数。回调地狱主要有两个问题:
- 多层嵌套;
- 每种任务的处理结果都存在两种可能性(成功或失败),那么需要在每种任务执行结束后分别处理这两种可能性。
es6用 Promise 解决回调地狱,本质上就是为了解决上面这两个问题。promise里有三大亮点:
1、 回调函数延迟绑定:回调函数不是直接声明的,而是通过后面的 then 方法的调用才传入的:
2、 返回值穿透:我们根据 then 中回调函数的传入值创建不同类型的 Promise,然后把返回的 Promise 穿透到外层,以供后续调用 —— 也就是promise的“链式调用”:
3、 错误冒泡:依靠 promise 穿透的特点,我们可以把前面产生的错误一直向后传递,直到末尾被 catch 接收,这样就不需要频繁地检查错误了。也不会因为前面的错误阻塞一些代码的执行:
是的,promise 也是基于回调的
promise如何保证顺序执行?
上面也说了,promise采用了延迟挂载的方式。而且你不知道当前promise的状态是pending、resolved还是rejected。那如果then注册的一套回调中如果既有同步任务也有异步任务,怎么保证他们按顺序执行呢?
关于此,promise/A+ 规范中提到:
onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
简单来说就是:promise强制then方法必须是异步的!我们不是在 JS 引擎层面实现 Promises,而是使用 JS 去实现 JS Promises。在JS里无法主动控制自身 execution context stack。可以通过 setTimeout/nextTick
等 API 间接实现。
那我们可不可以认为:promise中是利用了“事件循环中异步任务队列会顺序执行”的特点?(如有观点,请不吝留言)