上节,我们提到了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. 在【Xamarin+Prism开发详解三:Visual studio 2017 RC初体验】中分享了Visual studio 2017RC的大致情况,同时也发现大家对新的Visual Studio很是感兴趣。于是发时间深入研究了一下Visual Studio 2017RC 是不是和微软Connect()://2016上说得一样神。

    总共列出了12点,耐心点慢慢看! 1,添加了不少[代码样式]的设置项目. 通过合理的设置每个人都能写出优美的代码,而且团队项目也可以达到统一代码风格. this首选项:可以设置[字段,属性,方法,事件 ...

  2. 一个简单的Java web服务器实现

    前言 一个简单的Java web服务器实现,比较简单,基于java.net.Socket和java.net.ServerSocket实现: 程序执行步骤 创建一个ServerSocket对象: 调用S ...

  3. Python标准库(1) — itertools模块

    简介 官方描述:Functional tools for creating and using iterators.即用于创建高效迭代器的函数. itertools.chain(*iterable) ...

  4. 烂泥:ubuntu下vsftpd虚拟用户配置

    本文由ilanniweb提供友情赞助,首发于烂泥行天下 想要获得更多的文章,可以关注我微信ilanniweb. 以前搭建vsftpd都是在centos下,本以为在ubuntu按照以前的步骤搭建即可.可 ...

  5. 烂泥:CentOS命令学习之scp复制

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 由于工作需要,需要把服务器A上的文件弄一份到服务器B上.自己比较懒不打算搭建FTP.Samba服务器,所以就打算使用scp命令,scp命令是通过ssh协 ...

  6. 从本地向 Github 上传项目步骤攻略(快速上手版)

    最近想把之前自己做的一些好玩的项目共享到Github,网上找了一圈上传教程,都感觉写的太深奥.复杂,云里雾里,特把自己的方法纪录如下: PS:这种方式一般适用于:开始做项目时,没有直接在github上 ...

  7. android 设置布局横屏竖屏

    只要在AndroidManifest.xml里面配置一下就可以了.在AndroidManifest.xml的activity(需要禁止转向的activity)配置中加入android:screenOr ...

  8. Fragment中添加ListView而不使用ListFragment

    最初的构想是,将Fragment和ViewPager结合起来, 然后突发奇想,在第一个Fragment里添加了ListView, 依照网上的建议,extends了ListFragment,接着各种报错 ...

  9. Java报表FineReport在医院院长查询分析系统中有什么用

    1.医院院长查询系统的价值 目前,大中型医院的信息处理正从传统手工方式飞速向电脑信息化建设方案转变,一个大中型医院担负着繁重的医疗和科研任务,以及繁杂的事务性工作,院长必须时刻与各科室保持密切的连续, ...

  10. [转]MVC3缓存之一:使用页面缓存

    本文转自:http://www.cnblogs.com/parry/archive/2011/03/19/OutputCache_In_MVC3.html 在以前的WebForm的开发中,在页面的头部 ...