JavaScript运行机制
转载:公众号《小猪的全栈之路》
先来看一下整个js的运行流程图解,有个整体印象,初学者看到可能感到疑惑,这是正常的,主要让大家对整个流程先有个概览预备的过程,文章后面会一一讲到,看完之后在回过头来看此图相信会有收获的。
JavaScript运行机制图解
上图我们可以分为两部分:浏览器中的 JS引擎
和 运行环境Runtime
,那它们的区别是什么?
- JS引擎:编译并执行代码的地方。如上图中可以看出JS引擎分为两大核心部分:
栈和堆
栈(Stack):js代码的执行都要压到此栈中执行。堆:存放对象、数组的地方,js垃圾回收就是检查这里。 Runtime:浏览器的运行环境,它提供了一些对外接口供JS调用,如网络请求接口。
JavaScript引擎是单线程的
首先我们来看浏览器下的JavaScript:浏览器是多线程的,且一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。
JS引擎是单线程的,也就是说在一个时间段内,事情只能一件一件的按先后顺序去做,第一件事没做完就不能做第二件事。
那js为什么是设计为单线程?答案:单线程只能做一件事情 ,原因是避免与DOM渲染冲突。假设javascript同时有两个线程在运行,一个线程在DOM节点添加内容,另一个线程删除了这个节点,那么浏览器应该以哪个线程为准?所以,为了避免冲突,javascript一诞生就设计为单线程。如何解决冲突?通过异步。
GUI渲染线程,此线程负责渲染html和css代码到浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行
事件触发线程,当一个事件被触发时该线程会把事件添加到任务队列的队尾,等待JS引擎的处理。
JavaScript同步(异步)任务
在JavaScript任务可以分为两种:
同步执行:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行下一个任务,若前一个任务耗费很长时间,则后面的任务会一直处于阻塞等待状态。
异步执行:在栈中执行代码时,如遇到异步函数,如setTimeout、异步Ajax、事件处理程序,会将这些异步回调函数交给浏览器的webApi来处理。
什么异步函数?
异步函数通常是由发起函数和回调函数构成的。如:
A(callback)
,函数A就是发起函数,callback就是回调函数它们都是在JS主线程调用的,其中发起函数用来发起异步过程,回调函数用来处理结果。
如:
setTimeout(callback,1000)
setTimeout就是发起函数、callback就是回调函数。
如:异步的Ajax
varxhr=newXMLHttpRequest(); xhr.onreadystatechange=callback;//callback为回调函数 xhr.open('get',url,true); xhr.send(null);// send为发起函数
可以看出发起函数和回调函数也可以是分离的。
栈
在js中,代码最终都是在栈中执行的,栈结构的特点是:先进后出,后进先出。
我们来看下面代码的运行结果:
function bar(){ console.log(1); foo(); } function foo(){ par(); console.log(3); } function par(){ setTimeout(function(){ console.log(2); },0); } bar();
运行的最终结果是:132。为什么结果不是123呢?
下我们来分析下代码运行时入栈和出栈的过程。
首先当调用函数 bar()
时,此函数就会先入栈,其内部的 console.log(1)
也会随之入栈执行。
执行完console.log(1)后,就要出栈,于是控制台先打印出结果1,只剩下bar()在栈中。接着再执行函数bar内部的函数foo,于是函数foo也开心的入栈了。
执行函数foo的内部代码,调用函数 par()
,于是函数par()也要跟着入栈。
由于函数par()内部执行遇到了 异步函数setTimeout
,异步函数则会由浏览器的Runtime运行环境的webApi来处理,等定时器设置的时间到达就会被放到任务队列中,此时栈中的代码会继续往下执行。
接着在执行par函数中的 console.log(3)
,控制台打印结果为3 ,此时栈的代码执行完毕后,会按照栈的特点进行
先进后出,后进先出顺序进行 出栈
。出栈顺序:先函数par()-->后函数foo()-->最后函数bar。
最后只剩下异步任务,由主线程去获取任务队列中的任务放在栈中去执行。也就是只有栈中的代码执行完毕之后,才最后执行任务队列中异步任务。
最后把setTimeout的回调函数放入到栈中执行:结果控制台输出为2。
setTimeout(function(){ console.log(2); },0);
所以代码的最终运行结果为132。
事件循环
当主线程中的任务执行完毕后,会从任务队列中获取任务一个个的放在栈中执行去执行,这个过程是循环不断的,所以整个的这种运行机制又称为事件循环。
小结
js引擎是单线程执行js代码,同步任务在栈中按顺序执行,如果某一个同步任务没有执行完毕,则后面的代码将会处于阻塞等待状态
栈中若执行遇到了异步任务(如定时器、异步Ajax、事件),会将此异步任务交给浏览器的webApi来处理。
webApi中的所有异步任务均会按照设定的时间进行等待,时间一到异步的回调函数就会被加入任务队列中。如果是异步ajax,就等待有响应结果后在把其回调函数加入到任务队列中。
异步任务什么时候执行呢!只有当栈中为空时,浏览器会通过事件循环机制来一个个的获取任务队列中的任务放到栈中进行逐个运行。即栈中的同步任务总是在读取
异步任务
之前执行定时器设置的时间不一定按照设定的时间进行等待执行,这得取决于栈中执行代码耗费的时间,因为异步函数总是等待栈中代码执行完毕之后再执行。
javaScript运行机制在线测试地址:http://latentflip.com/loupe
原作者youtube讲解视频 , 需翻墙!https://www.youtube.com/watch?v=8aGhZQkoFbQ