任何 JavaScript 代码片断在执行前都要进行编译(一般就在执行前)。所以,JavaScript编译器首先会对 var a = 2; 这段程序进行编译,而后作好执行它的准备,而且一般立刻就会执行它。node
编译通常分为三步:编程
码块被称为词法单元(token)。例如,考虑程序 var a = 2;。这段程序一般会被分解成
为下面这些词法单元:var、a、=、2 、;。空格是否会被看成词法单元,取决于空格在
这门语言中是否具备意义。数组
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的表明了程序语法
结构的树。这个树被称为“抽象语法树” (Abstract Syntax Tree,AST)。
var a = 2; 的抽象语法树中可能会有一个叫做VariableDeclaration 的顶级节点,接下
来是一个叫做Identifier(它的值是a)的子节点,以及一个叫做AssignmentExpression
的子节点。AssignmentExpression 节点有一个叫做 NumericLiteral(它的值是 2)的子
节点。promise
将 AST 转换为可执行代码的过程被称为代码生成。这个过程与语言、目标平台等息息
相关。
抛开具体细节,简单来讲就是有某种方法能够将var a = 2; 的AST转化为一组机器指
令,用来建立一个叫做 a 的变量(包括分配内存等),并将一个值储存在a 中。浏览器
js引擎是单线程的,编译和执行js的线程只有一个。
nodejs和浏览器还有别的线程用于处理其它任务,如处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程。异步
单线程因此有了事件循环。async
函数调用造成一个栈帧,放入当前执行栈中,帧中包含了当前context须要的参数和局部变量。编程语言
一个 JavaScript 运行时包含了一个待处理的消息队列。每个消息都关联着一个用以处理这个消息的函数。函数
其它线程将消息放到消息队列,js线程经过事件循环过程去获取消息。spa
在事件循环期间的某个时刻,运行时从最早进入队列的消息开始处理队列中的消息。为此,这个消息会被移出队列,并做为输入参数调用与之关联的函数。
函数的处理会一直进行到执行栈再次为空为止;而后事件循环将会处理队列中的下一个消息。
执行逻辑很简单,就是先清空当前context的micortask,再执行task
一个例子:
console.log('1'); setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) Promise.resolve().then(r=>console.log(16)) async function a1(){ console.log('13') await console.log('14') console.log('15') } process.nextTick(function() { console.log('6'); }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) a1() setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) })