1.JavaScript预解析JavaScript代码运行分为两个阶段:(1)预先预解析所有函数定义,函数体提升(当然不包括比如varbox=function(){})形式参数声明和赋值变量声明(不赋值)(2)按照js运行机制自上而下执行2.进程和线程进程是cpu资源分配的最小单位(是可以分配的最小单位拥有资源,独立运行)线程是cpu调度的最小单位(线程是基于进程的程序运行单位,一个进程可以有多个线程。例子:这里有多个工厂,每个工厂有1个或多个worker.此时factory就像一个进程,有自己的factory资源;worker就像线程,有多个worker在factory中写入和工作。factory的空间??是worker共享的,也就是说内存一个进程的空间是共享的。每个线程都可以共享内存。而且每个工厂都是相互独立存在的。应用程序必须运行在某个进程的某个线程上。一个进程至少有一个运行线程:主线程,它是在进程启动后自动创建的。3、浏览器进程浏览器内核是指支持浏览器运行的核心部分。它分为渲染引擎和JS引擎。现在JS引擎相对独立,内核更倾向于指代渲染引擎(1)浏览器内核分类Chrome、Safari:Webkit(Bink)Firefox:GeckoIE:Trident360、Sogou等国产浏览器:Trident+Webkit...(2)浏览器进程浏览器是一个多进程的浏览器,能运行是因为系统给它的进程分配了资源(cpu,内存)。简单来说,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。浏览器进程的组成:浏览器进程浏览器的主进程负责协调和总控制。只有一个。负责内容:浏览器页面展示;与用户的交互(前进、后退等);网络资源的管理和下载;Process,只有在使用插件时,最多创建一个GPU进程,用于3D绘图等浏览器渲染进程(浏览器内核,Renderer进程,内部多线程)。默认情况下,每个Tab页一个进程,互不影响。负责内容:页面渲染;脚本执行;事件处理浏览器是多线程的一个优点:避免单个标签页崩溃或单个插件崩溃影响其他整个浏览器,可以充分利用多核,方便使用沙箱模型隔离插件等进程,提高浏览器的稳定性。缺点是内存和cpu消耗会更大,也就是说用空间换取时间。Borwser进程与浏览器内核(Renderer进程)的通信过程:当Browser进程收到用户请求时,首先需要获取页面内容(如通过网络下载资源),然后将任务传递给通过RendererHost接口渲染进程。渲染线程接收请求。加载网页,渲染网页,可能需要Browser进程获取资源,GPU进程协助渲染。当然也可能有JS线程去操作DOM(可能会造成回流和重绘)。最后,Renderer进程将结果传递给Browser进程的Renderer接口。收到消息后,简单解释后交给渲染线程,然后开始渲染。Browser进程接收结果并绘制结果。4、浏览器的渲染进程对于前端操作来说是最重要的,线程化的渲染进程很多。渲染进程包含哪些线程?GUI渲染线程负责渲染浏览器页面,解析HTML和CSS,构造DOM树和RenderObject树,布局绘制等,??负责重绘(Repaint)和回流(Reflow)。GUI渲染线程和JS引擎线程是互斥的,当JS引擎正在执行时,GUI线程会被挂起,而当js引擎空闲时,GUI线程会被保存在一个队列中执行。JS引擎线程负责处理JavaScript脚本,执行代码事件触发线程主要负责将准备好的事件交给JS引擎线程执行,比如setTimeout定时器计数结束,ajax等异步请求成功和触发回调函数,用户触发点击事件等。线程会将准备好的事件添加到任务队列的尾部,等待JS引擎线程执行。定时器触发线程主要负责异步定时器等函数的处理,比如setTimeout和setInterval。当主线程依次执行代码时,遇到定时器,就会把定时器交给线程处理。当计数完成后,事件触发线程会将计数的事件添加到任务队列的尾部,等待JS引擎线程执行。异步HTTP请求线程负责执行异步请求等功能,如:ajax、axios、promise等主线程依次执行代码。当遇到异步请求时,异步请求函数会交给线程处理。监听状态码变化时,如果有回调函数,事件触发线程会将回调函数添加到任务队列的尾部,等待JS引擎线程执行。5、事件循环1浏览器中的事件循环JavaScript语言是单线程的,也就是说同一时间只能做一件事情。后来为了有效利用多核CPU的计算能力,HTML5提出了WebServer标准,允许JavaScript脚本创建多个线程,但是子线程完全由主线程控制,子线程不能操作DOM.所以新标准并没有改变JavaScript的单线程特性。简单描述一下JS的执行机制:首先判断JS是同步任务还是异步任务,同步任务进入主线程执行,异步任务进入事件表。异步任务在事件表中注册函数。异步函数分为宏任务和微任务,当满足触发条件时,将宏任务推入宏任务队列,将微任务推入微任务队列。同步任务在主线程中执行,直到同步任务执行完毕,主线程空闲,然后去微任务队列(micro-taskqueue)检查是否有可执行的异步任务,如果有所以,将它们推入主线程执行,直到所有的微任务都按顺序执行完毕,主线程空闲,然后去宏任务队列中查看是否有可执行的异步任务。如果有,就推入主线程执行上面的四步循环执行,也就是事件循环。一个完整的EventLoop流程:①所有的同步任务都在主线程上执行,形成一个执行栈(exectioncontextstack)。我们可以把执行栈看成是函数调用的栈结构,遵循先进后出的原则。除了主线程的执行栈外,还有一个任务队列(taskqueue),分为宏任务队列(macro-taskqueue)和微任务队列(micro-taskqueue)。一开始,执行栈是空的,宏任务队列(macro-taskqueue)中只有一个脚本代码(overallcode),微任务队列(micro-taskqueue)队列是空的。②宏任务队列(macro-taskqueue)中的全局上下文(script标签)会被压入执行栈,同步代码执行。在执行过程中会判断是同步任务还是异步任务,同步任务会依次执行。在相应的任务队列中放置一个事件,等待调用)。同步代码执行完毕后,script脚本会运行并出队。③上一步出队了一个宏任务,这一步要处理一个微任务。需要注意的是,宏任务出队时,任务是一个一个执行的,而微任务出队时,任务是一个一个执行的。因此,我们处理微任务步骤,将队列中的任务一个一个执行,并出队,直到队列清空。④执行渲染操作,更新页面⑤检查是否有Webworker任务,有则处理⑥上述过程不断循环,直到两个队列都为空宏任务队列可以有多个,微任务只有一个queue:常见的宏任务:setTimeout,setInterval,script(wholecode),I/Ooperations,UIrendering等;常见微任务:newPromise().then(回调),process.nextTick,MutationObserver(HTML5新特性)等。2Node中的事件循环Node中的事件循环与浏览器中的事件循环完全不同。Node使用V8作为js解析引擎,使用自己设计的libuv进行I/O处理。libuv是一个事件驱动的跨平台抽象层,封装了不同操作系统的一些底层特性,对外提供API,并在其中实现了事件循环:NodeJS运行机制如下:V8引擎解析JavaScript脚本解析代码调用NodeAPIlibuv库,负责NodeAPI的执行。它将不同的任务分配给不同的线程,形成一个EventLoop(事件循环),并将任务的执行结果以异步的方式返回给V8引擎。然后V8引擎将结果返回给用户。libuv引擎的事件循环分为6个阶段:定时器阶段:执行定时器的回调(setTimeout和setInterval)I/O回调阶段:处理最后一个循环idel中一些未执行的I/O回调,准备阶段:只使用内部由Nodepoll阶段:获取新的I/OO事件,执行I/O回调check阶段:执行setImmediate()回调closecallbacks阶段:执行socketclose事件回调大部分异步任务在timers、poll这三个阶段处理,和check,在NodeJS执行环境中的特殊情况:1)setTimeout和setImmediate很相似,区别主要在调用的时机上:setImmediate被设计为在poll阶段完成时执行,即,setTimeout被设计为在check阶段poll阶段空闲,到达set阶段时执行,但是它执行setTimeout(functiontimeout(){console.log('timeout');},0);setImmediate(functionimmediate(){console.log('immediate');});对于上面的代码,setTimeout可能会先执行,也可能会晚执行;取决于setImmediate的准备时间;因为当setTimeout指定的时间小于4ms时,会增加到4ms(4ms是H5de的新标准,2010之前的浏览器是10ms)但是如果两者在I/O回调内部回调的时候,总是先执行setImmediate,再执行setTimeout:constfs=require('fs')fs.readFile(__filename,()=>{setTimeout(()=>{console.log('timeout');},0)setImmediate(()=>{console.log('immediate')})})//即时te//timeout//因为这两段代码写在I/O回调中,所以I/O回调是在poll阶段执行的。执行回调时清空队列,找到SetImmediate回调,立即跳转到检查阶段执行Callback});2)process.nextTickprocess.nextTick独立于EventLoop,它有自己的队列,会先于其他微任务队列执行: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=>promise13浏览器和Node的EventLoop区别在浏览器环境下,微任务的任务队列是在每个宏任务执行完之后执行;在Node中环境,在node10及之前的版本中,微任务会在事件循环的各个阶段之间执行,即执行完一个阶段后,微任务队列中的任务就会被执行。Node从node11版本开始,EventLoop原有的操作发生了变化。一旦一个stage中的宏任务执行完毕,微任务队列就会立即执行,这与浏览器是一致的。4Webworker因为JS是单线程的,当遇到计算量大或者高延迟的任务时,用户界面可能会暂时“冻结”,无法进行其他操作。所以HTML5提出了WebWorker,让JavaScript创建一个多线程的环境,让主线程创建Worker线程,并把一些任务分配给后者。主线程运行的同时,Worker线程在后台运行,两者互不干扰。Worker完成计算任务后,将结果返回给主线程。WebWorker的优势在于可以承担一些密集型或高延迟的任务,使主线程流畅,不会被阻塞或变慢。缺点:JSWorker内部代码无法跨域加载,无法访问DOM。并非所有浏览器都支持此新功能。WebWorker使用方法:主线程调用Worker线程:主线程通过newWorker()调用Worker的构造函数创建一个新的Worker线程Mainthread调用worker.postMessage()方法向Worker发送消息。主线程通过worker.onmessage指定监听函数接收子线程发回的消息。//主线程:varinput=document.getElementById('number')document.getElementById('btn').onclick=function(){varnumber=input.value//1.创建一个Worker对象varworker=newWorker('worker.js')//3.绑定接收消息的监听器worker.onmessage=function(event){console.log('主线程接收子线程返回的数据-thread:'+event.data)alert(event.data)}//2.向子线程发送消息worker.postMessage(number)console.log('主线程向子线程发送数据:'+number)}console.log(this)//windowWorker线程响??应:Worker内部通过onmseeage()监听事件,通过postMessage(data)方法向主线程发送数据//worker.js文件函数fibonacci(n){returnn<=2?1:fibonacci(n-1)+fibonacci(n-2)//递归调用}console.log(this)//[objectDedicatedWorkerGlobalScope]this.onmessage=function(event){varnumber=event.dataconsole.log('子线程收到主线程发送的数据:'+number)//计算varresult=fibonacci(number)postMessage(result)console.log('子线程向主线程返回数据:'+result)//alert(result)alert是窗口方法,不能在子线程中调用//子线程中的全局对象不再是window,所以无法在子线程中更新界面}参考:https://github.com/ljianshu/B...https://juejin.im/post/5bb054...10分钟讲解JavaScript的运行机制了解JS引擎的执行机制浏览器组成全面梳理JS引擎的运行机制
