JavaScript中的回调是什么?
当你开始学习JavaScript时,你很快就会听到“回调函数”这个术语。回调是JavaScript执行模型中不可分割的一部分,很重要的是要很好地理解它们是什么以及它们是如何工作的。
什么是JavaScript回调?
在JavaScript中,回调是作为参数传递给第二个函数的函数。接收回调函数的函数决定是否以及何时执行回调:
函数myFunction(回调){/ / 1。做某事/ / 2。然后执行回调回调()}函数myCallback(){//做点别的}myFunction(myCallback);
在上面的例子中,我们有两个函数:myFunction
而且myCallback
.顾名思义,myCallback
用作回调函数,我们将它传递给myFunction
作为一个论证。myFunction
然后可以在准备执行回调时执行回调。
很多博客文章会说必威滚回调之所以被称为回调是因为你告诉某个函数当它准备好回答时就回调你。一个不那么容易混淆的名字是“callafter”:也就是说,调用这个函数后你已经完成了所有的事情。
为什么我们需要回调函数?
你会经常听到人们说JavaScript是单线程的.这意味着它一次只能做一件事。当执行较慢的操作时——比如从远程API获取数据——这可能会有问题。如果您的程序冻结直到数据返回,这将不是一个很好的用户体验。
JavaScript避免这种瓶颈的方法之一是使用回调。我们可以将第二个函数作为参数传递给负责获取数据的函数。然后开始数据获取请求,但JavaScript解释器不会等待响应,而是继续执行程序的其余部分。当从API接收到响应时,执行回调函数,并可以对结果做一些事情:
函数fetchData(url,cb){/ / 1。对url进行API请求/ / 2。如果响应成功,执行回调cb(res);}函数回调(res){//做一些有成效的事情}//做某事fetchData(“https://必威西盟体育网页登录sitepoint.com”,回调);//做点别的
JavaScript是一种事件驱动语言
你还会听到有人说JavaScript是一种事件驱动的语言.这意味着它可以侦听和响应事件,同时继续执行进一步的代码,而不会阻塞它的单个线程。
它是怎么做到的呢?你猜对了:回调。
想象一下,如果您的程序将一个事件监听器附加到一个按钮上,然后坐在那里等待某人点击该按钮,同时拒绝做任何其他事情。那可不太好!
使用回调,我们可以指定特定的代码块应该运行以响应特定的事件:
函数handleClick(){//做一些事情(例如验证一个表单)//在用户点击按钮时的响应}文档.querySelector(“按钮”).addEventListener(“点击”,handleClick);
在上面的例子中,handleClick
函数是一个回调,它是在响应web页面上发生的动作(单击按钮)时执行的。
使用这种方法,我们可以对任意多的事件做出反应,同时让JavaScript解释器自由地继续它需要做的任何其他事情。
一级和高阶函数
在学习回调时,您可能还会遇到“一级函数”和“高阶函数”。这些听起来很可怕,但实际上并不可怕。
当我们说JavaScript支持一级函数,这意味着我们可以像对待常规值一样对待函数。我们可以将它们存储在一个变量中,我们可以从另一个函数中返回它们,正如我们已经看到的,我们可以将它们作为参数传递。
至于高阶函数,这些简单的函数要么接受一个函数作为参数,要么返回一个函数作为结果。有几个原生JavaScript函数也是高阶函数,例如setTimeout
.让我们使用它来演示如何创建和运行回调。
如何创建回调函数
模式与上面相同:创建一个回调函数,并将其作为参数传递给高阶函数:
函数问候(){控制台.日志(“Hello, World !”);}setTimeout(问候,1000);
的setTimeout
函数执行问候
函数,延迟1秒,并记录“Hello, World!”对控制台说。
注:如果你不熟悉setTimeout
,看看我们的流行setTimeout JavaScript函数:示例指南.
我们也可以让它稍微复杂一点,传递问候
函数表示需要问候的人的名字:
函数问候(的名字){控制台.日志(`你好,$ {的名字}!`);}setTimeout(()= >问候(“吉姆”),1000);
注意,我们使用了一个箭头函数来包装对的原始调用问候
.如果我们没有这样做,函数将立即执行,而不是延迟执行。
如您所见,在JavaScript中有多种创建回调的方法,这很好地将我们带到了下一节。
不同类型的回调函数
部分得益于JavaScript对一级函数的支持,在JavaScript中有多种声明函数的方式,因此在回调中也有多种使用它们的方式。
现在让我们来看看这些,并考虑它们的优点和缺点。
匿名函数
到目前为止,我们一直在给函数命名。这通常被认为是很好的做法,但它绝不是强制性的。考虑下面的例子,它使用回调函数来验证一些表单输入:
文档.querySelector(“形式”).addEventListener(“提交”,函数(e){e.preventDefault();//执行一些数据验证//如果一切正常,那么…这.提交();});
如您所见,回调函数没有命名。没有名称的函数定义称为匿名函数.匿名函数在只在一个地方调用函数的简短脚本中非常有用。并且,当它们被声明为内联时,它们也可以访问它们的父作用域。
箭头功能
箭头函数在ES6中引入。由于它们简洁的语法,并且因为它们有一个隐式的返回值,它们经常被用于执行简单的一行程序,例如下面的例子,它从数组中过滤重复的值:
常量加勒比海盗=[1,2,2,3.,4,5,5];常量独特的=加勒比海盗.过滤器((埃尔,我)= >加勒比海盗.indexOf(埃尔)= = =我);// [1,2,3,4,5]
但是要注意,它们并不绑定自己的这
值,而不是从它们的父作用域继承。这意味着,在前面的例子中,我们不能使用箭头函数来提交表单:
文档.querySelector(“形式”).addEventListener(“提交”,(e)= >{...//未捕获的TypeError: this。Submit不是一个函数// this指向窗口对象,而不是窗体这.提交();});
箭头函数是近年来我最喜欢的JavaScript功能之一,它们绝对是开发人员应该熟悉的东西。如果你想了解更多关于箭头函数的知识,请查看我们的JavaScript中的箭头函数:如何使用胖和简洁的语法教程。
命名功能
在JavaScript中创建命名函数主要有两种方法:函数表达式和函数声明.两者都可以与回调一起使用。
函数声明方法创建函数函数
关键字并给它一个名称:
函数myCallback(){...}setTimeout(myCallback,1000);
函数表达式包括创建一个函数并将其赋值给一个变量:
常量myCallback=函数(){...};setTimeout(myCallback,1000);
或者:
常量myCallback=()= >{...};setTimeout(myCallback,1000);
属性声明的匿名函数也可以标记函数
关键字:
setTimeout(函数myCallback(){...},1000);
以这种方式命名或标记回调函数的优点是有助于调试。让我们的函数抛出一个错误:
setTimeout(函数myCallback(){扔新错误(“轰!”);},1000);//未捕获错误:Boom!// myCallback文件:///home/jim/Desktop/index.js:18// setTimeout处理程序*文件:///home/jim/Desktop/index.js:18
使用命名函数,我们可以准确地看到错误发生的位置。然而,看看当我们去掉名字时会发生什么:
setTimeout(函数(){扔新错误(“轰!”);},1000);//未捕获错误:Boom!// <匿名>文件:///home/jim/Desktop/index.js:18// setTimeout处理程序*文件:///home/jim/Desktop/index.js:18
在这个独立的小示例中,这不是什么大问题,但随着代码库的增长,这是需要注意的问题。甚至还有一个ESLint规则执行这种行为。
JavaScript回调函数的常见用例
JavaScript回调函数的用例广泛而多样。正如我们所看到的,它们在处理异步代码(如Ajax请求)和响应事件(如表单提交)时非常有用。现在让我们再看几个回调的地方。
数组的方法
遇到回调的另一个地方是在JavaScript中使用数组方法时。这是你在编程过程中会越来越多地做的事情。例如,假设你想对一个数组中的所有数字求和,考虑以下简单的实现:
常量加勒比海盗=[1,2,3.,4,5];让合计=0;为(让我=0;我<加勒比海盗.长度;我++){合计+ =加勒比海盗[我];}控制台.日志(合计);/ / 15
虽然这可以工作,但可以使用更简洁的实现Array.reduce
你猜对了,它使用回调对数组中的所有元素执行操作:
常量加勒比海盗=[1,2,3.,4,5];常量合计=加勒比海盗.减少((acc,埃尔)= >acc+埃尔);控制台.日志(合计);/ / 15
node . js
还应该指出的是node . js它的整个生态系统严重依赖于基于回调的代码。例如,下面是规范Hello, World!例子:
常量http=需要(“http”);http.createServer((请求,响应)= >{响应.writeHead(200);响应.结束(“Hello, World !”);}).听(3000);控制台.日志(“服务器运行在http://localhost:3000”);
无论您是否使用过Node,这段代码都应该很容易理解。本质上,我们需要Nodehttp
模块,并调用createServer
方法,我们向其传递一个匿名箭头函数。每当Node在端口3000上接收到请求时,都会调用此函数,它将以200状态和文本“Hello, World!”
节点还实现了一个称为error-first回调.这意味着回调的第一个参数为错误对象保留,而回调的第二个参数为任何成功响应数据保留。
下面是Node文档中的一个例子,展示了如何读取文件:
常量fs=需要(“fs”);fs.readFile(“/ etc /主机”,“use utf8”,函数(犯错,数据){如果(犯错){返回控制台.日志(犯错);}控制台.日志(数据);});
在本教程中,我们不想深入研究Node,但希望这类代码现在应该更容易阅读。
同步与异步回调
一个回调是同步执行还是异步执行取决于调用它的函数。让我们看几个例子。
同步回调函数
当代码是同步它从上到下,一行一行地运行。操作一个接一个地发生,每个操作都等待前一个操作完成。中已经看到了同步回调的示例Array.reduce
上面的函数。
为了进一步说明这一点,这里有一个使用这两种方法的演示Array.map
而且Array.reduce
计算以逗号分隔的数字列表中最大的数:
看钢笔
回到基础:JavaScript中的回调函数是什么?(1)由Si必威西盟体育网页登录tePoint (@必威西盟体育网页登录SitePoint)
在CodePen.
主要动作发生在这里:
常量最高=输入.价值.取代(/\ s +/,”).分裂(”、“).地图((埃尔)= >数量(埃尔)).减少((acc,瓦尔)= >(acc>瓦尔)?acc:瓦尔);
从上到下,我们做以下事情:
- 获取用户的输入李><李>删除任何空白李><李>分隔逗号处的输入,从而创建一个字符串数组李><李>使用回调函数映射数组的每个元素,将字符串转换为数字李><李>使用
减少
遍历数字数组以确定最大的数字李>
为什么不尝试一下CodePen上的代码,并尝试修改回调以产生不同的结果(例如找到最小的数字,或所有奇数,等等)。
异步回调函数
与同步代码相比,异步JavaScript代码不会逐行从上到下运行。相反,异步操作将注册一个回调函数,以便在完成后执行。这意味着JavaScript解释器不必等待异步操作完成,而是可以在运行时继续执行其他任务。
异步函数的一个主要示例是从远程API获取数据。现在让我们看一个这样的例子,了解它是如何使用回调的。
看钢笔
回到基础:JavaScript中的回调函数是什么?(2)由Si必威西盟体育网页登录tePoint (@必威西盟体育网页登录SitePoint)
在CodePen.
主要动作发生在这里:
获取(“https://jsonplaceholder.typicode.com/users”).然后(响应= >响应.json()).然后(json= >{常量的名字=json.地图(用户= >用户.的名字);的名字.forEach(的名字= >{常量李=文档.createElement(“李”);李.textContent=的名字;ul.列表末尾(李);});});
上面示例中的代码使用FetchAPI向一个虚假的JSON API发送一个虚假用户列表的请求。一旦服务器返回响应,我们就运行第一个回调函数,该函数试图将响应解析为JSON。在此之后,运行第二个回调函数,它构造一个用户名列表并将它们附加到列表中。注意,在第二个回调中,我们使用另外两个嵌套回调来检索名称和创建列表元素。
再一次,我鼓励您尝试一下这些代码。如果你看一下API文档,还有很多其他资源可以获取和操作。
使用回调时需要注意的事情
回调在JavaScript中已经存在很长时间了,它们可能并不总是最适合您正在尝试做的事情。让我们看几件需要注意的事情。
小心JavaScript回调地狱
我们在上面的代码中看到,可以嵌套回调。这在处理相互依赖的异步函数时尤其常见。例如,您可以在一个请求中获取一个电影列表,然后使用该电影列表获取每个电影的海报。
虽然这对于一到两层嵌套是可以的,但您应该意识到这种回调策略不能很好地伸缩。不久之后,你就会得到混乱而难以理解的代码:
获取('...').然后(响应= >响应.json()).然后(json= >{//做一些处理获取('...').然后(响应= >响应.json()).然后(json= >{//做更多的处理获取('...').然后(响应= >响应.json()).然后(json= >{//进行均匀处理获取('...').然后(响应= >响应.json()).然后(json= >{//执行更多的处理});});});});
这被亲切地称为回调地狱,我们有一篇文章专门介绍如何避免这种情况:从回调地狱中拯救出来.
更喜欢更现代的流量控制方法
虽然回调是JavaScript工作方式中不可或缺的一部分,但该语言的最新版本增加了改进的流控制方法。
例如,承诺和异步等待…
为处理上述代码提供更清晰的语法。虽然超出了本文的范围,但您可以在JavaScript承诺概述而且现代JS中的流控制:对promise的回调到Async/Await.
结论
在本文中,我们研究了回调到底是什么。我们了解了JavaScript执行模型的基础知识,回调函数如何适用于该模型,以及为什么需要它们。我们还了解了如何创建和使用回调、不同类型的回调以及何时使用它们。现在您应该已经牢牢掌握了如何使用JavaScript中的回调,并能够在自己的代码中使用这些技术。
我们希望你喜欢阅读。如果你有任何意见或问题,请随时与我联系推特.