簡單解釋單線程至朗、任務(wù)隊列的概念
單線程:JavaScript是一個單線程語言,瀏覽器只會分配一個js引擎線程來解析和執(zhí)行js同步代碼屉符。即任務(wù)是串行的,后一個任務(wù)需要等待前一個任務(wù)的執(zhí)行锹引。
任務(wù)隊列:所有同步任務(wù)都在主線程上執(zhí)行矗钟,形成一個執(zhí)行棧。主線程之外嫌变,還存在一個“任務(wù)隊列”真仲,指定過回調(diào)函數(shù)的事件發(fā)生時就會進(jìn)入“任務(wù)隊列”,等待主線程讀取初澎。線程把棧中任務(wù)做完之后秸应,就會來看“任務(wù)隊列”中的事件,“任務(wù)隊列”是一個先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)碑宴,排在前面的事件優(yōu)先被主線程讀取软啼。讀取過程基本上是自動的,只要執(zhí)行棧一清空延柠,“任務(wù)隊列”上第一位的事件就自動進(jìn)入主線程祸挪。但是,如果包含“定時器”贞间,主線程首先要檢查一下執(zhí)行時間贿条,某些事件只有到了規(guī)定的時間雹仿,才能返回主線程。主線程從“任務(wù)隊列”中讀取事件整以,這個過程是循環(huán)不斷的胧辽,所以整個的運行機(jī)制又稱為“Event Loop”。
setTimeout:N毫秒之后執(zhí)行某個函數(shù)公黑,一次一個ID邑商,實際延遲時間比N毫秒久,會在其他的運行完了以后最后來看setTimeout的內(nèi)容凡蚜,可想象為一個鬧鐘人断,設(shè)定了30秒后吃飯,30秒之后鬧鐘響了朝蜘,但當(dāng)時還在寫作業(yè)恶迈,那么會等作業(yè)寫完之后再來看鬧鐘的內(nèi)容并去執(zhí)行它。
setInterval:每隔N毫秒執(zhí)行某個函數(shù)谱醇,只有一個ID
瀏覽器對定時器setTimeout很懈怠蝉绷,當(dāng)用戶焦點離開該界面時,瀏覽器久變懶了枣抱,原本設(shè)置500ms做一件事情熔吗,但你沒有看它,它可能就1000ms才去做一件事情佳晶。
異步:異步就是一個猴急的人不愿意等桅狠,于是叫了一個黃牛(回調(diào)函數(shù))幫他等結(jié)果。
異步和回調(diào)一般同時出現(xiàn)轿秧。
比如排隊取號中跌,一個猴急的人不愿意等,但排隊取號是不可能馬上拿到號碼的菇篡,因為他不可能拿到未來的東西漩符。即用同步的方式無法拿到結(jié)果。
所以他派了一個黃牛(函數(shù))幫他排隊驱还,他自己去干別的事情嗜暴,等黃牛拿到了再把結(jié)果告訴他。
function 排隊取號(黃牛) {
setTimeout(function f2() {
黃牛('你的號碼是:233')
}, 3000)
}
function 黃牛(result) {
console.log(result)
}
排隊取號(黃牛)
黃牛是我的议蟆,所以黃牛拿到的就是我拿到的闷沥,如果你不清楚為什么,那就改寫一下代碼:
function 排隊取號(黃牛) {
setTimeout(function f2() {
黃牛('你的號碼是:233')
}, 3000)
}
//上面和下面分開看
var 我的號 = undefined
function 黃牛(result) {
我的號 = result
console.log(我的號)
}
排隊取號(黃牛)
面試題:
for(var i=0;i<5;i++){
console.log(i)//0,1,2,3,4
}
for (var i = 0; i < 5; i++) {
(function (i) {//這一行的i是一個新的變量i咐容,也可以叫j
setTimeout(function () {
console.log(i);//打印的i是新的變量i舆逃,這個i是全局中傳遞進(jìn)來的,沒有進(jìn)行自增操作
}, i * 1000);
})(i);//把全局的i的值傳遞給函數(shù)中的i或者j
}//大約0s后打出0,大約1s后打出1,大約2s后打出2路狮,大約3s后打出3虫啥,大約4s后打出4
for (var i = 0; i < 5; i++) {
setTimeout((function (i) {
console.log(i);
})(i), i * 1000);
}
//首先改寫代碼
for (var i = 0; i < 5; i++) {
var t1 = function (i) {
console.log(i);//t1的返回值是undefined
}
var t2 = t1(i)//t2是調(diào)用t1的結(jié)果
var t3 = i * 1000//0,1000,2000,3000,4000
setTimeout(t2, t3);
}
//五次循環(huán)分別執(zhí)行了五次setTimeout。分別是
//setTimeout(undefined,0)
//setTimeout(undefined,1000)
//setTimeout(undefined,2000)
//setTimeout(undefined,3000)
//setTimeout(undefined,4000)
//產(chǎn)生了undefined但并沒有打印undefined奄妨,運行的5次打印了5次t1(i)涂籽,這個i是新建的局部變量i,不是全局中的展蒂,全局中的i值會賦值給局部變量的i
//最終結(jié)果是0,1,2,3,4,沒有延時苔咪,因為setTimeout第一個參數(shù)為undefined锰悼,所以定時器什么也沒做。
for(var i=0;i<5;i++){
setTimeout(function(){
console.log(i)
},1000*i);
}//setTimeout是異步团赏,js引擎在for循環(huán)結(jié)束后才會開始執(zhí)行setTimeout的內(nèi)容箕般,也就是說console.log(i)之前for循環(huán)已經(jīng)結(jié)束了,i變?yōu)?舔清,所以結(jié)果是:大約0s后打出5,大約1s后打出5丝里,大約2s后打出5,大約3s后打出5体谒,大約4s后打出5
for (var i = 0; i < 5; i++) {
(function () {
setTimeout(function () {
console.log(i);
}, i * 1000);
})(i);
}//結(jié)果是:大約0s后打出5,大約1s后打出5杯聚,大約2s后打出5,大約3s后打出5抒痒,大約4s后打出5
//如果覺得不好理解可以將一行拆分為多行幌绍,聲明一個函數(shù)t,調(diào)用這個函數(shù)t
for (var i = 0; i < 5; i++) {
function t() {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
t(i);
}
做一個倒計時器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<select name="" id="mySelect" placeholder="選擇一個時間">
<option value="1" selected>1分鐘</option>
<option value="5">5分鐘</option>
<option value="10">10分鐘</option>
<option value="20">20分鐘</option>
</select>
<button id="startButton">start</button>
<button id="pauseButton" disabled>pause</button>
<button id="resumeButton" disabled>resume</button>
<div id="outputDiv">
</div>
<script>
var timeLeft = 10
let lastTimerID
function showTime() {
//通過id名可以直接獲取到頁面上的元素
outputDiv.textContent = timeLeft + '秒';
if (timeLeft === 0) return
timeLeft -= 1
lastTimerID = setTimeout(showTime, 1000)
}
startButton.onclick = function () {
var valueNumber = parseInt(mySelect.value, 10)
var seconds = valueNumber * 60
timeLeft = seconds
if (lastTimerID) {
window.clearTimeout(lastTimerID)
}
showTime()
pauseButton.disabled = false
}
pauseButton.onclick = function () {
if (lastTimerID) {
window.clearTimeout(lastTimerID)
resumeButton.disabled = false
pauseButton.disabled = true
}
}
resumeButton.onclick = function () {
showTime()
pauseButton.disabled = false
resumeButton.disabled = true
}
</script>
</body>
</html>
面試題
var startTime = +(new Date()),endTime;
setTimeout(function(){
endTime = +(new Date());
console.log(1);
console.log(endTime - startTime);
},3000);
setTimeout(function(){
endTime = +(new Date());
console.log(2);
console.log(endTime - startTime);
},2000);
while(+(new Date()) - startTime < 5000){}
console.log(+(new Date()) - startTime);
請問alert(1)和alert(2)的先后順序和時間間隔故响。
5s鐘的同步執(zhí)行完之后傀广,開始執(zhí)行setTimeout異步代碼,因為第二個插入的時間間隔早彩届,先執(zhí)行伪冰。
因為5s時間超過了3s,所以兩個函數(shù)幾乎同時執(zhí)行樟蠕。如果同步時間是1s贮聂,那就是第2s之后執(zhí)行第二個函數(shù),第3s后執(zhí)行第一個函數(shù)寨辩,代碼運行總時長3s多一點點
setTimeout(..) 并沒有把你的回調(diào)函數(shù)掛在事件循環(huán)隊列中寂汇。它所做的是設(shè) 定一個定時器。
當(dāng)定時器到時后,環(huán)境會把你的回調(diào)函數(shù)放在事件隊列中,如果這時候事件循環(huán)中已經(jīng)有 20 個項目了會怎樣呢?你的回調(diào)就會等待,定時器只能確保你的回調(diào)函數(shù)不會在指定的 時間間隔之前運行,但可能會在那個時刻運行,也可能在那之后運行,要根據(jù)事件隊列的 狀態(tài)而定(PS: 這就是造成定時器不準(zhǔn)確的緣由)捣染。
setTimeout(..0)(hack)進(jìn)行異步調(diào)度,基本上它的意思就是把這個函數(shù)插入到當(dāng)前事件循環(huán)隊列的結(jié)尾處骄瓣。