Skip to content

A. 基础知识 #1

@Akiq2016

Description

@Akiq2016

理解与表达有限,欢迎指正。

JavaScript异步编程的重要性

JavaScript与大多数编程语言不同,他是运行在单线程环境中。因此,JavaScript必须使用异步API来保持对用户操作的响应。

为什么是单线程

单线程,简单来讲,就是任务排着队,一个个被执行。
假设并不是单线程=>存在两个线程,同时操作一个节点,线程1删除该节点,线程2修改该节点,则增加了复杂度。

JavaScript执行环境

如下图所示,单线程意味着当我执行 [读取文件] 操作时,停在这等待回应;回应完了执行 [HTTP请求]
代码,又等待回应……显而易见,系统资源没有被合理利用。
pic1

事实上你在浏览器中并不会遇到上面这个问题,因为我们 [异步] 处理了这些耗时任务。那我们说的 [异步] ,是怎么实现的?

我们知道浏览器核心包括渲染引擎和JavaScript引擎。这听起来好像跟JavaScript引擎(以Chrome V8为例)有关。是V8实现了异步机制吗?实际上不是的,V8只是浏览器众多模块中其中之一。

pic1-1

如下图,V8有用于内存分配的堆(heap),以及调用栈(call stack)。但你不会在V8中看到setTimeout | DOM | HTTP 请求这些你所常用的有关异步的东西。

pic2

因为这些是浏览器其他模块所提供的东西。包括web APIs(比如DOM | AJAX | time out),event loop(事件循环) 和 callback queue(事件队列)等。整个JavaScript执行环境的流程,如下图所示。

pic3

(看着上图,我们顺时针分析)主线程运行,产生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的第一个。

pic-4

在上面这个图形中有很多信息需要消化,能看懂说明上面所讲的异步编程的运行机制已经明白。来看这个图表:垂直方向上为时间轴(毫秒),一个个蓝色块代表被执行的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

下一篇

B.分布式事件

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions