首页 » 小蓝 » 致杨先生,关于《javascript到底是如何运行的》

致杨先生,关于《javascript到底是如何运行的》

致杨先生:

前言

引用 javascript到底是如何运行的?--杨先生

当我们讨论javascript运行机制这种问题,有别于语法,语法我们有标准可以参考,对于运行机制,我们应该明确是什么引擎。

在这我以V8为例,猜想杨先生大概也是V8,从node源码的角度,补充一二。
行文没有顺序,只是针对一些观点的补充。

正文


在js中,有多种类型的观察者,包括文件I/O观察者,网络请求观察者等(其实这里的观察者一般来讲就是宿主环境提供的API)

观察者其实不是什么API,而是一种结构。

举个例子,如果是网络IO观察者,它的结构是这样的:

{
    fd,
    callback
}

这个是uv__io_s 类型的对象,而我们常说的事件循环里的一个个单元,就是这样的对象。

而文件IO观察者,想必也是差不多,待我进一步研读源码再来求证。


一个JavaScript运行时包含一个待处理的任务队列,该队列是先进先出的,队列里的每一个任务都与一个函数相关联(一般是调用API时指定的回调函数)。当[执行栈]为空时,[执行栈]会从[任务队列]中取出队列里最前面的一个任务进行处理。

任务队列一直一个值得我们深刻研究的话题,在杨先生的描述里,js有一个“执行栈”,一个任务队列。执行栈 先执行一次,然后从队列里取出一个,如此往复,直到结束。

某个程度上可以这么描述,不过对于杨先生的水平,我认为是要有更高的要求的,这里面,有很多不太严谨的地方。

我将从几个问题来展开:

1.什么是任务队列?

任务队列是 default_loop_struct,来自结构体定义 uv_loop_s。正如前面所说,里面存放的是观察者。

2.什么是 Tick

我们知道有一个 process.nextTick 方法,但是 Tick 到底是什么,要从任务队列的运行机制去了解。

default_loop_struct 里面的观察者是以链表的结构存储的。而 Tick 里是以 数组 的方式存储的。

杨先生之前的描述只是 执行栈 与 Tick 之间的交互,实际上,每次取出一个任务,都是从Tick里取的。而Tick其实是 default_loop_struct的全量镜像。

注:是不是全量,我还需要继续考证,在此仅供参考。

所以真正的关系链是:

main() <---  Tick  <--- default_loop_struct

在这里顺便说一下,process.nextTick 会让一个callback加到Tick里,而其他的 timer 之类(setTimeout,setImmediate),只会加到 default_loop_struct 。此中区别,还需细细品味。

3.任务队列的时机

在杨先生文中的那个图,我不能说它是错的,但是并没有表达出一个关键信息,就是执行轨迹。

真实的情况是 main()执行完所有JS代码后,进入uv_run(),再去看看 default_loop_struct 有没有观察者,没有则程序结束,如果有,则进入epoll_wait()

以上不理解没关系,可以日后细细研究,在此我只想表达一点,JS不会一边执行代码一边跟任务队列做互动。不信你可以试试,连续一堆 setTimeout ,然后直接process.exit(),那么还没来得及进入uv_run(),程序就结束了,这些任务自然一个都执行不了。

其他

以上还有一些用语不太严谨,不过为了行文连贯,做出一定的牺牲,需要考究准确性的地方,我们还需要私下讨论为佳。