函数有一些看不到的属性
fun.[[scope]]
scope就是看不到的属性,这个属性是函数调用的时候生成的,这个属性装的就是函数的作用域
js的执行过程
1.语法检测 检测你的代码有没有基本的语法错误
2.预编译(全局预编译,局部预编译)
3.逐行执行
目标:理解预编译过程,作用域,作用域链
作用域
1.全局作用域
2.函数作用域
预编译-全局
在逐行执行之前,语法检测
没有语法错误则进行预编译
var a;
function fun(){};
function a(){};
预编译-全局,发生在代码执行前一刻,会发生三个步骤
1.生成一个空对象GO(global object),GO = {}
2.向GO里添加所有不在函数体内的var声明的变量名作为GO对象的属性名,值为undefined
GO = {
a : undefined
}
3.向GO对象里添加所有没被其他函数包裹的函数声明,函数名作为GO对象的属性名,值为函数体
Go = {
a : undefined,
fun:function(){};
a:function(){};
}
如果遇到函数名与变量名同名,则将变量的undefined覆盖掉
Go = {
a:function(){};
fun:function(){};
}
如果变量遇到同名,则不作改变
如果遇到函数名与函数名同名,则从上至下以最后一个同名函数的函数体覆盖
// 1.会生成一个对象,这个对象装的就是作用域,称为GO(global object)。当全部挂载完成以后,然后代码再去逐行执行
GO = {
}
// 2.分析var声明。
变量名作为GO对象的属性名,值为undefined
go = {
a : undefined
}
// 3.分析函数声明
// 函数名作为GO对象的属性名,值为函数体
// 如果遇到同名的,直接干掉
go = {
a : function,
fun : function fun(){
},
abc : function,
}
// 走到第27行的时候,a产生一次赋值
// 此时go对象变成了
go = {
a : 100,
fun : function fun(){
},
abc : function,
}
// 逐行执行
// 看着go对象里面的内容执行
全局预编译完成后,则开始逐行执行
执行到函数调用,局部预编译开始
局部预编译分为四个步骤
1.执行函数体的前一刻生成AO 对象(活动对象) AO = {}
2.将函数形参添加到AO 对象中,赋值为实参值
3.将函数体内所有变量声明找出,赋值undefined,如果遇到同名(包括形参名)不作改变
4.将函数体内的函数声明找出,函数名作为AO对象的属性名,值为函数体,遇到同名则覆盖
例题:
// 第一题
function test(a,b){
console.log(a); //function a(){};
console.log(b); //undefine
var b = 234; //b从undefinde,被赋值234
console.log(b); //234
a = 123; //a为转化全局属性,属性值为123
console.log(a); //123
function a(){};
var a; //这里的a在预编译时已经提升上去了,且在逐行执行过程中被赋值为123
b = 234; //b转化为全局变量,赋值为234
var b = function(){}; //b被重新赋值
console.log(a); //打印的还是123
console.log(b); //function(){};
}
test(1);
// 1.分析GO,生成自己的GO对象
// GO = {}
// 2.分析变量声明,没有就直接略过
// GO = {}
// 3.分析函数声明
// GO = {
// test : function test(a,b)
// }
1.在执行前一刻生成AO对象
AO = {}
2.将函数体中的形参和变量声明找出来,在AO 对象中初始属性值为undefined
AO = {
a : undefine
b : undefine
}
3.将形参与实参统一
AO = {
a : 1
b : undefine
}
4.分析函数声明,函数名为AO 对象的属性名,值为函数体
AO = {
a :function a(){};
b : undefine
}
// 第二题
console.log(test);
function test(test){
console.log(test); //打印输出为预编译后的结果 :function test(){}
var test = 234; //重新赋值为234
console.log(test); //234
function test(){}
}
test(1);
var test = 123; //在函数体AO对象中找得到test,则用函数体内的test,找不到则在GO中找
1.分析GO,生成自己的GO对象
GO = {}
2.分析变量声明,找出全局变量声明,将变量声明的名作为GO对象的属性名,赋值为GO对象的属性值
GO = {
test : 123
}
3.分析函数声明,找出全局函数声明,将函数声明的名作为GO对象的属性名,赋值为GO对象的属性值,并覆盖在GO对象中的同名的属性值
GO = {
test : function test(test){
console.log(test);
var test = 234;
console.log(test);
function test(){}
}
}
// 1.在执行前一刻生成AO对象
// AO = {}
// 2.将函数体中的形参和变量声明找出来,在AO 对象中初始属性值为undefined,遇同名不做改变
// AO = {
// test : undefined
// }
// 3.将形参与实参统一
// AO = {
// test : 1
// }
// 4.分析函数声明,函数名为AO 对象的属性名,属性值为函数体,同名则覆盖掉
// AO = {
// test : function test(){}
// }
*/
// 第三题
function test(){
console.log(b); //undefined
if(a){
var b = 100;
}
c = 234; //c为全局变量,在GO中,属性名为c,赋值为234
console.log(c); //234
}
var a; //undefined
test();
a = 10; //10
test();
console.log(c); //234
1.分析GO,生成自己的GO对象
GO = {}
2.分析变量声明,找出全局变量声明,将变量声明的名作为GO对象的属性名,赋值为GO对象的属性值
GO = {
a : undefined
}
3.分析函数声明,找出全局函数声明,将函数声明的名作为GO对象的属性名,赋值为GO对象的属性值,并覆盖在GO对象中的同名的属性值
GO = {
a : undefined
test : function test(){
}
1.在执行前一刻生成AO对象
AO = {}
2.将函数体中的形参和变量声明找出来,在AO 对象中初始属性值为undefined,遇同名不做改变
AO = {
b : undefined
}
3.将形参与实参统一
AO = {
b : undefined
}
4.分析函数声明,函数名为AO 对象的属性名,属性值为函数体,同名则覆盖掉
AO = {
b : undefined
}
// 第四题
function bar(){
return foo;
foo = 10;
function foo(){};
var foo = 11;
}
console.log(bar());
// 1.分析GO,生成自己的GO对象
// GO = {}
// 2.分析变量声明,找出全局变量声明,没有就直接略过
// GO = {}
// 3.分析函数声明,找出全局函数声明,将函数声明的名作为GO对象的属性名,赋值为GO对象的属性值,并覆盖在GO对象中的同名的属性值
// GO = {
// bar : function bar(){
// return foo;
// foo = 10;
// function foo(){};
// var foo = 11;
// }
// }
// 1.在执行前一刻生成AO对象
// AO = {}
// 2.将函数体中的形参和变量声明找出来
// AO = {
// foo : undefined
// }
// 3.将形参与实参统一,没有就略过
// AO = {
// foo : undefined
// }
// 4.分析函数声明,函数名为AO 对象的属性名,属性值为函数体,同名则覆盖掉
// AO = {
// foo : function foo(){};
// }
全局预编译
总结:变量声明提升,函数整体提升
变量声明提升:变量名拿到最上面去,对变量的赋值,等预编译完成后,执行的时候再赋值给他,提升上去后,就相当于在js代码的最上面声明了变量却没有赋值
函数整体提升:将整个函数声明全部提升到顶端,提升上去后,就相当于在js代码的最上面声明函数、且有函数值
注意:这两句话不能颠倒,且与函数名同名,则被函数函数覆盖,变量名与变量名同名,则不作改变
局部预编译
总结:将形参和实参统一,然后变量声明提升,函数整体提升,提升到函数体的最上面,如果变量名与形参同名,形参不作改变。