简述 JavaScript 被执行的那些事情
JavaScript 是脚本语言
JavaScript 是解释型语言
JavaScript 执行流程
不同浏览器有不同的 JavaScript 引擎(运行时),主流的 JS 引擎有:
- V8,chromium 内核的引擎,主要用于 chrome 和 node,目前来说是最主流,大概也是性能最高的 JS 引擎
- SpyderMonkey,火狐的 JS 殷勤
- JavaScriptCore,webkit 的引擎,它的名字好多,还包含 SquirrelFish、Nitro,不过项目名称是 JavaScriptCore
抛开早期的 JS 引擎不谈,现在的 JS 引擎的确是会先对 JS 进行编译——即时编译(just-in-time compiling)——,将其转化成二进制的机器语言并处理数据,从而提升运行速度。不过本质上 JS 还是一种解释型语言,因为代码还是一边运行一边执行的。
下面这张图是 v8 的执行过程:
其中 bytecode 就是二进制的机器码,是由 JIT 编译而成,具体执行的过程是在 baseline 这里(针对 v8,其他的引擎的名称不一样,大致执行流程是差不多的)。
内存管理
JavaScript 的内存管理和其他主流的编程语言差不多,主要就是存储没这么昂贵的(primitive data type)和函数会被存放在 stack 中,存储起来比较昂贵的会被存放到 heap 中:
以下面代码为例:
function c() {
console.log('c');
}
function b() {
c();
console.log('b');
}
function a() {
b();
console.log('a');
}
a();
这时候的 stack 中的保存值为:
当 c 执行完毕后,c 执行完毕后,就会被退栈,然后执行 b,以此类推。搭配上引擎的事件池,这也是为什么目前 JS 对于异步的操作非常高效的缘故:
递归也是这样的操作,每次调用之后就会将当前函数再一次推到栈中,一直到 base case。这也是为什么一旦 base case 没有定义好,就会出现 stack excced limits 的报错。
垃圾回收
JavaScript 高级程序设计第四章学习笔记 有提,常规的有两种:
-
标记清理(mark-and-sweep) :最常用的垃圾回收策略。
其基本策略就是根据 执行上下文 去判断当前变量是否还在使用中,并且进行标记。一旦离开了 当前执行上下文 的范围内,该函数就会被标记为 离开当前执行上下文。在 垃圾回收 机制运行时,它会检查所有的标记,确认无用的变量后,再加上 待删除 的标记,最后执行内存清理,清理掉 待删除 的标记,释放空间。
-
引用计数
引用计数(reference computing) 是另外一种没那么常用的垃圾回收机制。其基本思路是标记每一个变量被引用的次数,当这个函数被调用时,引用的次数 +1,当保存该值引用的值被其他的覆盖了,引用的次数 -1。最终当引用的次数为 0 的时候,就被认定可以安全地释放其内存,并且会在下次垃圾回收机制运行的时候释放其空间。
在互相引用的情况下,这个回收机制就会出现问题——引用计数永远不会为 0,这也代表着互相调用的内存永远不会被清空。
其中一个人为的补救方式是将指针对象指向 null,即:
const someSbject = {}; // ........ someSbject = null; // 完全切断变量与引用之间的关系
reference
-
SpiderMonkey
-
Firing up the Ignition interpreter
-
JavaScript V8 Engine Explained
-
JavaScript’s Memory Management Explained
其他一些相关的学习红宝书第四章也有,这里是笔记:
-
JavaScript 高级程序设计第四章学习笔记
温故知新后发现之前有些囫囵吞枣的地方也变清楚了 -
BOM 操作学习笔记
event loop 的一部分笔记在这里有提到