一篇文章看懂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 相关文章推荐
JavaScript语法着色引擎(demo及打包文件下载)
Jun 13 Javascript
复制Input内容的js代码_支持所有浏览器,修正了Firefox3.5以上的问题
Jun 21 Javascript
基于Jquery实现的一个图片滚动切换
Jun 21 Javascript
jquery弹出关闭遮罩层实例
Aug 06 Javascript
JavaScript function 的 length 属性使用介绍
Sep 15 Javascript
jQuery仅用3行代码实现的显示与隐藏功能完整实例
Oct 08 Javascript
在web中js实现类似excel的表格控件
Sep 01 Javascript
微信小程序-滚动消息通知的实例代码
Aug 03 Javascript
基于require.js的使用(实例讲解)
Sep 07 Javascript
浅谈layui 表单元素的选中问题
Oct 25 Javascript
JavaScript实现简易计算器小功能
Oct 22 Javascript
如何使用vue3打造一个物料库
May 08 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
Symfony2使用第三方库Upload制作图片上传实例详解
2016/02/04 PHP
PHP递归实现层级树状展开
2016/04/01 PHP
PHPExcel简单读取excel文件示例
2016/05/26 PHP
PHP递归获取目录内所有文件的实现方法
2016/11/01 PHP
javaScript对象和属性的创建方法
2007/01/15 Javascript
jQuery 对象中的类数组操作
2009/04/27 Javascript
jQuery maxlength文本字数限制插件
2010/04/16 Javascript
Prototype源码浅析 String部分(三)之HTML字符串处理
2012/01/15 Javascript
引用外部脚本时script标签关闭的写法
2014/01/20 Javascript
点击标签切换和自动切换DIV选项卡
2014/08/10 Javascript
jquery实现textarea输入框限制字数的方法
2015/01/15 Javascript
jQuery实现鼠标双击Table单元格变成文本框及输入内容后更新到数据库的方法
2015/11/25 Javascript
JavaScript事件学习小结(三)js事件对象
2016/06/09 Javascript
Node.js如何自动审核团队的代码
2016/07/20 Javascript
jQuery弹出层后禁用底部滚动条(移动端关闭回到原位置)
2016/08/29 Javascript
微信小程序中实现一对多发消息详解及实例代码
2017/02/14 Javascript
Vue中添加过渡效果的方法
2017/03/16 Javascript
基于JavaScript实现验证码功能
2017/04/01 Javascript
JavaScript实现提交模式窗口后刷新父窗口数据的方法
2017/06/16 Javascript
ztree简介_动力节点Java学院整理
2017/07/19 Javascript
详解Vue demo实现商品列表的展示
2019/05/07 Javascript
使用vue制作滑动标签
2019/09/21 Javascript
Node Mongoose用法详解【Mongoose使用、Schema、对象、model文档等】
2020/05/13 Javascript
[09:33]2015国际邀请赛第四日TOP10
2015/08/08 DOTA
[00:35]可解锁地面特效
2018/12/20 DOTA
Python中绑定与未绑定的类方法用法分析
2016/04/29 Python
Python实现计算字符串中出现次数最多的字符示例
2019/01/21 Python
详解Python 切片语法
2019/06/10 Python
利用CSS3实现毛玻璃效果示例源码
2016/09/25 HTML / CSS
HTML5 History API 实现无刷新跳转
2016/01/11 HTML / CSS
俄语地区最大的中国商品在线购物网站之一:Umka Mall
2019/11/03 全球购物
学校消防演习方案
2014/02/19 职场文书
设备管理实施方案
2014/05/31 职场文书
车辆年检委托书范本
2014/10/14 职场文书
违纪检讨书范文
2015/01/27 职场文书
【海涛dota解说】DCG联赛第一周 LGD VS DH
2022/04/01 DOTA