0%

JavaScript的函数调用与this

JavaScript函数有4种调用方式。每种方式的不同方式在于this的初始化。对this关键字有解释:一般而言,在JavaScript中,this指向函数执行时的当前对象。注意this是保留关键字,你不能修改this的值。

作为一个函数调用

先看一个最简单的实例,在浏览器中:

1
2
3
4
function 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,这不是我们想要的结果。《js语言精粹》中指出这里是语言设计上的一个错误,this应该仍然绑定到外部函数的this变量中。这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。但是我们可以有一个很容易的解决方案去解决这个问题,对myObject进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var 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
8
var 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
12
var 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
10
var 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
4
var Quo = function (string) {
this.status = string;
};
Quo.prototype.id = 1;

id为Quo构造器函数prototype对象的一个属性,然后再次进行测试:

1
2
3
4
var 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
2
3
4
5
6
7
function myFunction(a, b) {
return a * b;
}
myFunction.call(myFunction, 10, 2); //返回20

myArray = [10,3];
myFunction.apply(null, myArray); //返回30

可见两者的区别在于第二个参数:apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
另外一个结合前面定义的Quo构造器调用模式使用的例子:

1
2
3
4
5
var statusObject = {
status: 'A-OK'
};
var status = Quo.prototype.get_status.apply(statusObject);
console.log(status); // A-OK

在这里即使用了apply并将statusObject作为get_status的this,结果即为A-OK。

以上即为四种函数的调用方法。


参考文章:
Javascript的函数调用与this
JavaScript 函数调用

拓展阅读:
闭包深入:让你分分钟理解 JavaScript 闭包
一道 javascript 面试题求教 - V2EX

🍭支持一根棒棒糖!