看完当前文章,了解 call,apply,bind 之后,阅读这篇文章 js 手写 call,apply,bind 可以了解其原理,并能手写实现,也是较常见的前端面试题。
引言:call,apply,bind 都是函数 Function 原型上的方法,三者的功能都是用来改变函数中的 this 指向。
1. Function.prototype.call()
用法:fn.apply(object, [arg1, arg2, ……])
(注:以下用到的 [] 中括号都表示是可选的,而非必填的参数)
object
: 是需要改变函数 fn
中 this
指向的目标对象
arg1, arg2……
: 为传入函数 fn
中的参数,为序列形式
功能:用来改变函数 fn
内部的 this
指向,指向当前传入的第一个参数 object
,传入函数 fn
中的参数为 arg1, arg2……
,并且会执行函数 fn
,返回值为函数 fn
的返回值。
示例:
var age = 21, name = '李逍遥', god = {name: '上帝', age: 99999}
function people(a,b) {
console.log(this.age)
console.log(this.name)
console.log(a,b)
}
people() // this 指向 window
// 21
// 李逍遥
// undefined undefined
people.call(god, '参数1', '参数2') // this 指向被改变,指向传入的对象 god
// 99999
// 上帝
// 参数1 参数2
2. Function.prototype.apply()
用法:fn.apply(object, [argArray])
object
: 是需要改变函数 fn
中 this
指向的目标对象
argArray
: 为传入函数 fn
中的参数,为数组形式(要传给函数中的参数,放到一个数组里,作为 apply 第二个参数传进去)
功能:用来改变函数 fn
内部的 this
指向,指向当前传入的第一个参数 object
,传入函数 fn
中 的参数为 argArray
列表里的参数,并且会执行函数 fn
,返回值为函数 fn
的返回值。
示例:
var age = 21, name = '李逍遥', god = {name: '上帝', age: 99999}
function people(a,b) {
console.log(this.age)
console.log(this.name)
console.log(a,b)
}
people() // this 指向 window
// 21
// 李逍遥
// undefined undefined
people.apply(god, ['参数1', '参数2']) // this 指向被改变,指向传入的对象 god
// 99999
// 上帝
// 参数1 参数2
注:可见 call 和 apply 的唯一区别,就是传入参数的格式不一样,前者是以序列形式传入,后者是以数组形式。
3. Function.prototype.bind()
用法:fn.bind(object, [arg1, arg2, ……])
object
: 是需要改变函数 fn
中 this
指向的目标对象
arg1, arg2, ……
: 为传入函数 fn
中的参数,为序列形式
功能:用来改变函数 fn
内部的 this
指向,指向当前传入的第一个参数 object
,传入函数 fn
中的参数为 arg1, arg2……
,不会执行函数 fn
,返回值为当前改变 this 指向后的函数 fn
,所以若想执行 fn
,需要这样使用 fn.bind(object, [arg1, arg2, ……])()
,后面加一个括号,因为前面执行结果得到的是函数 fn。
示例:
var age = 21, name = '李逍遥', god = {name: '上帝', age: 99999}
function people(a,b) {
console.log(this.age)
console.log(this.name)
console.log(a,b)
}
people() // this 指向 window
// 21
// 李逍遥
// undefined undefined
const changePeople = people.bind(god, '参数1', '参数2') // this 指向被改变,指向传入的对象 god,并返回改变 this 后的函数 people
console.log(changePeople)
// ƒ people(a,b) { console.log(this.age) console.log(this.name) console.log(a,b) }
changePeople()
// 99999
// 上帝
// 参数1 参数2
注:fn.bind(object, [arg1, arg2, ……]),给函数传参也能通过这种形式,fn.bind(object)([arg1, arg2, ……]),这种就不仅改变了 this 指向,同时还传入参数去执行了函数 fn;fn.bind(object, [arg1, arg2, ……])可理解传入的参数会被存入到一个闭包变量,再次执行时就不用再传参,如: people.bind(god, '参数1', '参数2')()
4. 使用场景
为什么需要 call, apply, bind 来改变函数中的 this 指向呢?因为有些实例中是没有一些方法的,需要通过 call, apply, bind 来改变 this 指向当前实例,这样函数执行时就会作用到当前实例,从而达到没有该方法的实例借用该方法的效果。举个生活中的例子:我(实例A)有一个铁哥们小王(实例B),他家里有一台 PS5(工具/方法),我呢非常想玩一款 PS5 游戏,但是自己又没钱去买 PS5,于是,我就去找小王“借”(call/apply/bind)来 PS5 来玩一段时间(这段时间 PS5 归属人就是我,不再是小王),玩完后再还给他。简单地说,就是 call, apply, bind 相当于“借用”的功能。
示例:将类数组对象或者其它可迭代对象转为数组,通过使用数组的方法 slice() 实现,但对象里是没有这个方法的,所以需要通过 call/apply/bind 借用 slice()。
var obj = {0:'a',1:'b',2:'c',length:3}
var arr = Array.prototype.slice.call(obj, 0)
// Array.prototype.slice.call(obj, 0) 相当于 obj.slice(0),但 obj 属于对象类型,对象是没有该方法的,所以直接这样使用会报错,这时就得使用 call 等来借用数组的方法。
console.log(arr) // ['a','b','c']
5. call/apply/bind 区别
call 和 apply 都会执行函数,而 bind 不会执行,会返回改变 this 指向后的函数; call 和 bind 传参方式都一样,第一个参数都是所要指向的对象,第二个及后续参数都为要传入给函数的参数,而 apply 的传参区别在于第二个参数是数组,数组里存放的就是要传进去的参数。
总结:call/apply/bind 都是用来改变函数中的 this 指向,只不过 call 传参是个序列, apply 是个数组,call/apply都会执行函数,而 bind 不会执行,会返回改变 this 指向后的函数。call/apply/bind 可以理解就是一个能够借用他人方法的工具,充当“借”的功能。