setTimeout 的黑魔法

2021-02-19 程式設計師之家

作者:李三思

原文:www.cnblogs.com/fly-snow/archive/2016/04/24/5427865.html

(點擊文末閱讀原文即可前往) 

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='do'>Do long calc - bad status!</button></td>

            <td><div id='status'>Not Calculating yet.</div></td>

        </tr>

        <tr><td><button id='do_ok'>Do long calc - good status!</button></td>

            <td><div id='status_ok'>Not Calculating yet.</div></td>

        </tr>

    </table>    

<script>

function long_running(status_div) {

    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_div).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計算加上settimeout.

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開銷。

總結:

瀏覽器的內核是多線程的,它們在內核制控下相互配合以保持同步,一個瀏覽器至少實現三個常駐線程:javascript引擎線程,GUI渲染線程,瀏覽器事件觸發線程。

javascript引擎是基於事件驅動單線程執行的.JS引擎一直等待著任務隊列中任務的到來,然後加以處理,瀏覽器無論什麼時候都只有一個JS線程在運行JS程序。

當界面需要重繪(Repaint)或由於某種操作引發回流(reflow)時,該線程就會執行。但需要注意 GUI渲染線程與JS引擎是互斥的,當JS引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JS引擎空閒時立即被執行。

當一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理。這些事件可來自JavaScript引擎當前執行的代碼塊如setTimeOut、也可來自瀏覽器內核的其他線程如滑鼠點擊、AJAX異步請求等,但由於JS的單線程關系所有這些事件都得排隊等待JS引擎處理。

微信公眾號內回複數字「1」

小編拉你進粉絲微信群

不是在文章評論裡回復

相關焦點

  • 重新認識javascript的settimeout和異步
    然後看了一下文章下面的評論,發現5樓和6樓的回答很有道理,主要意思就是說javascript引擎是單線程執行的,while循環那裡執行的時候,settimeout裡面的函數根本沒有執行的機會,這樣while那裡永遠為真,造成死循環。
  • 黑魔法城堡無限打金版
    《黑魔法城堡-無限打金》一款2DMMOrpg角色扮演手機網遊,美術風格為暗黑奇幻風。遊戲美術畫面精美,玩法有趣,遊戲中的人物、怪物均採用全新設計的高品質模型,動作更為自然,形象精緻生動,遊戲場景精美宏大,音效清晰逼真,營造出良好的遊戲氛圍,極具帶入感。
  • 嚮往的生活黑魔法遊戲怎麼玩 遊戲規則了解下
    嚮往的生活黑魔法遊戲怎麼玩 遊戲規則了解下時間:2018-06-25 18:06   來源:今日頭條   責任編輯:沫朵 川北在線核心提示:原標題:嚮往的生活黑魔法遊戲怎麼玩 遊戲規則了解下 在《嚮往的生活》之前的節目中一個數馬的遊戲難倒了眾人,近日播出的節目中黑魔法遊戲有讓很多觀眾摸不著頭腦,那下面就和小編一起來看看嚮往的生活黑魔法遊戲怎麼玩以及規則是什麼
  • 《哈利波特》:為什麼鄧布利多不讓斯內普擔任黑魔法防禦課教授?
    在他答應進入霍格沃茨當教授起,就一直希望擔任黑魔法防禦術的教授,而這個心願卻遲遲沒有達成。斯內普對黑魔法防禦術教授這個職位的執念,幾乎整個霍格沃茨師生都知道,可直到《混血王子》時,斯內普才如願所償,但最終還是難逃伏地魔的詛咒,連一個學期都沒有教完就離開了這個崗位。
  • 面試官:為什麼 Promise 比setTimeout() 快?
    作者:Milos Protic   譯者:前端小智 來源:devinduct原文:https://dmitripavlutin.com/javascript-promises-settimeout/
  • 哈利波特冷知識:黑魔法為什麼是邪惡的?伏地魔摸了摸自己的光頭
    在《哈利波特》中,鄧不利多和海格等人均有過類似黑魔法是邪惡的說法。英國魔法界官方和民間的打大多數人也對黑魔法持反對態度。在歐洲大陸的德姆斯特朗魔法學院,學生們可以通過專門的課程學習黑魔法,而英國的霍格沃茲魔法學校,卻只有一個黑魔法防禦術,涉及黑魔法知識的書籍也都放置在圖書館的禁書區,不讓學生隨意觀看。
  • 哈利波特:細數歷任黑魔法防禦課的老師,誰的業務能力最強呢?
    眾所周知,黑魔法防禦課當年遭到小湯姆·裡德爾的詛咒,每一任的老師都無法任教超過一年。而哈利在霍格沃茲的7年時間裡,果然就換了七位黑魔法防禦課的老師。那麼,這七位老師誰的業務能力更強呢,我們不妨來給他們排排序。第七位 吉德羅·洛哈特洛哈特教授在這7位當中絕對是屬於濫竽充數的那一種。
  • 【預言家日報】黑魔法防禦術教師總出事的原因(繼續補充中)
    【預言家日報】黑魔法防禦術教師總出事的原因巫師allya2002投稿
  • 哈利波特魔法世界你最想成為你的黑魔法防禦老師的是誰?
    如果教育局來檢查和指導,這些黑魔法防禦教授中的一些永遠不會通過測試。據說黑魔法防禦教授是接受詛咒的老師。多年來,教授在這個位置上,由於種種原因,總是必須在一年內成為之後離開。一些黑魔法防守技術教授的候選人確實不是很好。如果教育局的領導來視察,也許他們會被一些騙子和危險分子驚呆。但是這些人中哪一個是最壞的教授?
  • 黑魔法旋風來襲!達美樂萬聖小食18元嘗鮮!
    萬聖來臨,淘氣的你還不來放肆玩鬧~達美樂颳起了一股神秘的黑魔法旋風!女巫帽形鳳尾蝦神秘有料、圓嘟嘟芝士風味雞肉球怪萌力Max,還有暖心濃醇的熱飲,一口讓你「滿血復活」!萬聖相約達美樂,讓搞怪小餓魔們萌翻你~
  • 《哈利·波特》:斯內普為什麼沒有為了莉莉而放棄黑魔法呢?
    相信很多哈迷們都曾有過這樣的疑惑,既然斯內普如此深愛著莉莉,甚至不惜為了她而背叛伏地魔,那麼,當初他為什麼就不能為了莉莉而放棄黑魔法呢?畢竟作者J.K.羅琳曾在一次採訪中說過,如果斯內普不如此痴迷黑魔法的話,莉莉很有可能會愛上他。
  • 哈利波特裡學習成績優異的赫敏為什麼沒能在黑魔法防禦術得到優秀
    當然在原著中,三人組都各有優點和缺點,合作互補才能一路走到最後,可作為超級學霸的赫敏,在黑魔法防禦術考試中卻沒能優秀,只得到了一個良好,遠差於主角哈利波特,這是為什麼呢?其實,赫敏這個超級學霸,並不是全能型學霸,而是理論型學霸,實戰這種偏向實踐類的就是赫敏的弱項。
  • 聯大上,印度大使表示巴基斯坦使用黑魔法攻擊,並強調不是開玩笑
    「黑魔法」?如果不是在聯合國大會上,還以為是哄小孩子的動畫片臺詞。最搞笑的是,作為回應,巴基斯坦代表還得正式回復他。巴方代表穆尼爾·阿克拉姆指出,印方所言是沒有依據的謊言,巴基斯坦並沒有所謂的「黑魔法」武器,一切都是虛構的。同時譴責印方在克什米爾問題上所作的一切,都是虛偽毫無誠意的。
  • 《巴拉拉小魔仙》中使用魔法最魔幻的5個角色,第4個黑魔法好漂亮
    1.烏芝芝烏芝芝在劇裡可是非常強大的,可以說是黑魔仙小月的得力助手了,他其實是一本黑魔傳書,在嚴莉莉黑化後,小月把他當作黑化禮物送給了嚴莉莉,他也教了嚴莉莉很多的黑魔法。特別是只要是嚴莉莉想要的魔法,他都能找到,可以說是非常了解黑魔法了,畢竟小月根本沒空一直教嚴莉莉魔法。
  • 達美樂「黑魔法」來襲 萬聖節小食18元嘗鮮,更有暖湯熱飲上新
    (原標題:達美樂「黑魔法」來襲 萬聖節小食18元嘗鮮,更有暖湯熱飲上新)
  • 美國老翁辦世界唯一魔法學校 一生研習黑魔法
    英國媒體3月2日報導,68歲的奧伯倫·澤爾·瑞文哈特來自美國加州,將一生的精力都花在學習黑魔法上,甚至去超市購物時,也都穿著魔法袍帶著魔杖。與哈利·波特一樣,這些學生都要學習、研究對抗「黑魔法」的法術。校長奧伯倫稱:「魔法界的人們稱我為真實世界中的鄧布利多,但在《哈利·波特》出現之前,他們也叫我梅林或甘道夫。」(梅林和甘道夫分別是亞瑟王傳奇和《魔戒》中的大法師)。  格瑞魔法學校對外宣稱共有735名學生,奧伯倫透露,其中100名學生的年齡不滿18歲,他們就是現實版的「哈利·波特」。
  • 從實體零售的變遷,看電商還有哪些黑魔法需要學
    二、零售商業的黑魔法我們抬沃爾瑪的名頭來舉個例子。大家都熟悉的沃爾瑪的slogan是:天天低價/為客戶省錢。但大家可能都不知道,這樣一個「平價超市」,它最高的綜合利潤可以達到70%以上。吃瓜群眾的罵街式疑問:怎麼做到的?
  • Ian Goodfellow發推講2個機器學習黑魔法,教你如何推導公式
    >大數據文摘作品作者:小魚、土豆《深度學習》(花書)作者Ian Goodfellow今早連發了10條推特,細數了他最喜歡的兩個機器學習「黑魔法文摘菌給大家搭配了斯坦福的一門MOOC,一起學習風味更佳~拉至文末查看喔~Goodfellow稱,這是關於機器學習,他最喜歡的兩個快速理解理論推導的「黑魔法」。
  • 春雨碳酸泡泡麵膜評測,開啟面膜界的"黑魔法"
    在大眾認知中,面膜向來是護膚必備品,春雨面膜作為一款明星面膜,秉持匠心,打造了一系列天然派的產品,春雨碳酸泡泡麵膜是春雨首次將泡泡的概念融入面膜中,春雨碳酸泡泡麵膜神奇的"黑魔法"面膜在春雨家族中獨特存在。  正值換季時節,也正是敏感高發期,春雨面膜天然無刺激已成為諸多愛美人士的必選面膜。