异步一直是前端开发里最让人头疼的一个难点,接下来的几篇文章,将围绕这个话题展开。html
众所周知,JS最初的目的是用于处理浏览器的用户交互和操做DOM,所以,若是JS设计成容许同时存在2个以上的线程,就会出现如下这种问题:前端
2个线程同时操做了同一个DOM节点(a线程要编辑该节点,而b线程删除该节点),那么此时浏览器将没法处理,由于没法判断以哪一个线程为基准。所以,JS只能是单线程。(Web Worker API虽然提供了多线程,但只是纯粹基于使用多核cpu的计算能力,其建立的子线程严格受控,不影响JS单线程的设计实质)
,单线程的设计就意味着,任务以排队的方式依此执行。ajax
基于单线程设计,不可避免的遇到一个情形:某些任务须要的时间很长,但不是由于任务自己太过复杂,难以处理,而是输入输出太慢(例如Ajax获取数据)。而在等待输入输出的过程当中,CPU是闲置的,为了充分利用资源,这一类任务被设计成容许暂时挂起,等到有告终果再执行的任务。segmentfault
如今有两种任务了:同步任务和异步任务api
接下来介绍JS的处理机制。promise
首先看来自MDN的一张图:浏览器
栈(stack),函数调用堆栈。
看这个例子:多线程
function a(){ console.log('a') } function b(){ console.log('from') a() // 这里调用了函数a } b()
在Chrome中运行,而且单步调试,能够看到如下步骤:闭包
b()
时,函数b进栈(如图1)b
中调用函数a
时,a
继续进栈(如图2)(这部份内容实际上对应着以前介绍闭包时,函数做用域链的生成部分,传送门)dom
堆(heap),内存区,用于存储对象。(这个目前不是很重要先不用管)
队列(queue),待处理消息队列, 每个消息都关联着一个用以处理这个消息的函数。
常见示例:
handleClick
函数,那么,当用户触发点击按钮的动做时,会有一个待处理消息进入queue,关联的函数为handleClick
。总体的执行过程以下(如图):
上述过程循环执行,因此称为事件循环(Event Loop)
// 简单的例子 var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function (){}; //指定回调函数, 这是一个异步任务,会被先提交到异步处理的api,等有告终果才会添加到消息队列 req.send();
补充说明如下,任务队列分红2类:
他们的区别下次讲解Promise时再说明(挖个坑)
上述Event Loop模型中,消息队列的新消息来源,除了有dom事件操做,ajax请求等,也多是定时任务,也就是由setTimeout
建立的任务。这个函数你们确定不陌生,可是也可能未必真的足够熟悉~。
setTimeout
接受两个参数:
如今看下如下2个例子:
//示例1 console.log(1); setTimeout(function(){console.log(2);},1000); console.log(3); // 输出结果 1 3 2 ,由于setTimeout指定了里面的函数要推迟1000毫秒才会执行
这个例子说明了setTimeout的基本做用,比较简单很少说。
//示例2 const s = new Date().getSeconds(); //获取当前的秒数 setTimeout(function() { // 输出 "2",表示回调函数并无在 500 毫秒以后当即执行 console.log("Ran after " + (new Date().getSeconds() - s) + " seconds"); }, 500); while(true) {//这个循环含义就是,至少要过2s,当前主线程任务才执行完毕 if(new Date().getSeconds() - s >= 2) { console.log("Good, looped for 2 seconds"); break; } } //实际输出 Good, looped for 2 seconds eventloop.html:15 Ran after 2 seconds
这个例子,首先使用setTimeout
指定了一个500毫秒后执行的回调函数,而后使用while
循环故意让当前运行超过2秒钟,根据上文的流程图可知:
其实在第500毫秒时,这个消息已经被添加到消息队列,可是因为当前的主线程并无执行完,调用栈还没有清空,因此在500毫秒不会执行setTimeout
指定的回调函数。实际上,即便把上述代码中的500
改为0
,结果也是同样的。
简而言之,setTimeout(fn,x毫秒)
的x只是指定了fn被执行的最小等待时间,息具体能在多少时间以后执行,取决于现有调用栈函数的执行进度,以及消息队列中前面的任务执行进度。
本文介绍了Event Loop模型过程以及常见的任务队列的几种任务队列消息来源,这是JS异步话题的基础篇。
参考文献:
MDN-EventLoop
JavaScript 运行机制详解:再谈Event Loop
惯例:若是内容有错误的地方欢迎指出(以为看着不理解不舒服想吐槽也彻底没问题);若是有帮助,欢迎点赞和收藏,转载请征得赞成后著明出处,若是有问题也欢迎私信交流,主页有邮箱地址