JavaScript 的函数调用与 this
JavaScript 函数有多种调用方式,每种方式的不同在于函数内 this
的初始化。对 this
关键字有解释:一般而言,在 JavaScript 中,this
指向函数执行时的当前对象。注意 this
是保留关键字,你不能修改 this
的值。
作为一个函数调用
先看一个最简单的实例,在浏览器中:1
2
3
4function myFunction() {
return this;
}
alert(myFunction()); // [object Window]
而当函数没有被自身的对象调用时,this
的值就会变成全局对象。在 Web 浏览器中全局对象是浏览器窗口(window
对象)。该实例返回 this
的值是 window
对象。也就是此函数即为 window
对象的函数,myFunction()
等同于 window.myFunction()
。但是此处在使用内部函数时存在一个 this
指向的问题,看下面的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 注意此处在Node环境和浏览器环境下值不同,
// Node环境下必须去掉var使value成为全局变量。
value = 4;
var myObject = {
value: 1,
double: function() {
//var that = this;
var helper = function() {
this.value = add(this.value, this.value);
};
helper();
}
};
function add(a, b) {
return a + b;
}
myObject.double();
alert(value); // 8
alert(myObject.value); // 1
在这里的 myObject
对象中,我们在 double
这个函数中使用了内部函数并赋值给 helper
,但是此处的 this.value
的 this
指向了全局对象,所以在执行这个函数后全局变量 value
的值变了但 Object
中的 value
属性值仍然是 1,这不是我们想要的结果。《JavaScript 语言精粹》中指出这里是语言设计上的一个错误,this
应该仍然绑定到外部函数的 this
变量中。这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的 this
被绑定了错误的值,所以不能共享该方法对对象的访问权。但是我们可以有一个很容易的解决方案去解决这个问题,对 myObject
进行修改:1
2
3
4
5
6
7
8
9
10
11
12
13
14var myObject = {
value: 1,
double: function() {
var that = this;
var helper = function() {
that.value = add(that.value, that.value);
};
helper();
}
};
myObject.double();
alert(value); // 4
alert(myObject.value); // 2
这里使用了一个 that
变量来指向 double
方法中 this
的值即 myObject
本身,这样就可以对 myObject
对象的属性 value
进行修改。
函数作为方法调用
这种方式即为,一个函数被保存为一个对象的属性,在此时此函数被成为一个方法。调用时 this
关键字被绑定到该对象,即:函数作为对象方法调用,会使得 this
的值成为对象本身。实例如下:1
2
3
4
5
6
7
8var myObject = {
firstName: "John",
lastName: "Doe",
fullName: function() {
return this.firstName + " " + this.lastName;
}
}
myObject.fullName(); //返回 "John Doe"
另一个例子:1
2
3
4
5
6
7
8
9
10
11
12var myObject = {
value: 0,
increment: function (inc) {
this.value += typeof inc === 'number' ? inc : 1;
}
};
myObject.increment();
console.log(myObject.value); // 1
myObject.increment(2);
console.log(myObject.value); // 3
在此例中,increment
方法对 myObject
对象中的 value
属性进行增加操作,increment
函数中的 this
即指向 myObject
对象本身。
构造器调用模式
如果函数调用前使用了 new
关键字,则是调用了构造函数。这看起来就像创建了新的函数,但实际上 JavaScript 函数是重新创建的对象:1
2
3
4
5
6
7
8// 构造函数
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var x = new myFunction("John", "Doe");
x.firstName; //返回 "John"
构造函数的调用会创建一个新的对象。新对象会继承构造函数的属性和方法。
构造函数中 this
关键字没有任何的值。this
的值在函数调用时实例化对象 (new object) 时创建。
看另一个例子:1
2
3
4
5
6
7
8
9
10var Quo = function (string) {
this.status = string;
};
Quo.prototype.get_status = function() {
return this.status;
};
var myQuo = new Quo("confused");
console.log(myQuo.get_status()); // confused
输出结果为 confused
。在这里使用 new
来调用时,会创建一个连接到该函数的 prototype
成员的新对象,同时 this
会被绑定到这个新对象上。即此时我们创建了 myQuo
对象,其连接到了 Quo
的 prototype
,且创建时的 this.status = string;
中的 this
指向这个创建的新对象,status
为新对象 myQuo
的属性,而不是 Quo
函数 prototype
的属性,可以做如下验证:1
console.log(myQuo.hasOwnProperty("status")); //true
作为对比,我们对 Quo 函数增加一个属性 id:1
2
3
4var Quo = function (string) {
this.status = string;
};
Quo.prototype.id = 1;
id
为 Quo
构造器函数 prototype
对象的一个属性,然后再次进行测试:1
2
3
4var myQuo = new Quo("confused");
console.log(myQuo.id); //1
console.log(myQuo.hasOwnProperty("get_status")); //false
console.log(myQuo.hasOwnProperty("id")); //false
这里可以看到在创建新对象 myQuo
时没有 id
属性和 get_status
函数,而这里 myQuo
继承了 Quo
的 id
属性,就通过原型链找到了 Quo.prototype.id
的值。
apply
调用模式
在 JavaScript 中,函数是对象。JavaScript 函数有它的属性和方法,call()
和 apply()
就是预定义的函数方法。这两个方法可用于调用函数。
对于 apply
:
- 在 JavaScript 严格模式 (strict mode) 下,在调用函数时第一个参数会成为
this
的值,即使该参数不是一个对象。 - 在 JavaScript 非严格模式 (non-strict mode) 下,如果第一个参数的值是
null
或undefined
,它将使用全局对象替代。
1 | function myFunction(a, b) { |
可见两者的区别在于第二个参数:apply
传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而 call
则作为 call
的参数传入(从第二个参数开始)。
另外一个结合前面定义的 Quo
构造器调用模式使用的例子:1
2
3
4
5var statusObject = {
status: 'A-OK'
};
var status = Quo.prototype.get_status.apply(statusObject);
console.log(status); // A-OK
在这里即使用了 apply
并将 statusObject
作为 get_status
的 this
,结果即为 A-OK
。
Reflect.apply
ES6 标准中新增的 Reflect.apply
方法允许通过指定的参数列表发起对目标函数的调用。相比于前面的 apply
调用模式,它更清晰明了、简洁易懂。用法是1
Reflect.apply(target, thisArgument, argumentsList)
接受的三个参数分别是目标函数,绑定的 this
参数和调用的参数列表。