预编译:
预编译中存在两个重要的知识点:函数声明提升和变量声明提升,函数声明提升是一种整体提升,它会把函数声明和函数体一起提升到前面。变量声明提升则是一种局部提升,它仅仅将变量的声明提前了,但是并没有将赋值也一起提前。
说预编译前先说两个知识点:
(1)暗示全局变量:
未经变量声明直接赋值的变量,归window所有。
(2)一切声明的全局变量,都是window的属性:
在全局作用域中通过var关键字声明的变量,会被当成window的属性,无法被删除。又称不可配置属性。
不可配置属性:在window中,经过var声明的全局变量不能通过delete操作来删除,不可配置的属性delete不掉。但是未经声明的全局变量可以被删除。
预编译发生在函数执行前一刻:
步骤:
- 隐式的创建一个函数的执行期上下文对象即ActivationObject,简称AO。
- 找出函数的形参和通过var关键字声明的变量,将其当做AO对象的属性名添加在AO对象中,值设为undefined。
- 将实参和形参相统一。
- 将函数内的函数名作为AO对象的属性名添加到AO对象中,值为其函数体。
例子:
function test (a, b) {
console.log(a) ;
function a () {}
a = 222;
console.log(a);
function b () {};
console.log(b);
var b = 111;
var a ;
console.log(b);
}
test(1);
分析:
-
创建AO对象:
AO{
} -
找出形参和var声明的变量,当成AO的属性名添加在AO对象上
AO{
a:undefined,
b:undefined,
} -
将形参和实参相统一
AO{
a:1,
b:undefined,
} -
找到函数体中的函数声明,将其函数名作为AO的属性添加到AO中,值为其函数体。
AO{
a:function a(){},
b:function b(){},
}
预编译环节结束,开始函数的执行。
//打印 console.log(a)//打印function a(){}
//a赋值 a = 222;
//打印 console.log(a);//打印222
//打印 console.log(b);//打印 function b(){}
//b赋值 b = 111;
//打印 console.log(b);//打印111
在函数预编译环节,其实不仅在函数内部创建了局部对象AO,也在全局内创建了全局对象GO(Global Object)。
例如:
function test(a,b){
console.log(a);//function a(){}
console.log(c);//function c(){}
function a(){};
var c = 3;
d = 3;//暗示全局变量
console.log(d);//3
console.log(c);//3
b = 5;
function b() {};
console.log(a);//function a(){}
console.log(b);//function b(){}
a = 10;
console.log(a);
}
test(1,2);
console.log(d);//3
分析预编译环节:
//分析
1.
AO{
}
GO{
}
2.
AO{
a:undefined,
b:undefined,
c:undefined
}
GO{
d:undefined
}
3.
AO{
a:1,
b:2,
c:undefined
}
GO{
d:undefined
}
4.
AO{
a:function a(){},
b:function b(){},
c:undefined
}
GO{
d:undefined
}
预编译结束,开始函数执行:
test执行://函数执行的操作,最终就是针对AO对象和GO对象进行操作
console.log(a);//function a(){}
console.log(c);//undefined
c = 3;
d = 3;//程序执行到此处,发现没有声明d呀,好吧,添加到window上吧,当成window属性
console.log(d);//3
console.log(c);//3
b = 5;
console.log(a);//function a(){}
console.log(b);//5
a = 10;
console.log(a);//10
//函数外部:
console.log(d);//检测是否为暗示的全局变量,虽在函数内部,实际为window对象的属性,可以配置
再如例子:
//对于一般的例如:
function test(a){
a = a + '10';
}
var a = 10;
console.log(a);
test(a);
console.log(a);
分析:
-
1.创建全局作用域GO对象,和局部作用域AO对象
GO{ a:undefine, } AO{ a:undefine; }
-
2.寻找变量声明和形参并当成各自对象的属性添加到各自对象上
GO{ a:undefine, } AO{ a:undefine; }
-
3.实参形参相统一,
GO{ a:undefine, } AO{ a:undefine; }
-
4.找函数声明,并添加
GO{ a:undefine, test:function test(){} } AO{ a:undefine; }
预编译环节结束,开始函数执行:
1.a=10
GO{
a:10,
test:function test(){}
}
AO{
a:undefine;
}
2.console.log(a);
在全局作用域内,寻找自身的属性是否有,很明显有打印10,此处说一下,函数没被调用就是坨废物.不用管它里面写什么,
3.test(a);
这里就不是废物了,此时a=10,传入函数体内执行
a = a+‘10’;//此处的a为’1010’
GO{
a:10,
test:function test(){}
}
AO{
a:‘1010’;
}
4.console.log(a);//按照所处的作用域,寻找自身所属对象上是否含有该属性,全局对象上a属性的值依旧为10,这里特别强调一点,此时的a指的是全局对象上的a属性即GO的,不要因为执行了test而混淆,test的a是test作用域下的属性,也就是AO的,外部访问不到。如果给函数test内部a=a+‘10’后再加上一句console.log(a);
此时就会打印出’1010’,因为此时访问的是自身AO对象的a属性值。
通俗的说:**局部作用域内没有的,可以到全局作用域上去找,但是全局作用域内没有,不能到局部作用域去找.作用域链可以从儿子到祖辈找,不能反过来.就像儿子可以拿取爹的财产一样,但是爹不能拿儿子的(不过现实中都可以拿,自己意会)**还有如果局部作用域中的属性与全局作用域的属性名相同,不用管太多,就近原则,自己有就用自己的,这是作用域链的查找方式.
下章讲讲作用域链吧