第一步: 检查语法错误
浏览器或者node环境将所有JS检查一遍,检查是否有语法错误,注意并不会执行,这里是确保可执行,然后进行第二步:预编译
第二步:预编译
预备知识:变量提升
首先要理解函数声明整体提升,变量 声明提升。这里要注意变量的提升,一般我们声明一个变量都是
var a = 1;
console.log(a) //1
//但实际上拆分成了两步,真正执行是这样的
var a; // 这一步会提升到执行环境顶部
a = 1;
console.log(a) //1
如果将console.log放a前面会打印出undefined
console.log(a) //undefined
var a = 1;
// 实际上函数会按照下面步骤执行
// 因为变量的声明提升了,但是赋值并没有提升
var a;
console.log(a); // 这里a已经定义了,但没有赋值
a = 1;
预编译步骤:
1、预编译的时候会创建一个AO对象(Activation Object)执行上下文;
2、找形参和变量声明,将形参和变量作为AO对象的属性名,值为undefined;
3、将形参和实参统一;
4、在函数体里找函数声明,值赋给函数体
注意: AO按如下顺序填充:
- 函数参数(若有传参, 会被赋值, 若未传参, 初始化值为undefined) 优先级第二
- 函数声明(若发生命名冲突, 会覆盖) 优先级最高
- 变量声明(初始化变量值为undefined, 若发生命名冲突, 会忽略) 优先级第三
具体的步骤:
0:函数的在运行的瞬间,生成一个活动对象(Active Object)就是所谓的AO
1:分析参数
1-1:函数接收参数,添加到AO的属性上面,值全部都是undefine,如AO.age=undefine
1-2:接收实参,形成AO对应的属性值
2:分析变量声明,如var age,
2-1:如果AO上还没有age属性,则添加AO 属性,值是undefine
2-2:如果AO 上面已经有了age属性,则不做任何操作。
3:分析函数的声明,如果funcion foo(){},
3-1: 则把函数赋给AO.fooo,如果数据属性已经存在,则要被现在的新的值覆盖
最后开始按优先级赋值
举个例子:
function fn (a) {
console.log(a)
}
fn(10) // 10
function fn (a) {
var a = 11
console.log(a)
}
fn(10) // 11
// 因为变量声明的优先级比参数的优先级高,所以a=11
function fn (a) {
console.log(a)
var a = 11
function a () {
console.log('我优先级最高')
}
}
fn(10) // [Function: a]
// 函数声明优先级最高
function fn (a) {
var a = 11
function a () {
console.log('我优先级最高')
}
console.log(a)
}
fn(10) // 11
// 注意,与上一段代码不同的是,在a被打印出来之前,
// a 经历的成长是:
// 1.形参a传过来,AO对象创造属性 AO.a = undefinded
// 2.结合实参,AO.a = 10
// 3.变量声明,它本应该在AO对象内创造 a 属性并赋值为undefinded,但a属性已经存在,不做改动
// 4.遇到函数声明,a 被赋值为一个函数体,即由 a = 10 变为 a = Function
// 5.开始执行函数,遇到 var a = 11 这一句,a 被赋值为 11,即 a = Function 变为 a = 11
2.然后就到了函数的执行阶段, 此阶段当前函数中使用到的所有变量和函数声明都会从当前函数的[[Scope]]作用域链中查找, 根据作用域链中对象的位置首先会查找当前函数的AO对象, 如果没有再查找上层对象, 最后找到全局对象, 如果都没有则会报错(变量未定义).