执行js文件的流程:
①通篇检查语法错误
②预编译
③解释一行执行一行
一、由实例引发的思考
首先我们来看两个例子:
test()
function test() {
console.log(1) //1
}
可以看到先执行函数,后声明函数是可以的。
console.log(a) //undefined
var a=20
console.log(a) //Uncaught ReferenceError
可以看到,先打印a,后定义a会undefined;而直接打印a会报错
那么也就意味着,我们的js文件执行前,会将变量的声明提升,赋值不提升。
总结:会将函数声明+定义提升、变量的声明提升,赋值不提升。
但这种说法还是太过简略,一些复杂的问题就无法解决。
此外你要明白一个基本概念:暗示全局变量
暗示全局变量:只要未声明直接赋值,暗示为全局变量,存到window对象内。
function f()
{
var a=5;
l=4
}
f()
console.log(l)
console.log(a)
在这里我们打印a会报错,因为a为局部变量;而l因为没有使用var 所以被暗示为全局变量,打印值为4
二、全局上下文GO:
1.GO===Window,(GO对象的创建发生在在页面加载完成时的那一刻)也就是全局执行时的前一刻。
2.步骤如下:
①寻找var变量声明(在GO对象中即为键为变量名,值为undefined)
②寻找函数声明并赋值函数。(在GO对象中即为键为函数名,值为函数体)
③执行。
举例一
console.log(a,b)
function a(){}
var b=function(){}
GO对象创建过程:
①寻找var变量声明,并赋值为undefined
GO=
{
b:undefined
}
②寻找函数声明,赋值函数
GO=
{
b:undefined
a:function a(){}
}
③执行:
console.log(a,b)//GO对象中a:function a(){} b:undefined
function a(){}
var b=function(){}
怎么样?有没有豁然醒悟的感觉,如果没有,别急,我们继续向下看:
实例二
var x=1,y=z=3
function add(n)
{
return n=n+1
}
y=add(x)
function add(n)
{
return n=n+2
}
z=add(x)
GO对象创建过程:
①寻找var变量声明,并赋值为undefined
GO=
{
x:undefined
y:undefined
z:undefined
}
②寻找函数声明,赋值函数
GO=
{
x:undefined
y:undefined
z:undefined
add:function add(){return n=n+1}
->function add(){return n=n+2}
}
③执行:
var x=1,y=z=3//x:undefined->1
//y:undefined->3
//z:undefined->3
function add(n) //已提升
{
return n=n+1
}
y=add(x)//y:undefined->3->add
function add(n)
{
return n=n+2
}
z=add(x)//z:undefined->3->add
console.log(x,y,z)//1 3 3
三、函数上下文AO:
1.当一个函数被调用时,首先创建函数上下文这个对象(所以AO对象创建在函数执行的前一刻)
2.步骤如下:
①寻找函数形参、变量声明(设定为undefined)。(在AO对象中即为键为形参或变量名,值为undefined)
②将实参赋值给形参。(在AO对象中即为键为形参,值为实参)
③寻找函数声明并赋值函数。(在AO对象中即为键为函数名,值为函数体)
④执行函数。
举例一:
function test(a)
{
console.log(a)
var a=1
console.log(a)
function a(){}
console.log(a)
var b=function(){}
console.log(b)
function d(){}
console.log(d)
}
test(2)
AO对象的构建过程:
①寻找函数形参、变量声明。
AO=
{
//①寻找函数形参、变量声明。
a:undefined
b:undefined
}
②赋值形参
AO=
{
//②形参赋值。
a:undefined ->2
b:undefined
}
③寻找函数声明并赋值函数。
AO=
{
//寻找函数声明并赋值函数。
a:undefined ->2->function(){}
b:undefined ->function(){}
d:function(){}
}
④执行:
function test(a)
{
console.log(a) //现在的AO中 a:function(){}
var a=1 //a:undefined ->2->function(){}->1
console.log(a) //1
function a(){} //已提升不再执行
console.log(a)//1
var b=function(){}//已提升步不再执行
console.log(b)//现在的AO中 b:function(){} function(){}
function d(){}
console.log(d)//现在的AO中 d:function(){}
}
test(2)
举例二、
相信经过上个例子,你已经懂得了这个过程,那我们在来看一个例子:
function test(a,b)
{
console.log(a)
c=0
var c
a=5
b=6
console.log(b)
function b(){}
function d(){}
console.log(b)
}
test(1,2)
AO对象创建过程:
①寻找函数形参、变量声明
AO={
a:undefined
b:undefined
c:undefined
}
②赋值形参
AO={
a:undefined->1
b:undefined->2
c:undefined
}
③寻找函数声明,赋值函数
AO={
a:undefined->1
b:undefined->2->function b(){}
c:undefined
d:function d(){}
}
④执行:
function test(a,b)
{
console.log(a) //AO对象中 a:1
c=0 c:undefined->0
var c
a=5 //undefined->1->5
console.log(b)//AO对象中:function b(){}
b=6//undefined->2->function b(){}->6
console.log(b) //AO对象中:b:6
function b(){} //已提升不再更新AO
function d(){}//已提升不再更新AO
console.log(b) //AO对象中:b:6
console.log(d)//AO对象中:function d(){}
}
test(1,2)
总结:预编译做过的事执行就不要再做!!
四、全局上下文GO+函数上下文AO:
实例一
var a = 1;
console.log(a);
function test(a) {
console.log(a);
var a = 123;
console.log(a);
function a() { }
console.log(a);
var b = function () { }
console.log(b);
function d() { }
}
var c = function () {
console.log("I at C function");
}
console.log(c);
test(2);
GO对象的构建:
GO:
{
//1.变量声明:undefined
a:undefined
//函数声明:函数体
test: function test(){}
c:function c(){}
}
执行:
GO
{
//执行
var a = 1; //a:undefined->1
console.log(a);//1
function test(a) {...}//已提升、不执行
function c() {...}//已提升、不执行
console.log(c,test);//GO中:function c(){...},function test(){...}
test(2);//执行testAO
}
AO对象的创建:
testAO:
{
//形参、变量定义为undefined
a:undefined
b:undefined
//形参赋值
a:undefined->2
//寻找函数赋值函数
a:undefined->2->function(){}
b:undefined
d:function(){}
}
test执行:
function test(a) {
console.log(a);//AO对象中a:function(){}
var a = 123;//a:undefined->2->function(){}->123
console.log(a);//AO对象中a:123
function a() { }//已提升
console.log(a);//AO对象中a:123
var b = function () b:undefined->function(){}
console.log(b);//AO对象中b:function(){}
d=2//d:function(){}->2
function d() { }//已提升
console.log(d)
}
实例二
var b=3
console.log(a)
function a(a)
{
console.log(a)
var a=2
console.log(a)
function a(){}
var b=5
console.log(b)
}
a(1)
GO对象的构建:
GO:
{
//1.变量声明:undefined
b:undefined
//函数声明:函数体
a: function a(){...}
}
执行:
GO
{
//执行
var b = 3; //b:undefined->3
console.log(a);//a: function a(){...}
function a(a) {...}//已提升、不执行
a(1);//执行aAO
}
aAO:
AO:
{
//形参、变量定义为undefined
a:undefined
b:undefined
//形参赋值
a:undefined->1
//寻找函数赋值函数
a:undefined->1->function(){}
}
a执行:
function a(a){
console.log(a) //AO:a:function(){}
var a=2//a:undefined->1->function(){}->2
console.log(a)//AO:2
function a(){}//已提升
var b=5//b:undefined->5
console.log(b)AO:5
}
结果为:
实例三(坑点)
a=1
console.log(a)
function test()
{
console.log(a)
a=2
console.log(a)
var a=3
console.log(a)
}
var a
console.log(a)
test()
console.log(a)
相信现在的你已经足够熟练了,那在这里我就不再写的那么详细了,直接写执行,一起看下坑点:
GO:
{
a:undefined->1
test:function(){}
}
AO:
{
a:undefined->2->3
}
那么问题来了 我们在执行test的时候第一个a打印出来到底是undefined还是1呢?我们执行完test打印的a为多少?不执行又是多少呢?我们先来看下结果
我们可以看到在test外的打印a值无论test是否执行,它都为1;而在test内的第一个a为undefined。这说明了一个问题:
AO与GO是独立的 它们各自管理各自的数。但这么说并不准确,因为若某值在AO中没有,GO中有,则在函数中用GO中的值;但反过来AO有GO无,在全局用则会报错。
如果你还是不明白那继续看下面这个例子:
实例三
function test()
{
var a=b=1
console.log(b)
}
test()
console.log(b)
console.log(a)
看到这你可能会发出疑问?现在不是讲GO与AO结合么,这里也没有涉及GO,别急,再仔细回忆下前言中我提到的暗示全局变量。
在这个函数里其实我们的b为全局变量而不是局部变量,因为我们只var a;没有var b;所以默认暗示b挂在了window上(注意是test()之后才有了这个暗示)所以GO、AO对象如下:
GO
{
b:1
test:test(){}
}
AO:
{
a:undefined
}
执行:
function test(){
var a=b=1 //AO:a:undefined->1 、GO: b:undefined->1
console.log(a,b) //AO:a:1, GO:b:1
}
test()
console.log(b)//全局有
console.log(a)//全局没有报错
所以我们的执行test时过程为先找AO、AO有a为1,无b,找GO GO有b为1.
而在函数外打印时,报错说明了GO无就是无,无法找AO这一特点。
总结一下:函数执行找AO,AO无找GO
全局执行找GO、GO无就是无
特例:若有if语句怎么办呢?
function test()
{
console.log(b)
if(a)
{var b=2}
console.log(b)
c=3
console.log(c)
}
var a
test()
a=1
console.log(a)
console.log(c)
此时我们的GO、AO对象:
GO:
{
a:undefined->(test()执行完赋值为1)
test:function(){}
c:3
//TEST执行:
AO{
//(c加到全局,因为是暗示全局变量)
b:undefined(现在的a为undefined,所以b无法赋值)
}
}
可能看到这里你有些懵,但是没关系我们结合结果来再说一遍:
function test()
{
console.log(b) //在AO第一步提升了变量声明
if(a)//a为GO中的,此时为赋值为undefined,所以无法进行给b赋值
{var b=2}
console.log(b) //在AO第一步提升了变量声明
c=3 //暗示成全局变量
console.log(c)
}
var a //GO第一步已提升
test()
a=1
console.log(a)//赋值为1
console.log(c)//打印全局变量c
相信你现在已经明白了,那我们再来总结一下:
若if{}内有变量的声明即var…,那么在创建AO的时候将它声明,是否赋值,就要看条件是否满足了。那我们再提升一下:预编译只要有var、let、const等变量声明语句我们就在对象中添加这个值:undefined,至于具体的赋值,根据执行过程来判定。
自己试一下:
1.
function test()
{
a=1
function a(){}
console.log(a)
var a =2
console.log(a)
}
console.log(test())
console.log(a)
报错原因是因为这里的a一直都是属于AO 即使a=2在函数里,因为后面有var a也使得它成为了局部变量。
2.
function test()
{
return a
a=1
function a(){}
var a =2
}
console.log(test())
这个结果是因为return后函数不再执行下去。
3.
a=1
function test(a)
{
function e(){}
console.log(arguments,a)
arguments[0]=2
console.log(arguments,a)
console.log(e)
if(a){var b=3}
var c
a=3
var a
console.log(b)
f=5
console.log(c)
console.log(a)
console.log(arguments)
}
var a
test(4)
argument[0]即为a改变argument[0]也改变,改变argument[0],a也改变。
总结
总结一下预编译:当一个JS文件执行前首先会进行GO对象的生成->遇到函数执行,则会先进行AO对象生成(可能会有暗示全局变量提升到GO)->函数执行