9月阅读周·JavaScript异步编程设计快速响应的网络应用:深入理解JavaScript事件,事件的调度篇

举报
叶一一 发表于 2024/09/24 09:20:40 2024/09/24
【摘要】 背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效,已经坚持阅读七个月。已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScri...

背景

去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。

没有计划的阅读,收效甚微。

新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。

这个“玩法”虽然常见且板正,但是有效,已经坚持阅读七个月。

已读完书籍《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》

当前阅读周书籍《JavaScript异步编程设计快速响应的网络应用》

事件的调度

如果想让Javascript中的某段代码将来再运行,可以将它放在回调就

是一种普通函数,只不过它是传给像setTimeout这样的函数,或者绑定为像

document.onready 这样的属性。运行回调时,我们称已触发某事件(譬如延时结

束或页面加载完毕).

当然,可怕的总是那些细节,哪怕是像setTimeout这样看起来很简单的东

西。对setTimeout的描述通常像这样:

给定一个回调及n毫秒的延迟,setTimeout就会在n毫秒后运行该回调。

但是,正如我们将在这一节乃至这一章里看到的,以上描述存在严重缺陷。

大多数情况下,该描述只能算接近正确,而在其他情况下则完全是谬误。要想真

正理解 setTimeout,必须先大体理解Javascript事件模型。

现在还是将来运行

在探究setTimeout之前,先来看一个简单的例子。该情形常常会迷惑JavaScript新手,特别是那些刚刚从Java和Ruby等多线程语言迁移过来的新手。

EventModel/loopWithTimeout.js

for (var i = 1; i <= 3; i++) {
  setTimeout(function(){ console.log(i); }, 0);
}

大多数刚接触JavaScript语言的人都会认为以上循环会输出1,2,3,或者重复输出这3个数字,因为这里的3次延时都抢着要第一个触发(每次暂停都调度为0毫秒后到时)。

要理解为什么输出是4,4,4,需要知道以下3件事。

  • 这里只有一个名为i 的变量,其作用域由声明语句var i 定义(该声明语句在不经意间让i的作用域不是循环内部,而是扩散至蕴含循环的那个最内侧函数)。
  • 循环结束后,i===4 一直递增,直到不再满足条件i<=3 为止。
  • JavaScript事件处理器在线程空闲之前不会运行。

前两条还属于JavaScript 101的范畴,但第三个更像是一个“惊喜”。一开始使用JavaScript的时候,我也不太相信会这样。Java令我担心自己的代码随时会被中断。上百万种潜在的边界情况让我焦虑万分,我一直在想:“要是在这两行代码之间发生了什么稀奇古怪的事,会怎么样呢?”

然后,终于有一天,我再也没有这样的担心了……

线程的阻塞

下面这段代码打破了我对JavaScript事件的成见。

EventModel/loopBlockingTimeout.js

var start = new Date;
setTimeout(function(){
var end = new Date;
console.log('Time elapsed:', end - start, 'ms');
}, 500);
while (new Date - start &lt; 1000) {};

按照多线程的思维定势,我会预计500毫秒后计时函数就会运行。不过这要求中断欲持续整整一秒钟的循环。如果运行代码,会得到类似这样的结果:

大家得到的数字可能会稍有不同,这是因为 setTimeout 和 setIn-terval一样,其计时精度要比我们的期望值差很多。不过,这个数字肯定至少是 1000,因为 setTimeout 回调在 while循环结束运行之前不可能被触发。

那么,如果 setTimeout 没有使用另一个线程,那它到底在做什么呢?

队列

调用 setTimeout 的时候,会有一个延时事件排入队列。然后setTimeout调用之后的那行代码运行,接着是再下一行代码,直到再也没有任何代码。这时 JavaScript 虚拟机才会问:“队列里都有谁啊?”

如果队列中至少有一个事件适合于“触发”(就像1000毫秒之前设定好的那个为期 500毫秒的延时事件),则虚拟机会挑选一个事件,并调用此事件的处理器(譬如传给 setTimeout 的那个函数)。事件处理器返回后,我们又回到队列处。

输入事件的工作方式完全一样:用户单击一个已附加有单击事件处理器的 DOM(Document Object Model,文档对象模型)元素时,会有一个单击事件排入队列。但是,该单击事件处理器要等到当前所有正在运行的代码均已结束后(可能还要等其他此前已排队的事件也依次结束)才会执行。因此,使用 JavaScript的那些网页一不小心就会变得毫无反应。

你可能听过事件循环这个术语,它是用于描述队列工作方式的。所谓事件循环,就像代码从一个循环中不断取出而运行一样:

runYourScript();
while (atLeastOneEventIsQueued) {
fireNextQueuedEvent();
};

总结

事件的易调度性是 JavaScript语言最大的特色之一。像 setTimeout这样的异步函数只是简单地做延迟执行,而不是孵化新的线程。JavaScript 代码永远不会被中断,这是因为代码在运行期间只需要排队事件即可,而这些事件在代码运行结束之前不会被触发。


作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏️ | 留言📝

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。