Javascript 中有几个有趣的方法,它们位于 Function 对象原型上面,分别是:

  • Function.prototype.call
  • Function.prototype.apply
  • Function.prototype.bind

今天我们就说说它们的用法和区别。

Function.prototype.call

MDN 上面是这样定义它的

fun.call(thisArg, arg1, arg2, …)

第一个参数是传递给 funthis 对象,后面的参数则是 function 所接受的参数列表

1
2
Math.max.call(null, 5, 6, 7, 1) // 7
Math.max.call(undefined, 2, 3, 4, 1) // 4

因为 max 函数不需要 this 对象,所以一个参数可以传递 null 或者 undefined

1
2
3
4
5
6
function helloWorld(){
console.info(`Hello, My name is: ${this.name}! arguments is: ${Array.prototype.slice.call(arguments)}`)
}

helloWorld.call({name: 'bin hong'}) // Hello, My name is: bin hong! arguments is:
helloWorld.call({name: 'Tome'}, 1, 2, 3, 4) // Hello, My name is: Tome! arguments is: 1,2,3,4

上面例子可以看得出,我们能够通过使用 call 来动态的指定 function 中的 this 的真正对象。

上面的例子还能够得知,arguments 这个保留字就是在 function 中拿到参数列表的属性,同时使用 Array 对象原型上面的 slice 方法能够将其转化为数组。

1
2
3
4
5
6
7
const args = [1, 2, 3, 4]
Array.prototype.slice.call(args)
// 等同于
args.slice()
Array.prototype.slice.call(args, -1)
// 等同于
args.slice(-1)

使用 callapply 来调用 javascript 对象原型方法需要理解其第一个参数 this 的含义,如同上面例子一样,我们一般使用 slice 的方法就是在一个 array 对象上面使用,就是这个 array 就是调用 slicethis 对象,所以使用 call 或者 apply 的时候,第一个参数就是 array 对象,就是需要使用 slice 的对象。后续的参数,才是传递给 slice 的参数。

Function.prototype.apply

MDN 上面是这样定义它的

func.apply(thisArg, [argsArray])

和 Function.prototype.call 类似,第一个参数是传递给functhis对象,但后面参数则是func的参数数组

1
2
Math.max.apply(null, [5, 6, 7, 1]) // 7
Math.max.apply(undefined, [2, 3, 4, 1]) // 4

Function.prototype.apply 也能够动态的改变func中的this对象

所以能够使用 call 的地方也能够使用 apply, 唯一需要改变的是把参数列表换成参数数组

1
2
3
4
5
6
7
const args = [1, 2, 3, 4]
Array.prototype.slice.apply(args)
// 等同于
args.slice()
Array.prototype.slice.apply(args, [-1])
// 等同于
args.slice(-1)

Function.prototype.bind

MDN 上面这样定义它的

fun.bind(thisArg[, arg1[, arg2[, …]]])

它的第一个参数是传递给functhis对象,后面的参数则是传递给func的参数列表

1
2
3
4
5
6
7
8
9
10
11
const obj = {
name: 'Object A',
getName: function(){
return this.name
}
}
const unboundGetName = obj.getName
console.info(unboundGetName()) // this is means window, output is undefined

const boundGetName = unboundGetName.bind(obj)
console.info(boundGetName()) // this is bind to obj, output is Object A

bind 还有一个比较有意思的用法则是可以使一个函数拥有预设的一些初始参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function addSomeNumber(){
return Array.prototype.slice.call(arguments).reduce((acc, value) => acc + value, 0)
}

addSomeNumber(1,2,3,4) // output: 10

const alwaysAddTen = addSomeNumber.bind(null, 10)
alwaysAddTen() // output: 10
alwaysAddTen(5, 6, 7, 8, 9) // output: 45

// 减少方法的参数
function addNumber(number1, number2, number3){
return number1 + number2 + number3
}

const addNewNumber = addNumber.bind(null, 1, 2)
addNewNumber(3) // output: 6
addNewNumber(10) // output: 13

从上面的例子可以看到,从bind的第二个参数开始,都是传递给func的参数,所以我们可以通过bind方法,给func默认指定一些参数

总结

Function.prototype.callFunction.prototype.apply 属于间接执行某个方法的另一种途径,同时它们拥有改变方法内部的this的对象的能力,也就是说,如果我们想改变某个方法内部的this的指向,则可以使用call或者apply

Function.prototype.bind则是直接绑定方法的this指向,如果 bind 有两个以上的参数,那么后面的参数则作为方法的初始参数。因为bind完之后,方法并没有执行,所以还需要手动调用一次bind返回之后的函数