参考书籍:《JavaScript设计模式与开发实践》;
《JavaScript高级程序设计(第3版)》(以下简称《高级程序设计》)
JS的this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被某个对象的方法调用时,this等于那个对象。
除去不常用的with和eval的情况,具体到实际应用中 ,this的指向大致可以分为以下4种(其中还包含一些特殊情况需要被提及)。
1、作为对象的方法调用
//作为对象调用时,this指向这个对象
var obj = {
getA : function(){
alert ( this === obj );
}
}
obj.getA(); // true
2、作为普通函数调用
当函数不作为对象的属性调用时,也就是常说的用普通函数方式调用,此时的this总是指向全局对象。在浏览器中,这个全局对象是window对象。
window.name = 'globalName';
var getName = function(){
return this.name;
}
console.log( getName() ); // globalName
上面这个例子直接创建了一个函数并在全局调用,事实上,还可以将对象中的函数赋值给全局中的函数,请看如下代码:
//与上一段代码效果相同
window.name = 'globalName';
var obj = {
name : 'sven',
getName : function(){
return this.name;
}
}
var getName = obj.getName;
console.log( getName() ); // globalName
神奇的是,如果将对象中的函数赋值给对象中的函数,this还是会指向全局:
var name = "globalName";
var obj = {
name: "sven",
getName: function() {
return this.name;
}
};
console.log((obj.getName = obj.getName)()); // globalName (非严格模式下)
这是在《高级程序设计》第183页的一个例子。书本中的解释为:(函数调用时的)这行代码先执行了一条赋值语句,然后再调用赋值后的结果。因为这个赋值表达式的值是函数本身,所以this的值不能得到维持,结果就返回了"globalName"。要详细解释这个问题,网上有一篇文章可以参考:《 this在方法赋值过程中无法保持(隐式丢失)》
简单点想,以上这些都可看作普通函数在全局中调用,this自然是指向全局的。这也充分说明了之前提到的:JS的this对象是在运行时基于函数的执行环境绑定的,与函数声明时的环境无关。
除此之外,常常会出现函数内部嵌套函数的情况,匿名函数作为内部函数很常见。
关于匿名函数,《高级程序设计》中说,“匿名函数的执行环境具有全局性,因此其this对象通常指向window”。然而经过测试发现,函数内部的非匿名函数也是指向window的:
var name ="Window";
var obj={};
obj.Outer = function(){
function Inner(){
console.log(this.name); //"Window" (非严格模式);"undefined"(严格模式)
}
Inner();
}
obj.Outer();
内部函数在搜索this时,只会搜索到其活动对象为止,而其活动对象位于作用域链的前端,因此永远不能直接访问外部函数中的this,而是将它指向了全局对象。若是内部函数需要访问外部this,通常会这样处理:
var name ="Window";
var obj={
name : "outerName"
};
obj.Outer = function(){
var that = this;
function Inner(){
console.log(that.name); // outerName
}
Inner();
}
obj.Outer();
3、构造器调用
在js中,可以使用new运算符调用函数,此时的函数被当作一个构造函数,而构造器中的this就指向返回的对象:
var MyClass = function(){
this.name = 'sven';
}
var obj = new MyClass();
console.log( obj.name ); // 输出:sven
如果构造器显式地返回一个object类型对象,则运算结果为最终返回的对象:
var MyClass = function(){
this.name = 'sven';
return {
name: 'anne'
}
}
var obj = new MyClass();
console.log( obj.name ); // 输出:anne
若是返回非对象类型的数据,就不会造成上述问题:
var MyClass = function(){
this.name = 'sven' ;
return 'anne' ;
}
var obj = new MyClass();
console.log( obj.name ); // 输出:sven
4、call、apply调用
call、apply可以动态改变传入函数的this:
var obj1 = {
name: 'sven',
getName: function(){
return this.name;
}
};
var obj2 = {
name: 'anne'
}
console.log( obj1.getName.call(obj2) ); // 输出:anne
call、apply这两个函数很强大,它扩充了函数赖以运行的作用域,实现了对象和方法的解耦。call 和 apply 方法能很好地体现 JavsScript 的函数式语言特性,在 JavsScript 中,几乎每一次编写函数式语言语言风格的代码都离不开call和apply。常用的js原型继承模式,以及js诸多版本的设计模式中,都用到了它们。