javascript this

http://www.cnblogs.com/Uncle-Keith/p/5814578.html

http://www.cnblogs.com/Uncle-Keith/p/5776159.html

这次的分享,主要还是想跟大家聊聊Javascript语言中很重要的概念之一,对象。为什么说之一呢?因为Javascript其他重要概念还包括:作用域作用域链继承闭包函数继承数组......有机会会跟大家分享这些概念的。

以下的介绍会分为如下:

1:前言

2:概述

2.1:对象创建

2.2:对象键名与键值

2.3:对象属性

2.4:对象引用

3:对象属性

3.1:读取属性

3.2:属性赋值

3.3:查看所有属性

3.4:删除属性

3.5:遍历属性

1:前言

Javascript拥有七种数据类型,其中分为两类。原始数据和复杂数据类型。原始数据类型:包括字符串,数组,数字,布尔值,Null,Undefined。复杂数据类型:包括对象。

对象是Javascript的核心概念,也是最重要的数据类型。Javascript所有数据都可以视为对象。都可以使用内置的方法。但是除了两个例外,就是Null和Undefined。

代码如下:

1[1,2,3].toString()//"1,2,3"

2"ha".toString()//"ha"

3false.toString()//'false'

一个对象就是一系列属性的集合,一个属性包含一个名字和一个值。一个属性也可以是函数,这种情况下这个属性被称为方法,可以拿汽车来做一个形象化的对比。

汽车是一个对象,拥有属性颜色,重量,车型,方法便是可以开上马路了。

写在代码中,就是:

复制代码

1varcar={

2"color":"red",

3"weight":"1.4吨",

4"module":"SUV"

5"use":function(){

6return"drive";

7}

8};

复制代码

2:概述

2.1:对象创建

1varo={

2"p":"HelloWorld",

3"name":"KeithChou"

4};

上面的代码中,大括号就定义了一个对象,它被赋值给全局变量o。这个对象(对象的名字随便定义)内部包含一个键值对,p是键名,字符串HelloWorld是键值。键名和键值用":"分离,不同键值对之前通过","分离,最后一个键值可不用逗号。但是我建议最后都不要加上。右花括号"}"后边要加上分号";"。

对象创建有三种方法:

1varo1={};

2varo2=newObject();

3varo3=Object.crete(null);

上面三行语句都是等价的。第一种使用花括号来创建一个对象,这是最简便的方法。第二种采用构造函数的写法清晰的表明了意图。第三种写法多用于对象继承的场合。

2.2键名与键值

复制代码

1varo={​

2"say":"HelloWorld",

3​"name":"KeithChou",

4​"height":"180",

5​"age":21

6};

复制代码

以上用第一种方法创建对象,并赋值给全局变量o。键名即如下值:"say","name","height","weight"。键值即如下值:"HelloWorld","KeithChou"...

对象的所有键名都是字符串,加不加双引号都可以。对象的键值如果是英文,则必须加引号,否则会当成变量处理。如果是数字,加了引号则当成字符串处理,不加引号当成数字类型处理。

复制代码

1varo={​

2o1:180,

3​o2:"180"

4};

5o.o1//180,数字类型

6o.o2//"180",字符串类型

复制代码

注意,Javascript的保留字可以不加引号当作键名。

复制代码

1varo={​

2for:1,

3​class:2

4};

5

6o.for//1

7o.class//2

复制代码

2.3对象属性

对象的每一个“键名”又称为“属性”(property),它的“键值”又称为属性值,可以是任何数据类型。如果一个属性值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。

复制代码

1varo={​

2p:function(x){​​

3return2*x;​

4}

5};

6o.p(2)//4

复制代码

上面对象申明了一个方法p,它就是一个函数。这里简单说一下,函数声明的三个步骤:function,函数名,函数参数,函数体。其中函数名和函数参数都是可选的,即也可以不使用。不使用的情况下称为匿名函数。

属性可以动态创建,不必在对象声明的时候就指定。

1varobj={};

2obj.keith=123;

3obj.rascal=false;

4obj.keith;//123

因为对象的方法就是函数,所以也有name属性。

复制代码

1varobj={​

2m1:functionf(){},

3​m2:function(){}

4};

5obj.m1.name//"f"

6obj.m2.name//"",空字符串

复制代码

2.4对象引用

对于复杂数据类型,如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。也就是传址传递。

复制代码

varo1={​a:1};

varo2=o1;

o1.a;//1

o2.a;//1

o2.a=11;

o1.a//11

复制代码

上面o1和o2都指向了同一个对象,当o2修改了o1的属性a时,访问o1的属性a变成11。复杂数据类型的传址传递的特点就是当修改其中一个变量,会影响到其他对象对该变量的访问。

此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。

1varo1={};

2varo2=o1;

3varo1=1;

4o2//{}

上面代码中,o1和o2指向同一个对象,然后o1的值变为1,这时不会对o2产生影响,o2还是指向原来的那个对象。

但是,对于原始数据类型的值,则是传值传递。传值传递的特点就是修改其中一个变量,不会影响到其他变量。也就是只是对该变量copy一份而已。

1varx=1;

2vary=x;

3x=2;

4y;//1

上面的代码中,全局变量x发生变化,y的值并不变。也就是上面所说的copy了x的值,没有指向同一个内存地址。

3:对象属性

3.1:读取属性

1varo={

2name:"KeithChou",

3born:"1995"

4};

访问属性有两种方法。

1o.name//"KeithChou"

2o["name"]//"KeithChou"

第二种方法访问属性的时候必须加上引号,单引号双引号都可以。

3.2:属性的赋值

点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

1varo={};

2o.p="abc";

3o["p"]="abc";

3.3:查看所有属性

查看一个对象本身的所有属性,可以使用Object.keys方法。

复制代码

1varo={

2"say":"HelloWorld",

3"name":"KeithChou",

4"height":"180",

5"weight":"120"

6};

7Object.keys(o);

8//["say","name","height","weight"]

复制代码

可以看出,js返回一个数组对象。

3.4:删除属性

Javascript使用delete来删除属性。delete操作符只能用来删除对象本身的属性,而不能用于删除继承自原型的属性。

复制代码

1varo={p:1};

2Object.keys(o);

3//["p"]

4deleteo.p;

5o.p;

6//undefined;

7Object.keys(o);

8//[]

复制代码

delete删除了o对象的p属性。删除后,再次访问属性就会返回undefined。而且使用Object.keys访问对象属性时,o对象也不再包括任何属性。

toString()方法是对象o继承自原型Object.prototype的一个方法,虽然delete命令返回true,但该属性并没有被删除。

1varo={};

2deleteo.toString();//true

3o.toString();

注意,delele也可以用于删除一个不存在的属性,不过也不会报错,会返回true。不过这个看起来好像没有什么用处阿。

1varo={};

2deleteo.p//true

最后,delete操作符也不能用于删除var命令声明的变量,只能用来删除对象本身的属性。

1varo=1;

2deleteo;//false

3deletewindow.o//false

上面代码中,o是在全局作用域下声明的一个全局变量,全局变量默认是window对象的属性,默认情况下delete不得删除。

3.5:遍历属性

for...in循环用来遍历一个对象的所有属性。

复制代码

1varo={​

2a:1,

3​b:2,

4​c:3

5};​

6for(variino){​

7console.log(i);

8}//a,b,c

复制代码

上面代码中,定义了一个对象o,使用for..in循环来在控制台输出o对象中的每一个属性。variino是指声明一个变量i,用于遍历o对象中的所有属性。

for...in循环有两个使用注意点:

它遍历的是所有对象可遍历的属性,会跳过不可遍历的属性。

它不仅会遍历对象自身的属性,还会遍历继承的属性。

不管学习什么知识,习惯于把自己所学习的知识列成一个list,会有助于我们理清思路,是一个很好的学习方法。强烈推荐。

以下篇幅有点长,希望读者耐心阅读。

以下内容会分为如下部分:

1.涵义

1.1:this涵义

1.2:this指向的可变性

2.使用场合

2.1:全局环境

2.2:构造函数

2.3:对象的方法

3.使用注意点

3.1:避免多层嵌套this

3.2:避免数组处理方法中的this

3.3:避免回调函数中的this

1.涵义

1.1:this涵义

在我写的一篇关于构造函数与new关键字的关系的博文中谈及,new关键字总是会返回一个对象。这个对象可以是new调用构造函数时返回的空对象,也可以是在构造函数中使用return语句返回的复杂数据类型(包括对象,数组等)。

同样,与new关键字相同,this关键字总是返回一个对象。再说的详细一些,就是属性或方法“当前”所在的对象。

复制代码

1varKeith={

2firstName:'Chou',

3describe:function(){

4returnthis.firstName;

5}

6};

7

8console.log(Keith.describe());//'Chou'

复制代码

上面代码中,this.firstName表示describe方法当前所在对象的firstName属性。也就是说,在全局作用域下调用describe方法时,describe方法所在的当前对象是Keith,所以就是调用Keith.firstName。

1.2:this指向的可变性

由于对象的属性可以赋给另外一个对象,所以属性所在的当前对象是可变的。也就是说,this的指向是可变的。

按Ctrl+C复制代码

varKeith={

firstName:'Chou',

describe:function(){

returnthis.firstName;

}

};

varRascal={

firstName:'King'

}

Rascal.describe=Keith.describe;

console.log(Rascal.describe());//'King'

按Ctrl+C复制代码

上面代码中,Keith对象中的describe属性赋给Rascal,于是describe方法中当前所在的对象就是Rascal,所以this.firstName就指向Rascal。因为是传址传递,所以修改firstName会对原对象有影响。如有疑问,请移步这篇文章。这个例子可能不便于理解,再看看下面这个例子。

复制代码

1functionf(){

2returnthis.firstName;

3}

4

5varKeith={

6firstName:'Chou',

7describe:f

8};

9

10varRascal={

11firstName:'King',

12describe:f

13}

14

15console.log(Keith.describe());//'Chou'

16console.log(Rascal.describe());//'King'

复制代码

上面代码中,把方法移到全局作用域下,函数f内部使用了this关键字。随着f所在的对象不同,this指向也就不同。

在全局作用域下this关键字会指向顶层对象(也就是window对象)。

复制代码

1varname='keith';

2functionperson(){

3varname='rascal';

4returnthis.name;

5}

6

7console.log(person());//'keith'

复制代码

上面代码中,返回的是keith而不是rascal。原因在于this指向的是全局作用域。在全局作用域中定义一个函数,默认是指向window对象,而不是函数本身。但是,如果在函数内部不使用var来声明一个局部变量,那结果也会不同。

复制代码

1varname='keith';

2functionperson(){

3name='rascal';

4returnthis.name;

5}

6

7console.log(person());//'rascal'

复制代码

上面代码中,在函数内部没有使用var来声明一个局部变量,那么此时函数内部的name属性不是局部变量,而是全局变量。所以会覆盖掉前面的name属性。如果对于局部变量和全局变量不了解,可以访问这篇文章。 

只要函数被赋给另外一个变量,this的指向会发生改变。

复制代码

1varKeith={

2name:'keith',

3describe:function(){

4returnthis.name;

5}

6}

7

8varname='rascal';

9varf=Keith.describe;

10console.log(f())//'rascal'

复制代码

上面代码中,返回的是rascal,而不是keith。因为Keith.describe被赋值给了f变量,而全局作用域下有一个name变量,所以Keith内部的函数的this指向就会指向f运行时所在的对象(顶层对象,也就是window对象)

总结一下:

1.javascript语言中,一切皆为对象(除了undefined和null之外),运行环境也是对象,所以函数都是在某个对象之中运行,this就是这个对象(环境)。

2.this的指向是动态的。如果函数在全局作用域中,那么this就会指向全局环境;如果函数位于某个对象中,那么this就会指向该对象。

2.使用场合

this的使用场合可以分为以下几个场合。

2.1:全局环境(全局作用域)

在全局作用域中使用this对象,它指向的就是顶层对象,也就是window对象。

1functionkeith(){

2return(this===window)

3}

4

5console.log(keith())//true

上面代码中,不管是不是在函数内部,只要在全局作用域下运行,this就是指向顶层对象window。

2.2:构造函数

构造函数中的this,指向的是将要创建的对象实例。

复制代码

1functionKeith(){

2this.sex='boy';

3}

4

5varperson=newKeith();

6console.log(person.sex);//'boy'

复制代码

上面代码中,在全局作用域下定义了Keith构造函数,然后调用构造函数并赋值给person对象实例。

构造函数创建的三个基本要求:函数名首字母大写;构造函数内部使用this关键字来指向即将生成的对象实例;使用new关键字来调用构造函数并返回对象实例。

如果想更进一步深入了解构造函数与new关键字的关系,请移步至这篇文章。

2.3:对象的方法

当A对象的方法被赋予B对象,该方法中的this就从指向A对象变成指向B对象。所以要特别小心,将某个对象的方法赋值个另外一个对象时,会改变this的指向。

复制代码

1varkeith={

2sex:'boy',

3foo:function(){

4returnthis.sex;

5}

6};

7varrascal={

8sex:'girl'

9};

10rascal.foo=keith.foo;

11console.log(keith.foo());//'boy'

12console.log(rascal.foo());//'girl'

复制代码

上面代码中,把keith的foo函数赋值给了rascal,那么this的指向就从keith变成了rascal。这里涉及对象引用的知识,如有疑问,请移步至这篇文章。

如果某个方法位于多层对象的内部,这时为了简化书写,把该方法赋值给一个变量,往往会得到不一样的结果。

复制代码

1vara={

2b:{

3p:'keith',

4c:function(){

5returnthis.p;

6}

7}

8};

9

10varperson=a.b.c;

11console.log(person());//undefined

复制代码

上面代码中,c是两层对象里面的一个方法。为求简便,将其赋值给全局变量person,结果调用时,this指向了顶层对象window。而在window中变量p默认值为undefined。

要解决这个问题,可以只将c所在的对象赋值给person变量,或者是直接调用。

1varperson=a.b;

2console.log(person.c());//'keith'

3console.log(a.b.c());//'keith'

3.使用注意点

3.1:避免多层嵌套this

当在闭包中使用多层this,则this都会指向window。

复制代码

1functionkeith(){

2console.log(this);

3returnfunction(){

4returnthis;

5}

6}

7keith();//window

8keith()();//window

复制代码

上面代码中,在一个函数中返回另外一个匿名函数是闭包的特点之一,可以看出,当在闭包中使用this对象都会指向全局作用域中的window对象。

如果在函数外包含一个对象,则内部this指向全局作用域,而外部this对象指向当前作用域。

复制代码

1varo={

2f1:function(){

3console.log(this);

4(function(){

5console.log(this)

6})();

7}

8};

9

10o.f1();//Object,Window

复制代码

上面代码包含两层this,结果运行后,第一层指向当前对象,第二层指向全局对象。

实际执行的是如下代码。

复制代码

1functionkeith(){

2console.log(this);

3}

4varo={

5f1:function(){

6console.log(this);

7varf2=keith();

8}

9};

10

11o.f1();//Object,Window

复制代码

要实现多层this嵌套,有两种解决方法:

一是在第二层中改用一个指向外层this的变量。

复制代码

1varo={

2f1:function(){

3console.log(this);

4varthat=this;

5(function(){

6console.log(that);

7})();

8}

9};

10

11o.f1();//Object,Object

复制代码

上面代码中,定义了局部变量that,固定指向了外层的this,然后在内层中使用that,就不会发生this指向的改变。但是如果函数外部内有嵌套一个对象,this还是会指向全局。

二是Javascript中的严格模式。在严格模式下,如果内部函数的this指向了window对象,就会报错。

复制代码

1vara={

2count:0,

3fun:function(){

4'usestrict';

5returnthis.count++;

6}

7}

8

9varf=a.fun;

10console.log(f())//'TypeError:thisisundefined'

复制代码

上面代码中,fun方法使用严格模式声明。把a对象中的fun方法赋值给全局变量f,那么this此时指向window对象,在严格模式下,就会报错。如果函数外部没有嵌套一个对象,那么不会报错,而是会返回undefined。

3.2:避免数组处理方法中的this

数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。

复制代码

1varkeith={

2a:'Hello',

3b:['b1','b2'],

4c:function(){

5this.b.forEach(function(item){

6console.log(this.a+''+item);

7})

8}

9};

10keith.c();

11//undefinedb1

12//undefinedb2

复制代码

上面代码中,forEach方法的回调函数中的this,其实指向的是window对象,因此取不到keith.a的值,同上也属于避免多层嵌套this。也就是说,内层的this不指向外部函数,而是指向顶层对象。

要解决这个方法,可以使用that变量来代替回调函数中的this。

复制代码

1varkeith={

2a:'Hello',

3b:['b1','b2'],

4c:function(){

5varthat=this;

6this.b.forEach(function(item){

7console.log(that.a+''+item);

8})

9}

10};

11keith.c();

12//Hellob1

13//Hellob2

复制代码

另外一种方法,就是让this做为forEach方法的第二个参数,来固定它的运行环境。

复制代码

1varkeith={

2a:'Hello',

3b:['b1','b2'],

4c:function(){

5this.b.forEach(function(item){

6console.log(this.a+''+item);

7},this)

8}

9};

10keith.c();

11//Hellob1

12//Hellob2

复制代码

3.3:避免回调函数中的this

回调函数中的this往往会改变指向。

复制代码

1varo={

2f:function(){

3console.log(this===o);

4}

5};

6

7o.f();//true;

复制代码

上面代码中,调用o对象的f方法,返回true。

但是,如果将f方法指定给某个按钮的click事件,this的指向就变了。

1$('button').on('click',o.f);

上面代码中,使用了jquery方法来获取button元素,并绑定click事件。点击按钮后控制台会显示false。原因是此时this不再指向o对象了,而是指向按钮的DOM对象,因为f方法是在按钮对象的环境中被调用的。

总结一下:

a:如果想要多层嵌套this关键字,最常用的解决方法就是使用that变量,固定指向外层的this,然后在内层中使用that变量。就不会发生内层this指向全局的问题。

b:如果在回调函数中使用this关键字,注意this的指向问题。