背景
其实对于JavaScript
语言来说,执行环境是单线程的,也就是说一次只能完成一个任务,当有多个任务的时候,需要排队一个个的实现,换言之,就是前一个任务执行完成了,才会执行后面的任务。
这样的执行环境很简单,也比较单纯,但是也存在一定的问题,那就是当我们有的任务执行的时候比较耗时的时候,我需要执行完整个耗时的任务,才可以开始执行下面的任务,这样就有可能造成阻塞的现象,对于前端来说常见的就是浏览器无响应(假死),也就是js的执行时间比较长,导致页面卡死在某一个地方,其他的任务是无法正常的执行的。这个时候,即使我们进行界面的刷新,也不能解决上面的问题,造成用户体验差。
为了避免和解决这个问题,js语言将任务执行模式分为同步和异步。
同步模式
:等待前一个任务执行完成,在执行下一个任务,任务是依次进行执行的
异步模式
:每一个任务都有一个或是多个回调函数(callback
),前一个任务执行结束之后,并不是执行后面的任务,而是执行回调函数,后面的任务执行也不是等前一个任务执行完成,而是和前一个任务几乎是并行的,所以程序的执行顺序和任务的排列顺序是不一致的,异步的。
回调函数
这份事故异步编程最基本的方法,我们首先从概念的角度了解一下,什么叫回调函数:
回调函数的英文定义:A callback is a function that is passed as an argument to another function and is executed after its parent function has completed。
上面的英文很清楚的看出什么是回调函数:回调函数本质上就是一个参数,将一个行数作为参数传递到另外一个函数中,当另外一个行数执行完成,在执行传进去的这个函数,这个过程就是回调。
可能比较晦涩,现在我们用js语言进行举例:函数A作为参数(函数引用)传递到另外一个函数B中,并且这个函数B执行函数A,那么A就叫做回调函数,如果A是没有名字的函数,那么就叫匿名回调函数。
我们也可以举一个生活中的例子:春节放假结束的时候,你和你的父母离别的时候,你父母和你说 “到单位了打一个电话,我们很担心你”,然后你到单位了就给你的父母发了一条短信。这就是一个回调的过程。你的父母留了一个参数函数(要求你打电话)给你,然后你到单位,到单位就是主函数,必须要到单位之后,主函数执行完成之后,再执行传进去的函数,然后你的父母就收到了一条短信。
我们如果从代码的角度进行描述,假设现在f1()和f2()的两个函数,如果什么都不操作:
const f1 = ()= {...}
const f2 = ()= {...}
const test =()=>{
f1();
f2();
}
上面的执行很显然结果是f2()
要等待f1()
执行完成之后才可以继续执行,如果f1()
是一个很耗时的行数,就会导致程序的阻塞,这个时候我们可以将上面的函数改成回调行数的形式.
function f1(callback){setTimeout (function(){ f1的任务代码callback();},1000)})
执行代码就变成下面这样:
f1(f2);
采用这样的方式,我们将同步的操作变成了异步的操作,这样的话不会因为f1()
执行缓慢进而阻塞了程序,相当于先执行程序的主要逻辑,将耗时的操作进行可推迟。
我们可以从另外一个例子
// 定义主函数,回调行数最为参数
A = (callback) =>{
callback();
console.log("我是主函数")
}
// 定义回调行数,也就是当做参数的函数
B=()=>{
setTimeout("console.log('我是回调行数')",3000) // 用来模仿一个很耗时的操作
}
A(B) // 调用主函数,将函数B传进去
// 输出的结果
我是主函数
我是回调函数
}
我们分析一下上面的代码,我们让代码先去执行callback()
的回调函数,但是输出的结果却是先输出了主函数的语句,后输出回调函数的语句。这就说明了。主函数不用等待回调行数执行完,可以接着执行自己的代码。根据上面的代码我们看到了我们一般讲比较耗时的操作用作回调行数。
从上面我们看到了一个回调是否是异步的主要取决于传输的参数的函数是都是异步的。
ps:setTimeout
、setInterval
的函数调用得到其返回值,由于两个行数都是异步的,即:他们的调用时序和主流程序是相对独立的,所以没有办法在主体函数中获取他们的返回值,他们被打开的时候,程序也不会停下来等待,否则也就失去了setTimeout
、以及setInterval
的意义了,所以用return
是没有意义的,只能使用callback
,callback
的意义在于将timer执行的结果通知给代理函数进行及时处理。
回调函数这种方式的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以”去耦合“,有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
网友评论