(一)函数声明、变量的定义
/*函数表达式*/
var fun1 = function(){
//.....
}
/*函数声明*/
function fun2(){
//......
}
/*变量声明*/
var a = 1;
/*不带var的变量声明*/
b = 2; //不带var的变量声明会作为全局变量
(二)预处理
当进入一个js环境时,并不是直接从上到下开始执行代码,而是会先扫描所有js,将var声明的变量和通过函数声明方式声明的函数集合在一起组成一个词法环境(LexicalEnvironment,简称LE)。
预处理包括全局的预处理和函数的预处理。
全局预处理
总所周知执行js代码最先进入的是全局,所有会先创建全局执行环境,所有会先进行全局的预处理。
示例代码
console.log(a) //undefined
console.log(b) //报错
console.log(fun1)//函数的引用
console.log(fun2) //报错
var a = 1;
b = 2;
function fun1(){
//......
}
fun1 = 4;
var fun2 = function(){
//......
}
console.log(a) // 1
console.log(b) // 2
console.log(fun1) // 4
console.log(fun2) // 函数的引用
说明
全局的预处理一开始创建的LE:
LE{
a:undefined,
fun1:函数的引用
}
我们可以通过预处理的LE得到以下结论:
- 可以综合前面提到的“var声明的变量和通过函数声明方式声明的函数......”结合起来看,b和fun2是不符合条件的因此不会在预处理的过程中加入到LE中去。所以一开始运行的console代码中,b和fun2会报错
- 通过上面的LE也可以看出,预处理对于变量的处理,只是保存了变量,而没有保存变量的值。所以一开始运行的console代码a为undefined。
- 对于fun1,在代码中有两处声明,分别是变量和函数,遇到这种情况遵循函数大于变量的原则。
全局预处理完成以后再往下执行代码,通过一些赋值语句来改变原来LE中对应的值或者在LE中新增一些通过其他声明方式声明的变量或函数,因此在最后执行的console之前LE更改为:
LE{
a:1,
fun1:4,
b:2,
fun2:函数的引用
}
函数预处理
函数的预处理和全局的预处理大致一致,区别在于函数有参数。
示例代码
function test(a,b){
console.log(a);//4
console.log(b);//函数的引用
console.log(c);//报错
console.log(d);//undefined
var a = 1;
function b(){
//......
}
c = 2;
var d = 3;
console.log(a);//1
console.log(b);//函数的引用
console.log(c);//2
console.log(d);//3
}
test(4,5);
说明
当函数运行到“test(4,5)”时进入到test函数,此时创建再增加一个新的执行环境,进行预处理得到LE:
LE{
a:4,
b:函数的引用,
d:undefined,
/*arguments表示参数的长度*/
arguments:2
}
我们可以通过预处理的LE得到以下结论:
- 函数的预处理LE中会多一个表示参数长度的arguments。
- 参数加入LE中,不仅仅是新增了一个参数,而且会对它进行赋值。这一点区别于函数体内普通的变量。
- 参数与函数体内的函数命名冲突时也是遵循上面提到的原则,函数优先。
函数执行到下一组console的时候,LE更改为:
LE{
a:1,
b:函数的引用,
d:3,
arguments:2,
c:2
}
以上是预处理的内容,其实如果把全局的环境也看作是一个没有参数的函数,那么也就不需要特别区分了全局预处理和函数预处理了。函数在执行是还有其他一系列操作,欢迎查看指正另一篇博文《JS作用域链和函数执行》