-
Notifications
You must be signed in to change notification settings - Fork 0
Description
理解与表达有限,欢迎指正。
JavaScript异步编程的重要性
JavaScript与大多数编程语言不同,他是运行在单线程环境中。因此,JavaScript必须使用异步API来保持对用户操作的响应。
为什么是单线程
单线程,简单来讲,就是任务排着队,一个个被执行。
假设并不是单线程=>存在两个线程,同时操作一个节点,线程1删除该节点,线程2修改该节点,则增加了复杂度。
JavaScript执行环境
如下图所示,单线程意味着当我执行 [读取文件] 操作时,停在这等待回应;回应完了执行 [HTTP请求]
代码,又等待回应……显而易见,系统资源没有被合理利用。
事实上你在浏览器中并不会遇到上面这个问题,因为我们 [异步] 处理了这些耗时任务。那我们说的 [异步] ,是怎么实现的?
我们知道浏览器核心包括渲染引擎和JavaScript引擎。这听起来好像跟JavaScript引擎(以Chrome V8为例)有关。是V8实现了异步机制吗?实际上不是的,V8只是浏览器众多模块中其中之一。
如下图,V8有用于内存分配的堆(heap),以及调用栈(call stack)。但你不会在V8中看到setTimeout | DOM | HTTP 请求这些你所常用的有关异步的东西。
因为这些是浏览器其他模块所提供的东西。包括web APIs(比如DOM | AJAX | time out),event loop(事件循环) 和 callback queue(事件队列)等。整个JavaScript执行环境的流程,如下图所示。
(看着上图,我们顺时针分析)主线程运行,产生heap和stack。JavaScript代码块依次push进stack中执行,当调用到WebAPIs方法时,比如SetTimeout,由浏览器的模块帮我们处理,如果有callback,Web APIs将callback放入callback queue当中。
而event loop是stack与callback queue之间的一个 [桥梁] 。它的工作很简单:看看stack里面是否空了,如果空了,就把callback queue队列中的第一个任务,拿出来push进stack中执行。stack中执行完了,event loop 再次把 callback queue 中的任务 push 进 stack 中执行,如此反复。
以上,可得出结论,JavaScript的执行环境的机制实现了异步。
在这里截取引用朴灵老师的一些批注来补充强调说明一些概念:
要分清楚JavaScript执行环境和执行引擎的关系:通常说的引擎指的是虚拟机 (对于Node来说是V8、对Chrome来说是V8、对Safari来说JavaScript Core,对Firefox来说是SpiderMonkey);JavaScript的执行环境很多,上面说的各种浏览器、Node、Ringo等。
对于Engine(执行引擎)来说,他们要实现的是ECMAScript标准。对于什么是event loop,他们没兴趣,不关心。
一般而言,操作分为:发出调用 & 得到结果。发出调用,立即得到结果是为同步。发出调用,但无法立即得到结果,需要额外的操作才能得到预期的结果是为异步。同步就是调用之后一直等待,直到返回结果。异步则是调用之后,不能直接拿到结果,通过一系列的手段才最终拿到结果(调用之后,拿到结果中间的时间可以介入其他任务)。上面提到的一系列的手段其实就是实现异步的方法,其中就包括event loop。以及轮询、事件等。
学习链接
Philip Roberts: What the heck is the event loop anyway? (视频)
WebKit技术内幕
参考链接
使用或参考了阮老师博客中的图片,感谢。个人认为文章内容具有误导性,朴灵老师已评注指正 ,可辩证的学习。朴灵评注原链在印象笔记中,已失效,直接在百度中搜索关键词看相关转载文章即可。
JavaScript定时器 是如何工作的
先梳理一下我们有哪些函数可以操作定时器。
var id = setTimeout(fn, delay)
clearTimeout(id)
var id = setInterval(fn, delay)
clearInterval(id)
有个概念需要强调,定时器只是计划代码在未来的某个时间执行,但是他的执行时机是不能保证的。他取决于当前的stack中任务是否执行完,以及定时器的回调是否是callback queue的第一个。
在上面这个图形中有很多信息需要消化,能看懂说明上面所讲的异步编程的运行机制已经明白。来看这个图表:垂直方向上为时间轴(毫秒),一个个蓝色块代表被执行的JavaScript代码块。
第一个JavaScript代码块执行大约18ms。执行过程中,用户mouse click操作,mouse handler被放入callback queue中。(我们永远也不会知道用户什么时候会做什么操作(点击 滚动 等等),因此这些事件被认为是异步的)。10ms过后,setTimeout的callback被放进queue。
当第一个JavaScript代码块被执行完,callback queue中已有[mouse click handler, timer callback],浏览器拿出第一个并执行。执行完毕,浏览器又去队列中拿出任务来执行。
注意,在执行mouse handler的过程中,Interval定时器的回调,被放入queue中;在执行timer的过程中,新的Interval定时器的回调出现,但是被丢掉。这是为了保证,在前一个相同Interval的回调执行完之前,不会向队列插入新的该Interval回调。这保证了setInterval创建的定时器 [至少] 等待指定的间隔后,才执行,避免连续运行。
如果我们想要让一个函数,在未来的某个时机执行,可以用定时器。但是他们存在一个大的缺陷。
var fireCount = 0
var start = new Date
var test = setInterval(() => {
if (new Date - start > 1000) {
clearInterval(test)
console.log(fireCount)
return
}
fireCount ++
})
虽然该定时器会尽可能频繁的运行其事件,但是他的触发频率只有250次左右。换成while循环则触发将近400万次/秒。这是因为HTML规范中推行的间隔是最少4ms
学习链接
How JavaScript Timers Work