1.1 定义

回调是指将一个函数作为参数传递另一个函数,并且作为参数的函数可以被执行, 其本质上是一个高阶函数。高阶函数至少满足下列一个条件:

  • 接受一个或多个函数作为输入
  • 输出一个函数

如:JS中的 map方法

注意,这里仅仅传递了**函数定义我们并没有在参数中**执行函数。仅是传递cb,而不是cb()
同时,回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调

和普通函数的区别

回调函数区别于普通函数,在于它的调用方式。只有当某个函数被作为参数,传递给另外一个函数,或者传递给宿主环境,然后该函数在函数内部或者在宿主环境中被**调用**,才称为回调函数。

使用

在执行之前确保回调函数是一个函数

在调用之前检查作为参数被传递的回调函数确实是一个函数

  1. function getInput(options, callback){
  2. //确保callback是一个函数
  3. if(typeof callback === "function"){
  4. //调用它,既然我们已经确定了它是可调用的
  5. callback(options);
  6. }
  7. }

回调函数的this问题

由于传递回调函数,仅是传递了其定义,并未立即调用;所有回调函数中的this最终由其调用对象决定的。
在下面代码中,client.setUsername仅是传递了一个函数定义,client 并不是setUsername的调用者。

  1. var client = {
  2. setUserName: fucntion (args){
  3. this.fullName = firstName + " " + lastName;
  4. }
  5. }
  6. function getUserInput(callback){
  7. callback(args);
  8. }
  9. getUserInput("Barack","Obama",client.setUserName);


使用Call和Apply函数来保存this


  1. // client对象会被Apply方法使用来设置this对象
  2. function getUserInput(firstName, lastName, callback. callbackObj){
  3. callback.apply(callbackObj, [firstName, lastName]);
  4. }
  5. getUserName("Barack", "Obama", client.setUserName, client);


1.2 同步回调与异步回调

回调函数有两种不同的形式,同步回调异步回调
两者最大区别在于:

  • 同步回调函数是在其执行函数内部被执行的,
  • 异步回调函数是在执行函数外部被执行的。

下面代码中,callback 是在 doWork 函数内部执行,所以这是一个同步回调

  1. // 回调函数
  2. let callback = function(){
  3. console.log('i am do homework')
  4. }
  5. // 执行函数
  6. function doWork(cb) {
  7. console.log('start do work')
  8. cb()
  9. console.log('end do work')
  10. }
  11. // 调用执行函数
  12. doWork(callback)

异步回调,函数并不是在它的执行函数内部被执行的,而是在其他的位置其他的时间点被执行的,

  1. function foo() {
  2. alert("Hello");
  3. }
  4. setTimeout(foo, 3000)

V8 执行setTimeout 时,会立即返回 ; 等待 3000 毫秒之后,foo 函数才会被 V8 调用。
foo 函数并不是在 setTimeout 函数内部被执行的,所以这是一个异步回调。

  • 对于同步回调函数的执行时机,理解起来比较简单,就是回调函数在执行函数内部被执行;
  • 而异步回调函数在什么时机和什么位置执行,并又不是都能由我们决定的,其可能存在特定的API,在执行到特定步骤时,由浏览器自己调用。

如,下面的XHR的执行过程:
image.png

XHR处理回调

使用xhr.send来发起网络请求之后:

  1. 渲染进程会将请求发送给网络进程,然后网络进程负责资源的下载;
  2. 等网络进程接收到数据之后,就会利用 IPC 来通知渲染进程;
  3. 渲染进程接收到消息之后,会将 xhr 返回的状态封装成任务添加都任务队列中;
  4. 等主线程循环系统执行到该任务的时候, 就会根据相关的状态来调用对应的回调函数。
    • 如果网络请求出错了,就会执行 xhr.onerror
    • 如果超时了,就会执行 xhr.ontimeout
    • 如果是正常的数据接收,就会执行 onreadystatechange 来反馈相应的状态。

setTimeOut的实现

Node处理文件读取

当遇到I/O操作时,Node代码在发起一个系统调用后继续向下执行,操作完成后,再执行对应的回调函数(异步)
回调 - 图2