在Java,C#等面向对象的语言中,this的含义是明确且具体的,固定指向运行时的当前对象。而由于Javascript的动态性(边解释边执行),this的指向在运行时才能确定,跟定义在哪儿无关。
this总是返回一个对象,就是属性或方法 当前 所属的对象。一般来讲,就是“.”前的那个对象(个人认为这种说法是非常容易误导人的)
1
2
3
4
5
6
7
8
9
10
|
var
name =
'张三'
;
var
obj = {
name:
'李四'
,
introduce:
function
(){
console.log(
'my name is'
+
this
.name);
}
};
obj.introduce();
var
f1 = obj.introduce;
f1();
|
在这个例子中,obj.introduce() "."左边是obj,所以this指向obj这个对象。introduce方法里面的this.name沿着作用域链往上找就是“李四”;
我们都知道函数后面带括号表示执行,不带括号就是把函数指针赋值给变量,但不执行函数,所以f1其实就是指代 function(){console.log('my name is' + this.name);},不妨可以console出来验证一下。f1()是在全局window对象中,所以这里的f1()其实就是window.f1(),也就是说"f1()"打印输出的应该是全局变量“张三”
关于刚才提到的f1函数,我们可以进一步验证下
1
2
3
4
|
function
f1(){
return
this
;
};
console.log(f1() === window);
|
在这里,可以得出一个结论,如果一个函数运行在全局对象中,那么,this就是指向顶层对象,在浏览器中即为window对象。
基于前面的分析,我们把上例稍微改变一下
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
obj = {
name:
'李四'
,
describe:
function
() {
console.log(
'my name is'
+
this
.name);
}
};
var
obj2 = {
name:
'张三'
};
obj2.describe = obj.describe;
obj2.describe();
|
this指向obj对象,这点已经分析过了。这个例子中“obj2.describe = obj.describe;”说白了就是给obj2对象添加一个describe方法,this同样指向当前所在的对象,所以输入的就是“张三”,obj2对象实际变成了
1
2
3
4
5
6
|
obj2 = {
name:
'张三'
,
describe:
function
() {
console.log(
'my name is'
+
this
.name);
}
};
|
this的使用场合
1、全局环境 this--->指向全局对象 window
全局中的this就是顶层对象window
这个例子说明,不管this是不是在函数内部,只要是在全局环境下运行,this都指向window
这里需要稍微提示下,在严格模式下,全局中的this就不再是window对象了,而是undefined
2、构造函数 this--->指向新对象
构造函数中的this,指向生成的新对象
1
2
3
4
5
6
|
var
a = 3;
//全局变量a
function
Test(){
this
.a = 5;
//对象属性a
}
var
obj =
new
Test();
console.log(obj.a);
|
上面代码定义了一个构造函数Test,同时创建了Test的实例对象obj,我们知道通过new操作符创建构造函数的新实例对象,主要经历了4个步骤:
(1)、创建一个新对象
(2)、将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
(3)、执行构造函数中的代码(为这个新对象添加属性)
(4)、返回新对象
根据这4个步骤,obj对象其实就是
1
|
Test {a: 5}
|
所以,很显然obj.a打印出来的a就是5,而不是3了
再来看一个例子:
1
2
3
4
5
6
7
8
9
10
11
|
var
Obj =
function
(p) {
this
.p = p;
};
Obj.prototype.m =
function
() {
return
this
.p;
};
var
o =
new
Obj(
'Hello World!'
);
o.p;
o.m();
|
首先创建了一个构造函数Obj,给Obj的原型对象添加了一个方法m,m同样返回属性p,然后创建一个Obj的实例对象o,基于前面的分析,最终输出的就应该是两个'Hello World!'了
3、对象的方法 this--->指向当前这个对象
当A对象的方法被赋予给B对象,该方法中的this就会从指向A对象变成了指向B对象。所以需要特别注意,将某个对象的方法赋值给另一个对象,会改变this的指向
先来验证第一句话,作为对象的方法被调用,this指向当前这个对象
由此说明,obj.foo方法执行时,它内部的this指向了当前对象obj。先来看一个例子:
1
2
3
4
5
6
7
8
9
|
function
test(){
console.log(
this
.a);
}
var
a = 3;
//声明一个全局变量a
var
obj = {};
//创建一个对象obj
obj.a = 5;
//给obj添加一个属性a
obj.m = test;
//给obj添加一个方法m,并且把test指针指向了m
console.log(obj);
obj.m();
|
再来看一个例子:
1
2
3
4
5
6
7
8
9
10
|
var
name =
"sky"
;
var
obj = {
name:
"zt"
,
say:
function
(){
console.log(
"I am "
+
this
.name);
}
}
obj.say();
//I am zt
var
fn = obj.say;
fn();
//I am sky
|
obj.say()这个就不需要多说,在对象自己的方法里面,this指向当前的对象obj,所以是zt
把obj对象的say方法赋予给另一个fn函数,所以this会从obj指向fn,而fn运行在全局环境中,所以输出的全局变量sky
4、内部函数(匿名函数) 匿名函数的执行环境具有全局性,this对象通常指向window
1
2
3
4
5
6
7
8
9
10
|
var
name =
'Window'
;
var
obj = {
name:
'My Object'
,
getName:
function
(){
return
function
(){
return
this
.name;
}
}
};
console.log(obj.getName()());
//Window
|
按照上面的说法,似乎可以很快确认输出的是Window。我们不妨做下拆解,执行obj.getName(),得到的是一个匿名函数function(){return this.name;},此时,这个匿名函数运行在全局环境中,再次执行这个函数,里面的this自然就指向了全局Window
在定时器里面的this是否也是指向window呢?
1
2
3
4
5
6
7
8
9
10
|
var
name =
"sky"
;
var
obj = {
name:
"zt"
,
say:
function
(){
setTimeout(
function
(){
console.log(
"I am "
+
this
.name);
},3000);
}
};
obj.say();
|
可以看到,执行say方法,其实就是一个一次性定时器,3s之后执行function(){console.log("I am"+this.name);},可能按照我们前面的说法“.”前的对象是obj,所以this指向obj,这个没错,但是,这是say方法里的this,定时器的代码是在全局作用域window对象中执行的,所以这里面的this指向window,当然严格模式下是undefined,所以最终输出的是全局的sky而不是obj对象里面的zt
从上面的两个例子中可以看到,getName和say两个方法中,内层函数中的this没有按预想的指向外层函数对象,而是指向全局对象window,那如果我们需要指向外层函数对象,怎么办呢?这里就需要用到我们常见的“留住this”
1
2
3
4
5
6
7
8
9
10
11
|
var
name =
"sky"
;
var
obj = {
name:
"zt"
,
say:
function
(){
var
that =
this
;
//把this保存到一个变量中
setTimeout(
function
(){
console.log(
"I am "
+that.name);
},3000);
}
};
obj.say();
//I am zt
|
1
2
3
4
5
6
7
8
9
10
11
|
var
name =
'Window'
;
var
obj = {
name:
'My Object'
,
getName:
function
(){
var
me =
this
;
//this指向obj对象,我们把这个this放到一个变量中保存起来
return
function
(){
return
me.name;
//成功留住了this
}
}
};
console.log(obj.getName()());
//My Object
|
最后,再来看一个经典的面试题
1
2
3
4
5
6
7
8
9
10
11
12
|
var
x=5,o={
x:10,
doit:
function
doit(){
var
x=20;
setTimeout(
function
(){
console.log(
this
.x);
//5
}, 10);
}
};
console.log(o.doit());
//undfined
(
function
(){console.log(
this
.x)})();
//5
|
参考上面的第4条,可以很容易得出结论 匿名函数 “function(){console.log(this.x)}”运行在全局window中,所以输出的两个5没有问题,关键是为什么o.doit()打印出来是undefined,原因很简单,doit方法没有return任何东西,它是没有返回值的。那如果给它加一个返回值呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
x=5,o={
x:10,
doit:
function
doit(){
var
x=20;
setTimeout(
function
(){
console.log(
this
.x);
//5
}, 10);
return
this
.x;
//用在对象的方法中,this指向当前的对象o
}
};
console.log(o.doit());
//10 返回的是"return this.x;"
(
function
(){console.log(
this
.x)})();
//5
|
本文转自 frwupeng517 51CTO博客,原文链接:http://blog.51cto.com/dapengtalk/1869458