前言
学而不思则罔,思而不学则殆。
一.异步(async)与同步(sync)
举个梨子:1.早上起床你可以一边先洗脸刷牙,洗漱完了再去烧个水,这些事情都是一件一件完成后才能接着下一件。
2.换种方式,你在刷牙洗脸的时候,也可以烧水喝,不用等着洗刷完,这二者之间就是异步的。
同步代码演示:
1 | var a=0 |
结果为1。
print函数会等change函数完成之后去执行,所以结构输出为1,因为change函数修改了全局变量a的值,change执行之后才执行的print函数。
异步代码演示:
1 | function a(){ |
运行结果:
1 | a完成了 |
以上代码会先执行函数a,而且不会等到a中的延迟函数执行完才执行函数b, 在延迟函数被触发的过程中就执行了函数b,当js引擎的event 队列空闲时才会去执行队列里等待的setTimeout的回调函数,这就是一个异步的例子。
调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非确切的时间。
所以即使,时间设置为0,也是会照样先执行函数b
二.回调函数到底是啥子
来看几个经典的回调函数代码,从代码中体会概念
1 | //异步请求的回调 |
1 | //点击事件的回调函数 |
1 | //数组遍历中每一项调用的回调函数 |
1 | function getNodes(parms,callpack){ |
所以回调与同步、异步并没有直接的联系,回调只是一种实现方式,既可以有同步回调,也可以有异步回调,还可以有事件处理回调和延迟函数回调,这些在我们工作中有很多的使用场景
三.为啥要用回调函数
1.关于回调函数和js单线程以及js异步机制
我们都知道js是单线程的,这种设计模式给我们带来了很多的方便之处,我们不需要考虑各个线程之间的通信,也不需要写很多烧脑的代码,也就是说js的引擎只能一件一件事的去完成和执行相关的操作,所以所有需要执行的事情都像排队一样,等待着被触发和执行,可是如果这样的话,如果在队列中有一件事情需要花费很多的时间,那么后面的任务都将处于一种等待状态,有时甚至会出现浏览器假死现象,例如其中有一件正在执行的一个任务是一个死循环,那么会导致后续其他的任务无法正常执行,所以js在同步机制的缺陷下设计出了异步模式。
在异步执行的模式下,每一个异步的任务都有其自己一个或着多个回调函数,这样当前在执行的异步任务执行完之后,不会马上执行事件队列中的下一项任务,而是执行它的回调函数,而下一项任务也不会等当前这个回调函数执行完,因为它也不能确定当前的回调合适执行完毕,只要引它被触发就会执行。
举个例子:
我要购买一个东西,当我点进物品的详情页之后,图片资源还未请求完毕,而此时我就可以点击add to cart, 发起另一个请求,js任务列表中的添加购物车事件就会开始执行,它的执行也不会干扰到图片资源的请求任务,这也是异步执行机制的妙处,不会影响用户的操作。
2.js的单线程浏览器内核的多线程
说到js的单线程,顺便再了解一下关于浏览器内核的多线程,关于浏览器工作原理此处不做讲解,因为自己研究的不深入,等待研究学习研究透彻再分享。
1.GUI渲染线程
GUI渲染线程
负责渲染浏览器界面HTML元素,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
当界面需要重绘(Repaint)或由于某种操作引发回流(重排)(reflow)时,该线程就会执行。
在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被”冻结”了,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
2.JavaScript引擎线程
JavaScript引擎
,也可以称为JS内核,主要负责处理Javascript脚本程序,例如V8引擎。
JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序(单线程)。
注意⚠️:GUI渲染线程和JavaScript引擎线程互斥!
由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。
因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JavaScript引擎为互斥的关系,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。
如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
3.事件触发线程
当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。
这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。
4.定时触发器线程
setInterval
与setTimeout
所在线程
浏览器定时计数器并不是由JavaScript引擎计数的, 因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确。
通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
5.异步http请求线程
在XMLHttpRequest在连接后是通过浏览器新开一个线程请求。
将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JavaScript引擎执行。