今天上班时候突然接到了面试电话,然后在环境很不好的情况下被问到了this问题,之前以为自己理解的还可以,但是当面对真正的考验的时候还是表现的不尽如人意。于是今天下班回家,痛定思痛,重新复习一下javascript中非常重要的基础概念——关键字this
。
What is this
?
简单说this就是函数当前的运行环境,但是难点就在于运行环境是可以变的。 例如下面一个例题:
1
2
3
4
5
6
7
8
9
10
11
12var name = "Bob";
var nameObj ={
name : "Tom",
showName : function(){
alert(this.name);
},
waitShowName : function(){
setTimeout(this.showName, 1000);
}
};
nameObj.waitShowName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14var subway={
name:'1号线',
speed:0,
run:function(speed){
this.speed=speed; //绑定到对象本身
function test(speed){
this.speed=speed+50;//竟然绑定到全局变量了,真是匪夷所思啊
}
test(speed);
}
};
subway.run(100);
console.log(subway.speed);//100
console.log(speed);//150
先把这两道题放着,最后再来分析。
In JavaScript, as in most object-oriented programming languages,
this
is a special keyword that is used within methods to refer to the object on which a method is being invoked. Thethis
keyword is relative to the execution context, not the declaration context.
this
指向函数执行时的当前对象(或理解为调用函数的对象)。该关键字在Javascript中和执行环境,而非声明环境有关。
对this
的定义相当简单,但是我们要知道的是如何确定是那个对象调用了函数,在不同的运行环境下,this
到底指向谁?
Where is this
?
Understanding JavaScript’s this keyword
通过Twitter的Angus Croll的博客,我们来梳理下this在不同上下文中的指向。
1. Global context
this
指向全局对象(浏览器是window
,nodejs是global
)
1
alert(this); //window
2. Function context
至少有4种情况通过函数应用改变this的指向
a) 作为方法调用
方法和函数的区别就是,前者能找到一个明确的归属(对象),而后者是全局的
this
指向调用者
1
2
3
4
5
6
7
8
9
10
11
12var a = {
b: function() {
return this;
}
};
a.b(); //a;
a['b'](); //a;
var c = {};
c.d = a.b;
c.d(); //c
b) 作为没有归属的方法调用
this
is the global object (or `undefined` in strict mode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var a = {
b: function() {
return this;
}
};
var foo = a.b;
foo(); //window
var a = {
b: function() {
var c = function() {
return this;
};
return c();
}
};
a.b(); //window
a.b()
的例子不太好理解,因为当我们执行a.b()的时候,返回内容是c方法的执行结果,但是c并不是谁的属性,他是没有归属的,因此c的this表示全局环境。
1
2
3
4
5
6
7var a = {
b: function() {
return (function() {return this;})();
}
};
a.b(); //window
自执行函数的调用者都是全局环境——window
c) 通过Function.prototype.call, Function.prototype.apply调用
call, apply函数可以将this
指向第一个参数
1
2
3
4
5
6
7
8
9
10var a = {
b: function(x1, x2, x3) {
return this;
}
};
var d = {};
a.b.call(d, x1, x2, x3); //d
a.b.apply(d, [x1, x2, x3]); //d
d) 通过new调用一个构造函数
this
指向新建对象
1
2
3
4
5var A = function() {
this.toString = function(){return "I'm an A"};
};
new A(); //"I'm an A"
3. Evaluation context
eval表达式中的this
和eval执行环境下的this
指向相同
1
2
3
4
5
6
7
8
9alert(eval('this==window')); //true - (except firebug, see above)
var a = {
b: function() {
eval('alert(this==a)');
}
};
a.b(); //true;
总结
以上各种情况其实都满足this指向函数执行时的当前对象这句话。无论是全局环境还是对象内部调用,我们只要判断在程序执行上下文中,是谁让代码执行的。
比如,在Global context下,函数执行时没有明显的调用对象,那肯定就是window调用的,因为函数的执行离不开context,及一个调用者的作用域。
有些在语句块内执行的函数,虽然我们可以发现它存在于对象内部,看起来就是那个对象调用了它。但是,仔细分析,就发现自执行函数,和代码块内部定义了函数再执行,跟包裹它的对象毫无关系。最简单的方法就是,把这个可以的代码段从这个对象体内拿出来,如果他仍然能执行,证明他就是被global context调用的函数。
apply和call方法其实就是借用了第一个参数的执行环境,执行一个未被这个对象定义过的函数。仔细想想,就会发现,这两个方法就是干着借窝生蛋的勾当,而执行上下文仍然在那个窝里。
最后eval实际上和apply,call没什么两样,也是借窝生蛋,窝还是原来的窝,只不过功能更强大了,不仅可以生蛋,还可以做任何事情。