当前位置: 首页 > Web前端 > JavaScript

JavaScript执行机制

时间:2023-03-27 18:38:25 JavaScript

大纲  1.情景分析  2.执行机制知识点  3.举例说明JavaScript执行机制  4.相关概念场景分析/*以下代码执行结果是什么?如果按照js是按照语句出现的顺序执行的思路,那么代码执行的结果应该是://"定时器启动"//"立即执行for循环"//"执行then函数"//"代码执行结束"但是结果不是这样的,结果是://"现在执行for循环"//"代码执行结束"//"执行then函数"//"定时器启动"*/setTimeout(function(){console.log('定时器启动')});newPromise(function(resolve){console.log('现在执行for循环');for(vari=0;i<10000;i++){i==99&&resolve();}}).then(function(){console.log('先执行函数')});console.log('codeexecutionends');2二、执行机制相关知识点2.1关于javascript  javascript是一种单线程语言。Web-Worker是在最新的HTML5中提出的,但javascript的核心是单线程并没有改变。因此,所有javascript版本的“多线程”都是用单线程模拟的。2.2.JavaScript的同步异步  单线程意味着所有任务都需要排队,只有上一个任务完成后才会执行下一个任务。如果前一个任务耗时很长,后一个任务就得一直等下去。  如果队列是因为计算量大,CPU太忙,那就算了,但大部分时间CPU是空闲的,因为IO设备(输入输出设备)很慢(比如Ajaxoperationsreaddatafromthenetwork),只好等结果出来再进行。  JavaScript语言的设计者意识到,此时主线程可以完全忽略IO设备,挂起等待的任务,运行最先排队的任务。等到IO设备返回结果,再回去继续执行挂起的任务。  因此,所有的任务可以分为两种,一种是同步任务(synchronous),一种是异步任务(asynchronous)。同步任务是指在主线程上排队等待执行的任务,只有上一个任务执行完才能执行下一个任务;异步任务是指不进入主线程而是进入“任务队列”(taskqueue)的任务,只有当“任务队列”通知主线程有异步任务可以执行时,任务才会进入执行的主线程。  1。同步和异步任务进入不同的执行“地方”,同步进入主线程,异步进入EventTable和注册函数。  2。当EventTable中指定的事情完成后,这个函数就会被移入EventQueue中。  3.主线程中的任务执行后为空。它会去EventQueue中读取相应的函数,进入主线程执行。  4.上述过程会不断重复,这就是常说的事件循环(eventloop)。  5.我们不禁要问,我们怎么知道主线程执行栈是空的呢?js引擎有一个监控进程,会不断检查主线程执行栈是否为空。一旦为空,就会去EventQueue中查看是否有等待调用的函数。2.3.JavaScript宏任务和微任务  你以为同步和异步执行机制流程就是整个JavaScript执行机制吗?不是,JavaScript除了广义的同步任务和异步任务外,还有更详细的任务定义:    macro-task(宏任务):包括整体代码脚本,setTimeout,setInterval    微任务(微任务):Promise、process.nextTick  不同类型的任务会进入对应的EventQueue。  事件循环的顺序决定了js代码的执行顺序。进入整体代码(宏任务)后,开始第一个循环。然后执行所有微任务。然后再从宏任务开始,找到其中一个要执行的任务队列,然后执行所有的微任务。3、举例说明JavaScript3.1的执行机制。同步控制台.log(1);控制台日志(2);console.log(3);/*执行结果:1,2,3个同步任务,一步一步依次执行*/3.2,同步和异步console.log(1);setTimeout(function(){console.log(2);},1000)console.log(3);/*执行结果:1,3,2同步任务,按顺序一步步执行异步任务,放入消息队列,等待同步任务执行完成,读取消息队列执行*/3.3,进一步分析异步任务console.log(1);setTimeout(function(){console.log(2);},1000)setTimeout(function(){console.log(3);},0)console.log(4);/*猜测:1,4,2,3但实际上:1,4,3,2分析:同步tasks,按顺序一步步执行异步任务,在读取异步任务时,将异步任务放入Event表(事件表)中,当满足一定条件或指定事件完成时(这里的时间分别为0ms和1000ms)。当指定的事件完成时,它会从事件表中注册到事件队列(eventqueue)中。当同步事件完成时,从事件队列中读取该事件。实施。(因为3的事情先完成,所以先从Event表中注册到EventQueue中,所以最先执行的是3而不是之前的2)*/3.4、宏任务和微任务console.log(1);setTimeout(function(){console.log(2)},1000);newPromise(function(resolve){console.log(3);resolve();}).then(function(){console.log(4)});console.log(5);/*同步异步判断的结果应该是:1,3,5,2,4但实际上结果是:1,3,5,4,2为什么会这样?因为用同步和异步的方式来解释执行机制是不准确的,更准确的说法是宏任务和微任务:因此,执行机制是:执行宏任务===>执行微任务===>执行另一个宏任务===>连续循环的意思是:在一个事件循环中,执行第一个宏任务,执行宏任务,执行当前事件循环中的微任务,执行完后进入下一个事件循环,或者执行下一个Amacrotask*/3.5,是否充分理解JavaScript执行机制实例console.log('1');setTimeout(function(){console.log('2');process.nextTick(function(){console.log('3');})newPromise(function(resolve){console.log('4');resolve();}).then(function(){console.log('5')})})process.nextTick(function(){console.log('6');})newPromise(function(resolve){console.log('7');resolve();}).then(function(){console.log('8')})setTimeout(function(){console.log('9');process.nextTick(function(){console.log('10');})newPromise(function(resolve){console.log('11');resolve();}).then(function(){console.log('12')})})/*1.第一轮事件循环流程分析如下:整体脚本作为第一个宏任务进入主线程,遇到console.log,输出1遇到setTimeout,其回调函数分发到宏任务EventQueue。我们暂且记录为setTimeout1。当遇到process.nextTick()时,它的回调函数被分发到microtaskEventQueue。我们将其表示为process1。遇到Promise,直接执行newPromise,输出7。然后分发到microtaskEventQueue。我们将其表示为then1。又遇到了setTimeout,它的回调函数被分发到宏任务EventQueue中,我们记录为setTimeout2。MacrotaskEventQueueMicrotaskEventQueuesetTimeout1process1setTimeout2then1上表是第一轮事件循环宏任务结束时各个EventQueue的状态。这时候1和7已经输出了。我们找到了两个微任务,process1和then1。执行process1,输出6。执行then1,输出8。好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。2.那么第二轮时间loop从setTimeout1宏任务开始:先输出2,接下来遇到process.nextTick(),同样分发到microtaskEventQueue,记录为process2。新的Promise立即执行输出4,然后也分发到microtaskEventQueue,记录为then2。宏任务EventQueue微任务EventQueuesetTimeout2process2then2第二轮事件循环宏任务结束,我们发现有两个微任务process2和then2可以执行。输出3.输出5.第二轮事件循环结束,第二轮输出为2,4,3,5.3.第三轮事件循环开始,只剩下setTimeout2执行。直接输出9。分发process.nextTick()到微任务事件队列。将其表示为process3。直接执行newPromise,输出11,将then分发到microtaskEventQueue中,记为then3。MacrotaskEventQueueMicrotaskEventQueueprocess3then3第三轮事件循环macrotask执行结束,执行process3和then3两个microtask。输出10.输出12.第三轮事件循环结束,第三轮输出9,11,10,12.整个代码,一共三个事件循环,完整输出为1,7,6,8,2、4、3、5、9、11、10、12。*/4。相关概念4.1。为什么JS是单线程的?  JavaScript语言的一大特点就是单线程,即同一时间只能做一件事。那么,为什么JavaScript不能有多线程呢?这样可以提高效率啊。  JavaScript的单线程和它的使用有关。作为一种浏览器脚本语言,JavaScript的主要目的是与用户交互和操作DOM。这就决定了它只能是单线程的,否则会带来非常复杂的同步问题。例如,假设JavaScript同时有两个线程,一个线程向某个DOM节点添加内容,另一个线程删除这个节点,那么浏览器应该以哪个线程为基础呢?  因此,为了避免复杂性,JavaScript从诞生之日起就是单线程的,这已经成为这门语言的核心特征,以后也不会改变。  为了利用多核CPU的计算能力,HTML5提出了WebWorker标准,允许JavaScript脚本创建多个线程,但子线程完全由主线程控制,不得操纵DOM。因此,这个新标准并没有改变JavaScript的单线程特性。4.2.为什么JS需要异步?  如果JS中没有异步,只能自上而下执行。如果上一行的解析时间很长,后面的代码就会被阻塞。对于用户来说,阻塞就意味着“卡住”,导致用户体验不佳。4.3.JS单线程是如何实现异步的?  既然JS是单线程的,只能在一个线程上执行,那怎么是异步的呢?  就是通过事件循环(eventloop),了解事件循环机制,了解JS的执行机制。4.4.任务队列  “任务队列”是一个事件队列(也可以理解为消息队列)。当一个IO设备完成一个任务时,一个事件被添加到“任务队列”中,表明相关的异步任务可以进入“执行栈”。主线程读取“任务队列”,也就是读取里面有哪些事件。  “任务队列”中的事件,除了IO设备事件外,还包括一些用户产生的事件(如鼠标点击、页面滚动等)。只要指定了回调函数,这些事件就会在发生时进入“任务队列”,等待主线程读取。  所谓的“回调函数”(callback)就是会被主线程挂掉的代码。异步任务必须指定一个回调函数。当主线程开始执行异步任务时,执行相应的回调函数。  “任务队列”是一个先进先出的数据结构,前面的事件先被主线程读取。主线程的读取过程基本上是自动的。一旦执行栈清空,“任务队列”上的第一个事件就会自动进入主线程。但是由于后文提到的“定时器”功能,主线程必须先查看执行时间,而有些事件只能在指定的时间后才返回主线程。  读取一个异步任务,先将异步任务放入事件表(Eventtable),当放入事件表的异步任务完成某事或满足一定条件(如setTimeout事件到来,鼠标点击后并获取数据文件),这些异步任务被推入事件队列(EventQueue)。此时的异步任务是执行栈空闲时才能读取的异步任务。4.5.EventLoop  主线程从“任务队列”中读取事件。这个过程是循环的,所以整个运行机制也称为事件循环(eventloop)。  EventLoop是javascript4.6的执行机制,setTimeout(fn,0)  setTimeout(fn,0)的意思是指定一个任务在主线程最早可用空闲时间执行,也就是说,越早越好。它将一个事件添加到“任务队列”的末尾,因此它不会被执行,直到同步任务和“任务队列”的现有事件都被处理完。  HTML5标准规定setTimeout()的第二个参数的最小值(最短间隔)不得低于4毫秒,低于此值会自动增加。在此之前,旧版浏览器将最小间隔设置为10毫秒。另外,对于那些DOM变化(尤其是那些涉及页面重新渲染的),它们通常不会立即执行,而是每16毫秒执行一次。这时候使用requestAnimationFrame()的效果比setTimeout()要好。  需要注意的是,setTimeout()只是将事件插入“任务队列”,主线程只有在当前代码(执行栈)执行完后,才会执行它指定的回调函数。如果当前代码耗时很长,可能要等待很长时间,所以没办法保证回调函数会在setTimeout()指定的时间执行。参考网址https://juejin.im/post/59e85e...https://www.cnblogs.com/Maste...https://blog.csdn.net/highboy...