
原文发表在: 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 operationok[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)发布会的经典段式。(写这篇文章的时候,实在被最新的发布会感动了一把)。如果没看过去找找吧。
感谢阅读,后会有期!