• 前端见微知著JavaScript基础篇:this or that ?


    上节,我们提到了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作用域的问题,本文愚拙,还望以此文抛砖引玉。

  • 相关阅读:
    Android按返回键退出程序但不销毁,程序后台运行,同QQ退出处理方式
    android 下动态获取控件的id
    Android大图片裁剪终极解决方案 原理分析
    如何使用Android MediaStore裁剪大图片
    最新的Android Sdk 使用Ant多渠道批量打包
    nodejs学习(1)
    C#——企业微信一般操作之一
    html(1)——转圈等待效果+鼠标移动悬浮显示相关信息
    SQL注入小结
    Java实现二叉树地遍历、求深度和叶子结点的个数
  • 原文地址:https://www.cnblogs.com/scy251147/p/4844078.html
Copyright © 2020-2023  润新知