JavaScript代码运行分为两个阶段:javascript
全部函数定义提早,函数体提高(固然不包括如var box = function() {} )
形参声明并赋值
变量声明(不赋值)前端
按照js运行机制从,从上到下执行java
举例:此处有多个工厂,每一个工厂有1个或多个工人。此时工厂就比如进程,有单独专属本身的工厂资源;工人就比如是线程,多个工人在工厂中写做工做。工厂的空间是工人们共享的,这象征一个进程的内存空间是共享的,每一个线程均可以共享内存。而且每一个工厂之间相互独立存在。node
浏览器内核是指支持浏览器运行的最核心的部分,分为渲染引擎和JS引擎。如今JS引擎比较独立,内核更加倾向于说渲染引擎ios
(1)浏览器内核分类git
(2)浏览器进程github
浏览器进程的组成:ajax
浏览器的主进程,负责协调、主控,只有一个。
负责内容:浏览器页面显示;与用户交互(前进、后退等);网络资源的管理、下载;各个页面的管理,建立和销毁其余进程等axios
浏览器是多线程的优点:避免单个Tab页崩溃或单个插件崩溃影响其余整个浏览器,能够充分多核优点,方便使用沙盒模型隔离插件等进程,提升浏览器的稳定性。缺点是,内存和cpu消耗会更大,有点空间换时间的意思。segmentfault
Borwser进程与浏览器内核(Renderer进程)的通讯过程:
Browser进程收到用户请求,首先须要获取页面内容(譬如经过网络下载资源),随后将该任务经过RendererHost接口传递给Render进程
对于前端操做来讲 ,最重要的是渲染进程,而且渲染进程也是多线程的。
渲染进程包含哪些线程?
GUI渲染线程
JS引擎线程
事件触发线程
好比setTimeout定时器计数结束、ajax等异步请求成功并触发回调函数、用户触发点击事件等,该线程会将整装待发的事件加入到任务队列的队尾,等待JS引擎线程的执行。
定时器触发线程
主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理。当计数完毕后,事件触发线程会将计数完毕的事件加入到任务队列的尾部,等待JS引擎线程执行。
异步HTTP请求线程
主线程依次执行代码是,遇到异步请求,会将异步请求函数交给该线程处理。当监听到状态码变动,若是有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS引擎线程执行。
JavaScript语言是单线程的,意思是同一时间只能作一件事。后来为了有效利用多核CPU的计算能力,HTML5提出Web Server标准,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,而且子线程不能操做DOM。因此新标准并无改变JavaScript单线程的本质。
简单描述JS的执行机制:
以上四步循环执行,就是event loop。
一个完整的Event Loop过程:
① 全部的同步任务都在主线程上执行,造成一个执行栈(exection context stack),咱们能够认为执行栈是一个函数调用的栈结构,遵循先进后出的原则。除了主线程的执行栈,还存在一个任务队列(task queue),任务队列分为宏任务队列(macro-task queue)和微任务队列(micro-task queue)。
一开始执行栈为空,宏任务队列(macro-task queue)里只有一个script代码(总体代码),微任务队列(micro-task queue)队列为空。
② 宏任务队列(macro-task queue)中的全局上下文(script标签)会被推入执行栈,同步代码执行。在执行的过程当中会判断是同步任务仍是异步任务,同步任务依次执行,异步任务会经过对一些接口的调用而产生新的macro-task和micro-task(只要异步任务有了运行结果,就会在对应的任务队列中放置一个事件,等待调用)。同步代码执行完了,script脚本会行和出队的过程。
③ 上一步出队的是一个macro-task,这一步要处理的是micro-task。须要注意的是,当macro-task出队时,任务是一个一个执行的,而micro-task出队时,任务是一队一队执行的。所以,咱们处理micro-task这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。
④ 执行渲染操做,更新页面
⑤ 检查是否存在Web worker任务,若是有,则对其进行进行处理
⑥ 上述过程重复循环,直到两个队列都清空
宏任务队列能够有多个,而微任务队列只有一个:
Node中的事件循环与浏览器的是彻底不一样不一样的东西。Node采用V8做为js的解析引擎,而I/O处理方面使用本身设计的libuv。
libuv是一个基于事件驱动的跨平台抽象层,封装了不一样操做系统的一些底层特性,对外提供API,事件循环也是在它里面实现:
NodeJS运行机制以下:
libuv引擎的事件循环分为6个阶段:
绝大部分的异步任务都在timers、poll、check这个3个阶段处理
NodeJS执行环境下的特殊状况:
1)setTimeout和setImmediate
两者很是类似,区别主要在于调用时机不一样:
setTimeout(function timeout () { console.log('timeout'); },0); setImmediate(function immediate () { console.log('immediate'); });
对于以上代码,setTimeout可能执行在前,也可能执行在后;
取决于setImmediate的准备时间;由于当setTimeout指定时间小于4ms,则增长到4ms(4ms是H5de新标准,2010年之前的浏览器是10ms)
可是若是两者在I/O callback内部回调时,老是先执行setImmediate,后执行setTimeout:
const fs = require('fs') fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0) setImmediate(() => { console.log('immediate') }) }) // immediate // timeout // 由于这两个代码都写在I/O回调中,I/O回调是在poll阶段执行,当回调执行完毕后队列清空,发现SetImmediate回调,因此当即跳转到check阶段执行回调。 });
2)process.nextTick
process.nextTick是独立于Event Loop以外的,它有一个本身的队列,会优先于其余micro-task队列执行:
setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') }) }, 0) process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') }) }) }) }) // nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
浏览器环境下,micro-task的任务队列是每一个macro-task执行以后执行;
Node环境下,在node10及其之前版本,micro-task会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会执行micro-task队列的任务
Node在node11版本开始,Event Loop的运行原来发生了变化,一旦一个阶段里的宏任务执行完,就会当即执行微任务队列,这一点与浏览器一直。
因为JS是单线程,当遇到计算密集型或高延迟的任务,用户界面可能会短暂“冻结”,不能作其余操做。
因而HTML5提出Web Worker,它容许JavaScript创造多线程环境,容许主线程建立Worker线程,将一些任务分配给后者。主线程运行的同时,Worker线程在后台运行,二者互不干扰,等到Worker完成计算任务,在把结果返回给主线程。
Web Worker的优势是能够承担一些密集型或高延迟任务,使主线程流畅,不被阻塞或拖慢。
缺点:
Web Worker使用方法:
主线程调用Worker线程:
// 主线程: var input = document.getElementById('number') document.getElementById('btn').onclick = function () { var number = input.value //一、建立一个Worker对象 var worker = new Worker('worker.js') // 三、绑定接收消息的监听 worker.onmessage = function (event) { console.log('主线程接收分线程返回的数据: '+event.data) alert(event.data) } // 二、向分线程发送消息 worker.postMessage(number) console.log('主线程向分线程发送数据: '+number) } console.log(this) // window
Worker线程响应:
//worker.js文件 function fibonacci(n) { return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2) //递归调用 } console.log(this)//[object DedicatedWorkerGlobalScope] this.onmessage = function (event) { var number = event.data console.log('分线程接收到主线程发送的数据: '+number) //计算 var result = fibonacci(number) postMessage(result) console.log('分线程向主线程返回数据: '+result) // alert(result) alert是window的方法, 在分线程不能调用 // 分线程中的全局对象再也不是window, 因此在分线程中不可能更新界面 }
参考资料:
https://github.com/ljianshu/B...
https://juejin.im/post/5bb054...
深刻浅出JavaScript运行机制
10分钟理解JS引擎的执行机制
浏览器组成
全面梳理JS引擎的运行机制