眾所周知,Javascript是單線程執行的,這也就是說:JavaScript在同一個時間上只能處理一件事。他不像C,Java等這些多線程的,可以開不同的線程去同時處理多件事情。
那么為什么別的語言都可以這么方便的去開多個線程去同時執行多個任務,JavaScript卻不行呢?
“天將降大任於斯人也,必先苦其心志,勞其筋骨,餓其體膚,空乏其身,行指亂其所為,所以動心忍性,曾益其所不能”
--《孟子》
正是因為JavaScript背負着重大的使命,所以他只能默默的看着別人擁有多線程。他作為瀏覽器腳本語言,只要是在於和用戶進行交互/處理前端數據/操作DOM。
以代碼舉例吧:
function addDom(){ var html = document.createElement("div"); html.innerHTML = "This is div "+i; document.getElementById("myDiv").appendChild(html); } function deleteDom(){ var myDiv = document.getElementById("myDiv"); //if(myDin.childNodes[0]){ myDiv.removeChild(myDin.childNodes[0]); //} }
注意:以上代碼注釋部分可以讓邏輯變的更嚴謹,此處需要報錯已證明觀點,所以加以注釋。
按照以上代碼執行addDom函數是能正確的生成這么一個DOM的吧,即便是在addDom函數還在執行過程中去執行deleteDom,deleteDom也將會在addDom執行完后才執行,那么就能完整的走完一個DOM的添加和刪除操作了,這正是因為JavaScript是單線程。
現在我們假如JavaScript可以有多線程,那么我們讓一個線程執行addDom函數的時候,在addDom還在執行的過程中再去執行deleteDom,這時候將會再開一個線程來執行,這時候瀏覽器腦子短路了,它不知道以哪個為主了...如果先執行完了deleteDom,那么效果就是即報了錯還沒達到想要的效果。
綜上所述,JavaScript確實不適用於多線程,為了交互,他只能獨自忍受了(所以當報錯不會繼續向下執行的時候,各位就別噴JavaScript了,好好檢查自己寫的代碼才是這時候該做的)。
事件列隊和異步執行
既然JavaScript是單線程執行的,那么有很多事件需要執行的時候,肯定需要排好隊一個個來的吧。接下來我們就扯扯事件隊列和異步。
JavaScript的事件隊列里就是編排着接下來將要被逐個執行的事件,只有當前一個任務被執行完了,才會接下來執行后面一個任務。當我們觸發一個事件,那么這個事件會被加入到事件列表末尾,當然不能插隊咯,畢竟都是良民吶~
以上就是對事件隊列的簡單介紹,那么異步又是怎么回事呢?
Javascript語言將任務的執行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。
同步就是按照事件隊列的順序,有條不紊的執行下來。
異步則不同,每個任務都可以有一或多個回調函數(callback),當前面那一個任務執行完后,不去執行下一個任務,先執行其本身存在的回調函數,而下一個任務不等前一個任務結束,自顧自的開始執行了,這時候執行順序就不一樣的,產生異步了。(注意:這里並不說明使用回調函數即產生異步哦)
貼上代碼:
var num = 0; function firstFn(){ num +=1; }; function secondFn(value){ if(value === 1){ console.log("The num is 1"); //The num is 1 } else{ console.log("The num still 0"); } } firstFn(); secondFn(num);
以上是能正常打印出來的,因為先執行了第一個函數,所以這時num已經被加1了,所以判斷生效。
var num = 0; function firstFn(){ setTimeout(function(){ num +=1; },0); // 這里我們用setTimeout做了異步處理 }; function secondFn(value){ if(value === 1){ console.log("The num is 1"); } else{ console.log("The num still 0"); //The num still 0 } } firstFn(); secondFn(num);
這時候,再這樣執行就沒用了。因為setTimeout將num++的事件放到了事件列表的末尾去了,second(num)是在它的前面,所以現在執行是打印 The num still 0。那么怎么證明該事件被放到最后了呢?看下面的代碼:
<div id="myDiv" onclick="addEvent()">click me</div>
var num = 0; function firstFn(){ setTimeout(function(){ num +=1; },0); }; function secondFn(value){ if(value === 1){ console.log("The num is 1"); //The num is 1 } else{ console.log("The num still 0"); } } firstFn(); function addEvent(){ secondFn(num); }
當我們點擊id為myDiv的div的時候,將觸發click事件吧,該事件會調用addEvent函數吧,addEvent函數會在事件隊列的末尾加入新的需要執行的事件吧。這時候我們點擊該div,就會打印 The num is 1 了。
同理理解setInterval。
下面順便貼一段有小伙伴提問過的問題代碼:
正常的代碼:
var i = 3; for(;i>0;i--){ console.log(i); //打印順序:3 2 1 };
"不正常"的代碼:
var i = 3; for(;i>0;i--){ setTimeout(function(){console.log(i);},0); //打印順序 0 0 0 };
人家問的就是為什么都設置延遲時間為0了,打印出來的還是不正常的。這也是因為異步,當函數被執行的時候,i的值已經為0。
對於setTimeout的通常描述:給定一個回調及N毫秒的延遲,setTimeout將會在N毫秒后運行該回調。
所以大多數情況下,這個描述只能算大致正確,不能算完全正確。
而且setTimeout還附帶了個隱藏的可大可小的坑(將由線程阻塞導致):
var start = new Date; setTimeout(function(){ var end = new Date; console.log("End Time: ",end - start," ms"); // 有幾次打印的是End Time: 1001 ms ,這還是在沒有其他阻塞的,只運行這一個事件的情況下出現偏差 },1000);
所以上面那個描述的N毫秒將在某些情況下會出現偏差。
好了,就寫這么多先吧。明天還得上班呢,今天算拖的很晚了...還是手機編輯的,本來打算睡覺,寫一半沒完成,心里怪難受的...
如理解有偏差,還望大家不吝指教,大家一起交流才能更好的進步。