一個小例子引發的思考
emmmm……
最近在看一個開源庫,看其中的栗子中發現了一段很有意思的代碼。栗子簡化一下是下面的這個樣子的:
function a() { console.log('a'); setTimeout(b); }
function b() { console.log('b') }
function c() { console.log('c') };
a(c());
可能你覺得這沒什麼,不就幾個簡單的方法調用麼,有什麼複雜的?那麼我們先來看一下在Chrome的控制檯裏面會輸出什麼?
可能的確如你所料,控制檯依次輸出了c、a、b(雖然有個不知道什麼鬼的undefined,這個等下再說),那說明你對JS中函數的執行順序有一定的瞭解。的確,前面聲明瞭三個方法,a,b,c,然後加上括號使a成爲語句執行。但是a(c())這種寫法怪怪的,內部怎麼執行的?還有這個輸出怎麼會有一個undefined(面試題埋坑啊)?
我同樣也有這樣的疑問,那麼深究之前,先整理一下我們的疑問。
這段代碼的輸出是什麼?a(c())這種寫法是什麼鬼?undefined是什麼鬼?setTimeout不寫時間參數會咋樣?會有瀏覽器差異麼?
那麼我們一個個的來爲我們疑問來尋找答案……
這段代碼的輸出是什麼?爲什麼會有這樣子的輸出?
其實答案已經看到了,就是c、a、b。因爲JS是單線程執行的,所以在執行a方法的過程中,先執行了()中的語句,也就是c()方法,所以順序執行也就是c、a、b。
emmm……說了和沒說一樣,沒關係往下看。
a(c())這種寫法是什麼鬼?
接着上個答案的來說。要明白a(c())這種寫法是什麼鬼?我們得先了解在JS中()是個什麼作用?對於普通的語句,()直接執行。對於函數來說,JavaScript解釋器會在默認的情況下把遇到的function關鍵字當作是函數聲明語句(statement)來進行解釋的。先來看下面的這幾個栗子:
(111) // 常量,當做語句處理。打印 111
(var a) // 變量聲明。 報錯
(a = 1) // 賦值語句,不要寫';'。打印 1
function(){console.log('aa')}() // 匿名函數,不是標準的函數聲明語句。報錯
(function(){console.log('aa')}()) // 立即執行函數。打印 aa
(function(){console.log('aa')})() // 立即執行函數。打印 aa
所以其實大致的意思已經很明瞭。 通俗的來講就是 因爲c首先是一個很標準的函數語句,然後()又可以執行語句,所以a(c())的執行順序就是先執行了c方法,然後繼續執行a方法。如果換成下面的這種方式c就不會執行了:
a(c()); // c、a、b
a(c); // a、b
a(function(){console.log('nini')}); // a、b
好像也沒有那麼繞……
undefined是什麼鬼?
基本路子搞明白了,那麼這個undefined是什麼鬼?其實很簡單,這是Chrome控制檯的一種默認機制,對於執行語句來說,控制檯會默認去拿上一行語句的輸出。
a = 1; // 打印 1
var a = 1; // 打印 undefined, 因爲這是兩行語句
(function(){return 1;})() // 打印 1
function a() {} a(); // 打印 undefined
function a() { return 11; } a(); // 打印 1
所以打印undefined的問題找到了,那麼問題來了,node中會不會打印呢?嘗試了一波兒發現,node中並不會打印,所以同樣是V8引擎,但是控制檯這一塊兒還是有差距的。
setTimeout不寫時間參數會咋樣?
終於碰到了最喜歡的setTimeout方法。查閱一堆亂七八糟資料後,setTimeout不寫時間參數的話,會由瀏覽器默認給加上延遲參數,具體多少各家瀏覽器都不一樣。
emmm……無所謂了,那麼 setTimeout(func,0) 和 setTimeout(func) 有什麼區別麼?
下面兩張圖是在控制檯進行了4次的對比試驗。
沒有時間參數
時間參數爲0
好像加上0的確會快一些。不過我們也知道,即使是0,setTimeout 的作用也只是加到當前執行的事件隊列當中,而且在瀏覽器端每次執行也會有4ms的延遲。具體的可以看一看我的另一篇: 看了這麼久JS,事件隊列你真的懂嗎?。
關於瀏覽器的4ms的差異延遲,我們暫時不用在意。當我們需要遇到性能瓶頸時可以去研究一下,具體的方案的實現還都挺有意思的。
會有瀏覽器差異麼?
有,不過控制檯的輸出影響不大。不必在意。
結尾
以上,基本上該思考的都思考了,不知道你看到這裏,還有什麼在思考的,倘若有的話,不妨說來聽聽。