JavaScript基础——回调(callback)是什么?

上篇文章《JavaScript基础——你真的了解JavaScript吗?》,我们明白了JavaScript是一个单线程、非阻塞、异步、解释性语言,清楚了什么是单线程、进程、阻塞、调用堆栈、异步回调、任务循环等概念,没看的或者不清楚的建议点击《JavaScript基础——你真的了解JavaScript吗?》再看一遍,只有理解了,才能轻松阅读理解本篇文章内容。

640?wx_fmt=png

什么是callback?

640?wx_fmt=png

JavaScript 是单线程工作,这意味着两段脚本不能同时运行,而是必须一个接一个地运行。我们人类是多线程工作。您可以使用多个手指打字,可以一边开车一边与人交谈。唯一一个会妨碍我们的是打喷嚏,因为当我们打喷嚏的时候,所有当前进行的活动都必须暂停。这真是非常讨厌,尤其是当您在开车并想与人交谈时。您可不想编写像打喷嚏似的代码。JavaScript由于单线程限制,防止阻塞,只能通过异步函数的调用方式,把需要延迟处理的事件放入事件循环队列。到目前为止,回调是编写和处理JavaScript程序异步逻辑的最常用方式。说了这么多,既然回调这么重要,到底什么是回调(callback)呢?

简单的定义:回调就是一个在另外一个函数执行完后要执行的函数

复杂的定义:在JavaScript中,函数是对象。因此函数可以将函数作为参数,并且可以由其他函数进行返回。执行此操作的函数称为高阶函数。任何作为参数传递的函数都称为回调函数。

640?wx_fmt=png

为什么需要回调?

640?wx_fmt=png

开篇已经介绍了JavaScript是单线程的,需要通回调函数处理异步相关的逻辑,理论还是过于生硬,我们还是来看段代码吧:

function first(){
   console.log(1);
}
function second(){
   console.log(2);
}
first();
second();

正如你所料,先执行first函数,再执行second函数,控制台将输出以下内容:

1
2

目前看来没什么问题,如果first()函数中含有某种无法立即执行的函数呢?例如,我们必须发送请求然后等待结果响应的API请求?为了模拟API请求,我们可以使用setTimeout函数模拟。我们将函数延迟500毫秒来模拟请求,我们更改后代码如下:

function first(){
   // Simulate a code delay
   setTimeout( function(){
       console.log(1);
   }, 500 );
}
function second(){
   console.log(2);
}
first();
second();

我们将 console.log(1) 延迟500毫秒输出,这段代码会怎么输出呢?

2
1

我们希望的顺序先执行first,再执行second,但是由于JavaScript是异步的,所有的延迟处理都要放入循环队列里,因此事与愿违,不能按照我们的希望顺序输出。如果希望这段代码按照我们的意愿输出,我们可以使用回调函数,确保某些代码执行完了,再执行另外一段代码。

640?wx_fmt=png

创建一个简单的回调

640?wx_fmt=png

说了这么多,让我们创建一个简单的回调!

我们打开编辑器,先输入如下代码:

function doHomework(subject) {
   alert(`Starting my ${subject} homework.`);
}

上面我们创建了doHomeWork的函数,我们接受一个变量,通过控制台调用,将得到下面的提示:

doHomework('math');
// Alerts: Starting my math homework.

接着,我们开始添加回调,在doHomework函数中添加一个参数callback,然后在第二个参数中回调我们定义的函数。代码如下:

function doHomework(subject, callback) {
   alert(`Starting my ${subject} homework.`);
   callback();
}
doHomework('math', function() {
   alert('Finished my homework');
});

正如你希望的,我们在控制台里运行上述代码,将会受到两个连续的alert,Starting my math homework,然后弹出 Finished my homework。

但是回调函数并不是非得在调用函数中定义,我们可以单独定义,修改后的代码如下:

function doHomework(subject, callback) {
   alert(`Starting my ${subject} homework.`);
   callback();
}
function alertFinished(){
   alert('Finished my homework');
}
doHomework('math', alertFinished);

此示例的输出结果和上段代码的结果一致,我们实现了在doHomework函数中调用alertFinished,实现了函数作为参数进行传递,实现了回调函数的创建。

640?wx_fmt=png

用回调编写真实业务场景!

640?wx_fmt=png

例如我们有一个需求,用NodeJs实现从论坛帖子列表数据中显示其中的一个帖子的信息及留言列表信息,代码如下:

DB/posts.json(帖子列表数据)

[
   {
       "id": "001",
       "title": "Greeting",
       "text": "Hello World",
       "author": "Jane Doe"
   },
   {
       "id": "002",
       "title": "JavaScript 101",
       "text": "The fundamentals of programming.",
       "author": "Alberta Williams"
   },
   {
       "id": "003",
       "title": "Async Programming",
       "text": "Callbacks, Promises and Async/Await.",
       "author": "Alberta Williams"
   }
]

DB/comments.json(评论列表)

[
   {
       "id": "phx732",
       "postId": "003",
       "text": "I don't get this callback stuff."
   },
   {
       "id": "avj9438",
       "postId": "003",
       "text": "This is really useful info."
   },
   {
       "id": "gnk368",
       "postId": "001",
       "text": "This is a test comment."
   }
]

Index.js

const fs = require('fs');
const path = require('path');
const postsUrl = path.join(__dirname, 'db/posts.json');
const commentsUrl = path.join(__dirname, 'db/comments.json');
//return the data from our file
function loadCollection(url, callback) {
   fs.readFile(url, 'utf8', function(error, data) {
       if (error) {
           console.log(error);
       } else {
           return callback(JSON.parse(data));
       }
   });
}
//return an object by id
function getRecord(collection, id, callback) {
   var collectobj=collection.find(function(element){
       return element.id == id;
   });
   callback(collectobj);
   return collectobj;
}
//return an array of comments for a post
function getCommentsByPost(comments, postId) {
   return comments.filter(function(comment){
       return comment.postId == postId;
   });
}
loadCollection(postsUrl, function(posts){
   loadCollection(commentsUrl, function(comments){
       getRecord(posts, "001", function(post){
           const postComments = getCommentsByPost(comments, post.id);
           console.log(post);
           console.log(postComments);
       });
   });
});

大家请注意,在loadCollection函数中小编没有使用try/catch捕捉异常,使用的是if/else,因为catch无法从readFile方法中获取异常。上述代码还需要完善,我没有包含任何错误处理。如果在任何步骤中发生错误,程序将无法继续。

错误处理是很重要的事情,我们写代码时要认证严格对待,比如我们要编写一个用户登录的功能。涉及从网页表单里获取用户名和密码,查询我们的数据库,确认用户信息是否正确,验证通过后,将用户引导到用户中心页面。如果用户名密码格式不正确,用户名密码不正确,我们应该将错误信息返回给用户,并引导用户重新登录。

640?wx_fmt=png

很好!我们一起把回调的内容学完了,理解了什么是回调,异步编程是我们的代码中使用的一种方法,用于推迟事件以便以后执行。当您处理异步任务时,回调是一种解决方案,以便它们按顺序执行。


如果我们有多个任务依赖于前几个任务的结果,那我们就要使用多个嵌套回调,但是就会引发“回调地狱”(过多的回调嵌套会使得代码变得难以理解与维护),还好Promise解决了“回调地狱”的问题,让我们以同步的方式编写代码,小编将会在下篇文章里详细介绍Promise,敬请期待!

注:本文内容参考

Brandon Morelli https://codeburst.io/javascript-what-the-heck-is-a-callback-aba4da2deced

Alberta Williams https://code.tutsplus.com/tutorials/javascript-callbacks-promises-and-async-functions-part-1--cms-30000?_ga=2.97423583.1776052358.1543636348-289309391.1524575295

专注分享当下最实用的前端技术。关注前端达人,与达人一起学习进步!

长按关注"前端达人"

640?wx_fmt=gif 640?wx_fmt=jpeg
  • 17
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值