Serenader

Learning by sharing

JavaScript 中的 this 用法以及 call(apply) 的理解

想要深入了解 JavaScript 这门语言,一个必须掌握的知识点就是对 this 的理解。其中, callapply 又与 this 有着密切的联系。趁现在有空,在网上看了一些教程之后,自己根据印象将其整理在这篇博文中。
想要深入了解 JavaScript 这门语言,一个必须掌握的知识点就是对 this 的理解。其中, callapply 又与 this 有着密切的联系。趁现在有空,在网上看了一些教程之后,自己根据印象将其整理在这篇博文中。

this 是什么

this 是 JavaScript 中的一个关键字。它用在对象的方法中。 this 总是指向调用该方法的对象。举个最简单的例子:

当 this 在对象的方法中

var obj = {
    name : 'object',
    sayName : function () {
        alert(this.name);
    }
};

obj.sayName(); // object
可以看出,当对象 obj 调用 sayName 这个方法时,this 指向的是 obj 。故 alert(this.name) 结果会是 object。即,当 this 出现在对象的方法中时,执行该方法时 this 指向的是这个对象本身。
接下来看看另外一个例子。

当 this 在函数里面

var test = function () {
	this.message = 1;
}
test();
alert(window.message); //1
可以看出,在直接调用函数时,函数内部的 this 指向的是全局对象。在这个例子中,全局对象是 window ,也就是这个例子是在浏览器中的情况。将例子稍微变形一下,可以更直观地看出 this 是指向全局对象。
var message  = "hi";

var test = function () {
	alert(this.message);
}

test(); // hi
上面的例子展示了当直接调用函数时,函数内部 this 的指向总是指向全局对象。接下来来看看构造函数中的 this 指向的是什么。

当 this 在构造函数里面

var name  = "global";
function Person () {
	this.name = "someone";
}

var sam = new Person(); 
alert(sam.name); // someone
以上代码很清楚的展示了构造函数中 this 的指向问题。当声明一个构造函数的实例时,构造函数内部的 this 都会指向新的实例。所以,在执行 var sam = new Person() 这条语句时, this 指向 sam ,所以此时 sam.name = "someone"
this 出现的位置大概有以上几种情况。另外,JavaScript 中的 call 和 apply 方法也和 this 有着密切的联系。接下来来看看 call 和 apply 的情况。

call 与 apply 是什么

call 方法应用于 Function 对象。语法为
call([thisObj[,arg1[, arg2[,   [,.argN]]]]])
说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
Apply 和 Call 两者在作用上是相同的,但两者在参数上有区别的。对于第一个参数意义都一样,但对第二个参数:apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。如 func.call(func1,var1,var2,var3)对应的 apply 写法为:func.apply(func1,[var1,var2,var3])。本文以 `call` 为例。
上面的文字应该可以很好的理解。但是想真正的掌握 call 还是得认真地研究一下才行。来看例子:

在 call 传入对象

var sayName = function () {
	alert(this.name);
}

var peter = {
	name: "peter"
}

sayName.call(peter); //peter
上面这个例子可以很直观地理解 call 的工作原理。也就是说,当在函数中调用 call 这个方法时,函数内部的 this 对象会自动指向 call 方法中的第一个参数。在上面这个例子中也就是 peter 这个对象了。所以在执行 sayName.call(peter) 时,函数内部的 this.name 则会自动指向 peter.name 。故最终结果为 peter。这是一个非常好理解的例子。接下来来看较为复杂的情况。
function Person1 () {
	this.name = "person1";
    this.sayName = function () {
    	alert(this.name);
    }
}

function Person2 () {
	this.name = "person2";
}

var sam = new Person2();

Person1.call(sam);

sam.sayName(); //person1
以上例子中,最关键的是 Person1.call(sam) 这一步。我们来看看在里面究竟发生了什么事。当调用 call这个方法时,函数 Person1 内部的 this 指向 sam 这个对象。相当于,给 sam 这个对象执行这两条语句:
this.name = "person1";
this.sayName = function () {
	alert(this.name)
}
故在此重写了原来 sam 对象中的 name 属性。而且获得了一个新的方法。所以可以成功的调用 sam.sayName() 而且结果返回 person1 。

在 call 中传入函数

function class1 () {
	this.message = "yeah";
}

function class2 () {
	this.sayMessage = function () {
    	alert(this.message);
    }
}

class2.call(class1);
alert(class1.sayMessage);
class1.sayMessage(); //undefined
上述例子中,我们调用了 class2 函数的 call 方法,传入了 class1 这个函数。结果 class1 拥有了 sayMessage 这个方法。但是直接调用这个方法返回的结果却是 undefined 而不是我们期望的 yeah 。这是为什么呢?为了搞清楚这里面的 this 指向,我们作如下修改:
var message = "hi";

function class1 () {
	this.message = "yeah";
}

function class2 () {
	this.message = "hello";
	this.sayMessage = function () {
    	alert(this.message);
    }
}

class2.call(class1);
alert(class1.sayMessage);
class1.sayMessage(); // hello

class1.message = "msg";

class1.sayMessage(); //msg
第一次调用 sayMessage 方法,返回 hello 。我们再来看一下 class2.call(class1) 这个过程。当执行这个方法时,class1 获得 class2 的 message 属性和 sayMessage 方法。所以此时有 class1.message = "hello" ,class1.sayMessage = function () {alert(this.message)}。因此执行 sayMessage 时返回 hello。当我们手动修改 class1.message 时,再调用这个方法,返回的值为我们修改的值。这证明了我们上面的推理是正确的。
我们再来看前一个例子。
function class1 () {
	this.message = "yeah";
}

function class2 () {
	this.sayMessage = function () {
    	alert(this.message);
    }
}

class2.call(class1);
alert(class1.sayMessage);
class1.sayMessage(); //undefined
这个例子中并没有定义 class1.message 这个属性。所以执行 sayMessage 方法时,返回为未定义。有个比较容易混淆的地方是, class1中定义的 this.message 并不是 class1.message 。而是 class1 的实例才拥有这个 message 属性。
类似的例子还有下面这个:
function Class1(){
    this.showTxt = function(){alert(this.name)}
}
function Class2(){
    this.name = 'class2';
}
var class1 = new Class1();
class1.showTxt.call(Class2);//undefined
alert(Class2.showTxt);//undefined
原因与上面的例子一样。 Class2 并没有定义 name 这个属性。

Reference