JavaScript回调函数的高手指南
摘要:本文将会解释回调函数的概念,同时帮你区分两种回调:同步和异步。
回调函数是每个前端程序员都应该知道的概念之一。回调可用于数组、计时器函数、promise、事件处理中。
本文将会解释回调函数的概念,同时帮你区分两种回调:同步和异步。
1.回调函数
首先写一个向人打招呼的函数。
只需要创建一个接受 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 定义)或箭头函数(用粗箭头 => 定义)同样可以作为回调使用。
2.同步回调
回调的调用方式有两种:同步和异步回调。
同步回调是“阻塞”的:高阶函数直到回调函数完成后才继续执行。
例如,调用 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() 是同步回调。
同步回调的步骤:
- 高阶函数开始执行:'map() starts'
- 回调函数执行:'greet() called'
- 最后高阶函数完成它自己的执行过程:'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'
3.异步回调
异步回调是“非阻塞的”:高阶函数无需等待回调完成即可完成其执行。高阶函数可确保稍后在特定事件上执行回调。
在以下的例子中,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 秒后执行。
异步调用回调的步骤:
- 高阶函数开始执行:'setTimeout()starts'
- 高阶函数完成其执行: 'setTimeout() completed'
- 回调函数在 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 时是同步还是异步的?
本文转自segmentfault:疯狂的技术宅,转载请先联系作者授权
原文链接:https://segmentfault.com/a/1190000038869766
JavaScript回调函数的高手指南的更多相关文章
- 理解 JavaScript 回调函数并使用
JavaScript中,函数是一等(first-class)对象:也就是说,函数是 Object 类型并且可以像其他一等对象(String,Array,Number等)一样使用.它们可以"保 ...
- 【JavaScript】JavaScript回调函数
什么是Javascript 回调函数? 函数和其他数据一样可以被赋值,删除,拷贝等,所以也可以把函数作为参数传入到另一个函数中. 这个函数就是所谓的回调函数 举例: //不带参数的case fun ...
- 理解javascript 回调函数
##回调函数定义 百度百科:回调函数 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数.回调函数不 ...
- JavaScript回调函数的实现
https://github.com/forsigner/blog/blob/master/source/_posts/javascript-callback.md 在JavaScript中,回调函数 ...
- JavaScript回调函数及数组方法测试
JavaScript回调函数及数组方法测试 具体代码如下: <!DOCTYPE html> <html lang="en"> <head> &l ...
- 如何定义 Java 的回调函数,与 JavaScript 回调函数的区别
JavaScript 中的回调函数 在 JavaScript 中经常使用回调函数,比如:get 请求.post 请求等异步任务.在我们请求之前以及请求之后,都需要完成一些固定的操作,比如:请求之前先从 ...
- JavaScript回调函数的理解
这里是个人对回调函数的一段理解 <!DOCTYPE html> <html> <head> <title>回调函数</title> < ...
- 关于javascript 回调函数
http://segmentfault.com/q/1010000000212522 如何避免Javascript中回调函数的嵌套? http://javascript.ruanyifeng.com/ ...
- javascript 回调函数应用
回调函数是什么在学习之前还真不知道js回调函数怎么使用及作用了,下面本文章把我在学习回调函数例子给各位同学介绍一下吧,有需了解的同学不防进入参考. 回调函数原理: 我现在出发,到了通知你”这是一个异步 ...
- javascript回调函数笔记
来源于:https://github.com/useaname/blog-study 在Javascript中,函数是第一类对象.意味函数可以像对象一样按照第一类被管理使用.回调函数是从一个叫函数式编 ...
随机推荐
- Springboot 加密方案探讨
背景 toB 的本地化 java 应用程序,通常是部署在客户机器上,为了保护知识产权,我们需要将核心代码(例如 Lience,Billing,Pay 等)进行加密或混淆,防止使用 jadx 等工具轻易 ...
- We Need More Bosses 题解
We Need More Bosses 题目大意 给定一张图,找到两个点,使得这两个点之间的所有路径必须经过的边最多. 思路分析 我们先来思考一下如果已知两个点,怎么求两个点之间必须经过的边的数量. ...
- 14. 从零开始编写一个类nginx工具, HTTP文件服务器的实现过程及参数
wmproxy wmproxy将用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,后续将实现websocket代理, 内外网穿透等, 会将实现过程分享出来, 感 ...
- 鬼谷子的钱袋(lgP2320)
主要思路:二进制拆分. 先将 \(m\) 进行二进制拆分. 注意金币总数有限,也就是说拆分后可能会多出来一组.多出来的这组如果不是 \(2^n\) 就不需要考虑了,因为不会和前面的重复. 接下来考虑重 ...
- 文心一言 VS 讯飞星火 VS chatgpt (123)-- 算法导论10.4 4题
四.用go语言,对于一个含 n个结点的任意有根树,写出一个 O(n)时间的过程,输出其所有关键字该树以左孩子右兄弟表示法存储. 文心一言: 在计算机科学中,左孩子右兄弟表示法是一种用于表示树状结构的方 ...
- Molecule 在构建工具中的选择
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:修能 朝闻道,夕死可矣 何为 Molecule? 轻量级的 ...
- node 实现上传 和 下载 文件接口 简易版
var express = require('express');var fs = require('fs');var path = require('path');var multipart = r ...
- 五分钟k8s实战-Istio 网关
在上一期 k8s-服务网格实战-配置 Mesh 中讲解了如何配置集群内的 Mesh 请求,Istio 同样也可以处理集群外部流量,也就是我们常见的网关. 其实和之前讲到的k8s入门到实战-使用Ingr ...
- python列表删除元素之del,pop()和remove()
del函数 如果知道要删除元素在列表中的位置,可以使用del语句: list_1 = ['one', 'two', 'three'] print(list_1) del list_1[0] print ...
- MySQL-管理员root@'locahost' 丢失,怎么处理?
版权声明:原创作品,谢绝转载!否则将追究法律责任. ----- 作者:kirin 跳过授权表 ----> 不开启验证功能(无密码登录) --skip-grant-tables 阻止所有tcp/i ...