JavaScript中call、apply和bind三个函数的区别

242 阅读3分钟

在学习JavaScriptthis指向问题的时候,这三个函数可以显示绑定this值,所以在这里做个比较,关于this,在下一篇文章中再进行总结。

call

call函数的第一个参数是一个对象,在调用函数的时候将其绑定到this上,从第二个开始的其他参数统称为arguments

任何形如fn()函数调用其实都可以写成fn.call()。当写成fn.call()的时候,就能清楚函数中this的指向,看下面的例子,

function f(){
    console.log(this)
    console.log(arguments)
}
f.call() // window
f.call({name:'frank'}) // {name: 'frank'}, []
f.call({name:'frank'},1) // {name: 'frank'}, [1]
f.call({name:'frank'},1,2) // {name: 'frank'}, [1,2]

从上可以看出,this永远指向的是第一个参数,比直接进行fn()调用容易看懂this指向的是什么。当call第一个不传,或者是传入undefined,那么默认是window。假如,第一个传入的是原始值,这个原始值就会被转换成它的对象形式,如下:

function f(){
    console.log(this)
    console.log(arguments)
}
f.call(1) // new Number(1) []

call的第一个参数应该了解了,那么对于call的其它参数,看下面的例子:

function sum() {
  let n = 0
  for (let i = 0; i < arguments.length; i++) {
    n += arguments[i]
  }
  return n
}

sum.call(undefined, 1, 2, 3, 4, 5) // 15

可以看出来,后面的参数都存到了arguments参数中,那么如果有一个数组,需要求和,那么对于call来说,只能一项项传入,就像下面这样,

sum.call(undefined, a[0], a[1], a[2], ...)

显然,当数组长度比较大的时候,这种方式显然不方便,那么这时候apply函数就要登场了。

apply

apply这个函数和call函数十分相似,对于第一个参数的处理都是一样的,只有后面的参数不同,apply第二个接受的参数是一个数组。所以对于上面那种情况,apply使用起来就很方便了,直接向下面这样调用:

sum.apply(undefined, a)

可以看出来,callapply在使用上只有第二个参数的区别,前者只接受一个一个传,而后者接受一个数组,其他方面两者都是一样的。所以,当你不确定参数的个数时,就用apply

fn.apply(asThis, params)

callapply都会直接调用函数,如果你想指定this但不想调用函数,那么这是bind就派上用场了。

bind

callapply 是直接调用函数,而 bind 则是返回一个新函数(并没有调用原来的函数),这个新函数会 call 原来的函数,call 的参数由你指定。

看下面一个例子:

function fn() {
    var a = 2;
    console.log(this, this.a);
}
var obj = {
    a: 3,
};
var a = 1;
var fn1 = fn.bind();
fn1(); // window 1
// 有参数,this指向第一个参数,即obj
var fn2 = fn.bind(obj);
fn2(); // obj 3

bind函数在第二个开始的参数是可选的,当绑定函数被调用时,这些参数加上绑定函数本身的参数会按照顺序作为原函数运行时的参数。看下面的一个例子:

function fn(a, b, c) {
  return a + b + c;
}

var _fn = fn.bind(null, 10);
var ans = _fn(20, 30); // 60

可以看出fn函数需要三个参数,_fn函数将 10 作为默认的第一个参数,所以只需要传入两个参数即可,如果你不小心传入了三个参数,也只会取前两个。

var _fn = fn.bind(null, 10);
var ans = _fn(20, 30, 40); // 60

这样的用处是,如果某些函数,前几个参数已经 “内定” 了,我们便可以用 bind 返回一个新的函数。也就是说,bind() 能使一个函数拥有预设的初始参数。这些参数(如果有的话)作为 bind() 的第二个参数跟在 this 后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。

看下面的例子,来自MDN

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]