上节,我们提到了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);
执行一下,我们就可以看到运行结果了:
但是看到这里,我们呆住了,为什么运行完毕,居然出来的结果是这个?为什么呢?其实别急,你运行出来一堆代码,就说明你的函数没有被执行,如果想被执行,我之前说的加什么? 对,就是加 (); 即可,我们来将函数真正的运行一下看看:执行 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作用域的问题,本文愚拙,还望以此文抛砖引玉。