也談如何實現bind、apply、call

z0gSh1u發表於2020-04-20

也談如何實現bind、apply、call

我們知道,JavaScript的bind、apply、call是三個非常重要的方法。bind可以返回固定this、固定引數的函式包裝;apply和call可以修改成員函式this的指向。實現bind、apply、call是前端面試的高頻問題,也能讓我們更好地理解和掌握JavaScript的函式的相關知識。本文將介紹如何自行實現bind、apply和call。

前情提要

本文先給出如下類定義和例項定義。

// Person類,每個人有姓名,有列印姓名的方法
function Person(_name) {
  this.name = _name
  this.sayMyName = function (postfix) {
    console.log(this.name + postfix)
  }
}
// 兩個例項
let alex = new Person('Alex')
let bob = new Person('Bob')

實現bind

不妨先回顧一下bind的使用方法:

let sayBobName = alex.sayMyName.bind(bob, '?')
sayBobName() // Bob?

可見:

  • bind返回一個函式副本(包裝過的函式),固定this和實參。this可不指向呼叫者
  • bind是函式原型上的一個方法

瞭解了這兩點,就不難寫出實現:

function MyBind(context, ...args) {
  let that = this // this是呼叫者,也就是被包裝的函式(alex.sayMyName)
  return function () { // 返回一個包裝過的函式
    return that.call(context, ...args) // 其返回值是以context為this的執行結果
  }
}
Function.prototype.bind = MyBind

實現call

在實現bind的過程中我們用到了call。那麼如何實現一個call?不妨也回顧一下call的使用方法:

alex.sayMyName.call(bob, '!') // Bob!

可見:

  • bind修改當前函式的this,可使其不指向呼叫者
  • 引數以...rest形式傳入

瞭解了這兩點,也不難寫出實現:

function MyCall(context, ...args) {
  context._fn = this // this是呼叫者,也就是當前函式。此步給context綁上該函式
  // 有個細節問題是,可能需要備份原先的context._fn,避免意外覆蓋
  let ret = context._fn(..args) // 模仿context自己呼叫_fn
  delete context._fn // 移除綁的函式
  return ret
}
Function.prototype.call = MyCall

實現apply

apply和call的作用相同,使用方法基本類似,唯一的不同是:

  • 引數以陣列形式傳入

故可寫出實現:

function MyApply(context, args) {
  return MyCall(context, ...args)
}
Function.prototype.apply = MyApply

穩定的三角

從上面我們看出,apply和call可以實現bind,那麼怎麼用bind實現apply和call,從而打造這一鐵三角關係呢?

    bind
  /      \      兩兩都可互相實現的三角形!
call -- apply

簡單!立即執行這個函式副本就可以了!

function AnotherMyCall(context, ...args) {
  return (this.bind(context, ...args))()
}

相關文章