目录
call
和apply
和bind
有两种实现方式,第一种是隐式绑定
,第二种是通过new
无论是通过隐式绑定
实现还是通过new
实现,核心都是针对this的绑定规则
具体关于this的绑定规则
可以看我这一篇博客
this绑定规则
call与apply
我们先回想一下call
的使用方法
javascript
function foo() {
return this
}
console.log(foo.call({ name: 18 }))
接下来让我们一步步实现
首先call
是一个实例方法
,所以我们的myCall
也需要设置在Function.prototype
上
javascript
Function.prototype.myCall = function () {
}
call
会传入多个参数,第一个是被绑定的this
,剩下的是函数调用的参数列表
javascript
Function.prototype.myCall = function (that, ...args) {
}
接下来我们就需要在myCall
中调用函数,我们可以通过this()
的形式直接调用函数,因为foo.myCall
本身就是一种隐式绑定
因为函数可能会有返回结果,所以我们需要将返回结果给返回出去
javascript
Function.prototype.myCall = function (that, ...args) {
const value = this(...args)
return value
}
接下来我们需要将this
的this
绑定到我们指定的that
身上,我们可以在that
上新增一个属性
,用这个属性
来存放函数
然而我们并不能确定that
身上拥有哪些属性
,没有哪些属性
,如果我们的函数存放到了原有的属性
上则可能会发生错误,这表明了我们无法确定存放函数的属性名
幸运的是ES6
中推出了新的基本类型Symbol
,使用它我们可以很轻松的得到唯一的属性名
具体关于Symbol
可以看我这篇文章
Symbol
javascript
Function.prototype.myCall = function (that, ...args) {
const fn = Symbol("fn")
that[fn] = this
const value = that[fn](...args)
return value
}
我们并不希望用户能在函数中访问到我们的fn
,在fn
运行完之后我们也需要将fn
删除,所以我们需要使用属性描述符
来配置fn
具体属性描述符
可以看我这篇文章
属性描述符
javascript
Function.prototype.myCall = function (that, ...args) {
const fn = Symbol("fn")
Object.defineProperty(that, fn, {
value: this,
enumerable: false,
configurable: true
})
const value = that[fn](...args)
delete that[fn]
return value
}
最后我们还需要针对that
来判断一下,如果传入的that
是null
或者undefined
,我们需要将函数
绑定到window
,如果传入的不是一个对象
,我们就需要将它装箱
javascript
Function.prototype.myCall = function (that, ...args) {
that = (typeof that === "null" || typeof that === "undefined") ? window : Object(that)
const fn = Symbol("fn")
Object.defineProperty(that, fn, {
value: this,
enumerable: false,
configurable: true
})
const value = that[fn](...args)
delete that[fn]
return value
}
最后我们来测试一下
javascript
function foo() {
return this
}
console.log(foo.myCall({ name: 18 }))
apply
apply
和call
类似,只有在传入参数上不一样,call
是将函数参数一个一个传入,而apply
是将参数合为一个数组
传入
javascript
Function.prototype.myApply = function (that, args) {
that = (typeof that === "null" || typeof that === "undefined") ? window : Object(that)
const fn = Symbol("fn")
Object.defineProperty(that, fn, {
value: this,
enumerable: false,
configurable: true
})
const value = that[fn](...args)
delete that[fn]
return value
}
bind
bind
和以上两个很类似,区别在于bind
返回一个函数
,这个函数
不能再通过call
和apply
来更改this指向
javascript
Function.prototype.myBind = function (that, ...args) {
that = (typeof that === "null" || typeof that === "undefined") ? window : Object(that)
const fn = Symbol("fn")
return (...args2) => {
const allArgs = args.concat(args2)
Object.defineProperty(that, fn, {
value: this,
enumerable: false,
configurable: false
})
const value = that[fn](...allArgs)
return value
}
}
需要注意的是,我们返回的函数不能删除fn
,如果删除了fn
的话运行会报错
,因为找不到这个属性
如果在返回的函数上进行call
或者apply
也不会更改新函数的this
,因为无论是call
还是apply
都是在原先函数上套了一个新函数
,由新函数隐式调用原函数
,但经过bind
绑定过后的函数
也被一个新函数包裹
,经历了两次调用
,所以原函数的this指向不会丢失
测试结果
javascript
function foo() {
return this
}
const newFoo = foo.myBind({ name: 18 })
console.log(newFoo())
console.log(newFoo.myCall(window))
console.log(newFoo.apply(window))