JavaScript的执行步骤
- 预解析阶段,在这个阶段会检查js的语法错误,然后进行变量的提升和函数的声明
- 执行阶段,按照代码书写的顺序从上往下执行
这里我们主要探讨的是第一个阶段,jsv8引擎在执行js代码的时候发生了什么
预解析
- 变量提升,就是把所有的变量提升到当前作用域最前面,不提升赋值操作
- 函数提升,就是把所有的函数声明提升到当前作用域的最前面,不调用函数
我们来看具体的实例
console.log(num); //10
var num = 10;
这里输出10,是因为预解析,变量提升
//使用函数关键字定义函数
fn();
function fn(){
console.log(10);//10
}
我们在函数声明前也可以调用,因为预解析,函数提升
当然了如果是函数表达式,就不能提前调用了,如
fun();
var fun = function(){
console.log(10);
}
运行这段代码只会报错
注意一点,匿名函数不参与预编译
接下来我们来看看js引擎具体干了什么事?
首先我们了解两个词
全部对象 GO(global object)
活动对象 AO(active object)
函数体内,预编译会提前把里面的变量声明和函数声明依据规则存放在AO对象内
AO规则
- 在函数执行前的一瞬间,生成一个AO对象
- 分析参数,以形参作为该对象的属性名,实参作为属性值
- 分析变量声明(var 变量声明),变量名作为AO对象属性名,值为undefined,如果变量名和形参重名,则不做改变
- 分析函数声明(function),函数名作为AO对象属性名,函数整体作为属性值,如果遇到同名(变量或者形参名),直接覆盖(函数体覆盖属性值)
GO规则
- 进入script标签,需要执行代码的前一瞬间
- 分析变量声明(var 变量声明),变量名作为GO对象的属性名,值为undefined
- 分析函数声明(function),函数名作为GO对象的属性名,函数整体作为属性值,如果遇到同名(变量名),直接覆盖(函数体覆盖属性值)
经典面试题
题1
f1();
console.log(c);
console.log(b);
console.log(a);
function f1(){
var a = b = c = 9;
console.log(a);
console.log(b);
console.log(c);
}
最后输出是 9 9 9 9 9 报错
第一步预解析
- 生成一个GO对象 GO:{}
- GO:{
f1:function(){}
} - 执行代码 f1();
- 因为执行了函数,所以生成了一个AO对象 f1:AO:{}
- f1:AO:{
a :undefined;
} - 再来执行函数体内的代码 输出 a = 9 b = 9 c = 9
- 再来指向函数体外的代码 输出 c = 9 b = 9 报错 因为a是函数体内的变量,不具备全局作用域
题2
function fun(a){
console.log(a);
var a = 5;
console.log(a);
}
fun(99)
-
执行函数的前一瞬间,生成一个AO对象
AO:{} -
分析参数,以形参作为该对象的属性名,实参作为该对象的属性值
AO:{
a:99
} -
分析变量声明,变量名作为AO对象的属性名,值为undefined,如果变量名和形参名相同,则不做改变,这里不做改变
-
分析函数声明,这里不考虑,因为里面没有函数
-
执行代码,一步一步开始执行
所以第一次输出a为99
后来a又做了一次赋值操作,所以第二次输出a为5
题3
function fun(){
return foo;
foo = 10;
function foo(){
};
var foo = 11;
}
console.log(fun()); //f f00(){}
题4
console.log(fun()); //11
function fun(){
foo = 10;
function foo(){
};
var foo = 11;
return foo;
}
题5
function fun(){
console.log(a);
if(true){
function a(){}
}
console.log(a)
}
fun()
这题涉及兼容性问题,在低版本的浏览器上的运行结果不一致