javascript闭包理解2
http://www.tuicool.com/articles/iURNzy
我们知道一个函数是有作用域的,在函数内部定义的局部变量只有在函数内部才可以访问的到。一旦函数访问结束被销毁,局部变量随之也会销毁,无法通过任何方式再次访问局部变量,除了闭包。也就是说,我们可以通过闭包这一个方法,从函数的外部去访问到函数内部的变量,即使那个函数已经被销毁。没错,闭包最重要的用法就是,我们只提供某些接口去访问和修改局部变量,而外部是不能直接访问到局部变量的。
说了那么多有关如何使用闭包,我们来看看闭包是长什么样子的。又到了举个栗子的环节,依然是最简单的people和name。
var people = function(){
var name = "Yika";
var sayName = function(){
return name; //访问了people函数的局部变量name
}
var setName = function(newName){
name = newName; //访问了people函数的局部变量name
}
return{
sayName: sayName,
setName: setName
}//返回一个对象
}
var p1 = people(); //函数return的是一个对象,这个对象里有两个函数sayName和setName
console.log(p1.name); //undefined. name是people函数里的局部变量,而不是p1对象的属性,当然为undefined
console.log(p1.sayName());//"Yika"
p1.setName("Ruyi"); //通过setName函数修改局部变量name的值
console.log(p1.sayName());//"Ruyi"
看完这个例子,想必对闭包多少有个了解啦,除了注释的内容,下面再做些补充。
问:为什么局部变量name属性在people执行完之后,没有被销毁呢,反而数值还保存在内存中。
答: 在例子中,函数注释那里专门写了(访问了people函数的局部变量name)。正是因为people函数里的sayName函数和setName函数访问了name属性,并且通过return传到了p1对象里,成了p1的两个方法。因为方法一直引用着people函数的局部变量,所以不会被消除,依然会在内存中。这样便形成了闭包,可以在函数外部访问到函数内部的局部变量。
对此,我们可以换个更直观的写法。
var people = function(){ var name = "Yika"; var obj = { sayName: function(){ return name; }, setName: function(newName){ name = newName } }; return obj; //直观的返回对象 } //下面的结果是一样的。
2:
Javascript 闭包(Closure)–摘自Node.js开发指南http://www.tuicool.com/articles/ZJvamiZ
JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体现出闭包的特性。
var generateClosure = function() {
var count = 0;
var get = function() {
count ++;
return count;
};
return get;
};
var counter = generateClosure();
console.log(counter()); // 输出 1
console.log(counter()); // 输出 2
console.log(counter()); // 输出 3
|
代码分析:
1、generateClosure() 函数中有一个局部变量 count ,初值为 0。
2、还有一个叫做 get 的函数,get 将其父作用域,也就是 generateClosure() 函数中的 count 变量增加 1,并返回 count 的值。
3、generateClosure() 的返回值是 get 函数。在外部我们通过 counter 变量调用了 generateClosure() 函数并获取了它的返回值,也就是 get 函数,接下来反复调用几次 counter() ,我们发现每次返回的值都递增了 1。
让我们看看上面的例子有什么特点,按照通常命令式编程思维的理解,count 是generateClosure 函数内部的变量,它的生命周期就是 generateClosure 被调用的时期,当 generateClosure 从调用栈中返回时,count 变量申请的空间也就被释放。问题是,在 generateClosure() 调用结束后, counter() 却引用了“已经释放了的” count变量,而且非但没有出错,反而每次调用 counter() 时还修改并返回了 count 。这是怎么回事呢?
这正是所谓 闭包的特性:当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境。
当你 return 的是内部 function 时,就是一个闭包。内部 function 会 close-over 外部function的变量直到内部 function结束。
上面例子中,当函数 generateClosure() 的内部函数 get 被一个外部变量 counter 引用时, counter 和generateClosure() 的局部变量就是一个闭包。
var generateClosure = function() {
var count = 0;
var get = function() {
count ++;
return count;
};
return get;
};
var counter1 = generateClosure();
var counter2 = generateClosure();
var counter3 = generateClosure();
console.log(counter1()); // 输出 1
console.log(counter2()); // 输出 1
console.log(counter1()); // 输出 2
console.log(counter1()); // 输出 3
console.log(counter2()); // 输出 2
console.log(counter3()); // 输出 1
|
上面这个例子解释了闭包是如何产生的:counter1 和 counter2 分别调用了 generateClosure() 函数,生成了两个闭包的实例。它们内部引用的 count 变量分别属于各自的运行环境。我们可以理解为,在 generateClosure() 返回 get 函数时,私下将 get 可能引用到的 generateClosure() 函数的内部变量(也就是 count 变量)也返回了,并在内存中生成了一个副本,之后 generateClosure() 返回的函数的两个实例 counter1 和 counter2 就是相互独立的了。
闭包有两个主要用途,一是实现嵌套的回调函数,二是隐藏对象的细节。
1. 嵌套的回调函数
由于闭包机制的存在,即使外层函数已经执行完毕,其作用域内申请的变量也不会释放,因为里层的
函数还有可能引用到这些变量,这样就完美地实现了嵌套的异步回调。
2. 实现私有成员我们可以把一个对象用闭包封装起来,只返回一个 “访问器” 的对象,即可实现对细节隐藏。
彻底理解javascript中的函数、闭包、作用域和作用域链
1.函数
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
2. 作用域
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
3. 作用域链
作用域链其实是一条法则,或者说规则,用来确定变量的作用域,确定变量来自哪里,变量是在哪里声明的:总是现在最小的作用域中寻找他的声明,如果没有找到,就到上一级的作用域中寻找,如果没有,就在到上一级寻找,一直寻找到全局作用域中,这样就形成了一条寻找变量来自哪里的链条。作用域链:即如何寻找变量来自哪里(在哪里声明)的链条。如下例:
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()给修改了,也证明了作用域链就是这样一步一步向上一级寻找,这样运作的。
4. 函数的定义和调用
javascript中函数的定义和调用有两条十分重要,一定要注意:
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
上面的结果为什么不是1,2,3,而是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,而不是1,2,3.
例子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()的两次运行生成的两个定义,也就是两个不同的函数,他们引用的 i 是两个不同的拷贝。 不会相互影响。af() 函数中引用的 i 的值,只在生成af()的那一次ff()的运行有关,和后面生成 af2() 函数的那一次ff()的运行没有关系。
5. 闭包
上面最后的两个例子中都使用了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 这里在全局作用域中访问了函数作用域中的变量