上节,我们提到了this关键字的问题,并且追加了一句很有意义的话:谁调用我,我指向谁。的确,在javascript中,在默认情况下,this会指向一个已经初始化的window对象。所以你不论有多少全局变量,全局函数,默认都是追加到window对象上,所以在这种情况下无论怎么使用this,都是在这个window对象上去查找各种变量,函数等。

在实际编码中,this的默认情况只能适用于业务比较简单的场景中。但是在大部分业务场景中,this都需要改变其指向来实现一定的业务逻辑。这样一来,我们就得来好好的深究深究了。

上节我们提到过,Call方法,Apply方法和Bind方法,都会改变this的作用域,并且也用一定的实例来做了演示。但是如果真要想把这个this吃透,让我们分为以下两个部分来一一讲解: 自执行函数闭包

说道自执行函数,我在上节中也大概点了一下,无非就是在执行的函数后面加上 (); 即可实现。比如下面的方法:

1 (function(){
2     alert("Hi Morning!");
3 })();

当运行起来的时候,就直接会输出内容。

当然,光是这样的一种函数,是没有意义的,因为没法传参,如果想实现传参功能,还得按照下面的方式来进行:

1 (function(age){
2     alert(age);
3 })(30);

这样当执行起来的时候,就会直接输出我们传入的参数的值。其类似的写法应该和如下的方式是一样的:

1 function test(age){ 
2     alert(age); 
3 } 
4 test(30);

当然,由于这是自执行函数,有人如果想要不立即执行,而是需要在用到的地方执行,该如何来做呢?其实也是非常简单的,看下面:

1 var testAutoExec = (function(age){ 
2     return age; 
3 })(31); 4 5 alert(testAutoExec);

其实前一种方法更实用一些。自执行函数是不是很简单,但是说了半天也没说道this作用域的事儿呢,这个别急,后面讲this的时候,会用到自执行函数的。

接下来,我们说一点比较复杂一些的自执行函数。请看例子:

1 var f1 =function(){ 
2 var result = []; 
3 for (var i =0; i <3; i++) { 
4     result[i] =function(){ 
5         return i; 
6       } 
7     } 
8   return result; 
9 } 
10 
11 alert(f1); 
12 //返回函数整体
13 
14 alert(f1()); 
15 /*
16 返回内容如下: 
17 function(){return i}, 
18 function(){return i}, 
19 function(){return i} 
20 */
21 
22 alert(f1()[0]); 
23 /*
24 返回内容如下: 
25 function(){return i} 
26 */
27 
28 alert(f1()[0]()); 
29 /*
30 执行函数,返回内容:3
31 */

为了方便我已经将结果都注释好了,通过一步一步的执行,我们可以获得到结果,所以这里我们为了方便,循环执行一下:

1 var f1 =function(){ 
2 var result = []; 
3 for (var i =0; i <3; i++) { 
4     result[i] =function(){ 
5         return i; 
6     } 
7   } 
8  return result; 
9 } 
10 
11 var res = f1(); 
12 for (var i =0; i <3; i++) { 
13     alert(res[i]()); 
14 } 
15

那么大家来猜一猜,我们的最终结果是多少呢?

都是3,也就是 3,3,3

为什么会是这样呢?

因为,res[i]的内容是function(){return i}; 但是循环是先走完的,也就是三次循环全部走完,才alert结果的,这时候,i的值显然已经是3了,所以会连续出来三个一样的值。解决方式如下,我们可以通过引入自执行函数来解决这个问题:

1 
2 var f1 =function(){ 
3 var result = []; 
4 for (var i =0; i <3; i++) { 
5     result[i] =function(num){
6         returnfunction(){ 
7             return num; 
8         } 
9     }(i); 
10   } 
11  return result; 
12 } 
13 
14 var res = f1(); 
15 for (var i =0; i <3; i++) { 
16     alert(res[i]()); 
17 } 
18

或者:

1 var f1 =function(){ 
2 var result = []; 
3 for (var i =0; i <3; i++) { 
4     result[i] =function(num){ 
5         return num; 
6     }(i); 
7   } 
8  return result; 
9 } 
10 
11 var res = f1(); 
12 for (var i =0; i <3; i++) { 
13     alert(res[i]); 
14 } 
15

说道闭包,相信很多人在各种语言里面都有接触过。无论是.net也好,还是javascript也好,抑或是java,闭包存在的意义要么是无意间引入的bug,要么是为了业务逻辑的需要而引入的。

下面来看一个例子:

1 function fn(){ 
2 var age =4; 
3 return function(){ 
4     var n=0; 
5     alert(++n); 
6     alert(++age); 
7     } 
8 } 
9 
10 var fun = fn(); 
11 alert(fun);

执行一下,我们就可以看到运行结果了:

function(){ var n=0; alert(++n); alert(++age); }

但是看到这里,我们呆住了,为什么运行完毕,居然出来的结果是这个?为什么呢?其实别急,你运行出来一堆代码,就说明你的函数没有被执行,如果想被执行,我之前说的加什么? 对,就是加 (); 即可,我们来将函数真正的运行一下看看:执行 fun();

好了,我们看看运行结果:

先运行一遍,结果,输出了: 1  ,  5

再运行一遍,结果,输出了: 1  ,  6

再运行一遍,结果,输出了: 1  ,  7

为什么呢?

原来,fun这个函数本身的内容,上面已经贴出来了,函数内部有一个n,属于局部变量,无论你怎么运行,那么这个局部变量进去后都是0,然后累加一下,所以是1,这也就是为什么每次运行,都会输出1的原因。但是,为什么age就可以递增呢?原因就在于,age是一个驻留于内存之中的变量,无论你这个函数怎么运行,都是从内存中拿出这个变量,然后递增,所以每次运行,你所看到的age的值都是不一样的。

这里有人肯定会问,你怎么知道age在内存中,其实我就要反问了,如果age不在内存中,你觉得会发生什么状况,alert的时候,肯定因为找不到这个值而报undefined的错。并且,这个值是申明在闭包函数外部的,所以会一直驻留内存,因为fun函数本身没有对其进行任何初始化操作。

但是,闭包是存在了,我们该怎么删除呢?其实方法很简单,直接使用 fun = null;就搞定了,再运行的时候,我们就发现错误提示了:

1 function fn(){ 
2 var age =4; 
3     return function(){ 
4         var n=0; 
5         alert(++n); 
6         alert(++age); 
7     } 
8 } 
9 var fun = fn(); 
10 fun(); 
11 fun(); 
12 fun(); 
13 fun =null; 
14 //报错,提示fun is not a function
15 fun(); 
16

说到这里,不知道大家对闭包有没有一个整体的概念了?

上面的自执行函数和闭包讲完,这里将进入真正的主场:this。先从一个例子讲起:

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

这个例子会输出什么: The Window。 如果对这类的东西搞不清楚输出什么,可以用如下的方法来试验:

首先,可以alert出函数体,看看函数体是什么,我们运行: alert(object.getNameFunc()); 得到的结果如下:

function(){return this.name;}

通过函数体,我们可以看到,返回的是this.name, 由于在window上,我们已经附加了一个name为”The Window“的值,所以这里毫无疑问当然输出的是The Window 了。

Edit:2015年9月29日09:44:43

今早在《JavaScript启示录》的时候,看到这么一个例子:

1 var foo = 'foo';
2 var myObject = {foo: 'I am myObject.foo'};
3 var sayFoo = function() {
4 console.log(this['foo']);
5 };
6 // give myObject a sayFoo property and have it point to sayFoo function
7 myObject.sayFoo = sayFoo;
8 myObject.sayFoo(); // logs 'I am myObject.foo'
9 sayFoo(); // logs 'foo'

其实打印出myObject.sayFoo函数体和sayFoo函数体的时候,发现函数体都是一模一样的:function(){console.log(this['foo']);}。如果按照上面的方法分析的话,那么输出的结果应该是一样的,但是实际情况并非如此。所以this关键字比我们想象的更复杂,但是总是遵循一条军规:谁调用我,我指向谁。

在上 上面的例子中,其实是有一个闭包的,那么在闭包中,由于this没被任何context改变,所以依然指向window对象。

在上面的例子中,是没有闭包存在的,并且myObject很明确的就是context上下文,并且对sayFoo进行了调用,所以会指向myObject对象。

 

从这里我们可以看出,this的作用域并没有被改变掉,在大多数实际情况中,我们并不想这样,我们希望this的作用域指向getNameFunc中的值,那么该怎么做呢?我们把输出结果稍微更改一下:alert(object.getNameFunc().call(object)); 然后我们再看看输出结果,已经被更改成了 My Object. 至于原因,我在前面文章有介绍,总之一句话:对于this,谁调用我,我指向谁。

使用call可以解决这种方式,但是有没有其他的方法来解决呢?其实是有的,且看如下代码:

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

输出的结果是My Object。原因是什么呢?我们可以来一步一步的分析。

首先,打印出待返回结果的函数体,看看哪些变量放在内存,哪些变量,放在函数体内,使用alert(object.getNameFunc());来打印:

返回结果为: function(){return that.name;}

从返回结果,可以很明白的看出来,that是存在于内存中的数据,那么that指向了this,就会改变this的本身指向为当前对象(谁调用我,我指向谁原则),所以这里的this指向了object,那么当前打印出来的内容,毫无疑问就是My Object了。

其实,在C#中,我碰到这样的闭包,也是通过加一个临时变量来防止出现问题的。

 

本章到此结束,主要讲解了 自执行函数,闭包和this作用域的问题,本文愚拙,还望以此文抛砖引玉。

posted on 2015-09-28 15:23  程序诗人  阅读(569)  评论(1编辑  收藏  举报