前言
call()、apply() 和 bind() 方法三者作用都是改变this指向。
call() 方法接受的语法和作用与 apply() 方法类似,只有一个区别就是 call() 接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
二者都是函数对象 Function 的方法,且第一个参数都是要绑定对象的上下文。
bind() 函数会创建一个新的绑定函数,这个绑定函数包装了原函数的对象。调用绑定函数通常会执行包装函数。
手写 call、apply 及 bind 函数
call
- 首先
context为可选参数,如果不传的话默认上下文为window - 接下来我们通过
Symbol为context创建一个属性,并将值设置为需要调用的函数 - 因为
call可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来 - 然后调用函数并将对象上的函数删除
Function.prototype.myCall = function(context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const symbol = Symbol()
context[symbol] = this
const args = [...arguments].slice(1)
const result = context[symbol](...args)
delete context[symbol]
return result
}
apply
Function.prototype.myApply = function(context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const symbol = Symbol()
context[symbol] = this
let result
// 处理参数和 call 有区别
if (arguments[1]) {
result = context[symbol](...arguments[1])
} else {
result = context[symbol]()
}
delete context[symbol]
return result
}
bind
bind 的实现对比其他两个函数略微地复杂了一点,因为 bind 需要返回一个函数,需要判断一些边界问题,以下是 bind 的实现。
- 前几步和之前的实现差不多,就不赘述了
bind返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过new的方式,我们先来说直接调用的方式- 对于直接调用来说,这里选择了
apply的方式实现,但是对于参数需要注意以下情况:因为bind可以实现类似这样的代码f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments) - 最后来说通过
new的方式,在之前的章节中我们学习过如何判断this,对于new的情况来说,不会被任何方式改变this,所以对于这种情况我们需要忽略传入的this
Function.prototype.myBind = function (context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const _this = this
const args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
// 这边的 apply 严谨点可以自己实现
return _this.apply(context, args.concat(...arguments))
}
}