本文是一个正在学习前端的小尘记录的学习中遇到的一些问题,以及找到的解答方案,如有内容有误,可评论联系,小尘一定更加努力地学习以及积极更改错误的内容,期待与大家的一起进步~~
事情还得从预编译讲起(不一开始讲作用域链是为了大家能更好的理解)
相信作为新手来写JS函数代码的时候大家不少人都遇到过以下情况吧
🠛
function test(a,b){
console.log(a);
c = 0;
var c;
a = 3;
b = 2;
console.log(b);
function b(){
}
function d(){
}
console.log(b);
}
test(1)
相信不少同学和我最开始想的是一样的,认为第一个console.log(a)打印出来的结果是1;第一个console.log(b)打印出来的是2;而第二个console.log(b)打印出来的是b(){}吧。 然而事实并非如此。事实是第一个console.log(a)打印出来的结果是1;第一个console.log(b)打印出来的是2;而第二个console.log(b)打印出来的也是2。
而造成这种结果的原因则是javascript里面比较特殊的预编译! 这种预编译也分为两种情况,一种是发生在函数体执行之前的四部曲(这里则是属于发生在函数体执行之前的预编译):
1.创建一个AO对象(Activation Object)
2.找形参和函数体内变量声明,然后将变量声明和形参作为AO的属性名,值为undefined
3.将实参和形参统一
4.在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体
另外一种则是发生在全局变量的三部曲:
1.创建GO对象
2.找全局变量声明,将变量声明作为GO的属性名,值为undefined
3.在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体
在执行上方代码之前,谷歌V8引擎会对其预编译,如下:
AO:{ //先产生一个OA对象
a:undefined ->1 ->3, //第一次undefined都是第一次形参和变量的声明,重复的话不用管,一个对象里的key值是不能有重复的。
b:undefined ->function b(){} ->2, //然后第一次a的undefined变成1则是将形参a与变量a的统一
c:undefined ->0,
d:function d(){} //当形参和变量统一了之后就开始找函数声明了,从上往下将值赋予给函数体;
当以上操作都完成了之后,才会开始执行,而这时函数内则是这样:
function test(a,b){
var a;
var b;
var c;
a=1;
function b(){
}
function d(){
}
console.log(a); //1
c = 0;
a = 3;
b = 2;
console.log(b); //2
console.log(b); //2
}
test(1)
}
接下来就是发生在全局的预编译了➔
请君同看如下代码:
global = 100;
function fn(){
console.log(global);
var global = 200;
console.log(global);
}
fn();
这里相信大家也能根据上面的函数体执行之前的预编译猜测出这里两个console.log(global)打印出来的结果了吧
对!结果就是undefined以及200。
这里虽然有着一个全局变量global在全局里面赋予了一个100,但是在函数体内却不一样,在函数体内仍然优先在函数体内寻找有没有该变量,当浏览器引擎在函数体内寻找,很快就能发现var global = 200;这里var了一个global;故将var global提至该函数体顶部,且这个函数没有形参,也只有这么一个声明了这么一个变量,这一步就算完成了,而形参不存在,自然也不需要形参与函数体变量的统一了;剩下的函数声明该函数体内也是没有的,我们就可以开始执行了:
global = 100;
function fn(){
var global
console.log(global);
global = 200;
console.log(global);
}
fn();
结果显而易见啦,那咱们再来趁热打铁进一步
🠧
作用域链
在这,咱们先总体介绍一下作用域链:
1.[[scope]]
函数的作用域,是不可访问的(它是属于谷歌V8引擎访问的),其中存储了运行期上下文的集合。
2.作用域链定义
[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。
3.执行期上下文
当函数执行的时候,汇创建一个称为执行期上下文的内部对象(AO对象),一个执行期上下文定义了一个函数执行的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,当函数执行完毕,它所产生的执行期上下文会被摧毁。
4.查找变量
从作用域链的顶端依次往下查找。
这里科普一些小知识:像这样的函数体都有一些自带的属性
function test(){
}
console.log(test.name) //这里的name属性则是函数体自带的声明函数名字的
console.log(test.prototype) //原型,emmm,这里咱也没学通透,就不多讲了哈哈
console.log(test.[[scope]]) //作用域属性,隐式属性(我们不能检查到的)
......
当一个函数被执行时
function a(){
functionb(){
var b=222;
}
var a = 111;
b();
console.log(a);
}
var glob = 100;
a();
它会经过以下过程,很像是一个“先有鸡还是先有蛋”的问题,很绕,但是理解了之后也特别容易懂。
a 定义 a.[[scope]] ---> 0:GO{}
a 执行 a.[[scope]] ---> 0:AO{} 1:GO{}
b 定义 b.[[scope]] ---> 0:bAO{} 1:aAO{} 2:GO{}
图解:
简单来说,就是函数a的执行造成了函数b的创建,b的调用也是在a函数的里面,如果a执行完了,那么b也一定是执行完了
总结:函数执行的预编译还有作用域链虽然繁琐,但是却给javascript带来了一个很大的可执行空间,所以总体来说,函数的作用域链以及预编译给javascript带来的是保证一个不会出错的运行过程。
PS:希望大家能多给小尘提出建议哈,大家一起进步!