JS中的闭包和this

关于闭包,每次看书之后总是觉得自己理解了,可以隔一段时间之后,又总是容易混淆,所以还是记录一下!!

闭包即一个函数有权访问另一个函数作用域中的变量。

  每当定义一个函数的时候,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用该函数的时候,会为函数创建一个执行环境(每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。)及相应的作用域链(先通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链,然后创建函数的活动对象(使用arguments和其他命名参数的值来初始化函数的活动对象)并推入执行环境作用域链的最前端)。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅仅保存全局作用域(全局执行环境的活动对象)。但是,闭包的情况又有所不同。

  匿名函数的执行环境具有全局性,因此其this对象通常指向Window。但匿名函数作为返回值从另一个函数return时,它的作用域链被初始化为包含 包含函数的活动对象和全局变量对象。这样,匿名函数可以访问在包含函数中定义的所有变量。而且当该函数执行完毕后,该函数的执行环境和作用域链都会被销毁,但他的活动对象不会被销毁,仍然会留在内存中,因为匿名函数的作用域链仍然在引用这个活动对象。直到匿名函数被销毁后(设置匿名函数的引用为null),该函数的活动对象才被销毁。

1.作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。因为闭包所保存的是整个变量对象,而不是某个特殊的变量。如下代码:

 1   function createFunctions(){
 2     var result = new Array();
 3     for (var i=0; i < 10; i++){
 4       result[i] = function(){ 
 5         console.log('this:'+this); //this:object Window
 6         return i;
 7       };
 8     }
 9     return result; //返回匿名函数的数组,共10个
10   }
11 
12   var array = createFunctions();
13   console.log(array);  //(10) [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
14 
15   array.forEach(function(item,index){
16     var data = item(); //调用匿名函数
17     console.log(index,data);  //打印10次10
18   })
19   

2.两次调用createFunctions()函数返回的数组(因为result是局部变量,两次调用createFunctions()函数都会有自己的活动对象即变量)是不同的,且数组中包含的匿名函数也是不同的:

1   var array1 = createFunctions();
2   console.log(array[0]===array[0]);  //true:array中的第一个匿名函数与自己是相同的地址
3   console.log(array[0]===array[1]);  //false:array中的第一个匿名函数与第二个匿名函数不是同一地址
4 
5   console.log(array===array1);  //false:数组array不等于array1,即它们指向的数组首地址不相同,每次执行createFunctions()返回result的地址不相同  
6   console.log(array[0]===array1[0]);  //false:array中的第一个匿名函数与array1中的第一个匿名函数不是同一地址

3.将数组中的匿名函数改为命名函数,因为该命名函数为局部作用域,每次调用createFunctions()函数时会重新创建活动对象,包括重新创建命名函数,所以两次调用createFunctions()函数返回的数组是不同的,且数组中包含的命名函数也是不同的:

 1   function createFunctions1(){
 2     var result = new Array();
 3     for (var i=0; i < 10; i++){
 4       result[i] = function log_num(){ 
 5         console.log('this:'+this); //this:object Window
 6         return i;
 7       };
 8     }
 9     return result; //返回匿名函数的数组,共10个
10   }
11 
12   var array2 = createFunctions1();
13   var array3 = createFunctions1();
14   console.log(array2[0]===array2[0]);  //true:array2中的第一个命名函数与自己是相同的地址
15   console.log(array2[0]===array2[1]);  //false:array2中的第一个名函数与第二个名函数不是同一地址
16 
17   console.log(array2===array3);  //false:数组array2不等于array3,即它们指向的数组首地址不相同,每次执行createFunctions()返回result的地址不相同  
18   console.log(array2[0]===array3[0]);  //false:array2中的第一个名函数与array3中的第一个名函数不是同一地址

4.将数组中的局部命名函数改为全局命名函数,每次调用createFunctions()函数时返回的数组中都包含同一命名函数,虽然两次调用createFunctions()函数返回的数组是不同的,且数组中包含的命名函数是相同的:

 1   function log_num2(){ //全局命名函数
 2     console.log('this:'+this); //this:object Window
 3     return i; 
 4   };
 5 
 6   function createFunctions2(){
 7     var result = new Array();
 8     for (var i=0; i < 10; i++){
 9       result[i] = log_num2; //引用全局命名函数
10     }
11     return result; //返回匿名函数的数组,共10个
12   }
13 
14   var array2 = createFunctions2();
15   var array3 = createFunctions2();
16   console.log(array2[0]===array2[0]);  //true:array中的第一个命名函数与自己是相同的地址
17   console.log(array2[0]===array2[1]);  //true:array中的第一个命名函数与第二个命名函数是同一地址
18   console.log(array2[0]===array2[9]);  //true:array中的第一个命名函数与第十个命名函数是同一地址
19 
20   console.log(array2===array3);  //false:数组array2不等于array3,即它们指向的数组首地址不相同,每次执行createFunctions()返回result的地址不相同  
21   console.log(array2[0]===array3[0]);  //true:array2中的第一个命名函数与array3中的第一个命名函数是同一地址

5.但是在全局命名函数这种情况下,实际上已经不是闭包,因为result返回的数组中包含的函数是全局作用域的,并不能访问包含函数的作用域。所以通过调用命名函数是,出现未定义变量i的告警:

1   array2.forEach(function(item,index){
2     var data = item(); //调用命名函数打印Uncaught ReferenceError:i is not defined
3     console.log(index,data);  //未执行
4   })

总结:

1.函数内部的一个特殊对象是 this , this引用的是函数据以执行的环境对象,当在网页的全局作用域中调用函数时,this 对象引用的就是 window。一般情况下,匿名函数的执行环境为window,即匿名函数内的this指向window。this指向的执行环境是调用函数的环境,而不是函数执行时的活动对象(即作用域链的最前端)。

2.包含函数执行完成后才产生闭包,此时变量对象为最后一个值,所以当执行闭包时只能取得包含函数中任何变量的最后一个值。

3.通过执行var array2 = createFunctions2();var array3 = createFunctions2();,array2和array3引用的 createFunctions2()函数的活动对象是不同的,活动对象都是 createFunctions2()函数执行的时候创建的。

4.通过调用外部函数,在局部作用域中创建的命名函数,每次创建的活动对象不同,所以命名函数也是不同的,虽然名字相同,但地址不同。

猜你喜欢

转载自www.cnblogs.com/shuqiao/p/10042570.html
今日推荐