在JavaScript中对函数进行调用有多种方式, 以下是对四种函数调用模式的介绍, 以及call, apply, bind的区别.
方法调用
当一个函数被保存为对象的一个属性时,我们称它为一个方法,当一个方法被调用时,this被绑定到该对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
var myObject = { value: 0, increment: function (inc){ this.value += typeof inc === 'number' ? inc : 1; } };
myObject.increment(); console.log(myObject.value);
myObject.increment(2); console.log(myObject.value);
|
this直接修改了该对象的value属性,所以第二次调用increment是在value == 1的基础上增加.
函数调用
此模式为最常见的调用模式.
1 2 3 4 5
| function showThis(){ return this; } showThis();
|
当函数以此模式调用时,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); }; helper(); }
myObject.increment(); myObject.increment(2); myObject.double(); console.log(myObject.getValue());
|
上例在给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
| var Quo = function(string){ this.status = string; };
Quo.prototype.getStatus = function(){ return this.status; }
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);
logPersonName();
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));
console.log(multiply.bind(this, 2, 3)());
|
柯里化: 创建函数的拷贝, 在该拷贝中传入部分预设参数(creating a copy of a function but with some preset parameters), 常用于有许多有关数学计算的场景中.