提及javascript(如下简称js)这门语言,相信你们已经很是熟悉了,无论是前端开发仍是后端开发几乎无时无刻都要跟它打交道。虽然说开发者天天几乎都要操做js,可是你真的肯定你掌握了js的运行机制吗!下面咱们就来聊聊这话题。javascript
上图咱们能够分为两部分:浏览器中的JS引擎
和运行环境Runtime
,那它们的区别是什么?前端
如上图中能够看出JS引擎分为两大核心部分:栈和堆
java
栈(Stack):js代码的执行都要压到此栈中执行。 ajax
堆:存放对象、数组的地方,js垃圾回收就是检查这里。后端
JS引擎是单线程的,也就是说在一个时间段内,事情只能一件一件的按前后顺序去作,第一件事没作完就不能第二件事。那么在js引擎中负责解释和执行js代码的线程只有一个,咱们能够称之为主线程
。数组
固然浏览器的运行环境Runtime还提供一些其余的线程,如定时器线程、ajax线程、事件线程、网络请求和UI渲染的线程,为了和js主线程分开,咱们这里都统称它们为工做线程
。浏览器
因为浏览器是多线程的,因此工做线程和js主线程均可以执行任务,线程间互不干扰。网络
在JavaScript任务能够分为两种:多线程
异步任务:在栈执行代码的过程当中,如遇到异步函数,如setTimeout、异步Ajax、事件处理程序,会将这些异步代码交给浏览器的工做线程来处理,咱们把这些任务称之为异步任务。异步任务是不进入主线程,而是进入任务队列(queue task)。异步
什么异步函数?
异步函数一般是由发起函数和回调函数构成的。如:
A(callback)
它们都是在主线程调用的,其中发起函数用来发起异步过程,回调函数用来处理结果。 如:`setTimeout(callback,1000)` setTimeout就是发起函数、callback就是回调函数。 如:异步的Ajax
var xhr = new new XMLHttpRequest(); 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运行环境的工做线程来处理,等定时器设置的时间到达就会被放到任务队列中,此时栈的同步任务继续执行。
接着在执行par函数中的console.log(3)
,控制台打印结果为3 ,此时栈的代码执行完毕后,会按照栈的特色进行
先进后出,后进先出顺序进行出栈
。出栈顺序:先函数par()-->后函数foo()-->最后函数bar。
最后只剩下异步任务,由主线程去获取任务队列中的任务放在栈中去执行。也能够认为栈中的同步代码执行老是在读取异步任务
以前执行。
最后执行setTimeout中的回调函数:结果控制台输出为2。
setTimeout(function(){ console.log(2); },0);
因此代码的最终运行结果为132。
异步任务
以前执行