面试官:既然您提到了回调函数,能简单说一下回调函数吗?
我:就是将函数作为参数传递,让函数可以被其他函数使用。
面试官:能再详细说说吗?您作为一个老前端,相比理解的很透彻吧?
我:哈哈哈哈,不能。
面试官:哈哈哈哈哈,您真幽默,和您聊的很开心,回去等通知吧。
正文
我真没想到,我会再次因为这种古董面试题被摆了一道,五年前我能说的头头是道的面试题,如今居然被卡了。
哎,面试失败之后,立刻回家重新整理了一下相关的文档。
什么是回调函数
回调函数是将一个函数作为参数传递给另一个函数,并且可以在传入的那个函数中接受参数和返回值
回调函数和普通函数没有本质区别,没有本质区别,回调函数谁都可以用
这里我们写个回调函数被调用的例子
1 | function myDisplayer(some) { |
【注意点】
- 函数作为参数传递时,不要使用括号
- 正确:
myCalculator(5, 5, myDisplayer);
- 错误:
myCalculator(5, 5, myDisplayer());
- 正确:
回调函数的作用和使用场景
回调函数是一种常见的编程技术,他可以在异步操作完成后调用一个预定义的函数来处理结果。
回调函数通常用于处理事件、执行异步操作或响应用户输入等场景。
- 作用:
- 将代码逻辑分离出来,使得代码更加模块化和可维护
- 可以避免阻塞程序的运行,提高程序的性能和效率
- 可以实现代码的复用,因为他们可以被多个地方调用
- 使用场景:
- 事件处理,例如鼠标点击、键盘输入,网络请求等
- 异步操作,例如读取文件、发送邮件、下载文件等
- 数据处理,例如对数组进行排序、过滤、映射等
- 插件开发,例如WordPress插件等
- 异步编程:指在代码执行时不会阻塞程序运行的方式
- 事件驱动:指程序的执行是由外部事件触发而不是顺序执行的方式
回调函数的优缺点
- 优点
- 提高代码的复用性和灵活性:回调函数可以将一个函数作为参数传递给另一个函数,从而实现模块化编程,提高代码的复用性和灵活性
- 解耦合:回调函数可以将不同模块之间的关系解耦,使得代码更易于维护和扩展
- 可以异步执行:回调函数可以在异步操作完成后被执行,这样避免了阻塞线程,提高应用程序的效率
- 缺点
- 回调函数嵌套过多会导致代码难以维护:如果回调函数嵌套层数过多,代码会变得非常复杂,难以维护
- 回调函数容易造成竞态条件:如果回调函数中有共享资源访问,容易出现竞态条件,导致程序出错
- 代码可读性差:回调函数的使用可能会破坏代码的结构和可读性,尤其是在处理大量数据时
小结:代码灵活、易于扩展,但是不易于阅读、容易出错
回调函数与其它编程概念的关系
回调函数和闭包的关系
回调函数和闭包之间存在着紧密的关系。
回调函数是一个函数,在另一个函数中被作为参数传递,并在该函数执行完成后被调用。
闭包是由一个函数及其相关的引用环境组合而成的实体,可以访问函数外部的变量。
在某些情况下,回调函数需要访问到它所在的父函数的变量,这时就需要使用闭包来实现。
通过将回调函数放在闭包内部,可以将父函数的变量保存在闭包的引用环境中,使得回调函数能够访问到这些变量。同时,闭包还可以保证父函数中的变量在回调函数执行时不会被销毁,从而确保了回调函数的正确性
因此,回调函数和闭包是一对密切相关的概念,常常一起使用来实现复杂的逻辑和功能。
回调函数和Promise的关系
C++回调函数和Promise都是异步编程的实现方式。
回调函数是一种将函数作为参数传递给另一个函数,在异步操作完成后执行的技术。在C++中,回调函数通常使用函数指针或函数对象来实现。当异步操作完成后,会调用注册的回调函数,以便执行相应的处理逻辑。
而Promise则是一种更加高级的异步编程模式,它通过解决回调地狱问题,提供了更加优雅和简洁的异步编程方式。
Promise可以将异步操作封装成一个Promise对象,并通过链式调用then()方法来注册回调函数,以及catch()方法来捕获异常。当异步操作完成后,Promise会自动根据操作结果触发相应的回调函数。
因此,可以说C++回调函数和Promise都是异步编程的实现方式,但是Promise提供了更加高级和优雅的编程模式,能够更好地管理异步操作和避免回调地狱问题。
回调函数和观察者模式的关系
回调函数和观察者模式都是用于实现事件驱动编程的技术。
它们之间的关系是,观察者模式是一种设计模式,它通过定义一种一对多的依赖关系,使得一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
而回调函数则是一种编程技术,它允许将一个函数作为参数传递给另一个函数,在执行过程中调用这个函数来完成特定的任务。
在观察者模式中,当一个被观察的对象发生改变时,会遍历所有的观察者对象,调用其定义好的更新方法,以进行相应的操作。
这里的更新方法就可以看做是回调函数,因为它是由被观察对象调用的,并且在执行过程中可能需要使用到一些外部参数或上下文信息。因此,可以说观察者模式本身就包含了回调函数的概念,并且借助回调函数来实现观察者模式的具体功能。
如何编写高质量的回调函数
回调函数需要遵循以下几个原则
- 明确函数的目的和作用域。回调函数应该有一个清晰的目的,同时只关注与其作用范围相关的任务
- 确定回调函数的参数和返回值。在定义回调函数时,需要明确它所需的参数和返回值类型,这样可以使调用方更容易使用
- 谨慎处理错误和异常。回调函数可能会引发一些异常或错误,需要使用 try-catch 块来处理它
- 确保回调函数不会导致死锁或阻塞。回调函数需要尽可能快地执行完毕,以避免影响程序的性能和稳定性。
- 使用清晰且易于理解的命名规则。回调函数的命名应该清晰、简洁,并尽可能说明其功能和意义。
- 编写文档和示例代码。良好的文档和示例代码可以帮助其他开发者更容易地使用回调函数,同时也有助于提高代码的可维护性和可重用性。
- 遵循编码规范和最佳实践。 编写高质量的回调函数需要遵守编码规范和最佳实践,例如使用合适的命名规则、注释代码等。
回调函数的命名规范
回调函数的命名规范没有固定的标准,但是根据通用惯例和编码规范,回调函数的命名应该能够反映函数的作用和功能,让其他开发者能够快速理解并使用。
- 使用动词+名词的方式来描述回调函数的作用,例如onSuccess、onError等。
- 如果回调函数是用于处理事件的,可以以handleEvent或者onEvent作为函数名。
- 如果回调函数是用于处理异步操作完成后的结果,可以以onComplete或者onResult作为函数名。
- 在命名时要注意保持简洁明了,不要过于冗长,也不要使用缩写或者不清晰的缩写。
- 尽量使用有意义的单词或者短语作为函数名,不要使用无意义的字母或数字组合。
- 与代码中其他的函数名称保持一致,尽量避免出现命名冲突的情况。
回调函数的参数设计
回调函数的参数设计取决于回调函数所需执行的操作和数据。一般来说,回调函数需要接收至少一个参数,通常是处理结果或错误信息。其他可选参数根据需要添加。
回调函数使用
使用Function对象
Function 对象是 JavaScript 中的一种基本数据类型,它可以表示一个函数。可以使用 Function 对象来实现回调函数。如下所示:
1 | function callbackFunction(param1, param2) { |
使用arrow(箭头)函数
arrow函数是 JavaScript 中的一种简化版本的函数表达式,它可以更简洁地定义函数。可以使用 arrow 函数来实现回调函数。
1 | const callbackFunction = (param1, param2) => { |
回调函数中的this指向
- 在定时器
setInterval
和setTimeout
中
1 | var name = 'my name is window'; |
在上面这个例子中,我们可以看出来,this的指向是window
如果没有特殊指向,setInterval
和setTimeout
的回调函数中this的指向都是window,这是因为JS的定时器方法是定义在window下的。
但是平时很多场景下,都需要修改this的指向。这里总结了几种:
最常用的方法:在外部函数中将
this
存为一个变量,回调函数中使用该变量,而不是直接使用this
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var name = 'my name is window';
var obj = {
name: 'my name is obj',
fn: function () {
var that = this;
var timer = null;
clearInterval(timer);
timer = setInterval(function () {
console.log(that.name); //my name is obj
}, 1000)
}
}
obj.fn()
// undefined
// my name is window使用bind()方法(bind()为ES5的标准,低版本IE下有兼容问题,可以引入es5-shim.js解决)
bind()
的作用类似call和apply,都是修改this
指向。但是call和apply是修改this指向后函数会立即执行,而bind则是返回一个新的函数,它会创建一个与原来函数主体相同的新函数,新函数中的this指向传入的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var name = 'my name is window';
var obj = {
name: 'my name is obj',
fn: function () {
var that = this;
var timer = null;
clearInterval(timer);
timer = setInterval(function () {
console.log(that.name); // my name is obj
}.bind(this), 1000)
}
}
obj.fn()
// undefined
// my name is window在这里为什么不能用call和apply,是因为call和apply不是返回函数,而是立即执行函数,那么,就失去了定时器的作用。
使用es6的箭头函数:箭头函数的最大作用就是this指向。
1
2
3
4
5
6
7
8
9
10
11
12
13var name = 'my name is window';
var obj = {
name: 'my name is obj',
fn: function () {
var that = this;
var timer = null;
clearInterval(timer);
timer = setInterval(() => {
console.log(this.name); //my name is obj
}, 1000)
}
}
obj.fn()箭头函数没有自己的this,它的this继承自外部函数的作用域。
所以,在该例中,定时器回调函数中的this,是继承了fn的this。
当然箭头函数也有兼容问题,要是兼容低版本ie,需要使用babel编译,并且引入es5-shim.js才可以。
结语
回调函数其实本质上不能算是JS的独有手法,只是一种入门的编程思想罢了。
虽然面试官让我详细说说我依然感觉很荒诞,但是我没能详细说说,确实不太符合老年程序员的身份。
如今回顾了一下,倒也是颇为感慨,我的确挖的不够深,大开眼界。
参考
【JavaScript】【回调】回调函数 && 回调地狱 - 掘金 (juejin.cn)