从一个实例看完整的事件循环过程

一个完整的事件循环过程大概分为以下几步:

1.检查调用栈是否为空,如果不为空则等待调用栈执行完毕,为空则检查事件队列是否为空;如果事件队列为空则不执行任何操作,不为空则将队首的事件处理器压入执行栈执行;

2.调用栈中的代码执行完毕后检查微任务队列是否存在微任务,如果有则执行微任务,并且按顺讯执行完所有的微任务;

3.执行完所有的微任务后,进行判断是否要进行UI渲染,如果需要则进行UI渲染不需要则回到步骤1,如此就完成了一个事件循环;

如题通过一个实例来研究这个过程:

    <button id="clickMe">click me!</button>
    <script>
        // 获取一个button元素
        var clickMe = document.getElementById("clickMe");
        // 定义一个延迟函数
        function delay(second) {
            console.time('延迟代码执行时间');
            for (i = 0; i < 10000000000; i++) {
                if (i == 379999999*second) { // 379999999大概为当前浏览器环境下执行1秒能循环的次数
                    break;
                }
            }
            console.timeEnd('延迟代码执行时间');   
        }
        // 为button添加事件监听
        clickMe.addEventListener("click",function(){
            console.log("触发了clickMe");
            clickMe.innerHTML = "重新设置按钮内容";
            delay(2);
            setTimeout(function(){
                console.log("触发了setTimeout");
                delay(2);
                console.log("完成了setTimeout");
            },0);
            Promise.resolve().then(function(){
                console.log("触发了Promise")
                delay(2);
                console.log("完成了Promise"); // 此处可以打上断点观察
            });
        })
    </script>

代码分析:

前提:JavaScript是单线程的,这说明我们想要同一时间不可能执行两个函数,同样也不可能执行两个事件;如果触发多个事件就会保存在一个事件队列(task queue)里按顺序调用事件的处理器函数;执行处理器函数实在调用栈中(call stack),调用栈到底长什么样我们会在下面看到。首先我们写个一个按钮id为clickMe(正如它的内容),然后是脚本内容,包含一个指向dom元素的变量clickMe和一个延迟函数delay以及调用了一个绑定事件处理器的方法;

1.某一个时刻点击了clickMe,绑定在clickMe上的事件处理器被添加到了事件队列,需要提醒的是事件队列的操作跟调用栈执行并非同一线程,因为不如此的话在调用栈工作时产生的事件将无法添加到队列,我们知道这显然不是的。按照步骤1调用栈不存在可执行代码,随即检查事件队列是否为空,此时事件队列恰有我们触发的按钮点击事件的处理器函数,随即将处理器函数压入到调用栈中执行,然后按照从上到下的顺序执行代码,首先在控制台可以看到“触发了clickMe”,然后更改了按钮的html内容,内容并不会马上被渲染还是因为单线程,在代码执行时渲染引擎并不会执行渲染(当然并不代表就共用一个线程),然后又定义了一个超时器,当然回调函数会在一个“合适”的时机执行,然后是触发了一个promise,同样超时器的回调函数也会在一个合适的”时机“执行,此时快照如下:

1.png

2.当代码执行到了处理器函数末尾时,函数执行完毕出栈,开始下个步骤,检查是否存在微任务,微任务(micro task)是相对于宏任务(macro task)的,宏任务包含:主程序script,setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering,这些任务被分配到浏览器的不同部分去单独执行所以定义为宏任务,微任务包含:process.nextTick, Promise, Object.observe, MutationObserver;这些任务不需要单独执行而又有别于同步执行称为微任务;现在promise是微任务,setTimeout是宏任务,当检查微任务队时,我们知道promise的“时机”来了,随即将回调函数压入调用栈执行,首先我们会看到:“触发了promise”,大概经过两秒后我们又看到“完成了promise”,那你会问延迟函数的作用是什么,很简单方便截图,截图的作用呢?很简单验证事件处理器中修改了按钮的内容是否改变也就是是否发生了UI渲染,那打个断点不就行了还得费劲写个函数?嗯。。。看下面第二张图,将断点打在 “console.log("完成了Promise");” 观察按钮上的文字,对,它改变了!!!在单线程的JS函数执行时还能进行UI渲染?显然这是浏览器debugger策略,便于我们观察效果而已,但对我们研究真正过程会产生误解。执行到延迟函数时快照如下:

2.png

button的内容并没有改变,证明了在promise执行完之前不会渲染UI

7.png

3.promise执行完毕后开始进行UI渲染,渲染后快照如下图所示(在setTimeout得延迟函数中截图):

3.png

4.到第3步已经是一个完整的循环了,但我们仍旧写了一个定期器来验证微任务比宏任务执行的早并且在渲染之前执行,重复这个过程将定时器回调压入调用栈执行,我们借用延迟函数获得如下快照:

4.png

总结:理清事件循环的过程有助于我们更准确的知道我们的代码是如何执行的,当出现问题或者写代码之初就避免这样的问题,本文从一个实例出发分析了事件循环过程,并且也学习到浏览器并非单线程的,一个循环可能需要多个部分分工合作,以v8为代表的现代浏览器JS引擎在性能上的优化非常好,但这依旧不能成为我们写出不合理代码的理由,当然这个实例包含的情况并不丰富,有些问题并没有证明,例如:是否所有的微任务都会被执行完才开始渲染?浏览器是怎么判定需不需要渲染的?如果事件处理器函数不存在或者事件监听没有添加之前这个事件被触发了浏览器是怎么做的?这些问题就留给大家自己搞清楚吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343