目录
什么是回调?" >什么是回调?
为什么使用回调?" >为什么使用回调?
为什么回调如此重要?" >为什么回调如此重要?
回调地狱" >回调地狱
结语" >结语
首页 web前端 js教程 了解JS中的回调

了解JS中的回调

Oct 21, 2020 pm 05:47 PM
javascript 回调

了解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 element
function clicked (e) {
    this.classList.add('clicked')
}

// Adds click function as a callback to the event listener
button.addEventListener('click', clicked)
登录后复制

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

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

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

const numbers = [3, 4, 10, 20]
const lesserThanFive = numbers.filter(num => num < 5)
登录后复制

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

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

// Passing getLessThanFive function into filter
const lesserThanFive = numbers.filter(getLessThanFive)
登录后复制

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

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

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

// Create a function that accepts another function as an argument
const callbackAcceptingFunction = (fn) => {
    // Calls the function with any required arguments
    return fn(1, 2, 3)
}

// Callback gets arguments from the above call
const callback = (arg1, arg2, arg3) => {
    return arg1 + arg2 + arg3
}

// Passing a callback into a callback accepting function
const 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 callbackAcceptingFunction
const callback = (arg1, arg2, arg3) => {
    return arg1 + arg2 + arg3
}
登录后复制

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

// Now you know where this event object comes from! :)
button.addEventListener(&#39;click&#39;, (event) => {
    event.preventDefault()
})
登录后复制

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

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

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

为什么使用回调?

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

同步函数中的回调

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

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

const addOne = (n) => n + 1
addOne(1) // 2
addOne(2) // 3
addOne(3) // 4
addOne(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 < 5
const getMoreThanTen = num => num > 10

// Passing getLessThanFive function into filter
const lesserThanFive = numbers.filter(getLessThanFive)

// Passing getMoreThanTen function into filter
const moreThanTen = numbers.filter(getMoreThanTen)
登录后复制

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

异步函数里的回调

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

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

// Calls the callback after 1 second
setTimeout(callback, 1000)
登录后复制

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

const tenSecondsLater = _ = > console.log(&#39;10 seconds passed!&#39;)

setTimeout(tenSecondsLater, 10000)
console.log(&#39;Start!&#39;)
登录后复制

在上面的代码里,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(&#39;Hawaiian&#39;)

// These two only starts after orderPizza is completed
mopFloor()
ironClothes()
登录后复制

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

为什么?

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

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

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

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

事件循环

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

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

const addOne = (n) => n + 1

addOne(1) // 2
addOne(2) // 3
addOne(3) // 4
addOne(4) // 5
addOne(5) // 6
登录后复制

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

了解JS中的回调

命令在 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(&#39;laying the table&#39;)

orderPizza(&#39;Hawaiian&#39;, layTheTable)
mopFloor()
ironClothes()
登录后复制

JavaScript 的初始列表将会是:

了解JS中的回调

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

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

了解JS中的回调

JavaScript 等待披萨到达。

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

了解JS中的回调

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

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

了解JS中的回调

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

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

  • Todo-list-> Call stack

  • Waiting-list-> Web apis

  • Mental note-> Event queue

了解JS中的回调

JavaScript 事件循环

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

为什么回调如此重要?

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

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

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

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

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

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

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

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

// Callbacks in event listeners
document.addEventListener(button, highlightTheButton)
document.removeEventListener(button, highlightTheButton)

// Callbacks in jQuery&#39;s ajax method
$.ajax(&#39;some-url&#39;, {
    success (data) { /* success callback */ },
    error (err) { /* error callback */}
});

// Callbacks in Node
fs.readFile(&#39;pathToDirectory&#39;, (err, data) => {
    if (err) throw err
    console.log(data)
})

// Callbacks in ExpressJS
app.get(&#39;/&#39;, (req, res) => res.sendFile(index.html))
登录后复制

这就是回调!

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

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

回调地狱

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

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

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

// Look at three layers of callback in this code!
app.get(&#39;/&#39;, 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(&#39;/&#39;, 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 版本里,还有一些新的解决回调地狱的方法,比如: promisesasync/await。但是,会在另一个话题中解析它们。

结语

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

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

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

以上是了解JS中的回调的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1662
14
CakePHP 教程
1418
52
Laravel 教程
1311
25
PHP教程
1261
29
C# 教程
1234
24
如何使用WebSocket和JavaScript实现在线语音识别系统 如何使用WebSocket和JavaScript实现在线语音识别系统 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript实现在线语音识别系统引言:随着科技的不断发展,语音识别技术已经成为了人工智能领域的重要组成部分。而基于WebSocket和JavaScript实现的在线语音识别系统,具备了低延迟、实时性和跨平台的特点,成为了一种被广泛应用的解决方案。本文将介绍如何使用WebSocket和JavaScript来实现在线语音识别系

WebSocket与JavaScript:实现实时监控系统的关键技术 WebSocket与JavaScript:实现实时监控系统的关键技术 Dec 17, 2023 pm 05:30 PM

WebSocket与JavaScript:实现实时监控系统的关键技术引言:随着互联网技术的快速发展,实时监控系统在各个领域中得到了广泛的应用。而实现实时监控的关键技术之一就是WebSocket与JavaScript的结合使用。本文将介绍WebSocket与JavaScript在实时监控系统中的应用,并给出代码示例,详细解释其实现原理。一、WebSocket技

如何利用JavaScript和WebSocket实现实时在线点餐系统 如何利用JavaScript和WebSocket实现实时在线点餐系统 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket实现实时在线点餐系统介绍:随着互联网的普及和技术的进步,越来越多的餐厅开始提供在线点餐服务。为了实现实时在线点餐系统,我们可以利用JavaScript和WebSocket技术。WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端与服务器的实时双向通信。在实时在线点餐系统中,当用户选择菜品并下单

如何使用WebSocket和JavaScript实现在线预约系统 如何使用WebSocket和JavaScript实现在线预约系统 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript实现在线预约系统在当今数字化的时代,越来越多的业务和服务都需要提供在线预约功能。而实现一个高效、实时的在线预约系统是至关重要的。本文将介绍如何使用WebSocket和JavaScript来实现一个在线预约系统,并提供具体的代码示例。一、什么是WebSocketWebSocket是一种在单个TCP连接上进行全双工

JavaScript和WebSocket:打造高效的实时天气预报系统 JavaScript和WebSocket:打造高效的实时天气预报系统 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的实时天气预报系统引言:如今,天气预报的准确性对于日常生活以及决策制定具有重要意义。随着技术的发展,我们可以通过实时获取天气数据来提供更准确可靠的天气预报。在本文中,我们将学习如何使用JavaScript和WebSocket技术,来构建一个高效的实时天气预报系统。本文将通过具体的代码示例来展示实现的过程。We

简易JavaScript教程:获取HTTP状态码的方法 简易JavaScript教程:获取HTTP状态码的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教程:如何获取HTTP状态码,需要具体代码示例前言:在Web开发中,经常会涉及到与服务器进行数据交互的场景。在与服务器进行通信时,我们经常需要获取返回的HTTP状态码来判断操作是否成功,根据不同的状态码来进行相应的处理。本篇文章将教你如何使用JavaScript获取HTTP状态码,并提供一些实用的代码示例。使用XMLHttpRequest

javascript中如何使用insertBefore javascript中如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用于在DOM树中插入一个新的节点。这个方法需要两个参数:要插入的新节点和参考节点(即新节点将要被插入的位置的节点)。

JavaScript和WebSocket:打造高效的实时图像处理系统 JavaScript和WebSocket:打造高效的实时图像处理系统 Dec 17, 2023 am 08:41 AM

JavaScript是一种广泛应用于Web开发的编程语言,而WebSocket则是一种用于实时通信的网络协议。结合二者的强大功能,我们可以打造一个高效的实时图像处理系统。本文将介绍如何利用JavaScript和WebSocket来实现这个系统,并提供具体的代码示例。首先,我们需要明确实时图像处理系统的需求和目标。假设我们有一个摄像头设备,可以采集实时的图像数

See all articles