jascript事件循环机制

      事件循环机制控制了javascript代码的执行顺序。我们都知道javascript是单线程,这个线程中拥有唯一的一个事件循环。(新标准web workker有多线程的概念。)而事件循环机制主要以来调用栈来处理执行顺序,依靠任务队列来执行代码的执行。队列的概念可以参考https://segmentfault.com/a/11...      在一个线程中,调用栈是唯一的,但是任务队列可以是多个,并且分为macro-task(宏任务)及micro-task(微任务)两种类型。      这里需要区分一个概念任务及任务源。setTimeout及Promise是任务源。他们指定具体的执行任务进入任务队列。只有回调中的函数才会进入任务队列。就像setTimeout它其实是丽姬执行的,只是它的回调函数才会延迟执行。promise也是,本身是立即执行的,但是then才会在“未来”执行。      javascript的执行顺序是从整体代码开始做循环,之后全局上下文进入函数调用栈。直到调用栈清空。整体代码所处的macro-task执行完成,轮到micro-task任务执行。一直循环直到所有的任务执行完成。      当然,不同的任务源的任务会进入不同的任务队列。      具体的可以参考一下代码。      1.事件循环从macro-task开始,整体代码开始执行。整体代码script进入macro-task,并且执行代码main进入调用函数调用栈。遇到第12行的打印输出start      2.继续执行,遇到13行的setTimeout,它是宏任务源。便将其分发到对应的队列中。接着遇到16行的promise。promise.resolve会进入函数调用栈直接执行,因此打印promise1,接着将p.then1和p.then分发到对应的微任务队列中。继续执行代码,遇到第24行的打印便输出end。大致图示如下图。      3.script执行完毕,即第一个宏任务执行完毕,开始执行微任务。现在微任务只有一个队列,里面有p1.then1,p1.then2。队列是先进先出,因此先执行p1.then1,p1.then1进入函数调用栈,输出then1。      4. p1.then1执行完毕之后,出栈。但是此时的正在进行的微任务还未执行完完毕,会继续执行p1.then2,p1.then2进入函数调用栈,输出then2。此时,微任务正在进行的队列已经执行完毕。      5.当微任务执行完毕之后,第一轮循环结束,进入第二轮循环,继续执行宏任务,此时setTimeout执行,进入函数调用栈,输出setTimeout1。      6.此时,宏任务队列和微任务队列中都没有任务了。代码执行完毕,就不会有任何输出了。       我们上述的代码只涉及到一个宏任务及微任务队列的情况。但如果情况更加复杂会有什么样的表现呢?大家可以看看下面的代码。根据上面的原理试着自己分析下结果~      1.还是跟以前的例子一样,事件循环从macro-task开始,整体代码开始执行。输出start。setTimeout1,setTimeout2依次进入新的宏任务队列。p3.resolve执行,输出promise31,promise31。并将setTimeout3放入新的宏任务队列。因为setTimeout3不是整体代码中定义的,而是在promise中定义的,需要重新开启一个宏任务队列。然后p3.then1,p3.then2分别进入微任务队列。p3.resolve出栈后,整体代码继续执行,这里就不重新画图了,输出end。      2.整体代码已执行完成,循环进入微任务。此时p3.then1进入函数调用栈。输出then31。遇到新的定时,将set4放入宏任务队列。遇到新的promise,继续将p4.resolve入栈。输出promise41,promise42。遇到新的定时,将set5放入宏任务队列。此时需要注意的是,在微任务中继续有promise。此时的promise.then不再进入微任务队列,而是直接执行。因此输出then41。      3.微任务队列还未执行完毕,继续执行p3.then2。直接输出then32。此时微任务队列已经执行完毕,进入下一轮循环。      4.新的循环开始。队列是先进先出,因此在宏任务当前队列中,set1先执行,进入函数调用栈。输出setTimeout1。遇到新的promise,继续将p1.resolve入栈。输出promise1。还是跟上看一样,在宏任务中继续有promise。此时的promise.then不再进入微任务队列,而是直接执行。直接输出then1。      5.setTimeout1执行完毕,正在执行的宏任务队列还有任务,继续执行setTimeout2。setTimeout2进入函数调用栈。跟setTimeout1的分析一样,陆续输出setTimeout2,promise2,then2。      6.当前宏任务执行完毕,微任务内没有可执行的队列。继续下一轮循环。执行set3。输出setTimeout3。遇到新的promsie,还是跟上面的分析一样,输出promise5,then5。因为微任务一直没有可执行的队列。宏任务内的队列依次执行,输出setTimeout4,setTimeout5。

June 15, 2019 · 1 min · jiezi

Javascript 冒泡、捕获、事件代理

2019开工荒了两天,赶紧开始!为什么写事件代理、冒泡、捕获,首先冒泡和捕获是js事件的核心基础,事件代理原理来自冒泡和捕获。此文章略过标准浏览器和非标准浏览器的事件流讲解,原因很简单我们现在已经幸福了,已经不考虑IE6、7、8了直接说现代浏览器事件流,用两张图看看什么是冒泡 什么是捕获,其实从字面意思大概能看出 一个是向外一个是向内。标准事件流1、捕获阶段 (先从最外层向内查找)2、目标阶段(找到事件元(当前点击的dom))3、冒泡阶段(向上冒泡传递事件)事件冒泡<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <meta http-equiv=“X-UA-Compatible” content=“ie=edge”> <title>冒泡</title></head><style> * { margin: 0; padding: 0; } div { padding: 30px; } .div1 { background: red } .div2 { background: blueviolet } .div3 { background: yellowgreen }</style><body> <div class=“div1”> 父 <div class=“div2”> 子 <div class=“div3”> 孙 </div> </div> </div> <script> // javascript事件绑定addEventListener接收三个参数, // 1、事件名称字符串且不带on // 2、回调函数 // 3、事件流方式(默认为冒泡(false),捕获为true) window.onload = function() { document.querySelector(’.div1’).addEventListener(‘click’, function() { console.log(‘点击div1’) }, false) document.querySelector(’.div2’).addEventListener(‘click’, function() { console.log(‘点击div2’) }, false) document.querySelector(’.div3’).addEventListener(‘click’, function() { console.log(‘点击div3’) }, false) } </script></body></html>先演示下冒泡事件捕获<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <meta http-equiv=“X-UA-Compatible” content=“ie=edge”> <title>冒泡</title></head><style> * { margin: 0; padding: 0; } div { padding: 30px; } .div1 { background: red } .div2 { background: blueviolet } .div3 { background: yellowgreen }</style><body> <div class=“div1”> 父 <div class=“div2”> 子 <div class=“div3”> 孙 </div> </div> </div> <script> window.onload = function() { document.querySelector(’.div1’).addEventListener(‘click’, function(e) { console.log(‘点击父元素’) }, true) document.querySelector(’.div2’).addEventListener(‘click’, function(e) { console.log(‘点击子元素’) }, true) document.querySelector(’.div3’).addEventListener(‘click’, function(e) { console.log(‘点击孙子元素’) }, true) } </script></body></html>看看效果事件委托我理解的事件委托的好处有两点1、减少事件绑定次数2、可以给未知元素绑定事件(例如动态渲染的List)其原理就是利用事件冒泡向外传递的特性,下面代码解析一下:同样忽略低版本浏览器获取当前target兼容性问题<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <meta http-equiv=“X-UA-Compatible” content=“ie=edge”> <title>冒泡</title></head><style> * { margin: 0; padding: 0; } div { padding: 30px; border: 1px solid red } div span { display: inline-block; padding: 10px; border: 1px solid blueviolet }</style><body> <div class=“div1”> <span>1</span> <span>2</span> <span>3</span> <span>4</span> </div> <script> window.onload = function() { document.querySelector(’.div1’).addEventListener(‘click’, function(e) { //回调函数e为事件对象,同伙事件对象可以获取当前点击dom console.log(e.target) //获取到当前事件源(dom)后再去搞一些你想搞的事情就ok了 //搞事情代码 }, false) } </script></body></html>老规矩,看看效果最后顺便说一下事件对象功能很全的,看下图能获取到很多当前dom的周边,可以搞好多其它的事情。感谢阅读,欢迎吐槽!谢谢! ...

February 13, 2019 · 2 min · jiezi