了解JS中的回调

了解JS中的回调

你有无意中看到 “callback” 但并不知道其中的意思么?不用担心。不是只有你一个人这样。很多JavaScript 新手都难以理解回调

虽然回调比较令人困惑,你仍然需要彻底的学习理解它们,因为它在 JavaScript 中是一个很关键的概念。如果你不知道回调,那么你无法走的长远。

这就是今天这篇文章需要讲解的!你将要学习什么是回调以及它们为什么如此重要和怎么去使用。

这篇文章中你会看到 ES6 里的箭头函数。如果你还不熟悉它们,我建议你先看看ES6 post。(只要阅读箭头函数部分)。

什么是回调?

回调是一个函数,会作为一个参数传递到另一个函数中,并稍后去执行。(开发人员说在执行函数时调用另一个函数,这就是为什么 callbacks 称之为回调的原因)。

它们在 JavaScript 中很常见,以至于你可能不知道它们是回调函数的时候已经使用过它们。

一个可以接收回调函数的例子是addEventLisnter:

const button = document.querySelector('button')button.addEventListener('click', function(e) {    // Adds clicked class to button    this.classList.add('clicked')})

登录后复制

没看出来这是个回调?来看看下个例子。

const button = document.querySelector('button')// Function that adds 'clicked' class to the elementfunction clicked (e) {    this.classList.add('clicked')}// Adds click function as a callback to the event listenerbutton.addEventListener('click', clicked)

登录后复制

这里,我们通过 JavaScript 给一个按钮绑定了click事件。一旦检测到了点击时间,JavaScript 会执行clicked函数。所以,在这个例子中,当addEventListener函数接收一个回调函数时,clicked是一个回调。

现在知道回调是什么了么?:)

我们来看看另外一个例子。这一次,我们假设你想过滤一个数字数组来得到一个小于5的列表。这里,你给filter函数传递了一个回调函数。

const numbers = [3, 4, 10, 20]const lesserThanFive = numbers.filter(num => num 

现在,如果你把上面的代码用具名函数改一下,那么过滤数组就会变成这样:

const numbers = [3, 4, 10, 20]const getLessThanFive = num => num 

在这个例子中,getLessThanFive是个回调。Array.filter是一个可以接收回调的函数。

现在看看?当你知道回调后会发现无处不在。

下面这个例子告诉你怎么写一个回调函数和一个可以接收回调的函数。

// Create a function that accepts another function as an argumentconst callbackAcceptingFunction = (fn) => {    // Calls the function with any required arguments    return fn(1, 2, 3)}// Callback gets arguments from the above callconst callback = (arg1, arg2, arg3) => {    return arg1 + arg2 + arg3}// Passing a callback into a callback accepting functionconst result = callbackAcceptingFunction(callback)console.log(result) // 6

登录后复制

请注意,当你把回调传给另一个函数时,只是把引用传递过去了(不执行,因此没有())

`const result = callbackAcceptingFunction(callback)`

登录后复制

你只能在callbackAcceptingFunction里调用这个回调当你这么做时,你可以给这个回调函数传递可能需要任意数量的参数:

const callbackAcceptingFunction = (fn) => {    // Calls the callback with three args    fn(1, 2, 3)}

登录后复制

这些参数通过callbackAcceptingFunction传递到回调里,然后用它们的方式在回调里进行传递:

// Callback gets arguments from callbackAcceptingFunctionconst callback = (arg1, arg2, arg3) => {    return arg1 + arg2 + arg3}

登录后复制

这是一个回调的结构。现在,你知道了addEventListener包含了event参数:

// Now you know where this event object comes from! :)button.addEventListener('click', (event) => {    event.preventDefault()})

登录后复制

唷!这是回调的基本含义!只要记住关键字:将一个函数传递到另一个函数中,你将回想起上面提到的机制。

这种传递函数的能力是一个很大的事情。它是如此之大,以至于 JavaScript 中的函数都是高阶函数。高阶函数是函数式编程范式中非常重要的东西。

但我们现在并不讨论这个话题。现在,我确信你已经知道了回调以及如何使用了。但是,你为什么需要使用回调呢?

为什么使用回调?

回调有二种不同的使用方式 - 在同步函数和在异步函数中。

同步函数中的回调

如果你的代码执行是一个从上到下,从做到右的方式,顺序地,在下一行代码执行前会等到代码执行完成,那么你的代码是同步的。

我们来看个例子,以便于更早的理解:

const addOne = (n) => n + 1addOne(1) // 2addOne(2) // 3addOne(3) // 4addOne(4) // 5

登录后复制

在上面的例子中,addOne(1)先执行。当执行完成时,addOne(2)开始执行。当addOne(2)执行完成时,addOne(3)开始执行。这个过程一直执行到最后一行代码被执行。

但你想让一部分代码跟其他交换简单时,这时候可以在同步的函数里使用回调。

所以,回到上面的Array.filter例子,虽然过滤数组让其包含小于5的数字,同样地你也可以复用Array.filter去包含大于10 的数字。

const numbers = [3, 4, 10, 20]const getLessThanFive = num => num  num > 10// Passing getLessThanFive function into filterconst lesserThanFive = numbers.filter(getLessThanFive)// Passing getMoreThanTen function into filterconst moreThanTen = numbers.filter(getMoreThanTen)

登录后复制

这是你为什么在同步函数中使用回调。现在,让我们继续看看为什么我们在异步函数里使用回调。

异步函数里的回调

这里异步的意思是,如果 JavaScript 需要等待某个东西完成,在等待的过程中会执行其余的任务。

一个异步函数例子就是setTimeout。它会一段时间后执行回调函数。

// Calls the callback after 1 secondsetTimeout(callback, 1000)

登录后复制

如果你给JavaScript 另一个任务去完成时我们看看setTimeout是怎么工作的:

const tenSecondsLater = _ = > console.log('10 seconds passed!')setTimeout(tenSecondsLater, 10000)console.log('Start!')

登录后复制

在上面的代码里,JavaScript 去执行setTimeout。这时,会等待10秒且打印日志“10 seconds passed!”。

同时,在等到10秒去执行setTimeout时,JavaScript 会执行console.log("Start!")。

因此,如果你记录上面的代码,你会看到这一点。

// What happens:// > Start! (almost immediately)// > 10 seconds passed! (after ten seconds)

登录后复制

啊。异步操作听起来很复杂,不是么?但是我们为什么在 JavaScript 里到处使用呢?

要理解为什么异步操作很重要,想象一下 JavaScript 是你家里的一个机器人助手。这个助手很蠢。一次只能做一件事情。(这个行为称之为单线程)。

假设你告诉机器人助手帮你订点披萨。但是机器人助手如此蠢,在给披萨店打完电话后,机器人助手坐在你家门前,慢慢的等待披萨送来。在这个过程中不能做任何其他的事情。

等待的过程中,你不能让它去熨烫衣服,拖地板以及其他任何事情。你需要等20分钟,直到披萨送来,才愿意做其他的事情。

这个行为称之为阻塞。在等待一个任务执行完全之前,其他的操作被阻止了。

const orderPizza = flavour => {    callPizzaShop(`I want a ${flavour} pizza`)    waits20minsForPizzaToCome() // Nothing else can happen here    bringPizzaToYou()}orderPizza('Hawaiian')// These two only starts after orderPizza is completedmopFloor()ironClothes()

登录后复制

现在,阻塞操作是非常令人失望的。

为什么?

我们把愚蠢的机器人助手放在浏览器的运行环境里。想象一下,当按钮被点击时需要改变按钮的颜色。

那这个愚蠢的机器人会怎么做呢?

它会凝视着这个按钮,在按钮被点击之前,忽略掉其他任何的命令。同时,用户不能选择其他任何东西。看看现在这样的情况?这就是异步编程在 JavaScript 为什么如此重要。

但是真正理解在异步操作过程中发生了什么,我们需要理解另外一个东西-事件循环。

事件循环

想象事件循环,可以想象 JavaScript 是一个 todo-list 的管家。这个列表包含了所有你告诉它的事情。JavaScript 会按照你给的顺序,一步步的遍历这个列表。

假设你给JavaScript 的5个命令如下:

const addOne = (n) => n + 1addOne(1) // 2addOne(2) // 3addOne(3) // 4addOne(4) // 5addOne(5) // 6

登录后复制

这将会出现在 JavaScript 的todo 列表里。

1.png

命令在 JavaScript 的 todo 列表里同步显示。

除了 todo 列表,JavaScript 还保存了一个 waiting 列表,这个列表可以跟踪需要等待的东西。如果你告诉 JavaScript 需要定披萨,它会给披萨店打电话,并把"等待披萨送来"加到等到列表里。同时,它会做 todo 列表已经有的事情。

所以,想象一下有这样的代码。

const orderPizza (flavor, callback) {    callPizzaShop(`I want a ${flavor} pizza`)    // Note: these three lines is pseudo code, not actual JavaScript    whenPizzaComesBack {        callback()    }}const layTheTable = _ => console.log('laying the table')orderPizza('Hawaiian', layTheTable)mopFloor()ironClothes()

登录后复制

JavaScript 的初始列表将会是:

2.png

定披萨,拖地和熨烫衣服!

这是,当执行到orderPizza,JavaScript 知道需要等待披萨送来。因此,在把"等待披萨送来"加到等待列表中的同时会处理剩下的工作。

3.png

JavaScript 等待披萨到达。

当披萨送到时,按门铃会通知 JavaScript并做一个标记,当处理完其他杂事时,会去执行layTheTable。

4.png

JavaScript 知道通过标记里的命令需要去执行layTheTable。

然后,一旦处理完了其他的杂务,JavaScript 就会执行回调函数layTheTable。

5.png

当其他一切都完成时, JavaScript 会将其放置。

这就是我的朋友,事件循环。你可以用事件循环中的实际关键字来替代我们的巴特勒类比来理解所有的事情。

Todo-list-> Call stack

Waiting-list-> Web apis

Mental note-> Event queue

6.png

JavaScript 事件循环

如果你有20分钟空闲时间的话,我强烈推荐你看Philip Roberts在 JSConf 上关于事件循环的演讲。它会帮助你了解事件循环里的细节。

为什么回调如此重要?

哦。我们在事件循环上转了个大圈。现在我们回头来看。

之前,我们提到如果 JavaScript 专注地盯着一个按钮并忽略其他所有的命令,这是非常糟糕的。是吧?

通过异步回调,我们可以提前给 JavaScript 指令而不需要停止整个操作。

现在,当你让 JavaScript 监听一个按钮的点击事件时,它将”监听按钮”放在等待列表里,然后继续做家务。当按钮最终获取到点击事件时,JavaScript 会激活回调,然后继续运行

下面是一些常见的回调函数,告诉 JavaScript 应该怎么做:

当事件被触发(比如:addEventListener)

Ajax 执行之后(比如:jQuery.ajax)

文件读写之后(比如:fs.readFile)

// Callbacks in event listenersdocument.addEventListener(button, highlightTheButton)document.removeEventListener(button, highlightTheButton)// Callbacks in jQuery's ajax method$.ajax('some-url', {    success (data) { /* success callback */ },    error (err) { /* error callback */}});// Callbacks in Nodefs.readFile('pathToDirectory', (err, data) => {    if (err) throw err    console.log(data)})// Callbacks in ExpressJSapp.get('/', (req, res) => res.sendFile(index.html))

登录后复制

这就是回调!

希望,你现在已经弄清楚了回调是什么并且怎么去使用。在最开始的时候,你没必要创建很多的回调,更多的去专注于学习如何使用可用的回调函数。

现在,在结束之前,我们来看看回调的第一个问题 – 回调地狱

回调地狱

回调地狱是在多个回调嵌套出现时的一个现象。它发生在一个异步回调执行依赖上一个异步回调执行的时候。这些嵌套的回调会导致代码非常难以理解。

在我的经验里,你只会在 Node.js 里看到回调地狱。当你的 JavaScript 在前台运行时一般都不会遇到回调地狱。

这里有一个回调地狱的例子:

// Look at three layers of callback in this code!app.get('/', function (req, res) {    Users.findOne({ _id:req.body.id }, function (err, user) {        if (user) {            user.update({/* params to update */}, function (err, document) {            res.json({user: document})        })        } else {            user.create(req.body, function(err, document) {                res.json({user: document})            })        }     })})

登录后复制

现在,对你来说,解读上面的代码是一个挑战。相当的难,不是么?难怪在看到嵌套回调时,开发人员会不寒而栗。

解决回调的一个解决方案是将回调函数分解成更小的部分,以减少嵌套代码的数量

const updateUser = (req, res) => {    user.update({/* params to update */}, function () {        if (err) throw err;        return res.json(user)    })}const createUser = (req, res, err, user) => {    user.create(req.body, function(err, user) {        res.json(user)    })}app.get('/', function (req, res) {    Users.findOne({ _id:req.body.id }, (err, user) => {        if (err) throw err        if (user) {            updateUser(req, res)        } else {            createUser(req, res)        }    })})

登录后复制

阅读起来容易得多,不是么?

在新的JavaScript 版本里,还有一些新的解决回调地狱的方法,比如: promises 和 async/await。但是,会在另一个话题中解析它们。

结语

今天,我们学习了什么是回调,为什么会如此重要以及如何去使用它们。同时学习到了什么是回调地狱,以及如何避免。希望,回调不会让你感到害怕。

关于回调你还有其他的问题么?如果你有的话,请在下面留言,我会尽快回复你的。

相关免费学习推荐:js视频教程

以上就是了解JS中的回调的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2635134.html

(0)
上一篇 2025年3月7日 04:40:03
下一篇 2025年2月24日 11:45:32

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 详解JavaScript中的回调函数并使用它

    在JavaScript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用。既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回。 因为函数是第一类对象,我们可以在JavaS…

    2025年3月7日
    200
  • 开发react用什么工具?

    react可以用的开发工具有:1、Chrome React Dev Tools;2、React Sight;3、React Bootstrap;4、Create React App;5、React Styleguideist等等。 适用于所…

    2025年3月7日 编程技术
    200
  • 详细了解javascript中的modules、import和export

    在互联网的洪荒时代,网站主要用 HTML和 CSS 开发的。如果将 JavaScript 加载到页面中,通常是以小片段的形式提供效果和交互,一般会把所有的 JavaScript 代码全都写在一个文件中,并加载到一个 script 标签中。尽…

    2025年3月7日
    200
  • JavaScript今年25 岁了!

    javascript视频教程栏目介绍javascript的发展历程 相关免费学习推荐:javascript视频教程 最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。 本文已过…

    2025年3月7日
    200
  • node.js“多线程”如何处理高并发任务?

    下面本篇文章给大家介绍一下使用 nodejs “多线程”处理高并发任务的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 相关推荐:《nodejs视频教程》 摩尔定律 摩尔定律是由英特尔联合创始人戈登·摩尔(Gordo…

    2025年3月7日 编程技术
    200
  • 了解TypeScript中泛型(Generics)的概念和用法

    本文介绍TypeScript中泛型(Generics)的概念和用法,它为什么重要,及其使用场景。我们会以一些清晰的例子,介绍其语法,类型和如何构建参数。你可以在你的集成开发环境中跟着实践。 准备工作 要从本文中跟着学习的话,你需要在电脑上准…

    2025年3月7日 编程技术
    200
  • 10个你可能不知道的很棒的JS字符串技巧

    相关推荐:《javascript视频教程》 我们称一个字符序列为字符串。这几乎是所有编程语言中都有的基本类型之一。这里跟大家展示关于 JS 字符串的10个很棒的技巧,你可能还不知道哦? 1.如何多次复制一个字符串 JS 字符串允许简单的重复…

    2025年3月7日 编程技术
    200
  • javascript需要搭建环境吗

    如果是在html中运行javascript代码,不需要搭建环境;因为JavaScript是运行在浏览器中的语言,不需要搭建环境,只需要浏览器就可运行了。如果直接运行JS程序,需要下载安装node.js环境。 本教程操作环境:windows7…

    2025年3月7日
    200
  • javascript与php的区别是什么

    区别:1、JavaScript是一种前端语言,而PHP是一种服务器端语言;2、php的字符串连接符是“.”,JavaScript的字符串连接符是“+”;3、php只有变量名区分大小写,JavaScript全部区分大小写。 本教程操作环境:w…

    2025年3月7日
    200
  • javascript和SQL有什么区别

    区别:JavaScript是一种基于原型编程、多范式的动态脚本语言,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。而SQL是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。 本教程操作环境…

    2025年3月7日
    200

发表回复

登录后才能评论