本文着重于对JavaScript代码的执行机制进行剖析和说明。
在JavaScript中,可执行的JavaScript代码分三种类型:
不同类型的代码其执行机制也有所不同。
JavaScript语言规范没有包含任何线程机制,客户端的JavaScript也没有明确定义线程机制,但浏览器端的JavaScript引擎基本上还是严格按照”单线程”模型去执行JavaScript代码的。究其原因,应该还是为了简单吧,因为JavaScript的主要用途是与用户交互以及操作DOM,如果采用多线程,将会带来很复杂的同步问题。
JavaScript引擎是基于事件驱动的,引擎维护着一个事件队列,JavaScript引擎线程所作的就是不断的从事件队列中读取事件,然后处理事件,这个过程是循环不断的,所以整个的运行机制又称为事件循环(Event Loop)。
虽然HTML5提出的Web Worker允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM,所以可以说,Web Worker并没有改变JavaScript单线程的本质。
JavaScript引擎是单线程的,但浏览器本身是多线程的,JavaScript引擎线程只是浏览器里的一个线程,除此之外,浏览器通常至少还有以下四类线程:
不仅仅是JavaScript,解释性语言都存在执行上下文(execution context,也称执行环境,运行上下文)这样一个概念。
执行上下文定义了执行中的代码有权访问的其他数据,决定了它们各自的行为。
执行上下文大致可以分为两类:
而由eval()函数动态执行的代码运行在调用者的执行上下文之中,不会产生新的执行上下文。
执行上下文与作用域很容易被混淆成同一个东西,事实上两者的概念是完全不同的。
以函数为例,函数的执行上下文是完全与函数代码运行相关联的动态存在,相关代码运行结束了,与之相关联的执行上下文也就被释放了,而作用域更多的是一个静态的概念,如闭包作用域就与代码是否正在执行没有关系。
执行上下文与作用域的关联是:执行上下文会为执行中的代码维护一个作用域链,里面包含了代码可以访问的各个名字对象,当代码中出现访问某个标识符(变量名,函数名等),JavaScript引擎会根据这个作用域链顺序进行查找,如果存在则直接返回该对象,如果整个链里都不存在则会产生异常。
执行上下文只是一个抽象概念,在具体JavaScritp引擎实现中,它会被表示为一个至少包含以下三个属性的内部对象:
在JavaScript中,程序代码是在执行上下文环境里被执行的,这包括两个阶段:
从以上记述可以看到, 函数执行之前,函数的代码首先会被全部扫描,内部声明的函数,变量不分位置,全部事先登记到执行上下文的变量对象里。这为JavaScript语言带来了一个提升(Hoisting)的概念,即后面定义的名字,前面的代码也可访问。
示例代码如下:
(function() {
log(); //正常输出hello,因为下面定义的log()函数的作用域被提升到顶端
v(); //异常,因为下面定义的v变量的作用域虽被提升到顶端但值为undefined
function log() {
console.log('hello');
}
var v = log;
}());
JavaScritp的异步处理是通过回调函数实现的,即通过事件队列,在主线程执行完当前的任务,主线程空闲后轮询事件队列,并将事件队列中的任务(回调函数)取出来执行。
异步处理大致有以下几大类型,不同的异步处理由不同的浏览器内核模块调度执行,调度会将相关回调添加到事件队列中。
其中,Promise的优先级最高,排在其他所有类型的异步处理之前,而Promise以外的异步处理之间并没有优先级差别。