對於一般的developer(特別是那些具有並行計算/多線程背景知識的developer)來說,js的異步處理着實稱得上詭異。而這個詭異從結果上講,是由js的「單線程」這個特性所致使的。javascript
我曾嘗試用「先定義後展開」的教科書方式去講解這一塊的內容,但發現極其痛苦。由於要理清楚這個東西背後的細節,並將其泛化、以更高的視角來看問題,着實涉及很是多的基礎知識。等到我把這些知識講清楚、講完,無異於逼迫讀者抱着操做系統、計算機網絡這樣的催眠書看上好個幾章節,着實沉悶而乏味。前端
而且更關鍵的是,在走到那一步的時候,讀者的精力早已消耗殆盡,徹底沒有心力再去關心這個最開始的問題——js的異步處理爲什麼詭異。java
因此,我決定反過來,讓咱們像一個初學者那樣,從一無所知開始,git
先使用「錯誤的理念」去開始咱們的討論,而後用代碼去發現和理念相違背的地方。
再作出一些修正,再考察一些例子,想一想是否還有不大滿意和清楚的地方,再調整。如此往復,咱們會像偵探那樣,先從一個不大正確的假設開始,不斷尋找證據,不斷修正假設,一步步追尋下去,直到抵達最後完整的真相。github
我想,這樣的寫做方式,更符合一我的真正的求知和研究過程,並可以爲你帶來更多關於「探索問題」的啓發。我想,這樣的思惟方式和研究理念,比普通的知識更爲重要。它可以讓你成爲知識的獵人,有能力獨立地覓食,而沒必要被迫成爲嬰孩,只能坐等他人餵食。算法
好了,讓咱們先從一塊js代碼,開始咱們的探索之旅。數據庫
console.log('No. 1'); setTimeout(function(){ console.log('setTimeout callback'); }, 5000); console.log('No. 2');
輸出結果是:編程
No. 1 No. 2 setTimeout callback
這塊代碼中幾乎沒什麼複雜的東西,全是打印語句。惟一的特別是函數setTimeout
,根據粗略的網上資料顯示,它接受兩個參數:後端
另外一個重點是,setTimeout
是一個異步函數,意思是個人主程序沒必要去等待setTimeout
執行完畢,將它的運行過程扔到別的地方執行,而後主程序繼續往下走。也便是,主程序是一個步調、setTimeout
是另外一個步調,便是「異步」的方式跑代碼。瀏覽器
若是你有一些並行計算或者多線程編程的背景知識,那麼上面的語句就再熟悉不過了。若是在多線程環境,無非是另起一根線程去運行打印語句console.log('setTimeout callback')
。而後主線程繼續往下走,新線程去負責打印語句,清晰明瞭。
因此綜合起來,這段代碼的意思是,主線程執行到語句setTimeout
時,就把它交給「其它地方」,讓這個「其它地方」等待5秒鐘以後運行。而主線程繼續往下走,去執行「No. 2」的打印。因此,因爲其它部分要等待5秒鐘以後才運行,而主線程馬上往下運行了「No. 2」的打印,最終的輸出結果纔會是先打印「No. 2」,再打印「setTimeout callback」。
嗯,so far so good。一切看來都比較美好。
若是咱們對上述程序作一點變更呢?例如,我可不可讓「setTimeout callback」這個信息先被打印出來呢?由於在並行計算中,咱們常常遇到的問題即是,因爲你不知道多個線程之間誰執行得快、誰執行得慢,因此咱們沒法斷定最終的語句執行順序。這裏咱們讓「setTimeout callback」停留了5秒鐘,時間太長了,要不短一點?
console.log('No. 1'); setTimeout(function(){ console.log('setTimeout callback'); }, 1); console.log('No. 2');
咱們將傳遞給setTimeout
的參數改爲了1毫秒。屢次運行後會發現,結果居然沒有改變?!彷佛有點反常,要再也不改小一點?改爲0?
console.log('No. 1'); setTimeout(function(){ console.log('setTimeout callback'); }, 0); console.log('No. 2');
屢次運行後,發現依舊沒法改變。這實際上是有點奇怪了。由於一般的並行計算、多線程編程中,經過屢次運行,你實際上是能夠看到各類沒法預期的結果的。在這裏,居然神奇地獲得了相同的執行順序結果。這就反常了。
但咱們還沒法徹底下一個確定的結論,可不可能由於是setTimeout
的啓動時間太長,而致使「No. 2」這條語句先被執行呢?爲了作進一步的驗證,咱們能夠在「No. 2」這條打印語句以前,加上一個for
循環,給setTimeout
充分的時間去啓動。
console.log('No. 1'); setTimeout(function(){ console.log('setTimeout callback'); }, 0); for (let i = 0; i < 10e8; i++) {} console.log('No. 2');
運行這段代碼,咱們發現,"No. 1"這條打印語句很快地顯示到了瀏覽器命令行,等了一秒鐘左右,接着輸出了
No. 2 setTimeout callback
誒?!這不就更加奇怪了嗎?!setTimeout
不是等待0秒鐘後馬上運行嗎,就算啓動再慢,也不至於等待一秒鐘以後,仍是沒法正常顯示吧?何況,在加入這個for
循環以前,「setTimeout callback」這條輸出不是馬上就顯示了嗎?
綜合這些現象,咱們有理由懷疑,彷佛「setTimeout callback」必定是在「No. 2」後顯示的,也便是:setTimeout
的callback函數,必定是在console.log('No. 2')
以後執行的。爲了驗證它,咱們能夠作一個危險一點的測試,將這個for
循環,更改成無限while
循環。
console.log('No. 1'); setTimeout(function(){ console.log('setTimeout callback'); }, 0); while {} // dangerouse testing console.log('No. 2');
若是setTimeout
的callback函數是按照本身的步調作的運行,那麼它就有可能在某個時刻打印出「setTimeout callback」。而若是真的是按照咱們猜想的那樣,「setTimeout callback」必須排在「No. 2」以後,那麼瀏覽器命令行就永遠不會出現「setTimeout callback」。
運行後發現,在瀏覽器近乎要臨近崩潰、達到內存溢出的情形下,「setTimeout callback」依舊沒有打印出來。這也就證實了咱們的猜想!
這裏,咱們第一次出現了理念和現實的矛盾。按照一般並行計算的理念,被扔到「其它地方」的setTimeout
callback函數,應該被同時運行。可事實倒是,這個「其它地方」並無和後一條打印「No. 2」的語句共同執行。這時候,咱們就必需要回到基礎,回到js這門語言底層的實現方式上去追查,以此來挖掘清楚這後面的貓膩。
js的特性之一是「單線程」,也便是從頭至尾,js都在同一根線程下運行。或許這是一個值得調查深刻的點。想來,若是是多線程,那麼setTimeout
也就該按照咱們原有的理念作執行了,但事實卻不是。而這二者的不一樣,便在於單線程和多線程上。
找到了這個不一樣點,咱們就能夠更深刻去思考一些細節。細想起來,所謂「異步」,就是要開闢某個「別的地方」,讓「別的地方」和你的主運行路線一塊兒運行。但是,若是如今是單線程,也就意味着計算資源有且只有一份,請問,你如何作到「同時運行」呢?
這就比如是,若是你去某個辦事大廳,去繳納水費、電費、自然氣。那麼,咱們能夠粗略地將它們分爲水費櫃檯、電費櫃檯、自然氣櫃檯。那麼,若是咱們依次地「先在水費櫃檯辦理業務,等到水費的明細打印完畢、繳納完費用後;再跑去電費櫃檯打印明細、加納費用;再跑去自然氣櫃檯打印明細、加納費用」,這就是一個同步過程,必須等待上一個步驟作完後,才能作下一步。
而異步呢,就是說咱們沒必要在某個環節浪費時間瞎等待。好比,咱們能夠在「打印水費明細」的空閒時間,跑到電費和自然氣櫃檯去辦理業務,將「電費明細、自然氣明細的打印」這兩個任務提早啓動起來。再回過頭去繳納水費、繳納電費、繳納自然氣費用。其實,這就是華羅庚推廣優選法的時候舉的例子,燒水、倒茶葉、泡茶,如何安排他們的順序爲高效。
顯然,異步地去作任務更高效。但這要有一個前提,就是你作任務的資源,也便是幹活的人或者機器,得有多份才行。一樣按照上面的例子來展開討論,雖然有水費、電費、自然氣這三個櫃檯,可若是這三個櫃檯背後的辦事人員其實只有一個呢?好比你啓動了辦理水費的業務,而後想要在辦理水費業務的等待期,去電費櫃檯辦理電費業務。表面上,你去電費櫃檯下了申請單,請求辦理電費業務,可卻發現根本沒有辦事員去接收你的這個業務!爲什麼?由於這有且只有一個的辦事員,還正在辦理你的水費業務啊!這時候,你的這個所謂的「異步」,有何意義?!
因此從這個角度來看,當計算資源只有一份的時候,你作「異步」實際上是沒什麼意義的。由於幹活的資源只有一份,就算在表面作了名義上的「異步」,可最終就像上面的多櫃檯單一辦事員那樣,到了執行任務層面,仍是會一個接一個地完成任務,這就沒有意義了。
那麼,js的特性是」單線程「+」異步「,不就正是咱們討論的「沒有意義」的狀況嗎?!那又爲什麼要屢次一舉,幹一些沒有意義的事情呢?
嗯......事情變得愈來愈有趣了。
一般來說,若是一個事件出現了神奇和怪異的地方,基本上都是由於咱們忽略了某個細節,或者對某個細節存在誤解或是錯誤理解。要想把問題解決,咱們就必須不斷地回顧已有材料,在不斷地重複檢驗中,發現那幾根咱們忽略的貓膩。
讓咱們回顧一下關於js異步的宣傳片。一般爲了說明js異步的必要性,會舉出瀏覽器的資源加載和頁面渲染這個矛盾。
渲染,能夠比較粗糙地理解爲將「畫面」畫出來的過程。例如,瀏覽器要將頁面上的按鈕、圖片顯示出來,就必須有一個將「圖片」在網頁上畫出來的動做。又或是,操做系統要將「桌面」這個圖形界面顯示在顯示器上,就必需要把它相應的這個「畫面」在顯示器上畫出來的動做。歸結起來,這個「畫出來」的過程,就被稱之爲「渲染」。
例如,你點擊頁面上的一個button,讓瀏覽器去後端數據庫將數據報表取出來,在網頁上把數字顯示出來。而若是js不支持異步的話,整個網頁的就會停留,也便是「卡」,在鼠標點擊按鈕這一個動做上,頁面沒法完成後續的渲染工做。一直要等到後端把數據返回到了前端,程序流纔可以繼續跑下去。
因此這裏,js的「異步」實際上是爲了讓瀏覽器將「加載」這個任務分給「其它地方」,讓「加載過程」和「渲染過程」同步進行下去。
等等,又是這個「其它地方」?!!
我擦,不是說js是單線程而麼,計算資源不是隻有一份麼,怎麼又能夠「一邊加載、一邊渲染」了?!WTF,你這是在逗我玩兒麼?!
艹,到底這裏面哪句話是真的?!到底js是單線程是真的?仍是說瀏覽器能夠同時作「一邊加載、一邊渲染」這個事情是真的?!
如何才能解決這個疑惑?!很顯然,咱們必需要深刻到瀏覽器的內部,去看一看它究竟是怎麼樣被設計的。
在搜索引擎中,作一些關於瀏覽器和js的搜索,咱們不可貴到一些基本信息。js並非瀏覽器的所有,瀏覽器要掌管的事情太多了,掌管js的只是瀏覽器的一個組件,叫作js引擎。而最出名的、並在Chrome中使用的,就是大名鼎鼎的V8引擎,它負責js的解析和運行。
另外一方面咱們還知道,使用js的一個很大緣由,是由於它可以自由地去操控DOM元素、去執行Ajax異步請求、可以像咱們最開始舉的例子那樣,使用setTimeout
作異步任務分配。這些都是js優秀特性。
可使人驚訝的事情來了,當咱們去探索這個掌管js一切的V8引擎的時候,咱們卻發現,它並不提供DOM的操控、Ajax的執行以及setTimeout
的特性:
上圖來自Alexander Zlatkov,它的結構是:
JS引擎
Web APIs
明明是js的特性,爲何這些職能卻不是由js的引擎來掌管呢?嗯,interesting~~~
誒!不是「單線程」麼,不是加載過程被扔到其它地方麼?!js是單線程,也便是js在js引擎中是單線程、只可以分到一份計算資源,但是,加載數據的Ajax這個feature不是沒有被放到js引擎麼?!
艹!真TM是老狐狸啊!還覺得「單線程」和「一邊加載、一邊渲染」這兩種說法只有一種是對的,可結果是,都對!爲何呢?由於只說了js是單線程,可沒說瀏覽器自己是單線程啊!因此咯,渲染相關的js部分能夠和數據加載的Ajax部分是能夠同時進行的,由於它們根本就在兩個模塊,即兩個線程嘛!因此固然能夠並行啊!WTF!
誒~等等,讓咱們再仔細看看上面這張圖呢?!Ajax不在js引擎裏,但是setTimeout
也不在js引擎裏面啊!!若是Web APIs這部分是在不一樣於js引擎的另一根線程裏,它們不就能夠實現真正意義上的並行嗎?!那爲什麼咱們開頭的打印信息「setTimeout callback」,沒法按照並行的方式,優先於「No. 2」打印出來呢?
嗯......真是interesting......事情果真沒有那麼簡單。
顯然,咱們須要考察更多的細節,特別是,每一條語句在上圖中,是按照什麼順序被移動、被執行的。
談到語句的執行順序,咱們須要再一次將關注點放回到js引擎上。再次回看上面這幅結構圖,JS引擎包含了兩部分:一個是 memory heap,另外一個是call stack。前者關於內存分配,咱們能夠暫時放下。後面便是函數棧,嗯,它就是要進一步理解執行順序的東西。
函數棧(call stack)爲何要叫作「棧(stack)」呢?爲何不是叫作函數隊列或者別的神馬?這其實能夠從函數的執行順序上作一個推斷。函數最開始被引進,其實就是爲了代碼複用和模塊化。咱們指望一段本該出現的代碼,被單獨提出來,而後只須要用一個函數調用,就能夠將這段代碼的執行內容給插入進來。
因此,若是當咱們執行一段代碼時,若是遇到了函數調用,咱們會指望先去將函數裏面的內容執行了,再跳出來回到主程序流,繼續往下執行。
因此,若是把一個函數看做函數節點的話,整個執行流程實際上是關於函數節點的「深度優先」遍歷,也便是從主函數開始運行的函數調用,整個呈深度優先遍歷的方式作調用。而結合算法和數據結構的知識,咱們知道,要實現「深度遍歷」,要麼使用遞歸、要麼使用stack這種數據結構。然後者,無疑更爲經濟使用。
因此咯,既然指望函數調用呈深度優先遍歷,而深度優先遍歷又須要stack這種數據結構作支持,因此維護這個函數調用的結構就固然呈現爲stack的形式。因此叫作函數棧(stack)。
固然,若是再發散思考一下,操做系統的底層涌來維護函數調用的部分也叫作函數棧。那爲什麼不用遞歸的方式來實現維護呢?其實很簡單,計算機這麼個啥都不懂的東西,如何知道遞歸和返回?它只不過會勇往直前的一直執行命令而已。因此,在沒有任何輔助結構的狀況下,可以勇往直前地執行的方式,只能是stack,而不是更爲複雜的遞歸概念的實現。
另外一方面,回到咱們最開頭的問題,矛盾實際上是出如今setTimeout
的callback函數上。而上面的結構圖裏,還有一部分叫作「callback queue」。顯然,這一部分也是咱們須要瞭解的東西。
結合js的call stack和callback queue這兩個關鍵詞,咱們不難搜索到一些資料,來展開討論這兩部分是如何同具體的語句執行相結合的。
先在總體上論述一下這個過程:
setTimeout
的內容。以上內容比較抽象,讓咱們用一個具體的例子來講明。這個例子一樣來自於Alexander Zlatkov。使用它的緣由很簡單,由於Zlatkov在blog中使用的說明圖,實在是至關清晰明瞭。而目前我沒有多餘的時間去使用PS繪製相應的結構圖,就直接拿來看成例子說明了。
讓咱們考察下面的代碼片斷:
console.log('Hi'); setTimeout(function cb1() { console.log('cb1'); }, 5000); console.log('Bye');
哈哈,其實和咱們使用的代碼差很少,只是打印的內容不一樣。此時,在運行以前,整個底層的結構是這樣的:
而後,讓咱們執行第一條語句console.log('Hi')
,也便是將它壓入到call stack中:
而後js引擎執行stack中最上層的這條語句。相應的,瀏覽器的控制檯就會打印出信息「Hi」:
因爲這條語句被執行了,因此它也從stack中消失:
再來壓入第二條語句setTimeout
:
執行setTimeout(function cb1() { console.log('cb1'); }, 5000);
:
注意,因爲setTimout
部分並無被包含在js引擎中,因此它就直接被扔給了Web APIs的Timeout
部分。這裏,stack中的藍色部分起到的做用,就是將相應的內容「timer、等候時間5秒、回調函數cb1」扔給Web APIs。而後這條語句就能夠從stack中消失了:
繼續壓入下一條語句console.log('Bye')
:
注意,此時在Web APIs的部分,正在並行於js引擎執行相應的語句,即:等候5秒鐘。Okay,timer繼續它的等待,而stack這邊已經有語句了,因此須要把它執行掉:
相應的瀏覽器控制檯,就會顯示出「Bye」的信息。而stack中運行後的語句,就該消失:
此時,stack已經爲空。Event loop檢測到stack爲空,天然就想要將callback queue中的語句壓入到stack中。可此時,callback queue中也爲空,因而Event loop只好繼續循環檢測。
另外一方面,Web APIs這邊的timer,並行地在5秒鐘後開始了它的執行——什麼也不作。而後,將它相應的回調函數cb1()
,放到callback queue中:
Event loop因爲一直在循環檢測,此時,看到callback queue有了東西,就迅速將它從callback queue中取出,而後將其壓入到stack裏:
如今Stack裏有了東西,就須要執行回調函數cb1()
。而cb1()
裏面調用了 console.log('cb1')
這條語句,因此須要將它壓入stack中:
stack繼續執行,如今它的最上層是 console.log('cb1')
,因此須要先執行它。因而瀏覽器的控制它打印出相應的信息「cb1」:
將執行了的 console.log('cb1')
語句彈出棧:
繼續執行cb1()
剩下的語句。此時,cb1()
已經沒有其它須要執行的語句了,也便是它被運行完畢,因此,將它也從stack中彈出:
整個過程結束!若是從頭至尾看一遍的話,就是下面這個gif圖了:
至關清晰直觀,對吧!
若是你想進一步地把玩js的語句和call stack、callback queue的關係,推薦Philip Roberts的一個GitHub的開源項目:Loupe,裏面有他online版本供你作多種嘗試。
有了這些知識,如今咱們回過頭去看開頭的那段讓人產生疑惑的代碼:
console.log('No. 1'); setTimeout(function(){ console.log('setTimeout callback'); }, 0); console.log('No. 2');
按照上面的js處理語句的順序,第一條語句console.log('No. 1')
會被壓入stack中,而後被執行的是setTimout
。
根據咱們上面的知識,它會被馬上扔進Web APIs中。但是,因爲這個時候咱們給它的等待時間是0,因此,它的callback函數console.log('setTimeout callback')
會馬上被扔進「Callback Queue」裏面。因此,那個傳說中的「其它地方」指的就是callback queue。
那麼,咱們可以指望這一條console.log('setTimeout callback')
先於「No. 2」被打印出來嗎?
實際上是不可能的!爲何?由於要讓它被執行,首先它須要被壓入到call stack中。但是,此時call stack尚未將程序的主分支上的語句執行完畢,即還有console.log('No. 2')
這條語句。因此,event loop在stack還未爲空的狀況下,是不可能把callback queue的語句壓入stack的。因此,最後一條「setTimeout callback」的信息,必定是會排在「No. 2」這條信息後面被打印出來的!
這徹底符合咱們以前加入無限while
循環的結果。由於主分支一直被while
循環佔有,因此stack就一直不爲空,進而,callback queue裏的打印「setTimeout callback」的語句就更不可能被壓入stack中被執行。
探索到這裏,彷佛該解決的問題也都解決了,好像就能夠萬事大吉,直接封筆走人了。可事實倒是,這纔是咱們真正的泛化討論的開始!
作研究和探索,若是停留於此,就無異於小時候本身交做業給老師,目的僅僅是完成老師佈置的任務。在這裏,這個老師佈置的任務就是文章開頭所提出的讓人疑惑的代碼。但是,解決這段代碼並非咱們的終極目的。咱們須要泛化咱們的所學和所知,從更深層次的角度去探索,爲何咱們會疑惑,爲何一開始沒法發現這些潛藏在表面之下不一樣。咱們要繼續去挖掘,咱們到底在哪些最根本的問題上出現了誤解和錯誤認識,從而致使咱們一路如此辛苦,沒法在開頭看到事情的真相。
回顧咱們的歷程,一開始讓咱們載跟斗的,其實就是對「異步」和「多線程」的固定假設。多線程了,就是異步,而異步了,必定是多線程嗎?咱們潛意識裏是很想作確定回答的。這是由於若是異步了,但倒是單線程,整個異步就沒有意義了(回憶那個多櫃檯、單一辦事員的例子)。可js卻巧妙地運用了:使用異步單線程去分配任務,而讓真正作數據加載的Ajax、或者時間等待的setTimeout的工做,扔給瀏覽器的其它線程去作。因此,本質上js雖然是單線程的,可在作實際工做的時候,卻利用了瀏覽器自身的多線程。這就比如是,雖然是多櫃檯、單一辦事員,可辦事員將繳納電費、水費的任務,外包給其它公司去作,這樣,雖然本身仍然是一個辦事員,但卻因爲有了外包服務的支持,依舊能夠一塊兒並行來作。
另外一方面,js的異步、單線程的特性,逼迫咱們去把並行計算中的「同步/異步、阻塞/非阻塞」等概念理得更清楚。
「同步」的英文是synchronize,但在中文的語境下,卻很容易和「同時」掛鉤。因而,在潛意識裏有可能會有這樣一種聯想,「同步」就是「同時」,因此,一個同步(synchronize)的任務就被理解爲「能夠一邊作A,一邊作B」。而這個潛意識的印象,其實徹底是錯誤的(通常作A一邊作B,實際上是「異步」+「並行」的狀況)。
但在各種百科詞典上,確實有用「同時」來做爲對「同步」的解釋。這是爲何呢?其實這是對」同步「用做」同時「的一個混淆理解。若是仔細考慮」同時「的意思,細分起來,實際上是有兩種理解:
前者很容易理解,這裏我重點解釋一下後者。例如,我在中國大陸同美國的一個同窗開微信語音聊天,我這邊是22:00,他那邊是9:00。咱們作聊天這件事情的時候,是同一時刻(at the same time),但卻不在同一個時間參考體系(clock on the wall)。而在計算機中討論的同步,其實討論的是後者的」同一參考系「,同步,就是讓咱們的參考系統一塊兒來,放到同一個體系之下。
又好比,咱們在生活中很容易說,同步你的電腦、同步你的手機通信錄、同步你的相冊,說的是什麼呢?就是讓你的各個客戶端:PC、手機,同server端服務器的內容都保持一致,也便是你們都被放到一個一致的參考系裏面。不要說,你在PC裏有照片A,而在手機裏沒有A卻有B,這個時候,談論PC裏信息人與談論手機裏信息的人,就是在雞同鴨講。究其緣由,就是沒有把你們放到同一個參考系裏面。
因此,同步synchronize所指的」同時「,是你們把牆上的時鐘都調整到一致、調整爲同一個步調,也便是同時、同一時間參考系的意思。而不是說,讓事情在同一時刻並列發生。天然的,什麼是異步(asynchronize)呢,異步就是你們的時間參考系是不一樣的,例如我在中國大陸、你在美國,咱們的時間參考體系是不一樣的,這就是異步,不在同一個步調、頻段上。
事實上,每個獨立的人、每一塊獨立的計算資源,它都表明了一個各自的參考體系。只要你將任務分發給了其餘人或是其它計算資源,此時,就出現了兩個參考體系:一個是原有主分支的參考體系,另外一個是新的計算資源的參考體系。在並行計算中,有一個同步機制是使用語句barrier,目的是讓全部的計算分支在這一個位置節點都完成了計算。爲何說它是一種同步機制?按照咱們統一參考體系的理解,就是保證其餘全部計算分支完成計算,也就保證了其它分支的消失,從而只剩下主分支這一個參考體系。因而你們能夠談論一樣的東西,說一樣的話,不會有誤解。
另外一方面,若是要更深刻地理解js的設計,我認爲還須要回到計算機歷史的初期,例如那個只有單核的分時系統的時代。在那樣一個時代,操做系統所受到的硬件上的限制,不亞於js引擎在瀏覽器中所受到的限制。在一樣的限制下,曾經的操做系統會如何去巧妙運用那極爲有限的計算資源,讓整個操做系統給人以平滑、順暢和功能強大的錯覺?我想,js的這些設計一定和操做系統早期的設計緊密相關。因此在這個層面上,它將再一次回到操做系統這樣的基礎知識上。可否吃透現代的技術,其實很大層面上取決於你是否吃透了設計的歷史,是否理解在那些資源枯竭的年代,各路大神是如何巧妙地逢山開路,遇水搭橋。不管現代的計算機硬件資源有多麼豐富,它一定會由於目標的主次關係、業務的主次關係受到限制。而如何在限制中跳舞和創造,這是可以貫穿整個歷史的共同性問題。
Reference:
近期回顧
《沒有idea這把米,怎麼炊熟創業這碗飯》
《2018年08月寫字總結》
《財務自由所虛構的妄念》
若是你喜歡個人文章或分享,請長按下面的二維碼關注個人微信公衆號,謝謝!
更多信息交流和觀點分享,可加入知識星球: