• <tfoot id="ukgsw"><input id="ukgsw"></input></tfoot>
    
    • 久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
      分享

      深入理解JavaScript運行機制

       WindySky 2018-02-07

      前言

      • 本文是寫作在給團隊新人培訓之際,所以其實本文的受眾是對JavaScript的運行機制不了解或了解起來有困難的小伙伴。也就是說,其實真正的原理和本文闡述的并不完全符合,就如中學課本和大學課本一樣,大學老師會告訴你高中的一些東西是在某些理想情況下得到的結論,本文同理。
      • 本文的目的是希望大家閱讀之后能對JavaScript的運行機制有一個比較直觀比較快的認識,但更重要的是自己動手實踐,只有實踐才能真正發現問題和得到提升:)
      • 收到了大家的支持和反饋,非常感謝:)

      想要理解JavaScript的運行機制,需要分別深刻理解以下幾個點:

      • JavaScript的單線程機制
      • 任務隊列(同步任務和異步任務)
      • 事件和回調函數
      • 定時器
      • Event Loop(事件循環)

      JavaScript的單線程機制

      JavaScript的一個語言特性(也是這門語言的核心)就是單線程。什么是單線程呢?簡單地說就是同一時間只能做一件事,當有多個任務時,只能按照一個順序一個完成了再執行下一個。

      JavaScript的單線程與它的語言用途是有關的。作為一門瀏覽器腳本語言,JavaScript的主要用途是完成用戶交互、操作DOM。這就決定了它只能是單線程,否則會導致復雜的同步問題。

      設想JavaScript同時有兩個線程,一個線程需要在某個DOM節點上添加內容,而另一個線程的操作是刪除了這個節點,那么瀏覽器應該以誰為準呢?

      所以為了避免復雜性,JavaScript從誕生起就是單線程。

      為了提高CPU的利用率,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以這個標準并沒有改變JavaScript單線程的本質。

      任務隊列

      一個接一個地完成任務也就意味著待完成的任務是需要排隊的,那么為什么會需要排隊呢?

      通常排隊有以下兩種原因:

      • 任務計算量過大,CPU處于忙碌狀態;
      • 任務所需的東西為準備好所以無法繼續執行,導致CPU閑置,等待輸入輸出設備(I/O設備)。> 比如有的任務你需要Ajax獲取到數據才能往下執行

      由此JavaScript的設計者也意識到,這時完全可以先運行后面已經就緒的任務來提高運行效率,也就是把等待中的任務先掛起放到一邊,等得到需要的東西再執行。就好比接電話時對方離開了一下,這時正好有另一個來電,于是你便把當前通話掛起,等那個通話結束后,再連回之前的通話。

      所以也就出現了同步和異步的概念,任務也被分成了兩種,一種是同步任務(Synchronous),另一種是異步任務(Asynchronous)。

      • 同步任務:需要執行的任務在主線程上排隊,一個接一個,前一個完成了再執行下一個
      • 異步任務:沒有馬上被執行但需要執行的任務,存放在“任務隊列”(task queue)中,“任務隊列”會通知主線程什么時候哪個異步任務可以執行,然后這個任務就會進入主線程并被執行。> 所有的同步執行都可以看作是沒有異步任務的異步執行

      具體來說,異步執行如下:

      • 所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。

        也就是所有能被馬上執行的任務都在主線程上排好了隊,一個接一個的被執行。

      • 主線程之外,還存在一個“任務隊列”(task queue)。只要異步任務有了運行結果,就在“任務隊列”之中放置一個事件。

        也就是說每個異步任務準備好了就會立一個唯一的flag,這個flag用來標識對應的異步任務。

      • 一旦“執行棧”中的所有同步任務執行完畢,系統就會讀取“任務隊列”,看看里面有哪些事件。那些對應的異步任務,就結束等待裝袋,進入執行棧開始被執行。

        也就是主線程把之前的任務做完了之后,就會來看“任務隊列”中的flag,來把對應的異步任務打包來執行。

      • 主線程不斷重復以上三步。

        只要主線程空了,就會去讀取“任務隊列”。這個過程會被不斷重復,這就是JavaScript的運行機制。

      事件和回調函數

      事件

      “任務隊列”是一個事件的隊列(也可以理解成是消息的隊列),IO設備完成一項任務,就會在“任務隊列”中添加一個時間,表示相關的異步任務可以進入“執行棧”。接著主線程讀取“任務隊列”,查看里面有哪些事件。

      “任務隊列”中的事件,除了IO設備的事件以外,還包括一些用戶產生的事件(比如鼠標點擊、頁面滾動等等)。只要指定過回調函數,這些事件發生時就會進入“任務隊列”,等待主線程讀取。

      回調函數

      所謂“回調函數”(callback),就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數,當主線程開始執行異步任務,就是執行對應的回調函數。

      “任務隊列”是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧一清空,“任務隊列”上第一位的事件就自動進入主線程。但是,如果包含“定時器”,主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主線程。

      Event Loop

      主線程從“任務隊列”中讀取事件,這個過程是循環不斷的,所以整個的運行機制又稱為“Event Loop”(事件循環)

      為了更好地理解Event Loop,下面參照Philip Roberts的演講中的一張圖。

      Event Loop

      上圖中,主線程在運行時,產生了heap(堆)和stack(棧),棧中的代碼調用各種外部API,并在“任務隊列”中加入各種事件(click,load,done)。當棧中的代碼執行完畢,主線程就會讀取“任務隊列”,并依次執行那些事件所對應的回調函數。

      執行棧中的代碼(同步任務),總是在讀取“任務隊列”(異步任務)之前執行。

      var req = new XMLHttpRequest();
      req.open('GET', url);
      req.onload = function (){};
      req.onerror = function (){};
      req.send();
      

      上面的代碼中的req.send方法是Ajax操作向服務器發送數據,它是一個異步任務,意味著只有當前腳本的所有代碼執行完,系統才會去讀取“任務隊列”。所以,它與以下的寫法是等價的。

      var req = new XMLHttpRequest();
      req.open('GET', url);
      req.send();
      req.onload = function (){};
      req.onerror = function (){};
      

      也就是說,指定回調函數的部分(onload和onerror),在send()方法的前面或后面是無關緊要的,因為它們屬于執行棧的一部分,系統總是執行完它們才會去讀取“任務隊列”。

      定時器

      除了放置異步任務的事件,“任務隊列”還可以放置定時事件,即指定某些代碼在多少時間之后執行。這叫做定時器(timer)功能,也就是定時執行的代碼。

      SetTimeout()setInterval()可以用來注冊在指定時間之后單次或重復調用的函數,它們的內部運行機制完全一樣,區別在于前者指定的代碼是一次性執行,后者會在指定毫秒數的間隔里重復調用:

      setInterval(updateClock, 60000); //60秒調用一次updateClock()
      

      因為它們都是客戶端JavaScript中重要的全局函數,所以定義為Window對象的方法。

      但作為通用函數,其實不會對窗口做什么事情。

      Window對象的setTImeout()方法用來實現一個函數在指定的毫秒數之后運行。所以它接受兩個參數,第一個是回調函數,第二個是推遲執行的毫秒數。 setTimeout()setInterval()返回一個值,這個值可以傳遞給clearTimeout()用于取消這個函數的執行。

      console.log(1);
      setTimeout(function(){console.log(2);}, 1000);
      console.log(3);
      

      上面代碼的執行結果是1,3,2,因為setTimeout()將第二行推遲到1000毫秒之后執行。

      如果將setTimeout()的第二個參數設為0,就表示當前代碼執行完(執行棧清空)以后,立即執行(0毫秒間隔)指定的回調函數。

      setTimeout(function(){console.log(1);}, 0);
      console.log(2)
      

      上面代碼的執行結果總是2,1,因為只有在執行完第二行以后,系統才會執行“任務隊列”中的回調函數。

      總之,setTimeout(fn,o)的含義是,指定某個任務在主線程最早可得的空閑時間執行,也就是盡可能早地執行。它在“任務隊列”的尾部添加一個事件,因此要等到同步任務和“任務隊列”現有的事件都處理完,才會的到執行。

      HTML5標準規定了setTimeout()的第二個參數的最小值(最短間隔),不得低于4毫秒,如果低于這個值,就會自動增加。

      需要注意的是,setTimeout()只是將事件插入了“任務隊列”,必須等到當前代碼(執行棧)執行完,主線程才會去執行它指定的回調函數。要是當前代碼耗時很長,有可能要等很久,所以并沒有辦法保證回調函數一定會在setTimeout()指定的時間執行。

      由于歷史原因,setTimeout()setInterval()的第一個參數可以作為字符串傳入。如果這么做,那這個字符串會在指定的超時時間或間隔之后進行求值(相當于執行eval())。

      關于深入理解定時器的工作原理,這里推薦閱讀jQuery的作者John Resig的一篇文章: http:///blog/how-javascript-timers-work/

      我自己也翻譯了這篇文章,如有問題,歡迎指正:http:///2016/12/07/how-javascript-timers-work/

      參考阮一峰老師的博文 http://www./blog/2014/10/event-loop.html

      參考《JavaScript權威指南》

        本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發現有害或侵權內容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發表

        請遵守用戶 評論公約

        類似文章 更多

        主站蜘蛛池模板: 婷婷四房播播| 国产睡熟迷奷系列网站| 国产精品一区在线蜜臀| 国产日产欧产美韩系列麻豆| 国产精品欧美一区二区三区不卡| 成人拍拍拍无遮挡免费视频| 精品午夜福利在线观看| 亚洲一区二区精品极品| 日韩国产精品无码一区二区三区| 四虎在线播放亚洲成人| 精品无码一区在线观看| 中文字幕日韩精品国产| 搡女人真爽免费视频大全| 天堂V亚洲国产V第一次| 国产亚洲精久久久久久无码| 无码国产偷倩在线播放| 国产精品毛片无码| 中文亚洲成A人片在线观看| 亚洲综合色AAA成人无码| 亚洲国产五月综合网| 亚洲一区久久蜜臀av| 日韩精品久久久久久久电影蜜臀| 欧美不卡无线在线一二三区观| 在厨房被C到高潮A毛片奶水| 人妻系列无码专区免费| 在线 | 18精品免费1区2| 免费人成视频在线观看网站| 国产中文字幕在线一区| 亚洲乱码无码永久不卡在线| 麻豆国产AV剧情偷闻女邻居内裤| 亚洲AV永久无码精品三区在线 | 国产不卡一区二区在线| 亚洲AV高清一区二区三区尤物 | 欧美综合人人做人人爱| 久热综合在线亚洲精品| 国产强奷在线播放| 亚洲日韩精品欧美一区二区| 717午夜伦伦电影理论片| 蜜芽久久人人超碰爱香蕉| 国产成人一区二区不卡| 96在线看片免费视频国产|