假如面试回答js的运行机制时,你可能说出这么一段话:“Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕以后再去执行任务队列之中的事件。”但你能说出背后的缘由吗?html
进程:是系统资源分配和调度的单元。一个运行着的程序就对应了一个进程。一个进程包括了运行中的程序和程序所使用到的内存和系统资源。面试
线程:线程是进程下的执行者,一个进程至少会开启一个线程(主线程),也能够开启多个线程。ajax
同步和异步关注的是:消息(结果)通讯机制。浏览器
同步:发出调用后,在没有获得结果前,该调用不返回。可是一旦调用返回,就获得返回值数据结构
异步:发出调用后,调用直接返回,没有返回结果。但结果由回调函数给出,至于何时给出,不知道。(这个回调函数确定是在当前js执行完后才执行)多线程
阻塞和非阻塞关注的是:程序在等待调用结果时的状态.dom
阻塞调用:调用结果返回以前,当前线程被挂起。调用线程只有在获得结果后才会返回。
非阻塞调用:在不能马上获得结果以前,该调用不会阻塞当前线程。异步
JavaScript是单线程,程序按照顺序排列,前面的必须处理好,后面的才会执行。JavaScript的设计初衷是做为浏览器脚本语言,主要是简单用户交互、操做DOM等,因此这门语言要围绕单线程来设计,不然出现复杂的同步问题。函数
不矛盾!!!首先记住这句话:Js执行是单线程,但浏览器是多线程。oop
5.1:JS的单线程
一个浏览器进程中只有一个JS的执行线程,同一时刻内只会有一段代码在执行。
5.2:浏览器是多线程
查阅资料,有些文章也说是模块,本文就以浏览器是多线程来讲,它有以下常驻线程:
渲染引擎线程:负责页面的渲染。
JS引擎线程:负责JS的解析和执行(本文说的主线程就指js引擎线程)
定时器触发线程:处理定时事件,好比setTimeout, setInterval
事件触发线程:处理DOM事件
异步http请求线程:处理http请求
浏览器是Js的使用场景,浏览器自己是典型的 GUI 工做线程(GUI 工做线程在绝大多数系统中都实现为事件处理,避免阻塞交互)。故浏览器是事件驱动的(Event driven),浏览器中不少行为是异步,会建立事件并放入任务队列中。
因为Javascript 是单线程,它须要借助异步完成耗时或者时间不肯定的操做,这些操做由浏览器的其它线程执行,这造成了异步事件驱动。异步事件驱动每每由浏览器的两个或以上常驻线程共同完成的。例如ajax异步请求是由JS执行线程和异步http请求线程,事件触发线程共同完成的。
函数调用造成一个栈帧。
function foo(b) {
let a = 10;
return a + b + 11;
}
function bar(x) {
let y = 3;
return foo(x * y);
}
console.log(bar(7));
当调用 bar 时,建立了第一个帧 ,帧中包含了 bar 的参数和局部变量。
当 bar 调用 foo 时,第二个帧就被建立,并被压到第一个帧之上,帧中包含了 foo 的参数和局部变量。
当 foo 返回时,最上层的帧就被弹出栈(剩下 bar 函数的调用帧 )。
当 bar 返回的时候,栈就空了。
6.2:堆
对象被分配在一个堆中,一个用以表示一个内存中大的未被组织的区域。
每个线程只有一个栈,每个程序只有一个堆。
6.3:队列
一个 JavaScript 运行时包含了一个待处理的消息队列。每个消息都与一个函数相关联。
当栈为空时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数。
当栈再次为空的时候,也就意味着消息处理结束。
任务队列是一个先进先出的数据结构,当主线程执行栈一清空,任务队列的回调函数就自动进入主线程。任务分红两种:
1、同步任务:在主线程上排队执行的任务。只有执行完当前任务,才能执行后一个任务。
2、异步任务:该任务不进入主线程、而进入任务队列。当执行栈清空后,才去执行任务队列中的任务。
因为JavaScript只能一次执行一段代码(因为其单线程性质),这些代码块中的每个都“阻止”其余异步事件的进度。这意味着当异步事件发生时(如鼠标点击,定时器触发或XMLHttpRequest完成),它将排队等待稍后执行(这种排队实际发生的肯定会因浏览器到浏览器而异)。
1、全部同步任务都在主线程上执行,造成一个执行栈。
2、当遇到异步任务时(IO设备操做等),就在任务队列中添加一个事件,这个事件对应着该异步任务的回调函数。
3、执行栈中的全部同步任务执行完毕,系统就会读取任务队列,进入执行栈,开始执行。
4、主线程不断重复第三步。这就造成了事件循环
结论:Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕以后再去执行任务队列之中的事件。
工做线程完成一项任务,就向任务队列中添加一个事件。这里的完成任务是指完成操做(click、mouse、touch,ajax的数据彻底请求回来......),并不是指执行它的回调函数
a.onclick = function () {
console.log("roro")
}
如上段代码,仅是操做了click,但并无执行回调函数打印roro
事件循环是:主线程重复从任务队列中取消息(事件),执行对应回调函数的过程。
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各类外部API,它们在任务队列中加入各类事件(click,load,done)。只要执行引擎栈栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数。
setTimeout(function () {
console.log('a');
}, 5000)
Javascript执行引擎(主线程)运行的时候,产生堆和栈。程序中代码依次进入栈中等待执行,当调用setTimeout()方法时,在浏览器的定时器线程下处理延时方法,当setTimeout方法执行5秒后,到达触发条件,方法被添加到用于回调的任务队列。
当执行引擎的执行栈为空,执行引擎开始轮询检查任务队列是否有任务须要被执行,当检查到已经符合执行条件的延时方法时,将延时方法console.log('a')压入执行栈,引擎发现调用了log()方法,因而又将log()方法入栈。而后对执行栈依次出栈执行,输出‘a’,清空执行栈,整个执行完毕。
btn.onclick = function () {
setTimeout(function () {
console.log('a')
}, 0);
}
setTimeout(fn,0)的含义是:指定某个任务在主线程的空闲时间下,尽量早地执行。它被添加进任务队列,所以要等到同步任务和任务队列中的前一个事件都处理完,才会执行。
1、JS的执行线程(主线程)发起异步请求,浏览器会开一条新的HTTP请求线程来执行请求,继续执行栈中剩下的任务,
2、在新线程(HTTP请求线程)中,在执行请求的同时,浏览器会正常处理其余任务的执行。
3、在将来的某一时刻,当数据彻底请求回来之后,事件触发线程监视到以前发起的HTTP请求已完成,会将指定的回调函数放入任务队列中。
4、当浏览器执行栈空闲时,去扫描任务队列中的回调函数,依次压入执行栈中处理。
因此:ajax请求是异步。由浏览器新开一个线程请求,事件回调的时候放入Event loop任务队列等候处理。详细的例子,能够参考MDN文档对ajax的描述:同步和异步
这里顺带提一下:事件循环虽然涉及到相似队列的结构,并非采用栈的方式处理任务。事件循环做为一个进程被划分为多个阶段,每一个阶段处理一些特定任务,各阶段轮询调度。这些阶段能够是定时器处理,dom事件处理,ajax异步处理......
JavaScript引擎只有一个线程,强制异步事件排队等待执行,Javascript语言的事件循环,是浏览器的处理和行为。另外,本文是我我的的学习笔记,通篇结合我的的理解,在某些地方表述不严谨,若有错误,但愿指出。