上节,我们提到了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. 1.9 基础知识——GP2.10 高级别的领导检查(Higher level management)

    GP2.10 Review the activities,status,and results of XXX process with highter level management and res ...

  2. 敏捷软件开发(4)--- TEMPLATE METHOD & STRATEGY 模式

    1.TEMPLATE METHOD 泛型,也就是这个模式,是可以基于泛型的. 我们往往会有一些算法,比如排序算法.它的算法部分,我可以把它放在一个基类里面,这样具体类型的比较可以放在子类里面. 看如下 ...

  3. 6、软件配置工程师要阅读的书籍 - IT软件人员书籍系列文章

    软件配置管理工程师的工作也是贯穿整个项目过程的.其主要针对项目中的各种文档.技术源码等等进行归档控制.一般的配置项比如需求说明书,概要设计,详细设计,测试文档,用户手册等,还有源代码管理,数据库文档文 ...

  4. My97DatePicker时间控件使用

    刚刚工作中遇到一个修改时间空间的bug,顺带学习了My97DatePicker时间空间 网上查到的资料已经很详细: http://www.360doc.com/content/14/0606/11/1 ...

  5. CSS3 background-size属性

    请复制粘贴,图片请自带 <!DOCTYPE html > <html > <head> <meta charset="utf-8"> ...

  6. Linux Swap交换分区介绍总结

    Swap交换分区概念   什么是Linux swap space呢?我们先来看看下面两段关于Linux swap space的英文介绍资料: Linux divides its physical RA ...

  7. 由一个订单推送想到了ObservableCollection的神奇用法

    最近在做taobao的一个卖家应用,需要订阅taobao的订单推送,示例代码如下: 看到上面的OnMessage场景之后,我突然就鬼使神差的在想最近写的一个服务,其中的一个功能是需要定时的轮询一个集合 ...

  8. Sql Server之旅——第三站 解惑那些背了多年聚集索引的人

    说到聚集索引,我想每个码农都明白,但是也有很多像我这样的猥程序员,只能用死记硬背来解决这个问题,什么表中只能建一个聚集索引, 然后又扯到了目录查找来帮助读者记忆....问题就在这里,我们不是学文科,, ...

  9. SQL Server 分页方法汇总

    PageSize = 30 PageNumber = 201 方法一:(最常用的分页代码, top / not in) UserId UserId from UserInfo order by Use ...

  10. (原创)大数据时代:基于微软案例数据库数据挖掘知识点总结(Microsoft 决策树分析算法)

    随着大数据时代的到来,数据挖掘的重要性就变得显而易见,几种作为最低层的简单的数据挖掘算法,现在利用微软数据案例库做一个简要总结. 应用场景介绍 其实数据挖掘应用的场景无处不在,很多的环境都会应用到数据 ...