前言
JS异步执行机制具有非常重要的地位,尤其体现在回调函数和事件等方面。
- 异步加载也叫非阻塞模式加载
- 同步或非同步,表明着是否需要将整个流程按顺序地完成
阻塞或非阻塞,意味着你调用的函数会不会立刻告诉你结果
javascript的单线程和异步
js是单线程语言(能提高效率。作为浏览器脚本语言,js的主要用途是与用户互动,操作DOM。而这也就决定它只能为单线程,否则会带来很复杂的同步问题),浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。
- js是单线程语言,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。
详情见:https://www.cnblogs.com/woodyblog/p/6061671.html 虽然js是单线程,但是我们可以将任务分成两类
1.同步任务:需要执行的任务在主线程上排队,依次执行
2.异步任务:没有立马执行但是需要被执行的任务,放在任务队列(task queue,一个事件的队列或者消息的队列)里面。
Event Loop事件循环
打开网站的时候,网页的渲染其实是一堆同步任务,比如页面骨架和页面元素的渲染,但是像图片音乐等占用资源大耗时久的任务就是异步任务。主线程:
- 1.所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
- 2.主线程之外,还存在一个任务队列(task queue),只要异步任务有了运行结果,就在“任务队列”之中放置一个事件。
- 3.一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,就结束等待状态,进入执行栈开始被执行。
js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
- 4.主线程从"任务队列"中读取事件,这个过程是循环不断的,形成event loop(事件循环)
举例:
任务队列里放的是ajax这类的任务,是交给浏览器发起HTTP请求去执行的,当有了返回结果就会在任务队列中增加一个事件,表示该ajax请求已经返回了结果,任务队列里的任务和js主线程是同时执行的。不影响js是单线程的这个结论,只能说浏览器还会提供接口来供js。图例:同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。 当指定的事情完成时,Event Table会将这个函数移入Event Queue。 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
setTimeout
在使用setTimeout的时候,经常会发现设定的时间与自己设定的时间有差异,贴段代码看一下
setTimeout(() => {
task();
},3000)
console.log('执行console');
// 执行console
// task()
setTimeout是一个异步的所以会先执行console这个同步任务。但是,如果改成下面这段会发现执行时间远远超过预定的时间。
setTimeout(() => {
task()
},3000)
sleep(10000000)
我们来看一下是怎么执行的:
- task()进入到event table里面注册计时
- 然后主线程执行sleep函数,但是非常慢。计时任然在继续
- 3秒到了。task()进入event queue 但是主线程依旧没有走完
- 终于过了10000000ms之后主线程走完了,task()进入到主线程
所以可以看出其真实的时间是远远大于3秒的
解释几个容易困惑的问题
- 1.
setTimeout(f1,0)
,f1是不是立刻执行?
答案是不一定,要看主线程内的命令是否已经执行完了。这个任务会在主线程最早可得的空闲时间执行,就是主线程的任务执行结束之后立马执行
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},0);
// 先执行这里
// 执行啦
HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。
- 2. Ajax请求是否异步??
ajax请求内容的时候是异步的,当请求完成后,会触发请求完成的事件,然后把回调函数放入callback queue,等到主线程执行该回调函数时还是单线程的。 - 3. 界面渲染线程是单独开辟的线程,是不是DOM一变化,界面就立刻重新渲染?
如果DOM一变化,界面就立刻重新渲染,效率必然很低,所以浏览器的机制规定界面渲染线程和主线程是互斥的,主线程执行任务时,浏览器渲染线程处于挂起状态。