1、JS的執行順序問題
瀏覽器是按照從上到下的順序解析頁面,因此正常情況下,JavaScript腳本的執行順序也是從上到下的,即頁面上先出現的代碼或先被引入的代碼總是被先執行,即使是允許並行下載JavaScript文件時也是如此。
Javascript語言的執行環境是"單線程"(single thread)。所謂"單線程",就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行后面一個任務,以此類推。這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是只要有一個任務耗時很長,后面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript代碼長時間運行(比如死循環),導致整個頁面卡在這個地方,其他任務無法執行。為了解決這個問題,Javascript語言將任務的執行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。
2、JS的同步和異步概念
"同步模式"就是上一段的模式,后一個任務等待前一個任務結束,然后再執行,程序的執行順序與任務的排列順序是一致的、同步的;"異步模式"則完全不同,每一個任務有一個或多個回調函數(callback),前一個任務結束后,不是執行后一個任務,而是執行回調函數,后一個任務則是不等前一個任務結束就執行,所以程序的執行順序與任務的排列順序是不一致的、異步的。
"異步模式"非常重要。在瀏覽器端,耗時很長的操作都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax操作。在服務器端,"異步模式"甚至是唯一的模式,因為執行環境是單線程的,如果允許同步執行所有http請求,服務器性能會急劇下降,很快就會失去響應。
概念講到這里已經足矣,關鍵是真正要理解,我們可以打開某一個網站的請求為例,F12打開調試模式:

客戶端向服務器發出了三個請求,這三個ajax請求在時間調度上可以看到是相互重疊的,意味着異步的現象的存在,並不是執行完第一個請求再順序去執行第二第三個,而是在啟動了頭一個的請求之后緊接着馬上就去請求第二個,第三個請求也緊隨其后,而請求耗時都是在運行中,意味着回調函數還未得以執行,瀏覽器已經另開一個線程去走下面的方法了。
我在項目中並未深入理解這個異步現象中,以至於在調試下方代碼的時候出現了問題,具體見案例:
var area;//area變量,執行Fn1()方法后才會有值 function Fn1(areaId){ debugger; var url = 'admin/area/queryArea/'+areaId;
//自己封裝的一個ajax異步請求 $my.Get(url,function(data){ debugger; area= data; }); }
//打印area信息 function Fn2(area){ console.log(area); }
執行方式1:
Fn1(); Fn2();
console控制台會報錯,而異常原因還是是Fn2()中的area為undefined,按照我們正常的理解如果是順序執行,明明Fn1執行在前,為什么會得到這樣的結果呢,如果理解了Ajax的異步請求機制就很好理解,Fn1()執行,而后遇到ajax請求了,於是瀏覽器開另一個線程往下去順序執行Fn2()方法,Fn2()方法中的變量是依賴Fn1()中的回調賦值函數,但是此時請求還未完成,因此會報異常信息。
正確的寫法是如下:
var area;//area變量,執行Fn1()方法后才會有值 function Fn1(areaId){ debugger; var url = 'admin/area/queryArea/'+areaId; //自己封裝的一個ajax異步請求 $my.Get(url,function(data){ debugger; area= data; Fn2(area); }); } //打印area信息 function Fn2(area){ console.log(area); }
於是程序就能正常執行打印area信息了。實際上我們可以用調試模式來一步步跟代碼:
方法運行到了第一個debugger處,一步步調試進入到$my.Get()方法,這里封裝一個簡單的ajax的Get請求,默認為異步調用,但是方法卻並未進入到下一個debugger中而是直接跑到了別的方法里頭去了,在別處運行了一會兒程序才開始執行到第二個debugger中。
從中我們可以總結:假如有兩個ajax請求f1()和f2(),f1發起了請求去往服務器,而f1中的方法並不會馬上得到執行而是另開一個線程去執行f2,如果這兩個方法沒有依賴關系,而f1內的方法需要等到服務器響應后才會執行回調函數。在實際開發中,我們可能會有這樣的關系,即f2()的執行需要用到f1()回調的內容,如果僅僅攜程f1();f2();是會出現頁面報錯的,正確的寫法則是:f1(f2());
var username;
function fn1(){
ajax("user/getNname",function (data) {
//the callback function
username=data.username;
//fn2()順序執行才能拿到變量值
fn2();
})
//此處寫fn2()還是拿不到變量值,將為undefined
//fn2()
}
function fn2(){
console.log(username);
}
采用這種方式,我們把異步操作變成了同步操作,f1不會堵塞程序運行,相當於先執行程序的主要邏輯,將耗時的操作推遲執行。
回調函數的優點是簡單、容易理解和部署,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,而且每個任務只能指定一個回調函數,在js邏輯不是太復雜的情況下,采用這種方式是十分可行的。當然異步操作在面臨復雜的業務場景還有更好的辦法,這篇文章提煉總結的方法不錯:js的四種異步編程方式。可以參考學習。
