一篇文章看懂JavaScript中的回调


Posted in Javascript onJanuary 05, 2021

前言

回调函数是每个前端程序员都应该知道的概念之一。回调可用于数组、计时器函数、promise、事件处理中。

本文将会解释回调函数的概念,同时帮你区分两种回调:同步和异步。

回调函数

首先写一个向人打招呼的函数。

只需要创建一个接受 name 参数的函数 greet(name)。这个函数应返回打招呼的消息:

function greet(name) {
 return `Hello, ${name}!`;
}

greet('Cristina'); // => 'Hello, Cristina!'

如果向很多人打招呼该怎么办?可以用特殊的数组方法  array.map() 可以实现:

const persons = ['Cristina', 'Ana'];

const messages = persons.map(greet);
messages; // => ['Hello, Cristina!', 'Hello, Ana!']

persons.map(greet) 获取 persons 数组的所有元素,并分别用每个元素作为调用参数来调用 greet() 函数:greet('Cristina'), greet('Ana')。

有意思的是 persons.map(greet) 方法可以接受 greet()  函数作为参数。这样 greet()  就成了回调函数。

persons.map(greet) 是用另一个函数作为参数的函数,因此被称为高阶函数。

回调函数作为高阶函数的参数,高阶函数通过调用回调函数来执行操作。

重要的是高阶函数负责调用回调,并为其提供正确的参数。

在前面的例子中,高阶函数 persons.map(greet) 负责调用  greet()  函数,并分别把数组中所有的元素 'Cristina' 和 Ana ' 作为参数。

这就为识别回调提供了一条简单的规则。如果你定义了一个函数,并将其作参数提供给另一个函数的话,那么这就创建了一个回调。

你可以自己编写使用回调的高阶函数。下面是 array.map() 方法的等效版本:

function map(array, callback) {
 const mappedArray = [];
 for (const item of array) { 
 mappedArray.push(
 callback(item) );
 }
 return mappedArray;
}

function greet(name) {
 return `Hello, ${name}!`;
}

const persons = ['Cristina', 'Ana'];

const messages = map(persons, greet);messages; // => ['Hello, Cristina!', 'Hello, Ana!']

map(array, callback) 是一个高阶函数,因为它用回调函数作为参数,然后在其主体内部调用该回调函数:callback(item)。

注意,常规函数(用关键字 function 定义)或箭头函数(用粗箭头 => 定义)同样可以作为回调使用。

同步回调

回调的调用方式有两种:同步和异步回调。

同步回调是“阻塞”的:高阶函数直到回调函数完成后才继续执行。

例如,调用 map() 和 greet() 函数。

function map(array, callback) {
 console.log('map() starts');
 const mappedArray = [];
 for (const item of array) { mappedArray.push(callback(item)) }
 console.log('map() completed');
 return mappedArray;
}

function greet(name) {
 console.log('greet() called');
 return `Hello, ${name}!`;
}

const persons = ['Cristina'];

map(persons, greet);
// logs 'map() starts'
// logs 'greet() called'
// logs 'map() completed'

其中 greet()  是同步回调。

同步回调的步骤:

  1. 高阶函数开始执行:'map() starts'
  2. 回调函数执行:'greet() called'
  3. .最后高阶函数完成它自己的执行过程:'map() completed'

同步回调的例子

许多原生 JavaScript 类型的方法都使用同步回调。

最常用的是 array 的方法,例如:array.map(callback), array.forEach(callback), array.find(callback), array.filter(callback), array.reduce(callback, init)

// Examples of synchronous callbacks on arrays
const persons = ['Ana', 'Elena'];

persons.forEach(
 function callback(name) { console.log(name);
 }
);
// logs 'Ana'
// logs 'Elena'

const nameStartingA = persons.find(
 function callback(name) { return name[0].toLowerCase() === 'a';
 }
);
nameStartingA; // => 'Ana'

const countStartingA = persons.reduce(
 function callback(count, name) { const startsA = name[0].toLowerCase() === 'a';
 return startsA ? count + 1 : count;
 }, 
 0
);
countStartingA; // => 1

字符串类型的 string.replace(callback)  方法也能接受同步执行的回调:

// Examples of synchronous callbacks on strings
const person = 'Cristina';

// Replace 'i' with '1'
person.replace(/./g, 
 function(char) { return char.toLowerCase() === 'i' ? '1' : char;
 }
); // => 'Cr1st1na'

异步回调

异步回调是“非阻塞的”:高阶函数无需等待回调完成即可完成其执行。高阶函数可确保稍后在特定事件上执行回调。

在以下的例子中,later() 函数的执行延迟了 2 秒:

console.log('setTimeout() starts');
setTimeout(function later() {
 console.log('later() called');
}, 2000);
console.log('setTimeout() completed');

// logs 'setTimeout() starts'
// logs 'setTimeout() completed'
// logs 'later() called' (after 2 seconds)

later() 是一个异步回调,因为 setTimeout(later,2000) 启动并完成了执行,但是 later() 在 2 秒后执行。

异步调用回调的步骤:

  1. 高阶函数开始执行:'setTimeout()starts'
  2. 高阶函数完成其执行:'setTimeout() completed'
  3. 回调函数在 2 秒钟后执行:'later() called'

异步回调的例子

计时器函数异步调用回调:

setTimeout(function later() {
 console.log('2 seconds have passed!');
}, 2000);
// After 2 seconds logs '2 seconds have passed!'

setInterval(function repeat() {
 console.log('Every 2 seconds');
}, 2000);
// Each 2 seconds logs 'Every 2 seconds!'

DOM 事件侦听器还异步调用事件处理函数(回调函数的子类型):

const myButton = document.getElementById('myButton');

myButton.addEventListener('click', function handler() {
 console.log('Button clicked!');
});
// Logs 'Button clicked!' when the button is clicked

4.异步回调函数与异步函数

在函数定义之前加上特殊关键字 async 会创建一个异步函数:

async function fetchUserNames() {
 const resp = await fetch('https://api.github.com/users?per_page=5');
 const users = await resp.json();
 const names = users.map(({ login }) => login);
 console.log(names);
}

fetchUserNames() 是异步的,因为它以 async 为前缀。函数  await fetch('https://api.github.com/users?per_page=5') 从 GitHub 上获取前5个用户 。然后从响应对象中提取 JSON 数据:await resp.json()。

异步函数是 promise 之上的语法糖。当遇到表达式 await <promise>  (调用  fetch()  会返回一个promise)时,异步函数会暂停执行,直到 promise 被解决。

异步回调函数和异步函数是不同的两个术语。

异步回调函数由高阶函数以非阻塞方式执行。但是异步函数在等待 promise(await <promise>)解析时会暂停执行。

但是你可以把异步函数用作异步回调!

让我们把异步函数 fetch UserNames() 设为异步回调,只需单击按钮即可调用:

const button = document.getElementById('fetchUsersButton');

button.addEventListener('click', fetchUserNames);

总结

回调是一个可以作为参数传给另一个函数(高阶函数)执行的函数。

回调函数有两种:同步和异步。

同步回调是阻塞的。

异步回调是非阻塞的。

最后考考你:setTimeout(callback,0) 执行 callback 时是同步还是异步的?

到此这篇关于JavaScript中回调的文章就介绍到这了,更多相关JavaScript的回调内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
greybox——不开新窗口看新的网页
Feb 20 Javascript
jquery 模拟类搜索框自动完成搜索提示功能(改进)
May 24 Javascript
JavaScript中valueOf函数与toString方法深入理解
Dec 02 Javascript
js中的事件捕捉模型与冒泡模型实例分析
Jan 10 Javascript
简单介绍JavaScript数据类型之隐式类型转换
Dec 28 Javascript
jQuery实现倒计时(倒计时年月日可自己输入)
Dec 02 Javascript
jQuery实现字体颜色渐变效果的方法
Mar 29 jQuery
jQuery 表单序列化实例代码
Jun 11 jQuery
jquery animate动画持续运动的实例
Nov 29 jQuery
Angularjs实现数组随机排序的方法
Oct 02 Javascript
vue如何使用rem适配
Feb 06 Vue.js
Vue全局事件总线你了解吗
Feb 24 Vue.js
原生js中运算符及流程控制示例详解
Jan 05 #Javascript
vue3.0中友好使用antdv示例详解
Jan 05 #Vue.js
基于Vue2实现移动端图片上传、压缩、拖拽排序、拖拽删除功能
Jan 05 #Vue.js
在Angular项目使用socket.io实现通信的方法
Jan 05 #Javascript
利用JavaScript为句子加标题的3种方法示例
Jan 05 #Javascript
绘制微信小程序验证码功能的实例代码
Jan 05 #Javascript
详解JavaScript中分解数字的三种方法
Jan 05 #Javascript
You might like
2020年4月放送!《Princess Connect Re:Dive》制作组 & 角色声优公开!
2020/03/06 日漫
页面乱码问题的根源及其分析
2013/08/09 PHP
php函数重载的替代方法--伪重载详解
2015/05/08 PHP
PHP里的$_GET数组介绍
2019/03/22 PHP
php+mysql开发的最简单在线题库(在线做题系统)完整案例
2019/03/30 PHP
Mootools 1.2教程 滑动效果(Slide)
2009/09/15 Javascript
JavaScript 学习技巧
2010/02/17 Javascript
jQuery+CSS 实现随滚动条增减的汽水瓶中的液体效果
2011/09/26 Javascript
JavaScript事件处理器中的event参数使用介绍
2013/05/24 Javascript
简单的代码实现jquery定时器
2014/01/03 Javascript
js获取当前时间显示在页面上并每秒刷新
2014/12/24 Javascript
js判断一个字符串是否包含一个子串的方法
2015/01/26 Javascript
javascript实现的多个层切换效果通用函数实例
2015/07/06 Javascript
JavaScript切换搜索引擎的导航网页搜索框实例代码
2017/06/11 Javascript
微信小程序实现点击按钮修改文字大小功能【附demo源码下载】
2017/12/06 Javascript
vuex中的 mapState,mapGetters,mapActions,mapMutations 的使用
2018/04/13 Javascript
vue如何将v-for中的表格导出来
2018/05/07 Javascript
微信小程序如何自定义table组件
2019/06/29 Javascript
微信小程序 授权登录详解(附完整源码)
2019/08/23 Javascript
[00:32]2018DOTA2亚洲邀请赛Secret出场
2018/04/03 DOTA
Python使用函数默认值实现函数静态变量的方法
2014/08/18 Python
Python实现的tab文件操作类分享
2014/11/20 Python
Python使用progressbar模块实现的显示进度条功能
2018/05/31 Python
python3实现带多张图片、附件的邮件发送
2019/08/10 Python
Pytorch之扩充tensor的操作
2021/03/04 Python
参观考察邀请函范文
2014/01/29 职场文书
学习十八届三中全会精神实施方案
2014/02/17 职场文书
企业管理毕业生求职信范文
2014/03/07 职场文书
意向书范文
2014/03/31 职场文书
2014年社区矫正工作总结
2014/11/18 职场文书
店长岗位职责
2015/02/11 职场文书
2015年宣传思想工作总结
2015/05/22 职场文书
个人职业生涯规划之自我评估篇
2019/09/03 职场文书
css3 实现文字闪烁效果的三种方式示例代码
2021/04/25 HTML / CSS
浅谈MySQL user权限表
2021/06/18 MySQL
Spring Boot 整合 Apache Dubbo的示例代码
2021/07/04 Java/Android