模拟实现一个 call/apply/bind 方法

616 阅读2分钟

模拟实现一个 call/apply/bind 方法

基本问答题:call、apply 和 bind 是干嘛的?如何使用?它们之间有哪些区别?

  1. call、apply 和 bind,都是用来改变函数的 this 指向的。
  2. call、apply 和 bind 之间的区别比较大,前者在改变 this 指向的同时,也会把目标函数给执行掉;后者则只负责改造 this,不作任何执行操作。
  3. 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 方法做了两件事:
  1. 改变 this 的指向,将 this 绑到第一个入参指定的的对象上去;
  2. 根据输入的参数,执行函数。

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))
  }
}