模拟实现一个 call/apply/bind 方法
基本问答题:call、apply 和 bind 是干嘛的?如何使用?它们之间有哪些区别?
- call、apply 和 bind,都是用来改变函数的 this 指向的。
- call、apply 和 bind 之间的区别比较大,前者在改变 this 指向的同时,也会把目标函数给执行掉;后者则只负责改造 this,不作任何执行操作。
- call 和 apply 之间的区别,则体现在对入参的要求上。前者只需要将目标函数的入参逐个传入即可,后者则希望入参以数组形式被传入。
const a = {
value: 1
}
function getValue(name, age) {
console.log(name, age)
console.log(this.value)
}
getValue.call(a, 'zhbb', '18')
getValue.apply(a, ['zhbb', '18'])
call 方法的模拟
var me = {
name: 'zhbb'
}
function showName() {
console.log(this.name)
}
showName.call(me) // zhbb
结合 call 表现出的特性,我们首先至少能想到以下两点:
call 是可以被所有的函数继承的,所以 call 方法应该被定义在 Function.prototype 上call 方法做了两件事:
- 改变 this 的指向,将 this 绑到第一个入参指定的的对象上去;
- 根据输入的参数,执行函数。
showName 在 call 方法调用后,表现得就像是 me 这个对象的一个方法一样。
所以我们最直接的一个联想是,如果能把 showName 直接塞进 me 对象里就好了,像这样:
var me = {
name: 'zhbb',
showName: function() {
console.log(this.name)
}
}
me.showName()
模拟一下 call 方法,将call挂载在原型上
Function.prototype.myCall = function(context) {
// step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
context.func = this
// step2: 执行函数
context.func()
// step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
delete context.func
}
var me = {
name: 'zhbb'
}
function showFullName(surName) {
console.log(`${this.name} ${surName}`)
}
showFullName.call(me, 'zhang') // zhbb zhang
完整的 myCall 方法
Function.prototype.myCall = function(context, ...args) {
// step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
context.func = this
// step2: 执行函数,利用扩展运算符将数组展开
context.func(...args)
// step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
delete context.func
}
var me = {
name: 'zhbb'
}
function showFullName(surName) {
console.log(`${this.name} ${surName}`)
}
showFullName.myCall(me, 'zhang') // zhbb Lee
模拟apply实现
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 需要判断是否存储第二个参数
// 如果存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
模拟bind实现
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}