JavaScript执行机制

编译器和解释器

编译器/解释器可以用它处理的语言或者其他语言来编写。
解释器 :一行一行的快速读取和翻译文件,这就是JavaScript最初的工作原理。
编译器 :编译器提前运行并创建一个文件,其中包含了输入文件的机器码转换。

有两种途径可以将JavaScript代码转换为机器码:
(1)编译代码时,机器对代码开始运行前将要发生的事情有更好的理解,这将加快稍后的执行速度。不过,在这个过程之前需要花费时间。
(2)解释代码时,执行是立即的,因此要更快,但是缺乏优化导致它在大型应用程序下运行缓慢。

创建 ECMAScript 引擎的人集二者之长开发了 JIT(Just-in-time) 编译器。JavaScript 同时被编译和解释,但实际实现和顺序取决于引擎。

从JavaScript到机器码

就JavaScript而言,有一个引擎将其转换为机器码。引擎可以用任何语言开发,JavaScript引擎有:Chorme的V8,FireFox的SpiderMonkey,Safari的JavaScriptCore等。

解析和构建树

JavaScript文件进入引擎后,解析器进行词法解析,他将代码分解成token以确定他们的含义,这些token组成了AST(抽象语法树)。
编译器在语义分析中验证语言元素和关键词的正确用法,而ASTs在这个过程中扮演重要角色。之后,ASTs被用于生成实际的字节码或机器码。

JavaScript内存管理机制

内存堆

当你在JavaScript程序中定义了一个变量、常量或者对象时,需要内存来存储这个变量,这块内存就是内存堆。内存是有限的,对于复杂的程序,必须合理使用内存。JavaScript提供了自动垃圾回收机制,称为垃圾回收器(Orinoco),一旦变量离开了上下文并且不再使用时,它所占用的内存就会被自动回收并返回到可用内存池中。

标记与清除算法

标记与清除算法即将对象标记为可获得/不可获得,并将不可获得的对象从内存堆中安全清除。垃圾回收器周期性地从根部或者全局对象开始,移向被他们引用的对象,接着再移向被这些对象引用的对象,以此类推,因此不可获得对象会被清除。

内存泄漏

垃圾回收器的效率很高,但是内存管理是个复杂的过程。内存泄漏指的是程序之前需要用到部分内存,而这部分内存在用完之后并没有返回到内存池中。
以下会几点导致内存泄漏:
(1)全局变量
全局变量滞留在程序的整个执行过程中,无论是否被用到。
(2)事件监听器
为了实现某些功能,程序员可能会大量使用事件监听器。而用户在页面移动跳转中,忘记移除这些监听器,会导致监听器不断增加以及内存泄漏。

var element  = document.getElementById('button');
element.addEventListener('click', onClick)

(3)Intervals和Timeouts
当在这些闭包中引用对象时,除非闭包本身被清除,否则不会清除相关对象。
(4)移除DOM元素

var terminator = document.getElementById('terminate');
var badElem = document.getElementById('toDelete');
terminator.addEventListener('click', function()  
	badElem.remove();
});

在你通过 id = ‘terminate’ 点击了按钮之后,toDelete 会从 DOM 中移除。不过,由于它仍然被监听器引用,为这个对象分配的内存并不会被释放。
应该像下面这种方式处理,badElem 是局部变量,在移除操作完成之后,内存将会被垃圾回收器回收。

var terminator = document.getElementById('terminate');
terminator.addEventListener('click', function()  {
  	var badElem = document.getElementById('toDelete');
  	badElem.remove();
});

调用栈

栈是一种遵循先进后出(LIFO)的数据结构,用于存取数据。JavaScript引擎通过栈来记住一个函数最后执行的语句所在位置。

function multiplyByTwo(x) {
  	return x*2;
}
function calculate() {
 	 const sum = 4 + 2;
  	 return multiplyByTwo(sum);
}
calculate();

1.引擎了解到我们的程序中有两个函数
2.运行 calculate() 函数
3.将 calculate 压栈并计算两数之和
4.运行 multiplyByTwo() 函数
5.将 multiplyByTwo 函数压栈并执行算术计算 x*2
6.在返回结果的同时,将 multiplyByTwo() 从栈中弹出,之后回到 calculate() 函数
7.在 calculate() 函数返回结果的同时,将 calculate() 从栈中弹出,继续执行后面的代码。