實例代碼 js 性能、單線程、setTimeout 性能優化的解決方法
前言
-
之前有寫過關于瀏覽器中關于js線程的文檔,請移步 這里 查看!但覺得偏過于技術化了,對于實際理解意義不大,所以想乘此機會用一種大家都能懂的話語方式來記錄一下自己對 瀏覽器中js線程 的理解,以及建立在此基礎一些優化方案!
-
這篇文檔不是技術文檔,只是力求把相關概念用最土的話說清楚!
-
因為這部分技術在我看來是前端理解性能的高級話題了!所以我不敢保證我寫的會很好,亦不敢保證能一定帶給你實質性的超越。我唯恐自己經驗、理解有限,難免會有表達出錯的地方!所以歡迎你們 issue ,我們一切來參與起來把這個話題向更好的方向完善!
什么是線程?
- 對于這個問題,我想沒有一定的工作閱歷沉淀或者相關專業方面的學習,恐怕難以敘述清除這個術語了!你要把這個問題問我,估計我也很難回答上來!但又如何?對于搞開發這種需要實際干事的工作來說,恐怕只是讓你正確把一個專業術語表達出來,恐怕對你的工作沒有多大意義!我們只要知道這個東西是干嘛的就行了!至于怎么把這個術語表達清楚,那是專門干這行的人的工作!好了,首先還是允許我把從搜索引擎上搜到的線程的描述粘貼在下面:
線程是程序中一個單一的順序控制流程。進程內一個相對獨立的、可調度的執行單元,是系統獨立調度和分派CPU的基本單位指運行中的程序的調度單位。
-
從上面的描述中大概知道了幾個關鍵點:計算機給線程單獨分派的空間、獨立的單元、可執行的執行單元及順序控制流程!按我的理解就是“一個獨立的程序順序執行空間”!
-
如果做個比喻的話:線程就好比一個人去開公司,注冊公司、運營、銷售、推廣都是他一個人在干,而且是按照一定的順序干,比如需要先要有項目、再找供應商、等項目成熟了,再注冊公司,最后一步一步做大!這個順序都是最開始企劃好的!這些話換到瀏覽器里就是:我們寫的腳本只有一個執行單元來管理代碼,并且按我們寫代碼的順序依次執行代碼。看下面代碼例子:
var a = 'a';
var b = 'b';
console.log(a);
console.log(b);
//因為打印a的操作在前面,而打印b的操作在后面,那么打印a的代碼先執行,而打印b的操作后執行
-
順便說一下多線程唄,顧名思義就是多個單線程一起來管理我們的代碼,比如上面那個開公司的例子,老板等公司做大了之后,他終于明白了:他一個人再牛、再能干,可是公司大到一定程度,他一個人是忙不過來的,即便他一個人能省一筆很大的請工人所帶來的薪水開銷!于是他確定聘請工人來幫他工作,于是大家一起努力,共同把公司做得更大,這里每個工人就相當于一個線程!
-
多線程和單線程的區別:還是拿前面開公司的例子來說明,一個人(單線程)做的時候,自己想怎么做就怎么做,不用怕影響別人,而且消耗的社會資源(吃喝拉撒)也少,因為一個人再消耗也消耗不到哪里去!而且還能省下很多費用(工人費用等),但是問題公司怎么做都做不大,做來做去還是一個小作坊,不能充分利用工人閑散資源來擴展公司等!多個人(多線程)一起工作的時候,效率提高了,但同時社會資源開銷也大了,而且老板還要拿出一筆很大的費用來支付工人的工作,要不然老板估計的日子不好過!從這里我們作如下結論:
單線程:優點是簡單、占用資源少,缺點就是不能有效利用資源,比如多核cpu等,不適用那種復雜的業務場景
多線程:優點是快速、充分利用資源,缺點是資源消耗大,容易造成讓你的電腦死機等!
- 這樣理解是不是就能很好理解線程了呢, 其實現實中很多道理是想通的 !
為什么瀏覽器是單線程的?
- 如果需要回答這個問題,我們首先需要知道瀏覽器的工作原理!瀏覽器在得到你的html之后,解析文檔首先得到dom tree,然后解析css得到render tree。最后在通過js實現控制頁面的顯示、交互等!類似這種工作步驟就注定了瀏覽器里面的js不能是多線程的,為什么呢!試想一下如果是多線程的話:瀏覽器一邊解析得到dom tree,一邊又解析得到css tree,然后繪制頁面,假如此時dom tree還沒有完全解析得到,或者css tree沒有完全解析完成,那么此時繪制頁面會不會亂套?又或是一個還沒有繪制完成的元素用實現某些邏輯會不會有問題?這些都是限制瀏覽器中js不能使用多線程的原因!
瀏覽器通過事件隊列來實現處理異步邏輯
- 瀏覽器中js確實是單線程的,但是瀏覽器是怎么響應某個按鈕的點擊處理或者在將來某個時間執行特定腳本呢!這里就要提到時間隊列了,這是個什么概念呢!按我自己的理解就是:瀏覽器給腳本又單獨分配了一個線程,這里確實是多線程!只不過是瀏覽器宿主環境提供的,用來處理將來js中異步需要執行的代碼或處理某些事件的回調!為什么叫隊列呢!人家都叫隊列了,說明這些異步腳本不是隨便放進去的,而是按照一種方式有序地排在里面的!看下面的代碼:
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>demo</title>
</head>
<body>
<button id="demo">點擊</button>
<script type="text/javascript">
window.onload = function(){
var btn = document.getElementById('demo');
setTimeout(function(){
console.log('10之后');
},10000);
setTimeout(function(){
console.log('5秒之后');
},5000);
btn.onclick = function(){
console.log('btn');
};//我們在打開頁面大概5秒到10秒的時候點擊button(說明一下,并不是準確的,只是方便舉例)
//我們發現先打印‘5秒之后’,再打印‘btn’,最后打印‘10秒之后’
};
</script>
</body>
</html>
上面的打印順序說明異步代碼是一種順序(先來后到)在事件隊列里面排序的!將來觸發事件的時候總是以這樣一個順序去查找相應的代碼然后執行之!
從單線程的角度出發怎么提升瀏覽器性能
- 前面的分析得知,單線程就是一個人在工作,所以你就不要一下子給很多工作給他做,比如給他本來是多個人(多線程)做的工作,這樣即時能做,估計瀏覽器也會很卡,或者假死或卡死!實際上頁面場景中這樣的工作包括更新dom、監聽某種滾動、resize事件回調處理等!我們來看一個例子,我們需要用是js實現渲染一個2萬行6列的表格,我們首先不做代碼優化,看下效果會怎么樣,代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>渲染table測試</title>
<style type="text/css">
*{
margin: 0px;
padding: 0px;
}
.container{
width: 1000px;
margin: 0px auto;
font-size: 14px;
text-align: center;
color: #000;
}
table{
border-spacing: 0px;
width: 100%;
line-height: 1.5em;
}
</style>
</head>
<body>
<div id="box" class="container"></div>
<script type="text/javascript">
window.onload = function(){
var boxDom = document.getElementById('box');
var cTable = document.createElement('table');
var index = 0;//單元格索引,從0開始
for(var i = 0;i<20000;i++){
var tr = document.createElement('tr');
for(var j= 0;j<6;j++){
var td = document.createElement('td');
td.innerHTML = index;
tr.appendChild(td);
index++;
};
cTable.appendChild(tr);
};
boxDom.appendChild(cTable);
};
</script>
</body>
</html>
- 我們在瀏覽器中看到實際效果:瀏覽器等待了一段時間才渲染出來,在此期間瀏覽器像是被卡主了一樣什么都不能做!好,我們對上面的代碼做一個優化,使其分幾步完成上面的需求,這樣每次要做的事情就不會太多了!代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>渲染table測試</title>
<style type="text/css">
*{
margin: 0px;
padding: 0px;
}
.container{
width: 1000px;
margin: 0px auto;
font-size: 14px;
text-align: center;
color: #000;
}
table{
border-spacing: 0px;
width: 100%;
line-height: 1.5em;
}
</style>
</head>
<body>
<div id="box" class="container"></div>
<script type="text/javascript">
window.onload = function(){
var boxDom = document.getElementById('box');
var cTable = document.createElement('table');
var index = 0;//單元格索引,從0開始
//這次我們分5步來完成上面的任務,其實也可以分更多步
var oneStepNum = 4000;
var currentStep = 1;
var renderTable = function renderTable(){
for(var i = 0;i<oneStepNum;i++){
var tr = document.createElement('tr');
for(var j = 0;j<6;j++){
var td = document.createElement('td');
td.innerHTML = index;
tr.appendChild(td);
index++;
};
cTable.appendChild(tr);
};
boxDom.appendChild(cTable);
currentStep++;
if(currentStep>5){
clearTimeout(timer);
return;
};
var timer = setTimeout(renderTable,0);
};
renderTable();//渲染dom
};
</script>
</body>
</html>
-
我們發現代碼經過這樣優化之后,是不是dom很快就被渲染出來了呢!其思想就是:把一個很耗性能的操作或長時間的操作分解成一些耗性能較少或這耗時少的操作,并善于利用setTimeout用來分解一些操作,就不會造成耗時長的代碼使瀏覽器卡死的情況了!而且能快速的把頁面呈現在用戶面前!
-
其實很多優化代碼都是采用這種原理做的,比如防抖等!
來自:https://github.com/woai30231/webDevDetails/tree/master/12