摘要:單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行后一個任務。為了優化單線程的性能,將任務分成兩種,一種是同步任務,另一種是異步任務。每次循環的迭代,都將中的回調函數加入任務隊列等待執行。
今天在回顧JavaScript進階用法的時候,發現一個有趣的問題,話不多說,先上代碼:
for(var j=0;j<10;j++){ setTimeout(function(){console.log(j)},5000) }
看到這三行代碼,也許你會不耐煩道:又要講閉包?要吐了好么?別急,讓我們先來思考一下,這段代碼在瀏覽器中的執行結果是什么?
甲:順序打印0到9?
乙:這題我見過,打印十個10!
哪個答案正確?我們繼續上圖:
執行結果顯示,瀏覽器打印出了十個10(因為圖片處理的原因,按下回車到打印之前其實間隔了5秒左右),貌似乙勝出了。但如果你足夠細心,你會發現幾個問題:
為什么會循環打印十個10而不是0到9?
從結果來看,for循環執行完跳出之后,才開始執行setTimeout(所以j才等于10),為什么不是每次迭代都執行一次setTimeout呢?
如果上述三個問題你都能回答上來,恭喜你,你已經開始掌握了JavaScript深層次的知識,如果不能,那就乖乖往下看吧!
為什么會循環打印十個10
許多人習慣用第二個問題中的執行結果來回答這個問題:“for循環執行完跳出之后,才開始執行setTimeout,所以才打印了十個10”。這樣的答案,只能說是既應付了自己,又應付了別人。其實,要解答第一個問題,首先要解答的就是第二的問題。
為什么不是每次迭代都執行一次setTimeout大家都知道,JavaScript在ES6出現以前,是沒有塊狀作用域的,這就意味著, 在for循環中用var定義的變量j,其實是屬于全局的,即在全局范圍內都可以被訪問到,既然如此,那其實整個全局作用域中就只有一個j,每次for循環都i是在更新這個j。
那么現在關鍵的問題在于,為什么整個for循環會先于setTimeout執行,而不是我們正常理解的,一次迭代執行一次。
這就涉及到了JavaScript的核心特性:單線程。
JavaScript設計的初衷,是瀏覽器用來與用戶進行交互和DOM操作的。這就決定了它必須是單線程的,設想JavaScript同事有兩個線程,一個線程在DOM節點添加內容,一個線程刪除該節點,瀏覽器就會出現混亂。所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征,將來也不會改變。
單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等著。
為了優化單線程的性能,JavaScript將任務分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有主線程中的同步任務執行完畢,異步任務才會進入執行隊列執行。只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重復。
而setTimeout,就被JavaScript定義為異步任務。每次for循環的迭代,都將setTimeout中的回調函數加入任務隊列等待執行。也就是說,只有同步任務中的for循環完全結束,主線程中才會去任務隊列中找到尚未執行的十個setTimeout(十次迭代)回調函數并順序執行(先進先出)。而此時,i已經經過循環結束變成了10,所以,此時主線程執行的,是十個一摸一樣的打印i的回調函數,即打印十個10。至此就完美回答了第一和第二個問題,文章開頭的代碼與下面的代碼其實是等價的:
for(var i=0;i<10;i++){} setTimeout(console.log(i),5000) setTimeout(console.log(i),5000) setTimeout(console.log(i),5000) setTimeout(console.log(i),5000) setTimeout(console.log(i),5000) setTimeout(console.log(i),5000) setTimeout(console.log(i),5000) setTimeout(console.log(i),5000) setTimeout(console.log(i),5000) setTimeout(console.log(i),5000)
小小的一個setTimeout,牽扯出了很多JavaScript的深層次問題,雖然總結成一篇文章只有區區數百字,但是我在成文的過程中查閱了大量的資料,也做了許多實驗。
最后,給出一個很小但是仍然在困擾我的一個問題,希望有興趣的小伙伴可以跟我一起研究:
setTimeout(function(){while(true){}},6000); setTimeout(function(){console.log(1)},10000); setTimeout(function(){console.log(2)},5000);
上述代碼的執行順序是怎樣的?setTimeout的定時,是定時插入執行棧之后立即執行,還是立即插入執行棧定時執行?
期待大家的留言。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107100.html
摘要:一言以蔽之,閉包,你就得掌握。當函數記住并訪問所在的詞法作用域,閉包就產生了。所以閉包才會得以實現。從技術上講,這就是閉包。執行后,他的內部作用域并不會消失,函數依然保持有作用域的閉包。 網上總結閉包的文章已經爛大街了,不敢說筆者這篇文章多么多么xxx,只是個人理解總結。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結與《JavaScript忍者秘籍》 《你不知道的JavaScri...
下一篇:《你不知道的javascript》筆記_對象&原型 寫在前面 上一篇博客我們知道詞法作用域是由變量書寫的位置決定的,那this又是在哪里確定的呢?如何能夠精準的判斷this的指向?這篇博客會逐條闡述 書中有這樣幾句話: this是在運行時進行綁定的,并不是在編寫時綁定,它的上下文取決于函數調用時的各種條件this的綁定和函數聲明的位置沒有任何關系,只取決于函數的調用方式當一個函數被調用時...
摘要:異步請求線程在在連接后是通過瀏覽器新開一個線程請求將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件循環隊列中。 基礎:瀏覽器 -- 多進程,每個tab頁獨立一個瀏覽器渲染進程(瀏覽器內核) 每個瀏覽器渲染進程是多線程的,主要包括:GUI渲染線程 JS引擎線程 也稱為JS內核,負責處理Javascript腳本程序。(例如V8引擎) JS引擎線程負...
摘要:最受歡迎的引擎是,在和中使用,用于,以及所使用的。怎么處理每個引擎都有一個基本組件,稱為調用棧。也就是說,如果有其他函數等待執行,函數是不能離開調用棧的。每個異步函數在被送入調用棧之前必須通過回調隊列。例如方法是在中傳遞的回調函數。 ? 翻譯:瘋狂的技術宅 原文:www.valentinog.com/blog/engine… 從Call Stack,Global Me...
摘要:前言對于這門語言,其實我更喜歡稱它為,從一開始我們就已經涉及到異步編程,但是多數開發者從來沒有認真思考過自己程序中的異步,到底是怎么實現的,以及為什么會出現。 前言 對于JavaScript這門語言,其實我更喜歡稱它為ECMAScript,從一開始我們就已經涉及到異步編程,但是多數JavaScript開發者從來沒有認真思考過自己程序中的異步,到底是怎么實現的,以及為什么會出現。但是由于...
閱讀 1829·2021-09-22 15:55
閱讀 3521·2021-09-07 10:26
閱讀 628·2019-08-30 15:54
閱讀 684·2019-08-29 16:34
閱讀 839·2019-08-26 14:04
閱讀 3258·2019-08-26 11:47
閱讀 2134·2019-08-26 11:33
閱讀 2294·2019-08-23 15:17