簡單案例淺析JS線程機制

故事背景

故事的開始是這樣的,有一個需求,須要將一個List的數據加載到頁面上展現。javascript

需求看上去很簡單對吧,可是因爲List數據量巨大,而且須要對List裏的每一個對象進行必定的操做。前端

因此呢,每次都會形成幾秒鐘的瀏覽器假死,這對用戶的體驗簡直是殺傷性的。java

//最初的方法
function original(myList){
    for (var i; i < myList.length; i++) {
        //1.myList[i]對象的一系列操做
        doSomething(myList[i]);
        //2.將對象進行DOM加載 addChild(myList[i])
        addChild(myList[i]);
    }
}
複製代碼

一號案發現場

經過調試,發現addChild(myList[i])DOM加載是一個很是消耗性能的方法,小弟不才,想到的解決方案就是將List裏面的對象分批進行加載,因此寫下了以下解決方案。web

function solutionOne(myList){
    var tempList = new Array();
    for (var i=0; i < myList.length; i++) {
        //1.myList[i]對象的一系列操做
        doSomething(myList[i]);
        tempList.push(myList[i]);
        //2.當積累到100個對象或者遍歷結束的時候進行DOM加載
        if (tempList.length == 100 || (i + 1) == myList.length) {
            //3.將臨時數組一塊兒進行DOM加載 
            addChilds(tempList);
            tempList = new Array();
        }
    }
}
複製代碼

可是如今問題就來了,頁面一如既往的假死,好像個人解決方案並無起任何的做用。 :cookie:數組

這是爲何呢,明明已經分批加載頁面,講道理的話,頁面會一部分一部分的渲染纔對呀。瀏覽器

二號案發現場

這時候呢,經過google大佬,我瞭解了JS的線程機制的大體原理。 :lollipop:cookie

  • javascript是一門單線程語言,任何的併發都是單線程的假裝。

敲黑板敲黑板,牢記這句話就是分析JS線程機制的核心。下面看看單線程是如何實現併發操做的。前端工程師

JS與JAVA併發區別

由上圖能夠得知,JS在宏觀角度是多線程併發操做,可是微觀角度在一個時間永遠只有一個線程在運行。多線程

  • 瀏覽器內核是多線程,常駐三大線程爲:JavaScript引擎線程、GUI渲染線程、瀏覽器事件觸發線程。

JS多線程運行機制

簡單描述一下上圖,瀏覽器事件觸發線程 會把觸發的事件(例如click,keydown等)添加到 Event Queue 的隊尾,JavaScript引擎線程 會經過 Event Loop 不斷處理 Event Queue 裏面的任務,而且能夠經過 setTimeout 等方法產生一些異步任務加入隊尾。然而 JavaScript引擎線程GUI渲染線程 是互斥的,因此當一個線程在運行,另外一個會被掛起。併發

有了上面的理論基礎,理所固然,我將使用setTimeout來優化個人代碼,因而我寫下了以下解決方案。

function solutionTwo(myList){
    var tempList = new Array();
    for (var i = 0; i < myList.length; i++) {
        //1.myList[i]對象的一系列操做
        doSomething(myList[i]);
        tempList.push(myList[i]);
        //2.當積累到100個對象的時候進行DOM加載
        if (tempList.length == 100 || (i + 1) == myList.length) {
            //3.將臨時數組一塊兒進行DOM加載 
            setTimeout(function(){addChilds(tempList);},0);
            tempList = new Array();
        }
    }
}
複製代碼

根據個人推理,數組會很快遍歷完,而後會有不少回調函數加入隊列,而後分批渲染。

然而案發現場是慘重的,最後什麼都沒有加載上。 :cake:

找出真兇

是誰形成了二號案發現場呢,若是你是合格的前端工程師,可能很快就找到了真兇。

沒錯,疑犯就是setTimeout。 :candy:

由於setTimeout在將回調函數加入任務隊列時會檢查任務隊列是否已經包含了這個回調函數,若是包含則放棄添加,所以就形成了在執行回調函數的時候tempList已經被從新初始化了,因此什麼都加載不了。

舉個例子:
下面兩個方法將模擬相似進條度0-100的加載過程。

//方法1的顯示結果爲:直接顯示100
function myFun1(){
    for (var i = 0; i < 100; i++) {
        setTimeout(function(){
            document.getElementById("messages").innerHTML = i;},0);
    }
}
//方法2的顯示結果爲:從0開始顯示,直到100結束
function myFun2(i){
    if (i <= 100){
        document.getElementById("messages").innerHTML = i;
        setTimeout(function(){myFun2(i+1);},0);
    } else {
        return;
    }
}
複製代碼

故事講到這裏就該結尾了,兇手找到了,也獲得了最終的解決方案,對JS的線程機制也有了一個粗略的理解,對於JAVA出身的小菜鳥,向全棧webdev又邁出了一步。

最後附上最終解決方案:

function solutionFinally(myList,listIndex){
    if(listIndex < myList.length) {
        var tempList = new Array();
        var number = 0;
        while(listIndex < myList.length && number <= 100) {
            //1.myList[i]對象的一系列操做
            doSomething(myList[i]);
            tempList.push(myList[i]);
            listIndex++;
            number++;
        }
        //2.當積累到100個對象的時候進行DOM加載
        addChilds(tempList);
        setTimeout(function(){solutionFinally(myList,listIndex);},0);
    } else {
        return;
    }
}
複製代碼
相關文章
相關標籤/搜索