JavaScript的运行,可以概括为JS引擎、运行上下文,单线程,事件循环,事件驱动,回调函数等概念。
- JS Engine(JS引擎)
- Runtime(运行上下文)
- Call Stack (调用栈)
- Event Loop(事件循环)
- Callback (回调)
1. JS Engine
JS引擎主要是对JS代码进行词法、语法等分析,通过编译器将代码编译成可执行的机器码让计算机去执行。与JVM虚拟机类似,JS引擎中也有堆(Memory Heap)和栈(Call Stack)。
- 栈(Stack)。用来存储方法调用的地方,以及基础数据类型(如var a = 1)也是存储在栈里面的,会随着方法调用结束而自动销毁掉(入栈—>方法调用后—>出栈)。
- 堆(Heap)。JS引擎中给对象分配的内存空间是放在堆中的。如var foo = {bar: ‘baz’} 那么这个foo所指向的对象是存储在堆中的。
需要注意的是,JS中存在闭包的概念,对于基本类型变量如果存在于闭包当中,那么也将存储在堆中。
关于闭包的情况,就涉及到Captured Variables。Local Variables是最简单的情形,是直接存储在栈中的。而Captured Variables是对于存在闭包情况和with
,try catch
情况的变量。
function foo () {
var x; // local variables
var y; // captured variable, bar中引用了y
function bar () {
// bar 中的context会capture变量y
use(y);
}
return bar;
}
2. Runtime
JS在浏览器中可以调用浏览器提供的WebAPI,如window对象,DOM、XMLHttpRequest相关API等。这些接口并不是由V8引擎提供的,是存在浏览器当中的。因此简单来说,对于这些相关的外部接口,可以在运行时供JS调用,以及JS的事件循环(Event Loop)和事件队列(Callback Queue),把这些称为RunTime。有些地方也把JS所用到的core lib核心库也看作RunTime的一部分。
3. Call Stack
JS被设计为单线程运行的,这是因为JS主要用来实现很多交互相关的操作,如DOM相关操作,如果是多线程会造成复杂的同步问题。因此JS自诞生以来就是单线程的,而且主线程都是用来进行界面相关的渲染操作 (为什么说是主线程,因为HTML5 提供了Web Worker,独立的一个后台JS,用来处理一些耗时数据操作。因为不会修改相关DOM及页面元素,因此不影响页面性能),如果有阻塞产生会导致浏览器卡死。
如果一个递归调用没有终止条件,是一个死循环的话,会导致调用栈内存不够而溢出,如:
function foo() {
foo();
}
foo();
4.Event Loop & Callback
JS引擎其实并不提供异步的支持,异步支持主要依赖于运行环境(浏览器或Node.js)。
Event Loop只做一件事情,负责监听Call Stack和Callback Queue。当Call Stack里面的调用栈运行完变成空了,Event Loop就把Callback Queue里面的第一条事件(其实就是回调函数)放到调用栈中并执行它,后续不断循环执行这个操作。