Js 是单线程,js代码从上到下依次执行,比如我们写了两个函数,肯定是上面的函数先执行,下面的函数后执行。但是这种单线程有一个非常大的问题,那就是遇到耗时的任务,后面的任务只能等待它执行完,才能进行。比如ajax 请求,它从服务器上获取数据,这本来就耗时间, 如果网络再慢,就更耗时间,那么我们只能等待返回结果,结果出来之后再向下执行,等待的过程中,用户什么都不能做,如果是在渲染阶段,也会阻止渲染UI, 用户只能看到空白页面,体验太差。
对于这种比较耗时间的任务怎么办,js 决定把它放到一边,先运行后面的任务,然后再回来处理这些耗时的任务。这就引入了异步的概念,因为我们书写代码的顺序和它执行顺序是不一致了。下面有三个函数,两个基本函数,和一个ajax 函数(作异步操作),我们的书写顺序是ajax -> add -> subtract, 但执行顺序却是 add -> sbutract -> ajax;
function add(num1,num2) { return num1 + num2; } function subtract(num1,num2) { return num2 - num1 } function ajax() { var url = 'http://jsonplaceholder.typicode.com/posts/1'; var xhr = new XMLHttpRequest(); xhr.open("GET",url); xhr.onload= function () { console.log(xhr.responseText) } xhr.send() } ajax();
console.log(add(3,5))
console.log(subtract(3,5))
这又引出了另 一个问题,异步的任务放到什么地方了?它以后回来执行,那什么时候执行?这里其实要注意一个问题,真正执行ajax操作的不是我们的js,而是浏览器,是我们的浏览器发出的http 请求。当js 执行到ajax 函数的时候,它其实是告诉浏览器去执行http 请求,然后就立即返回,执行它后面的代码,就是add 和substract 函数。那么浏览器执行完http请求,从服务器拿到了数据,怎么办?它这时执行我们的回调函数(onload 函数),就是把返回的数据传递给回调函数,然后把回调函数插入到事件(任务)队列中。js运行完add 和subtract 函数时,就没有事情可以做了,它就会去轮询事件(任务)队列,刚一轮询,它就发现,有一个onload 回调函数,那么它就会把它取出来执行这个函数。在程序运行过程中,js会一直执行轮询,直到程序结束。
Js 代码在整个程序的运行过程中分为两个部分,一种是像add 函数这种,另一种是像ajax onload 回调函数这种,相对应js 运行也分成了两个部分,一个是主线程,一个是任务(异步回调函数)队列。
js代码具体运行如下:
1, js 代码一加载进来,它就会从上到下依次执行,它也就进入一个全局执行环境,当它遇到ajax 异步操作的时候,它会告诉浏览器去执行请求,然后立即返回,执行下面的代码,遇到add函数调用,它就会进入函数执行环境,执行完add函数时,它又遇到subtract 函数,它又会进入函数执行环境。当然这里比较简单,没有嵌套函数。如果函数中还嵌套一个函数,那么它就会进入子函数的执行环境,像下图一样,这就形成了一个执行栈,上面的执行后,再执行下面的,直到全局执行环境中的代码执行完毕,执行栈为空, 这就是就是js 的主线程。
2,浏览器去执行http 请求, 在未来的一段时间内,它或者从服务器获得数据或者失败,这时它就会把我们注册的成功或失败的回调函数放入到任务(回调函数)队例中。
3, 当执行栈中的所有代码执行完毕后,就是执行栈为空时,js就会去轮询我们的任务队列(左边图片),如果有任务,它就会从任务队列的起始位置 取出第一个任务(注册的回调函数)放到执行栈去执行(右边图片),等这个回调函数执行完毕后,执行栈再为空,js再去轮询我们的任务队列,如果还有回调函数,它再取出第一个放到执行栈执行。在整个程序的执行过程中,js 会一直轮询我们的任务队列,一有任务,就会执行,这就是事件循环。
所有的程序都是栈中执行,只有执行栈空了,它有能力处理下一个任务。只要一个函数进入到执行栈,栈就不为空,栈不为空,就不能处理下一个函数,只能等到函数执行完毕。这就是所谓的 “run–to-complete ” 一次只能做一件事情。这也要求我们的回调函数中,不能执行太多任务,如果执行太多任务,栈就不为空,也就阻止了下一个任务的执行,造成阻塞。
以上所述,就是js 中的非阻塞。