彻底理解javascript中的函数、闭包、作用域和作用域链

[color=red]1.函数[/color]
javascript中的函数可以分为三种:
1)全局函数;
2)对象中定义的函数;
3)函数中定义的函数,即局部函数,也可以叫私有函数或者内部函数;

// 全局函数
function gf(){
alert('global fucntion');
}
// 对象中定义的函数
var v = {};
v.init = function(){
alert('function of object');
}
// 私有函数
function gf2(){
return function pf(){
alert('private function');
}
}
// 调用它们:
gf(); // 弹出global fucntion
v.init(); // 弹出function of object
var a=gf2();
a(); // 弹出private function


[color=red]2. 作用域[/color]
javascript中变量没有块级作用域,只有全局作用域和函数作用域,还有对象作用域

// 全局变量
var gv = 'global variable';
// 对象中的变量,或者说属性,对象作用域可以用于来模拟实现“命名空间”
var obj = {};
obj.v = 'variable of object';
// 函数作用域
function gf(){
var fa = 'variable of fucntion';
return fa;
}
alert(gv); // 全局变量,可以在任何地方直接使用gv来访问
alert(obj.v); // 只能通过对象访问v
//alert(v); // ReferenceError: v is not defined
alert(gf()); // 只能通过函数访问fa
//alert(fa); // ReferenceError: fa is not defined

[color=red]
3. 作用域链[/color]
作用域链其实是一条法则,或者说规则,用来确定变量的作用域,确定变量来自哪里,变量是在哪里声明的:总是先在最小的作用域中寻找他的声明,如果没有找到,就到上一级的作用域中寻找,如果没有,就在到上一级寻找,一直寻找到全局作用域中,这样就形成了一条寻找变量来自哪里的链条。作用域链:即如何寻找变量来自哪里(在哪里声明)的链条。如下例:

var gv = 1;
function gf(){
var fv = 2;
return function pf(){
gv = 100;
fv = 200;
};
}
var f = gf();
alert(gv); // 弹出1
f();
alert(gv); // 弹出100

上面代码中pf()函数中的变量gv和fv来自哪里?首先在pf()的函数作用域中寻找,没有找到他们的声明或者定义,那么就往上一级作用域 gf() 的函数作用域中寻找,我们找到了fv的声明和定义,但是gv还是没有找到,就继续到上一级作用域,也就是全局作用域中寻找。
alert(gv)在f()调用的前后值不一样,也就证明了全局作用域中的gv被函数pf()给修改了,也证明了作用域链就是这样一步一步向上一级寻找,这样运作的。

[color=red]4. 函数的定义和调用[/color]
javascript中函数的定义和调用有两条十分重要,一定要注意:
1)函数的定义是在预处理过程中完成的;
1)函数中的变量的作用域是在该函数的定义时,通过作用域链法则确定的,变量的声明也是在函数定义时进行的,也就是在预处理过程中完成,所以就有了“变量的声明提前”的说法;
2)函数中的变量的值,是在该函数运行时,也就是调用时确定下来的;不是在定义函数时确定的;这一点一定要切记!!!
函数的作用域也称为函数的上下文,或者说执行环境。
所以函数的定义和调用是分开的,函数的定义确定作用域,分配内存;函数执行时,才根据作用域链来确定变量的值。
例子1:

function f1(){
var a = 1;
f2();
}
function f2(){return a;}
f1(); //ReferenceError: a is not defined

上面代码:函数f2()定义时,根据作用域法则来确定a的作用域:在函数f2()的作用域,在全局作用域都没有找到a的声明和定义,所以f2()函数中的a是没有声明和定义的;
所以在f1();调用是,fa()中的f2()会报错: a is not defined。f2()函数中a的作用域不会在运行时才去确定!运行时只会去确定他的值,如果他进行过声明和定义的话。
例子2:

function f(){
var a=[];
var i;
for(i=0; i<3; i++){
a[i] = function(){
return i;
}
}
return a;
}
var a = f();
console.log(a[0]()); //3
console.log(a[1]()); //3
console.log(a[2]()); //3

上面的结果为什么不是0,1,2,而是3,3,3呢?
分析如下:
1)定义函数f()时,确定了a和i的作用域,是在f()函数的作用域中;定义f()函数的同时,也定义了三个私有匿名函数,定义时也确定了三个私有匿名私有函数中变量i的作用域,根据作用域链法则,显然 i 是来自f()函数的作用域中的。注意此时只确定作用域,不能确定 i 的值,因为 i 来自函数f(),那么 i 的值的确定是发生在调用f()时。
而var a = f(); f()函数运行之后,i 的值才确定的,显然此时 i == 3;
所以调用 a[0](); a[1](); a[2](); 时,输出的 i 的值都是 3,而不是0,1,2.
例子3:

function ff(){
var i = g;
return function(){
return i++;
}
}
var g = 100;
console.log(g); //100
var af = ff();
console.log(af()); //100
console.log(g); //100
g = 1000;
console.log(g); //1000
console.log(af()); //101

var af2 = ff();
console.log(af2()); //1000
console.log(af2()); //1001
console.log(af()); //102

结果分析:
1)上面代码,最前面输出的两个100,很容易理解。
2)第三个console.log(g) == 100; 说明 ff()中的变量 i 的值是在调用ff()函数时,从全局变量 g 获得的,但是获得之后,改变 i 的值并不能同时改变 g 的值。i 只是在ff()调用时的那一刻从 g 哪里获得值,ff()不运行,那么 i 就和 g 没有关系了。
3)第4个输出值1000也好理解。第5个输出值,因为上一次调用af()使得 i 自增了, 所以输出了101,此时有自增 了 1 一次。
4)第6个和第7个值分别输出了:1000和1001是因为,var af2 = ff(); 使得ff()函数又调用了一次,所以ff()函数中 的 i 在ff()函数运行时,又从 g 那里获得值,而此时 g == 1000了,所以ff()函数中的 i 也等于 1000 了,所以分别输出 1000 和 1001.
5)最后一个af()输出 102而不是1002,是因为 af 函数 和 af2 函数的定义是发生在两个不同的时间点,是在ff()的两次运行生成的两个定义,注意这个例子和上面那个例子的不同,上面那个例子中是在定义全局函数时,同时也定义了三个私有函数。而本例,af 和 af2 函数的定义是在全局函数ff()的两次运行时才生成的,两次运行生成了两个函数的上下文,而两次运行时 i 的值不一样,所以生成的两个函数的上下文中的 i 也不一样的。因为 i 在ff函数的局部变量中,所以生成af 和 af2函数时上下文中的 i 的值是由那次 ff() 的运行时 i 的值确定的。因为 g 的值改变了,所以af 和 af2 上下文中的 i 的值也变了。

af 和 af2 是两个不同的函数,是不会共享上下文的。他们的上下文,也就是作用域,由生成他们定义的那次ff()函数的运行来确定(函数在其的[color=darkred]定义[/color]时确定其作用域),而运行ff()函数又确定他自己的局部变量 i 的值(函数[color=darkred]运行[/color]时确定其作用域中变量的值:两次分别为100和1000;),所以af 和 af2 上下文作用域中的 i 的值分别为100 和 1000。

例子4:

<script type="text/javascript">
console.log(f1);//f1()
console.log(f2);// undefined
function f1(){
console.log("f1!");
}
var f2 = function(){
console.log("2!");
}
</script>

分析结果:
函数的定义是在预处理过程完成的,所以运行时,可以打印出f1(),而变量 f2 声明是在预处理过程完成的,但是定义也就是赋值是在运行时完成的,所以打印 undefined (此时还没有定义)

[color=red]5. 闭包[/color]
上面两个例子(2,3)中都使用了javascript中的闭包技术。
闭包,就是将私有函数返回给一个全局变量,而因为私有函数中可以访问的变量的作用域是在定义它的时候确定的,也就是说私有函数可以访问它的父函数中定义的变量,那么将私有函数返回给全局变量之后,导致了在全局作用域中可以访问函数作用域中定义的变量了。这种在全局作用域中访问函数作用域的技术就是闭包技术。
例子:

function f(){
var b = "b";
return function(){
return b;
}
}
//console.log(b); //ReferenceError: b is not defined
var a = f();
console.log(a()); // 这里在全局作用域中访问了函数作用域中的变量b
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值