原文發表在: holmeshe.me , 本文是漢化重製版。
本系列在 Medium上同步連載。
用ajax胡亂做專案的時候踩過好多坑,然後對JS留下了“非常詭異”的印象。系統學習後,發現這個構建了整個網際網路表層的語言其實非常666。這次的學習已經告一段落,本篇也是這個系列的最後一部分。回頭看來,把學習記錄發出來這個經歷挺奇特的,以前是寫了給自己看,現在隨便搞搞發來掘金就3000+的總閱讀,頓時感覺有意義了很多。所以我也想明白了,你看,我就有動力寫。
其實沒啥新鮮的
阻塞操作的問題
from flask import Flask
import time
app = Flask(__name__)
@app.route("/lazysvr")
def recv():
time.sleep(10)
return "ok"
if __name__ == "__main__":
app.run(host='***.***.***.***', threaded=True)複製程式碼
<html>
<head>
</head>
<body>
<button type="button">Click Me!</button>
<script>
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://***.***.***.***:5000/lazysvr", false ); // false for synchronous request
xmlHttp.send( null ); // the thread is suspended here
alert(xmlHttp.responseText);
</script>
</body>
</html>複製程式碼
xmlHttp.send( null ); // it is the aforementioned blocking operation複製程式碼
ok複製程式碼
[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help, check https://xhr.spec.whatwg.org/.複製程式碼
來一波非同步
<html>
<head>
</head>
<body>
<button type="button">Click Me!</button>
<script>
var xmlHttp = new XMLHttpRequest();
-- xmlHttp.open( "GET", "http://192.241.212.230:5000/lazysvr", false );
++ xmlHttp.open( "GET", "http://192.241.212.230:5000/lazysvr", true ); // 1) change the param to "true" for asynchronous request
++ xmlHttp.onreadystatechange = function() { // 2) add the callback
++ if(xmlHttp.readyState == 4 && xmlHttp.status == 200) {
++ alert(xmlHttp.responseText);
++ }
}
xmlHttp.send();
-- alert(xmlHttp.responseText);
</script>
</body>
</html>複製程式碼
ok複製程式碼
setTimeout(callback, 3000);
function callback() {
alert(‘event triggered’);
}複製程式碼
<html>
<head>
</head>
<body>
<button type=”button” onclick=”callback()”>Click Me!</button>
<script>
function callback() {
alert(‘event triggered’);
}
</script>
</body>
</html>複製程式碼
新的fetch()介面
在第一個?中,我用回撥來舉例是因為比較直觀。其實更好的辦法是用fetch()來進行網路請求。這個函式會返回一個Promise物件,再用這個物件呼叫then()函式的話:
1. 非同步操作的程式碼就可以變成線性(更像同步)了;
2. 回撥地獄的問題可以得到解決了;
3. 所有的相關異常,可以在一個程式碼塊裡處理了:
<html>
<head>
</head>
<body>
<button type=”button” onclick=”callback()”>Click Me!</button>
<script>
fetch(‘http://***.***.***.***:5000/lazysvr')
.then((response) => {
return response.text();
}).then((text) => {
alert(text);
}).catch(function(error) {
console.log(‘error: ‘ + error.message);
});
</script>
</body>
</html>複製程式碼
執行結果和第一個?一樣,我還是留了按鈕給你試UI有沒有卡。
底層機制,多執行緒+事件迴圈
JS不是單執行緒嗎?
答案是,即是也不是。什麼意思?
var i;
for (i = 0; i < 1000; i++) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://***.***.***.***:5000/lazysvr", true );
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
alert(xmlHttp.responseText);
}
} // end of the callback
xmlHttp.send( null );
}複製程式碼
假設瀏覽器的pid是666(巧了,我做這個測試的時候還真是),我們用一小段指令碼(環境是Mac)本來觀察執行緒狀態:
#!/bin/bash
while true; do ps -M 666; sleep 1; done複製程式碼
初始值(我把無關的列和行都幹掉了):
USER PID ... STIME UTIME
holmes 666 ... 0:00.42 0:01.47
......
666 0:00.20 0:00.64複製程式碼
結束的時候:
USER PID ... STIME UTIME
holmes 666 ... 0:00.50 0:01.88
......
666 0:00.37 0:01.28複製程式碼
除了主執行緒,還有一條非常活躍的執行緒,我估摸著這條是用來監聽網路的(多路複用)套接字。
所以JS程式碼確實是執行在一條執行緒裡。但是如果從應用程式的角度來看,它其實是多執行緒。用同樣的辦法測一下Node吧。
“粗”暴的事件迴圈
上文提到,作業系統的中斷是以指令為粒度的,但是這個傳說中的事件迴圈,粒度就有點大了:
var i;
for (i = 0; i < 3; i++) {
alert(i);
}
setTimeout(callback, 0);
function callback() {
alert(‘event triggered’);
}複製程式碼
我們都知道結果是:
1
2
3
event triggered複製程式碼
簡單來說呢,雖然我們註冊了一個定時事件,並且指定它立即執行,但是JS引擎還是在執行時忠實的把本次迴圈跑完,才會去理剛剛註冊的那個事件。
這個代表一般事件中斷是以指令週期為單位,而JS是以迴圈週期為單位的。
有點尷尬了,這麼大粒度的事件處理會不會導致UI響應時間長呢?我覺得其實不會。即使在以指令週期為單位的事件響應裡,使用者的操作還是需要在本次"迴圈週期"結束放到主執行緒來,然後反映到UI。因為一切UI更新都要在主執行緒。所以,這個極其簡化的單執行緒設計本身並不會對UI效能造成影響。你覺得呢?
遲到的總結
這個系列中,我覆蓋了在JS裡被細化的 "等於" 操作符和 "null" 值,被簡化的 字串,陣列,物件和字典。然後我在這篇和這篇裡深入到prototype這一層進一步討論了一下物件。最重要的是,我三次提及了this的坑:
說明真的很重要。
最後就是本篇了,用我理解的角度聊了一下非同步。
如果你還記得的話,這個系列是我為新工作(臨時)學JS準備的。以現在上手程度來看,我覺得這個底子打的還不錯,希望對你也一樣。但是這個文章並不全面,所以我準備瞭如下的附加閱讀:
The debug technique 我用來除錯的方法
這篇很有趣,我第一次讀到,希望有機會能翻譯 interesting topic
Another place to understand “this”
A good blog, 最重要的事,
常來掘金看篇。
最後要承認第一段的結構是模仿喬幫主在第一次蘋果(iPhone1)釋出會的經典段式。(寫這篇文章的時候,實在被最新的釋出會感動了一把)。如果沒看過去找找吧。
感謝閱讀,後會有期!