Javascript闭包和this学习笔记

        

2017-03-15

上一篇详细说明了我对执行上下文和变量对象的理解,这篇文章记录一下我对Javascript中闭包与this指向的理解。


闭包

 
  首先什么是闭包?《Javascript高级程序设计》书中说,闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式就是在一个函数内部创建另一个函数。下面我们看一个例子。   

1
2
3
4
5
6
7
8
9
10
11
  function th1(){
    
    var name = "tian";
    
    return function th2(){
      alert(name);
    };
  
  }
  var result = th1();
  result(); // tian

我们来分析一下这个例子的完整执行过程。首先,全局执行上下文入栈,声明了一个值result,把函数th1()赋值给了这个变量,这个时候,调用th1()函数,th1()的函数执行环境入栈,之后调用result(),th2()的执行环境入栈,此时输出了name,th2()的作用域包括本身函数作用域,外部函数作用域(即th1())和全局作用域,所以这里的name是外部函数作用域中的变量。由此可见,我们倘若需要某个函数访问另一个函数作用域内的对象,我们可以使用闭包的方法。


 我们知道,当一个函数执行完成,局部活动变量就会被销毁,但是当我们使用闭包,就不会发生这种情况,由于闭包会携带包含它的函数的作用域,所以它会比其他函数占用更多的内存,那么除了会占用更多内存外,闭包还有没有别的缺点呢?下面我记录一下另外一个使用闭包值得注意的问题。  

1
2
3
4
5
6
7
8
9
10
11
12
function createFunctions(){
var result = new Array();
for (var i=0; i<10; i++){
result[i] = function(){
return i;
};
}
return result;
}

按照常理,我们会觉得这题随着i的增大,result[i]也会逐渐增大,事实上是不是如此?答案并不是,输出的值都是10,我们在createFunction()里使用了一个闭包,这个闭包是一个匿名函数,返回的是i的值,每个函数的作用域链中都保存着createfunction()函数的活动对象,所以他们引用的是同一个变量i,当外部函数返回后,i=10,此时匿名函数引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10,怎么避免掉这个问题呢?我们可以通过创建另一个匿名函数强制让闭包的行为符合预期。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function createFunctions(){
var result = new Array();
for (var i=0; i<10; i++){
result[i] = function(num){
   return function(){
return num;
}
};
}
return result;

 这个时候,我们没有直接把闭包赋值给数组,而是定义一个匿名函数,并将立即执行该匿名函数的结果赋值给了数组。通过中间量num,result数组中的每个函数都有一个num变量的副本,因此可以返回各自不同的数值。  


this指向

 
 学习javascript的过程中,this的指向问题有时候没有我们想的那么简单。上一篇笔记介绍执行上下文的时候,知道this指向是在执行上下文创建的时候确定的,而执行上下文是在函数调用的时候创建的,所以this指向谁是在函数被调用的时候才确定,而非函数被声明时,在全局函数中,this就等于window,而在函数被作为某个对象的方法被调用时,this等于那个对象,不过匿名函数的执行环境具有全局性,有时候会有一点不同,我们看一个例子。  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var name = "The Window";
var object = {
name: "My Object";
getNameFunc: function(){
return function(){
return this.name;
}
}
};
alert (object.getNameFunc()); //"The Window"

这题输出的是全局变量里的name,而非外部函数的name,我们知道函数在被调用时都会自动取得两个特殊变量:this和arguments.内部函数在搜索这两个变量时,只会搜索到活动对象为止,不会访问到外部函数的变量。那么怎么样才能让闭包访问这个对象呢?看下面改动的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = "The Window";
var object = {
name: "My Object";
getNameFunc: function(){
var that = this;
return function(){
return that.name;
}
}
};
alert (object.getNameFunc()); //"My Object"

 在定义匿名函数之前,我们声明了一个变量叫that,然后把this对象赋值给他,而在关闭闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声明的一个变量,所以调用object.getNameFunc(),就返回”My Object”.