前言: 回想当初第一次看到“回调函数”这个名词的时候,真的快把我难哭了。所有视频教程在讲到某个知识点的时候,大概都会说一句:“啊,这里怎么办呢?这里我们就需要用到一个回调函数...”。

等等,喂,关键是你还没讲什么是回调函数啊!,你让我怎么往下继续听啊...于是留下我一个人看着后面几十分钟的视频,但是脑子里还在纠结这个“回调函数”到底是什么玩意。

注意: 本文的前提知识是你需要了解 JS 的执行机制。我强烈建议你先熟悉前提知识点再往下看,因为我们最终的目的是手写 Promise🎉,但是之前讲解的这些知识点是需要你必须掌握的。

一. 函数

  1. 回调函数的基本概念我之前的文章虽然有些过,但是为了引入下文,在这里还是简单再提一嘴。我们先看一下 MDN 的解释。
    image.png
  2. 什么意思呢?还拿我们上一章节的例子。一个 setTimeout 里,隔了1秒去控制台打印一个 conole.log()
    image.png
  3. 让我们拆解一下。我们仔细看下面的一行代码,你发现了吗?我只不过是把声明一个箭头函数,变为了定义一个匿名函数。其实这两种写法的所产生的效果是一模一样的。(请暂时不要考虑箭头函数和普通函数的区别)
    image.png

    在这种情况下,上面的匿名函数箭头函数统称为函数 setTimeout 的回调函数。你一定需要明白的事情就是 setTimemout 也是一个函数! 单独的函数是无法被叫做回调函数的,回调函数的概念是相互的。所以更确切的说法应该是:
首先这里的箭头函数和匿名函数作为 setTimout 的参数,
然后在 setTimout 内部被调用,最后箭头函数和匿名函数才被称为 setTimout 的回调函数。
  1. 我发现其实有很多新手是没有搞懂函数不同的声明方式,所以才导致你们迷惑的。我们这里先来讲一下函数常用的三种定义方式。
    image.png
    在这里虽然这三个函数的执行结果是一样的,但是它们的区别是不小的。使用 function 关键字声明一个函数。被叫做函数声明。 而下面的两种方法是被叫做函数表达式

    image.png
  2. 虽然在使用方法上我们都是通过 "()" 去调用,但是其实它们还是有差别的。其中最重要的一个差别就是,使用函数声明定义的一个函数,该变量会被 js 解析器提升到代码开始执行之前。而使用函数表达式定义的函数则不会被提升。什么意思呢?
    image.png
    你会发现,我们竟然可以在定义 hello 函数之前去使用这个函数。而下面两个函数都报出了在赋值之前调用的错误。
    image.png
  3. 在这里我们再延伸一个知识点。我们在定义 hello 函数之前,首先定义一个 变量 name
    image.png

    虽然这里看起来 name 是在 hello 函数之前声明的,但是 hello 这个变量的声明其实是在变量 name 之前的。
  4. 其原因用简单的描述就是:js 解析器会在到你这个.js 文件后,会优先去查找使用 function 声明的函数名称,然后把这些函数名保存在一个对象中,并且提前创建一个引用来-->指向这个函数体。 具体细节还需读者自主查询,本文重点不在这里。
  5. 如果你读懂了上面的几个环节,那么我觉得这种写法你同样应该明白了。
    image.png

    那么我们趁热打铁,开启下一篇章。

二. 数据处理方式

  1. 我们都知道函数是为了帮助我们完成一些复杂的逻辑运算,可以反复调用的,并且函数是可以传递参数的。
    image.png
    比如上面的函数,允许我们传递两个数字类型的变量,然后该函数会把这两个数字的和返回给我们。
  2. ok,上面的内容很简单,我想大家应该都明白。如果我们日常和后端打交道的数据传输没有延迟,那么自然而然我们对数据处理起来就会非常方便。什么意思呢?假设我们 向后端发送请求获得数据 ,这一过程是一瞬间完成的,那么我们就可以顺着我们的想法从上往下写非常顺畅。
    image.png
    如上面刚写的的函数一样,你可以理解为我们向后端服务器请求了一个数据,数据一瞬间就返回了,那么们就可以把这个 data 给返回出去。然后在下面用一个变量去接受一下,随之进行一些运算。
  3. 就如上一篇文章讲的那样。很遗憾的是,我们在进行一些可能需要一定时间才可以完成的任务的时候,不可能是同步完成的,因为一行代码而阻塞后面整段代码的执行是非常不可取的事情。
  4. 这里我们来简单模拟一个场景。
    image.png
    我们定义了一个普通对象 data 来模拟后端即将传递给我们的数据。我们假设这个数据的可能需要花费 1s 的时间才能返回。我们可能自然而然的想到,那不是和之前一样嘛,在 setTimeout 里返回不就行了吗。
    image.png
    你可以先思考一下上面的这段代码,userData 会拿到正确的 data 值吗?
  5. 非常遗憾的是,我们是无法在同步的代码里第一时间拿到异步函数的返回值的。
    image.png
    那么造成上面的代码结果的原因是为什么?为什么是 undefined 呢?我们一步一步分析。

三. 理清思路

  1. 当下面画黄线的代码被执行的时候。紧接着下一步我们就需要马上跳进 getData 的函数体中去分析。
    image.png
  2. 进到这个函数里,首先就是执行 setTimeout 函数。 (我在这里再再再亿次强调,setTimeout 本身就是一个普通函数,没什么特别的。)
    image.png
  3. 但是 setTimeout 函数会把接收的回调函数给推送到任务队列里去排队。什么?你没看见回调函数?这不就是文章最开头说的函数的声明方式之一箭头函数吗?怎么这么快就忘了?自觉翻回上面去再看一遍⏰。

    image.png
  4. 紧接着 getData函数 执行完毕,看一眼,函数本身没有返回值,那么默认返回值为 undefined
  5. 这里需要特别注意! 我们如果在这里写了 return语句 ,这样才能算是 getData函数的 返回值。

    image.png

    而在我们的 data 却是 setTimout回调函数的返回值,你能拿到才怪。
  6. 那怎么办呢?我们继续往下看。

四. 回调函数

  1. 别着急往下看,经过上面的三个环节,我们先做个小测试🎁。
    image.png

    上面这段代码你看懂了吗?(下面是执行结果)
    cb.gif

    如果你对执行结果不是特别清楚,没关系。到这一步我只希望你能读懂下面这行代码,至少对于写法上,很清楚的知道为什么可以这样写

    image.png

    如果你暂时还没搞懂,那么我强烈建议你至少回过头再品味一下第一个标题 函数,这次要细读。
  2. 如果你读懂了,恭喜你🎉,你距离理解这个名词更近了一步。接下来我们稍微改造一下代码。
    image.png
  3. 上面这段代码乍一看很玄乎,别着急,我们慢慢分析。首先 modifyData函数 是我们提前设定好的一个处理后端数据的函数。(前提是如果我们能拿到后端给我们的数据)
  4. 既然我们没有办法在同步函数里拿到 data 后再去处理,那么我们就干脆提前给后端代码函数 getData 一个参数。

    一个我们提前设定好的处理函数。当 setTimeout 的回调函数拿到数据后,就帮我们执行这个函数,也就是 modifyData 函数。然后把 data 作为参数传递给 modifyData函数
  5. 我们在 setTimout 的回调函数中打印一下修改前后的值。
    image.png
    往往.gif
  6. 我们会发现,我们之后的代码都在回调函数里去处理的话,就能完美实现需求。也就是后端传过来的数据我们可以拿到了,并且可以处理成我们想要的数据了。

结语

在这里我们就先不引入解回调地狱的概念了了,我害怕都的读者确实会绕迷糊。
我会在放在手写 Promise 之前,引入这个概念。
希望读者可以细细体会上面的代码,一旦学会这些概念,你会看到和之前不一样的 js 世界🎁。


FFF方
451 声望12 粉丝