在JavaScript中对函数进行调用有多种方式, 以下是对四种函数调用模式的介绍, 以及call, apply, bind的区别.

方法调用

当一个函数被保存为对象的一个属性时,我们称它为一个方法,当一个方法被调用时,this被绑定到该对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//创建myObject.它有一个value属性和一个increment方法.
//increment方法接受一个可选的参数.如果参数不是数字,那么默认使用数字1.

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

this直接修改了该对象的value属性,所以第二次调用increment是在value == 1的基础上增加.

函数调用

此模式为最常见的调用模式.

1
2
3
4
5
function showThis(){
return this;
}
showThis();
//返回Window{...}

当函数以此模式调用时,this被绑定到全局对象.在浏览器中,全局对象为Window.this绑定到全局对象是语言设计上的一个错误,当内部函数被调用时,this应该仍然绑定到外部函数的this变量.这个设计错误的后果是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误值,不能共享该方法对对象的访问权,不过有个很容易的解决方案,在对象的方法内定义一个变量并给它赋值为this,那么内部函数就能通过那个变量访问到this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var myObject = {
value: 0,
increment: function (inc){
this.value += typeof inc === 'number' ? inc : 1;
},
getValue: function(){
return this.value;
}
};

function add(a,b){
return a+b;
};

myObject.double = function (){
var that = this; //解决方法
var helper = function(){
that.value = add(that.value,that.value);
//alert(this); //用以测试this的值,在浏览器中会弹出object Window
};
helper(); //以函数形式调用helper
}

myObject.increment();
myObject.increment(2);
myObject.double();
console.log(myObject.getValue()); //6

上例在给myObject增加double方法的函数中创建了一个内部函数helper,但helpler无法直接用this访问到myObject对象中的value属性,因为在helper中this为全局对象,可以在helper中添加alert(this)测试.为了让helper访问到外部函数myObject.double的this值,定义了一个变量that来保存this值,这个this值为myObject.

构造器调用

如果在一个函数前面带上new来调用,那么将创建一个隐藏连接到该函数的prototype成员的新对象,同时this将会被绑定到那个新对象上.

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建一个名为Quo的构造器函数,它构造一个带有status属性的对象.
var Quo = function(string){
this.status = string;
};

//给Quo的所有示例提供一个名为getStatus的公共方法.
Quo.prototype.getStatus = function(){
return this.status;
}

//构造一个Quo实例
var myQuo = new Quo("confused")
console.log(myQuo.getStatus());

call调用和apply调用

因为JavaScript是一门函数式的面向对象编程语言,所以函数也是对象,可以拥有方法.

bind(), apply(), call()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var person = {
firstname: 'John',
lastname: 'Doe',
getFullName: function(){
var fullname = this.firstname + " " + this.lastname;
return fullname;
}
}

var logName = function(lang1, lang2){
console.log(this);
console.log('Logged: ' + this.getFullName());
}

var logPersonName = logName.bind(person); // {1}

// logName(); //Error: this.getFullName is not a function at logName // {2}

logPersonName(); // Logged: John Doe


// var logName = function(lang1, lang2){
// console.log('Logged: ' + this.getFullName());
// }.bind(person); // {3}

logName.call(person, 'en', 'es');
logName.apply(person, ['en', 'es']);

行{1}利用bind方法, 在参数内传入person对象, 将this值绑定到person, 此时getFullName()就可以正常执行. 如果像行{2}那样直接执行, 那么this指向的就是全局对象window, 而window中没有定义firstname,getFullName(),lastname, 因此会抛出错误. 还可以像行{3}那样绑定this.

  • bind方法返回一个新的函数, 是原函数的拷贝.
  • call和apply直接调用函数执行

在call和apply中传入的第一个参数为this需要绑定到的对象, 后面为传入函数的参数, call和apply唯一的区别就是传入apply中的参数为数组. call和apply第二个及之后的参数是可选参数, 也可以不传入直接执行.

应用

function borrowing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var person = {
firstname: 'John',
lastname: 'Doe',
getFullName: function(){
var fullname = this.firstname + ' ' + this.lastname;
return fullname;
}
}
var person2 = {
firstname: 'Jane',
lastname: 'Doe'
}

console.log(person.getFullName.apply(person2));
console.log(person.getFullName.call(person2));

利用apply和call从person对象中’借’getFullName方法.

function currying

函数柯里化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function multiply(a,b){
return a*b;
}

var multiplyByTwo = multiply.bind(this, 2);
var multiplyByThree = multiply.bind(this, 3);

console.log(multiplyByTwo(2)); // 4


//给bind()传入第二个参数,第二个参数成为函数对象第一个参数的默认值,

// multiplyByTwo相当于:

// function multiplyByTwo(b){
// var a = 2;
// return a * b;
// }


// 也可以传入多个参数

console.log(multiply.bind(this, 2, 3)()); // 6

柯里化: 创建函数的拷贝, 在该拷贝中传入部分预设参数(creating a copy of a function but with some preset parameters), 常用于有许多有关数学计算的场景中.