上节,我们提到了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=; 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启示录》的时候,看到这么一个例子:

 var foo = 'foo';
var myObject = {foo: 'I am myObject.foo'};
var sayFoo = function() {
console.log(this['foo']);
};
// give myObject a sayFoo property and have it point to sayFoo function
myObject.sayFoo = sayFoo;
myObject.sayFoo(); // logs 'I am myObject.foo'
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作用域的问题,本文愚拙,还望以此文抛砖引玉。

前端见微知著JavaScript基础篇:this or that ?的更多相关文章

  1. 前端见微知著JavaScript基础篇:你所不知道的apply, call 和 bind

    在我的职业生涯中,很早就已经开始使用JavaScript进行项目开发了.但是一直都是把重心放在了后端开发方面,前端方面鲜有涉及.所以造成的一个现象就是:目前的前端知识水平,应付一般的项目已然是足够的, ...

  2. 第三篇:web之前端之JavaScript基础

    前端之JavaScript基础   前端之JavaScript基础 本节内容 JS概述 JS基础语法 JS循环控制 ECMA对象 BOM对象 DOM对象 1. JS概述 1.1. javascript ...

  3. 一步步学习javascript基础篇(0):开篇索引

    索引: 一步步学习javascript基础篇(1):基本概念 一步步学习javascript基础篇(2):作用域和作用域链 一步步学习javascript基础篇(3):Object.Function等 ...

  4. 前端之JavaScript基础

    前端之JavaScript基础 本节内容 JS概述 JS基础语法 JS循环控制 ECMA对象 BOM对象 DOM对象 1. JS概述 1.1. javascript历史 1992年Nombas开发出C ...

  5. 一步步学习javascript基础篇(3):Object、Function等引用类型

    我们在<一步步学习javascript基础篇(1):基本概念>中简单的介绍了五种基本数据类型Undefined.Null.Boolean.Number和String.今天我们主要介绍下复杂 ...

  6. 好程序员web前端分享HTML基础篇

    好程序员web前端分享HTML基础篇,最近遇到很多新手,都会问,如果要学web前端开发,需要学什么?难不难学啊?多久能入门之类的问题?那么今天好程序员就先来给大家分享一下web前端学习路线:HTML基 ...

  7. 前端之HTML基础篇

    HTML基础篇 目录                                                                               本章内容: 简介 1. ...

  8. 前端开发之JavaScript基础篇一

    主要内容: 1.JavaScript介绍 2.JavaScript的引入方法和输出及注释 3.javaScript变量和命名规则 4.五种基本数据类型 5.运算符 6.字符串处理 7.数据类型转换   ...

  9. web前端篇:JavaScript基础篇(易懂小白上手快)-2

    目录 一.内容回顾: ECMAScript基础语法 1.基本数据类型和引用数据类型 2.条件判断和循环 3.赋值运算符,逻辑运算符 4.字符串的常用方法 5.数组的常用方法 6.对象 7.函数 8.日 ...

随机推荐

  1. HTML5 画布canvas元素

    HTML5的canvas元素以及随其而来的编程接口Canvas API应用前景极为广泛.简单地说,canvas元素能够在网页中创建一块矩形区域,这块矩形区域可以成为画布,这其中可以绘制各种图形.可别小 ...

  2. 一秒钟看懂SaaS、CRM、OA、ERP、HR、进销存

    自2014年以来,SaaS.CRM.OA.ERP.HR.APM.进销存.财务系统等,这些名词大量出现在微信朋友圈.电视楼宇广告和千百万融资资讯中.它们到底是什么意思?相互之间又有什么区别?在这个飞速发 ...

  3. go sync.Mutex 设计思想与演化过程 (一)

    go语言在云计算时代将会如日中天,还抱着.NET不放的人将会被淘汰.学习go语言和.NET完全不一样,它有非常简单的runtime 和 类库.最好的办法就是将整个源代码读一遍,这是我见过最简洁的系统类 ...

  4. sqlplus: error while loading shared libraries: /u01/app/lib/libclntsh.so.11.1

    成功安装了Oracle 11g后,使用sqlplus登录数据库时遇到下面错误: [oracle@DB-Server ~]$ sqlplus / as sysdba   sqlplus: error w ...

  5. JDK动态代理和CGLIB的区别

    Aspect默认情况下不用实现接口,但对于目标对象,在默认情况下必须实现接口 如果没有实现接口必须引入CGLIB库 我们可以通过Advice中添加一个JoinPoint参数,这个值会由spring自动 ...

  6. request对象详解

    先来了解一下Request的主要方法: setAttribute(String name,Object):设置名字为name的request的参数值getAttribute(String name): ...

  7. Runtime.exec() sucks!!!!

    自己项目中使用到了 Runtime rt = Runtime.getRuntime(); Process p = rt.exec("query session");p.waitFo ...

  8. Java设计模式学习笔记(单例模式)

    最近一直在看<Head First设计模式>,这本书写的确实是很不错的,专注于怎么用最简单的方式最通俗的语言让人了解设计模式.据说GoF的设计模式那本书写的很好,是一本经典,但是就是难懂, ...

  9. Linux命令中使用正则表达式

    在使用grep.awk和sed命令时,需要使用正则表达式.比如我通过grep找代码编译结果中是否有错误.或者是否有我代码的错误.这里说下正则表达式基本的应用: • 匹配行首与行尾.• 匹配数据集.• ...

  10. netbeans打包成jar

    文件页里找到build.xml文件,打开在</project>前 加入以下代码保存之 按 Ctrl+C 复制代码 <target name="package-for-sto ...