先說一件大家都知道的一句話
javaScript語言的一大特點是單線程,單線程就是一次他只能做一件事~
其實我也在學(xué)js的時候,在很多博客都看過這句話,好像是一眼就看懂了,但是一直不知道這句話到底是干嘛的,到底有什么用~,所以就注定有些bug還是要踩一次.
第一個因為js是單線程的困惑來自于WebApi的動態(tài)創(chuàng)建元素, 對,異步并不是我在ajax里面學(xué)習(xí)到的,而是DOM里面學(xué)習(xí)到的,但是ajax讓我更深的去理解了什么是異步
先看下第一段讓我困惑,但是其實非常簡單的異步代碼,把核心邏輯簡化后的代碼(其實很簡單,每個人都看得懂)
<div>
<ul>
<li>我是第一個li</li>
<li>我是第二個li</li>
</ul>
<input id="btn" type="button" value="動態(tài)生成一個li">
</div>
var li = document.getElementsByTagName('li');
var btn = document.getElementById(('btn'));
?
//動態(tài)創(chuàng)建一個li元素
btn.onclick = function () {
var li = document.createElement('li');
li.innerHTML="我是動態(tài)創(chuàng)建的li";
document.getElementsByTagName('ul')[0].appendChild(li);
};
//給每一個li元素注冊點擊事件,當(dāng)點擊的時候,讓背景顏色變紅
for (let i = 0; i < li.length; i++) {
li[i].onclick = function () {
li[i].style.backgroundColor ='red';
}
}
?
console.log(1);
setTimeout(function () {
console.log(2)
}, 0);
console.log(3);
//輸出結(jié)果1 3 2
</pre>
執(zhí)行的結(jié)果卻是,動態(tài)創(chuàng)建的li標(biāo)簽,沒有點擊事件...
上面這個問題其實對于一個不了解什么是異步的小白來說,真的是一個很大的問題,拿到這個問題后,我開始了一大串的百度,最后,問題沒有得到實質(zhì)的解決,反而拿到一個新的概念,叫"異步";
什么是異步任務(wù)?
簡單的理解就是,js中所有的代碼塊都可以按照任務(wù)分為兩種任務(wù),一種是同步任務(wù),一種是異步任務(wù),而js遇到這兩種任務(wù)的時候,會按照同步和異步兩種類別進行區(qū)別對待.
同步任務(wù)進入主線程,從上往下執(zhí)行,一條一條代碼執(zhí)行,形成一個叫執(zhí)行棧的東西
異步任務(wù)會進入另外一個任務(wù)隊列中,要等待主線程執(zhí)行完了,才會執(zhí)行,
異步任務(wù)包括像需要用戶觸發(fā)的點擊事件,滾動事件,鍵盤事件,定時器等等,我喜歡簡單的去理解 ,我的理解是未來才會發(fā)生的事件就是異步事件
聽起來肯定還是有些復(fù)雜,很抽象,我們用一張圖來表示
看到上面這張圖中,左邊的同步任務(wù)非常好理解,那就來說一下右邊的異步任務(wù);
當(dāng)事件被識別為異步任務(wù)的時候,瀏覽器會把這個任務(wù)放在 Event Table
這個Event Table會把傳入的異步事件注冊為一個回調(diào)函數(shù),然后傳給Event Queue,
事件注冊為回調(diào)函數(shù)后,并放在Event Queue中等待,那什么時候這個回調(diào)函數(shù)會被執(zhí)行呢?
js中引擎中有一個monitoring process進程,在主線程執(zhí)行完畢后 , 會不斷的檢查Event Queue有沒有回調(diào)函數(shù)在等待執(zhí)行,如果有,就把這個回調(diào)函數(shù)放在主線程上執(zhí)行
而這個異步任務(wù)不斷排隊,主線程不斷檢查異步排隊,主線程不斷執(zhí)行的循環(huán)就叫事件循環(huán) Event Loop
了解了事件循環(huán),再來看上面的代碼,就能夠發(fā)現(xiàn)問題的原因:
1.定時器的時間就算是0.他也是一個異步任務(wù),HTML5標(biāo)準(zhǔn)規(guī)定了setTimeout()的第二個參數(shù)的最小值(最短間隔)栅屏,不得低于4毫秒,如果低于這個值,就會自動增加
2.動態(tài)創(chuàng)建元素的問題
//動態(tài)創(chuàng)建一個li元素
btn.onclick = function () {
var li = document.createElement('li');
li.innerHTML="我是動態(tài)創(chuàng)建的li";
document.getElementsByTagName('ul')[0].appendChild(li);
};
//給每一個li元素注冊點擊事件,當(dāng)點擊的時候,讓背景顏色變紅
for (let i = 0; i < li.length; i++) {
li[i].onclick = function () {
li[i].style.backgroundColor ='red';
}
}
1.btn.onclick = function(){} 已經(jīng)被注冊成一個回調(diào)函數(shù),等待在事件隊列中,注冊的回調(diào)函數(shù)中l(wèi)i沒有點擊事件,,此時for循環(huán)已經(jīng)結(jié)束
知道原理后其實簡單的修改一下就可以了,原理就是雖然你在隊列中排隊,但是點擊事件已經(jīng)注冊在排隊里的回調(diào)函數(shù)里面的, 把事件注冊在了未來
function fn() {
var li = document.createElement('li');
li.innerHTML = "我是動態(tài)創(chuàng)建的li";
document.getElementsByTagName('ul')[0].appendChild(li);
li.onclick = function () {
li.style.backgroundColor = 'red';
}
}
btn.onclick = fn;
知道異步后有什么用?
了解異步之后,對于回調(diào)函數(shù)的學(xué)習(xí)能起到一個順?biāo)浦鄣淖饔?而回調(diào)函數(shù)是js語言中非常重要的一個東西, 可以說是js中的精髓了, 異步是未來才會執(zhí)行的事件,而回調(diào)函數(shù)專門用來接收未來才會有結(jié)果的數(shù)據(jù).一張圖來解釋~
我們知道在ajax中請求拿到的結(jié)果是一個異步操作,而這個結(jié)果,是我在未來才能拿到的一個結(jié)果,而且作為函數(shù)的封裝者,這個未來的結(jié)果,我不能自己用, 而是要把未來的結(jié)果給別人用 . 這就讓人很蒙圈 .話不多數(shù),我們先來看下jQuery封裝的ajax
$.ajax({
type:'get',
url:"",
data:{},
success:function (data) {
//我的data是未來(請求成功后)才會有結(jié)果是數(shù)據(jù)
console.log("成功的回調(diào)函數(shù)")
}
})
success是一個異步結(jié)果,我們把他按照異步的原理簡化一下
//假設(shè)fn是一個幫我在服務(wù)器端拿數(shù)據(jù)的一個函數(shù),拿到后他不能自己處理,因為他的作用是幫我拿數(shù)據(jù)
function fn(a,callback){
//我是一個成功后的回調(diào)異步結(jié)果
setTimeout(function () {
//我是回調(diào)函數(shù),我把未來才會產(chǎn)生的結(jié)果作為參數(shù)傳給了我的調(diào)用者
a = 500;
callback(a);
},100)
}
//我需要用一個數(shù)據(jù),但是這個數(shù)據(jù)我在未來才能用的到,我傳入一個回調(diào)函數(shù),他幫我?guī)砹宋磥淼慕Y(jié)果
fn(100,function (data){
var b = 1000;
console.log(data + b); //1500
})
第一次js執(zhí)行,fn調(diào)用,傳參100,并在第二個參數(shù)里聲明了一個匿名函數(shù),此時主線程執(zhí)行完畢
事件隊列100毫秒后,把a=500;作為參數(shù)傳入回調(diào)函數(shù),同時調(diào)用回調(diào)函數(shù)
fn中的第二個參數(shù)拿到實參并執(zhí)行回調(diào)代碼
這樣callback就完成了他的使命,拿到異步結(jié)果,把結(jié)果作為參數(shù)給調(diào)用者進行運算
最后有興趣的再看下源生的封裝
function ajax (method, url, params, done) {
method = method.toUpperCase();
var xhr = new XMLHttpRequest();
if (typeof params === 'object') {
var tempArr = []
for (var key in params) {
var value = params[key]
tempArr.push(key + '=' + value)
};
params = tempArr.join('&');
};
?
if (method === 'GET') {
url += '?' + params
};
?
xhr.open(method, url, false);
var data = null;
if (method === 'POST') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
data = params
};
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
//this,responseText是未來的結(jié)果
//被done回調(diào)函數(shù)帶走了
done(this.responseText);
};
xhr.send(data);
};
// 調(diào)用者============================
ajax('get', 'time.php', {}, onDone(a){})</pre>