dom更新监听浅析

通过angular/vue/react知识的理解, 结合着浏览器多线程, event loop, 让我们来一起聊聊如何监听dom更新, 在vue中,相信大家对nextTick一定不会陌生,在react中也一定会setState的第二个参数callback陌生,他们的共同点都是页面渲染之后执行, 那么我们如果不在vue和react中使用这2种方式怎么做呢?不用担心,我们还可以使用h5 Mutation Observer,此方式具体用法, 我就不在此赘述啦, 那么如果在h5之前呢? 接下来就是我们今天要说的主题: setTimeout和setInterval(以下通过angularjs代码来解析, 因为早期angularjs框架中,并没有提供类似于vue/react中的那2种方式, 所以比较有代表性)
1.浏览器多线程
2.js引擎线程
3.异步任务
4.回调函数队列(macrotask queue) (event队列,我为什么叫回调函数队列, 是因为想通过这个名字更好的去理解形成这个队列先后顺序的原因)
5.微任务
6.微任务函数队列(microtask queue)
7.js function stack
8.event loop
首先对于js是单线程还是多线程,在这里我就不做解释了,有兴趣的大家可以去了解一下h5 webworker,相信大家就会对js是单线程还是多线程就会有自己的认识了,在这里我们没有用h5 webworker,就只讨论js是单线程处理, js 单线程就是我们平常口中所说的js引擎线程,执行我们所写js的线程;那么我们再谈谈浏览器多线程,浏览器多线程通常我们接触到的有以下几个线程:
1.js引擎线程
2.gui渲染线程 (用来渲染页面UI的线程,与js引擎线程互斥)
3.网络请求线程 (比如我们进行ajax请求的时候运行的线程)
4.定时器线程 (比如运行setTimeout setInterval的线程)
5.浏览器事件线程 (比如我们进行浏览器事件点击的线程)

接着为大家介绍异步任务,引入异步任务的原因就是因为当初js是单线程而不是多线程导致的,因为js是单线程,同步执行代码的,你想假设你进行一个http请求,你必须等这个请求结束,你才可以继续执行后面的代码,这样性能很差;所以在引入异步任务后,达到异步的效果,你进行网络请求,运行在网络请求线程,并不会影响后面的代码执行,达到异步并发的效果,所以我认为异步任务,可以来说是模拟js多线程的产物;

回调函数队列就是基于异步任务产生的,假设我们进行多个异步任务,那么怎么确定哪个异步任务回调函数执行的顺序呢?所以就有了回调函数队列,我们定义这些异步任务的时候,基本上都会带着回调函数,这些异步任务会在各自的线程中执行,那么他们执行好了,就会把回调函数加入到回调函数队列中,注意他们是谁先执行好,谁排到队列的前面,按照先进先出的原则,去从回调函数队列中去取,然后在js引擎线程中去执行,

这个时候就需要引入event loop,浏览器是基于事件驱动的,他会进行event loop,不断的去从回调函数队列中去取,直到回调函数队列取完;接着我们谈谈微任务,比如说浏览器原生的 Promise, object.observe等,而通过这些微任务的回调函数形成了一个微任务回调函数队列,同样按照先进先出的原则去执行。

注意微任务队列和回调函数队列是2个不同的概念,event loop是基于回调函数队列而不是微任务队列。
js function stack 由若干个function形成的一个栈,按照先入后出来执行,你可以理解为js引擎线程执行代码都是按这个原则来执行的,而如何形成这个function栈就要通过下面的解释来看。

首先根据我们的script标签会加入到回调函数队列,进行第一次的event loop,然后会执行js function stack里的代码,执行完js function stack 里面的代码后,会从微任务队列里面去取排在队列前面的微任务回调函数,加入到js function stack, 然后去执行,然后执行完再去从微任务队列中去取这个时候排在队列前面的函数,直到微任务队列里回调函数去玩,再进行下一次event loop,同上,直到回调函数队列取完。

第一次event loop (根据script标签包裹的js代码触发执行)
回调函数队列:setTimeout callbackFunc, ajax callbackFunc
微任务队列:Promise callbackFunc
js function stack:定义的同步代码
||
||定义的同步代码执行完
||
回调函数队列:setTimeout callbackFun, ajax callbackFunc
微任务队列:暂无
js function stack: Promise callbackFunc
||
||回调函数中定义的同步代码执行完
||
回调函数队列:setTimeout callbackFunc, ajax callbackFunc
微任务队列: (本次event loop中,所有微任务会被取完,微任务变空)
js function stack:

第二次event loop
回调函数队列:ajax callbackFun
微任务队列:process.nextTick callbackFunc, Promise callbackFunc(本次event loop中加入新的微任务)
js function stack:setTimeout callbackFunc

||
||回调函数中定义的同步代码执行完
||
回调函数队列:ajax callbackFunc
微任务队列: Promise callbackFunc
js function stack:process.nextTick callbackFunc

||
||回调函数中定义的同步代码执行完
||
回调函数队列:ajax callbackFunc
微任务队列:
js function stack: Promise callbackFunc

||
||回调函数中定义的同步代码执行完
||
回调函数队列:ajax callbackFunc
微任务队列: (本次event loop中所有定义的微任务都已经执行完,微任务队列为空)
js function stack:

第三次event loop 同上
回调函数队列:
微任务队列:
js function stack: ajax callbackFunc

||
||回调函数中定义的同步代码执行完
||
回调函数队列:
微任务队列: (本次event loop中所有定义的微任务都已经执行完,微任务队列为空,并且回调函数队列同样为空,不再进行event loop,除非再次有回调函数加入到回调函数队列中)
js function stack:

至此我们通过下面的例子来理解setTimeout和setInterval:
有时候我们在项目中通过js去动态操作dom,可能dom渲染花的时间有点长,然后我们通过js立刻就要获取这个dom元素,这个时候我们就会获取不到,平常我们的做法是把获取这个dom元素的代码放在setTimeout中,然后就可以正常获取成功,为什么这样可以成功呢?基于我们上文的理解,可以简化为如下过程:
html:
<div id="oee-login" class="login-bg" ng-if="config.showLoginStatus">

            <span>login</span>

</div>

js:
$scope.config={

        showLoginStatus:false
    }
    $scope.config.showLoginStatus=true;
    var loginElement=document.getElementById('oee-login');
     if(loginElement)
     {
         console.log("dom渲染后";
     }else{
         console.log("dom未渲染");
     }
     $timeout(function () {
         loginElement=document.getElementById('oee-login')
         if(loginElement)
         {
             console.log("dom渲染后" );
         }else{
             console.log("dom未渲染");
         }
     },0)

打印如下:
dom未渲染
dom渲染后

虽然我们设置scope.config.showLoginStatus=true; 但是gui线程却不会立刻去更 新UI,所以在本次event loop,这个时候我们去获取dom节点,是获取不到的;然后我们通过timeout后再去获取,由上文可得,
此时加入了第二次event loop,而第一次event loop后,gui线程进行了UI更新,此时dom节点就有了,所以我们这个时候再去获取就会获取成功了。
在这里我需要额外提一点,记得当时在做项目的时候,有一个表格是通过js配置完数据后动态生成的,设置完数据后,我就获取dom节点这个时候就同上,第一次event loop没有获取到,然而我设置了$timeout的第二个参数为0,在第二次event loop中
还是没有获取到,根据我的理解认为因为是这个表格dom太复杂,导致在第二次event loop的时候dom还是没有渲染好,然后我就把第二个参数设置的比较大一点,然后在第二次event loop中就获取到了dom节点。

通过上面的例子,我发现了一个问题,就是$timeout的第二个参数,设置多少才能成功获取,可能这次dom复杂度为10(这里便于理解dom复杂度,抽象出来的),你设置延时参数为100ms,下次dom复杂度为30,你该如何设置延时参数呢?
当时开始想法是用for循环如下:
js:
var isCreate=false;
do{
var loginElement=document.getElementById('oee-login');

if(loginElement)
{
isCreate=true;
}
}while(!isCreate);
不过很快就被我否定,根据上文的理解,gui线程与js引擎线程是互斥的,所以在同一次event loop中,dom节点永远都不会有,isCreate永远都是false,所以我们要把获取dom节点放在下一次event loop中,前文我们是通过$timeout来让加入到下一次event loop中,又因为延时参数不好设置,所以有了以下2种解决方案:
一:通过递归的方式:
js:
var isCreate=true;
var loginElement=null;
var timeoutEr=null;
judgeElementStatus();
function judgeElementStatus(){

loginElement=document.getElementById('oee-login');
if(loginElement)
{
if(timeoutEr){
timeout.cancel(timeoutEr); //取消隐藏的定时器 ps:在angular开发的时候最好在监听destroy时候取消隐藏的定时器 } console.log("dom渲染后" ); } else{ timeoutEr=timeout(function(){
judgeElementStatus();
},30);
}
}
二:通过setInterval在angularjs中用intervalinterval具体用法这里就不赘述,如下:
js:
var intervalEr=null;
var loginElement=null;
intervalEr=$interval(function(){
loginElement=document.getElementById('oee-login');

if(loginElement)
{
if(intervalEr){
$interval.cancel(intervalEr); //取消隐藏的定时器 ps:在angular开发的时候最好在监听destroy时候取消隐藏的定时器
}
console.log("dom渲染后" );
}
},30);
通过以上得到最终核心点,1.gui线程与js引擎线程是互斥的;2.通过js操作dom元素,我们需要在至少下一次event loop中才可以等到dom渲染成功
以上就是我的理解,欢迎大家一起讨论。

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

推荐阅读更多精彩内容