跟别的语言大相径庭的是:js的this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时候的环境。
除去不常用的with和eval的情况,具体到实际应用中,this的指向大致可以分为以下四种:
- 1.作为对象的方法调用。
- 2.作为普通函数调用。
- 3.构造器调用。
- 4.Function.prototype.call 或者 Function.prototype.apply
下面分为这四种情况分别进行调用:
1.作为对象的方法调用:
当函数作为对象的方法被调用的时候,this指向该对象:
var obj = {
a: 1,
getA: function () {
console.log(this === obj); // true
console.log(this.a); // 1;
}
}
obj.getA();
2.作为普通函数被调用:
当函数不作为对象的属性被调用时候,也就是我们所说的普通函数方式,此时的this总是指向全局的对象。在浏览器的js里面,这个全局对象是window对象。
// 创建全局的name对象 挂载在window上面
window.name = "globalName";
var getName = function () {
return this.name;
}
console.log(getName()); // 输出的是 globalName
或者
window.name = "globalName";
var myObject = {
name: "louis",
getName: function () {
return this.name;
}
}
var getName = myObject.getName;
console.log(getName()); // "globalName"
有时候我们会遇到一些困扰,比如在事件节点的div函数内部,有一个局部的callback方法,callback被作为普通的函数被调用时,callback内部的this指向了window,但我们往往想让它的指向div节点.
我是一个div
window.id = "window";
document.getElementById('div1').onclick = function () {
alert(this.id); // 输出:'div1'
var callback = function () {
alert(this.id); // 输出:'window'
}
callback();
};
此时有一种简单的解决方案,可以用一个变量保存div节点的引用:
document.getElementById('div1').onclick = function () {
var that = this; // 保存div的引用
var callback = function () {
alert(that.id); // 输出:'div1'
}
callback();
}
在ECMAScript 2015 中的严格模式下,这种情况下的this指向已经被规定为不会指向全局对象,而是undefined:
function func() {
"use strict"
alert(this); // undefined
}
func();
3.构造器的调用:
js中没有类,但是可以从构造器中创建对象,同时也提供了new运算符,使得构造器看起来像是一个类,
除了宿主提供的一些内置函数,大部分js函数都可以当成构造器使用,构造器的外表看起来和普通的函数没有什么区别,他们的区别在于调用方式,当使用new运算符调用函数的时候,该函数总是返回一个对象,通常情况下,构造器里面的this就是指向返回的这个对象。
var MyClass = function () {
this.name = "louis";
}
var obj = new MyClass();
console.log(obj.name);
但是new调用构造器时候,还要注意一个问题,如果构造器显式的返回了一个object对象那么此次运算结果最终会返回这个对象,而不是我们之前期待的this:
var MyClass = function () {
this.name = 'sven';
return { // 显式地返回一个对象
name: 'anne'
}
};
var obj = new MyClass();
alert(obj.name); // 输出:anne”
如果构造器不显式的返回任何数据,或是返回一个非对象类型的数据,就不会造成上述问题。
var MyClass = function () {
this.name = 'sven'
return 'anne'; // 返回string类型
};
var obj = new MyClass();
alert(obj.name); // 输出:sven”
4.Function.prototype.call 或者 Function.prototype.apply调用
跟普通函数调用相比,用 Function.prototype.call 或者 Function.prototype.apply可以动态的改变传入函数的this:
var obj1 = {
name: "louis",
getName: function () {
return this.name;
}
}
var obj2 = {
name: "kerry";
}
console.log(obj1.getName()); // 输出: louis
console.log(obj1.getName.call(obj2)); // 输出: kerry
call 和 apply 方法能够很好的体现 js的函数式语言特性 在js中几乎每一次编写函数式语言风格的代码都离不开call和apply
5.丢失的this
下面看一个经常遇到的问题:
var obj = {
myName: "louis",
getName: function () {
return this.name;
}
}
console.log(obj.getName()); // louis;
var getName2 = obj.getName;
console.log(getName2()) // undefined
当调用obj.getName时,getName方法是作为obj对象的属性被调用的,根据上文提到的规律,此时的this指向obj对象,所以obj.getName()输出'louis'。
当用另外一个变量getName2来引用obj.getName,并且调用getName2时,
此时是普通函数调用方式,this是指向全局window的,window上面并没有挂载任何属性所以程序的执行结果是undefined。
再看另一个例子,document.getElementById这个方法名实在有点过长,我们大概尝试过用一个短的函数来代替它,如同prototype.js等一些框架所做过的事情:
var getId = function (id) {
return document.getElementById(id);
};
getId('div1');
我们也许思考过为什么不能用下面这种更简单的方式
var getId = document.getElementById;
getId( 'div1' );
现在不妨花1分钟时间,让这段代码在浏览器中运行一次
我是一个div
var getId = document.getElementById;
getId( 'div1' );
在chrome friefox IE10 中执行过后就会发现,这段代码抛出一个异常,这是因为很多引擎的document.getElementById 方法的内部实现中需要用到this,这个this本来被期望指向document,当getElementById方法作为document对象的属性被调用时,方法内部的this确实是指向document的。
但是当getId来引用document,getElementById之后,再调用getId,此时就成了普通的函数调用了,函数内部的this指向了window,而不是原来的document。
我们可以尝试利用apply把document当做this传递给getId函数,修正 this指向问题。
document.getElementById = (function(func){
return function(){
return func.apply(document,arguments);
}
})(document.getElementById);
var getId = document.getElementById;
var div = getId('div1');
alert(div.id);