首頁  >  文章  >  web前端  >  js setTimeOut()應用

js setTimeOut()應用

hzc
hzc轉載
2020-07-03 10:51:572383瀏覽

setTimeout,前端工程師必定會打交道的一個函數.它看上去非常的簡單,樸實.有著一個很不平凡的名字--定時器.讓年少的我天真的以為自己可以操縱未來.卻不知樸實之中隱含著驚天大密.我還記得我第一次用這個函數的時候,我天真的以為它就是js實現多線程的工具.當時用它實現了一個坦克大戰的小遊戲,玩兒不亦樂乎.可是隨著在前端這條路上越走越遠,對它理解開始產生了變化.它似乎開始蒙上了面紗,時常有一些奇怪的表現讓我捉摸不透.終於,我的耐心耗盡,下定決心,要撕開它的面具,一探究竟.

要說setTimeout的淵源,就得從它的官方定義說起.w3c是這麼定義的

setTimeout() 方法用於在指定的毫秒數後呼叫函數或計算表達式。

看到這樣一個說明,我們明白了它就是一個定時器,我們設定的函數就是一個"鬧鐘",時間到了它就會去執行.然而聰明的你不禁有這樣一個疑問,如果是settimeout(fn,0)呢?按照定義的說明,它是否會立刻執行?實踐是檢驗真理的唯一標準,讓我們來看看下面的實驗

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <script>
        alert(1); 
        setTimeout("alert(2)", 0); 
        alert(3); 
    </script>
</body>
</html>

這是一個很簡單的實驗,如果settimeout(0)會立即執行,那麼這裡的執行結果就應該是1->2>3  . 然而實際的結果卻是1->3->2. 這說明了settimeout (0)並不是立即執行.同時讓我們對settimeout的行為感到很詭異.

js引擎是單執行緒執行的

我們先把上面的問題放一放.從js語言的設計上來看看是否能找到蛛絲馬跡.

我們發現js語言設計的一個很重要的點是,js是沒有多線程的.js引擎的執行是單線程執行.這個特性曾經困擾我很久,我想不明白既然js是單線程的,那麼是誰來為定時器計時的?是誰來發送ajax請求的?我陷入了一個盲區.即將js等同於瀏覽器.我們習慣了在瀏覽器裡面執行程式碼,卻忽略了瀏覽器本身.js引擎是單線程的,可是瀏覽器卻可以是多線程的,js引擎只是瀏覽器的一個線程而已.定時器計時,網絡請求,瀏覽器渲染等等.都是由不同的線程去完成的. 口說無憑,咱們依然看一個例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
</body>
<script>
    var isEnd = true;
    window.setTimeout(function () {
        isEnd = false;//1s后,改变isEnd的值
    }, 1000);
    while (isEnd);
    alert('end');
</script>
</html>

isEnd默認是true的,在while中是死循環的.最後的alert是不會執行的. 我添加了一個定時器,1秒後將isEnd改為false. 如果說js引擎是多線程的,那麼在1秒後,alert就會被執行.然而實際情況是,頁面會永遠死循環下去.alert並沒有執行.這很好的證明了,settimeout並不能作為多線程使用.js引擎執行是單線程的.

event loop

從上面的實驗中,我們更加疑惑了,settimeout到底做了什麼事情呢?

原來還是得從js語言的設計上尋找答案.

js引擎單執行緒執行的,它是基於事件驅動的語言.它的執行順序是遵循一個叫做事件隊列的機制.從圖中我們可以看出,瀏覽器有各種各樣的線程,比如事件觸發器,網絡請求,定時器等等.線程的​​聯繫都是基於事件的.js引擎處理到與其他線程相關的代碼,就會分發給其他線程,他們處理完之後,需要js引擎計算時就是在事件隊列裡面添加一個任務. 這個過程中,js並不會阻塞代碼等待其他線程執行完畢,而且其他線程執行完畢後添加事件任務告訴js引擎執行相關操作.這就是js的異步編程模型.

如此我們再回過頭來看settimeout(0)就會恍然大悟.js程式碼執行到這裡時,會開啟一個定時器執行緒,然後繼續執行下面的程式碼.該執行緒會在指定時間後往事件隊列裡面插入一個任務.由此可知settimeout(0)裡面的操作會放在所有主線程任務之後. 這也就解釋了為什麼第一個實驗結果是1->3-2 .

由此可見官方對於settimeout的定義是有迷惑性的.應該給一個新的定義:

在指定時間內, 將任務放入事件佇列,等待js引擎空閒後被執行.

js引擎與GUI引擎是互斥的

談到這裡,就不得不說瀏覽器的另外一個引擎---GUI渲染引擎. 在js中渲染操作也是異步的.比如dom操作的程式碼會在事件佇列中產生一個任務,js執行到這個任務時就會去調用GUI引擎渲染.

js语言设定js引擎与GUI引擎是互斥的,也就是说GUI引擎在渲染时会阻塞js引擎计算.原因很简单,如果在GUI渲染的时候,js改变了dom,那么就会造成渲染不同步. 我们需要深刻理解js引擎与GUI引擎的关系,因为这与我们平时开发息息相关,我们时长会遇到一些很奇葩的渲染问题.看这个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <table border=1>
        <tr><td><button id=&#39;do&#39;>Do long calc - bad status!</button></td>
            <td><p id=&#39;status&#39;>Not Calculating yet.</p></td>
        </tr>
        <tr><td><button id=&#39;do_ok&#39;>Do long calc - good status!</button></td>
            <td><p id=&#39;status_ok&#39;>Not Calculating yet.</p></td>
        </tr>
    </table>    
<script>
function long_running(status_p) {
    var result = 0;
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    document.querySelector(status_p).innerHTML = 'calclation done' ;
}
document.querySelector('#do').onclick = function () {
    document.querySelector('#status').innerHTML = 'calculating....';
    long_running('#status');
};
document.querySelector('#do_ok').onclick = function () {
    document.querySelector('#status_ok').innerHTML = 'calculating....';
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
};
</script>
</body>
</html>

我们希望能看到计算的每一个过程,我们在程序开始,计算,结束时,都执行了一个dom操作,插入了代表当前状态的字符串,Not Calculating yet.和calculating....和calclation done.计算中是一个耗时的3重for循环. 在没有使用settimeout的时候,执行结果是由Not Calculating yet 直接跳到了calclation done.这显然不是我们希望的.而造成这样结果的原因正是js的事件循环单线程机制.dom操作是异步的,for循环计算是同步的.异步操作都会被延迟到同步计算之后执行.也就是代码的执行顺序变了.calculating....和calclation done的dom操作都被放到事件队列后面而且紧跟在一起,造成了丢帧.无法实时的反应.这个例子也告诉了我们,在需要实时反馈的操作,如渲染等,和其他相关同步的代码,要么一起同步,要么一起异步才能保证代码的执行顺序.在js中,就只能让同步代码也异步.即给for计算加上settimeo0t.

settimeout(0)的作用

不同浏览器的实现情况不同,HTML5定义的最小时间间隔是4毫秒. 使用settimeout(0)会使用浏览器支持的最小时间间隔.所以当我们需要把一些操作放到下一帧处理的时候,我们通常使用settimeout(0)来hack.

requestAnimationFrame

这个函数与settimeout很相似,但它是专门为动画而生的.settimeout经常被用来做动画.我们知道动画达到60帧,用户就无法感知画面间隔.每一帧大约16毫秒.而requestAnimationFrame的帧率刚好是这个频率.除此之外相比于settimeout,还有以下的一些优点:

  • requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧,每帧大约16毫秒.

  • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

  • 但它优于setTimeout/setInterval的地方在于它是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。

总结:

  1. 浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。

  2. javascript引擎是基于事件驱动单线程执行的.JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。

  3. 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

  4. 当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

推荐教程:《JS教程

以上是js setTimeOut()應用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除