JS内部运行步骤
1. 编译阶段
- 词法分析
由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。 - 语法分析
将词法单元(token)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。 - 生成可执行代码
将 抽象语法树(AST) 转换为可执行代码的过程称被称为代码生成。
2. 执行阶段
- 创建一个全局对象(Global Object)
- 创建一个执行环境栈(Execution Context Stack)
- 创建全局执行上下文(Execution Context)
- 将产生的多个执行上下文进行入栈和出栈
下面以一个简单例子进行分析,主要讲解是第二部分执行阶段。
var a = 2;
function bar() {
var b = 2;
function foo() {
var c = 2;
}
foo();
}
bar();
2.1 创建一个全局对象
- 只存在一个这个全局对象
- 生命周期:随着程序的生命周期
- 属性:全局对象在创建时,将Math,String,Date,document 等常用的JS对象作为其属性;且属性在任何地方都能访问
- 属性window:指向全局对象
用伪代码模拟全局对象的大体结构如下:
//创建一个全局对象
var globalObject = {
Math:{},
String:{},
Date:{},
document:{}, //DOM操作
...
window:this //让window属性指向了自身
}
2.2 创建一个执行环境栈
这个栈用于存储数据
2.3 创建全局执行上下文
每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。
JS的执行作用域有三种:
- 全局环境:JavaScript代码运行起来会首先进入该环境
- 函数环境:当函数被调用执行时,会进入当前函数中执行代码
- eval、with(不建议使用,可忽略)
当浏览器首次载入你的脚本,它将创建全局执行上下文。
在全局代码中调用一个函数,就会创建一个执行上下文。
JS的引擎会以栈的方式来处理这些执行上下文。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。
而处于栈顶的上下文执行完毕之后,就会自动出栈。
2.4 将产生的多个执行上下文进行入栈和出栈
- 全局上下文推入执行环境栈底
- 代码开始从上往下执行,当代码执行到bar(),生成bar执行上下文,推入栈中
- 代码执行到foo(),生成foo执行上下文,推入栈中
- foo()执行完,foo执行上下文出栈
- bar()执行完,bar执行上下文出栈
- 全局上下文执行完,上下文出栈
如下图所示:
执行上下文的生命周期
执行上下文的生命周期包括三个阶段:创建阶段->执行阶段->回收阶段
1.创建阶段
当函数被调用,但未执行任何其内部代码之前, 会做以下三件事:
1.1创建变量对象
- 初始化函数的参数argumen
- 函数声明提升
- 变量声明提升
函数声明的优先级要高于var声明。
1.2 确定作用域链
作用域链是在变量对象之后创建的
作用域链是由当前作用域与上层一系列父级作用域组成
当被要求解析变量时,JavaScript始终从代码嵌套的最内层开始, 如果最内层没有找到变量, 就会跳转到上一层父作用域中查找,直到找到该变量。
1.3 确定this指向
this是在执行上下文创建阶段确定的,即在函数执行的时候确定的
2.执行阶段
执行阶段JS引擎会进行变量赋值、函数引用、执行其他代码,执行顺序取决于代码的位置
在执行阶段变量对象(Variable Object)变为活动对象(Active Object)。
VO => AO
变量对象和活动对象其实是同一个对象,只是处于执行上下文的不同生命周期不过只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。
3.回收阶段
执行上下文出栈等待虚拟机回收执行上下文