手寫實現一下繼承 call apply bind

實現 call

來看下 call 的原生表現形式:es6

var foo = {
  value: 1,
}

function bar() {
  console.log(this.value)
}

bar.call(foo) // 1

從上面代碼的執行結果,咱們能夠看到,call 首先改變了 this 的指向,使函數的 this 指向了 foo,而後使 bar 函數執行了。數組

總結以下:app

  1. call 改變函數 this 指向,
  2. 調用函數

思考一下:咱們如何實現上面的效果呢?代碼改造以下:函數

//將bar函數掛載到foo對象上,使其成爲foo的方法,用foo.bar來調用測試

var foo = {
  value: 1,
  bar: function () {
    console.log(this.value)
  },
}
foo.bar() //1

爲了模擬 call 方法,咱們能夠這樣作:this

  1. 將函數設爲某個對象的屬性(或者方法)
  2. 經過該對象的屬性調用該函數
  3. 刪除該對象上的這個屬性(或者方法)

代碼以下:prototype

Function.prototype.myCall = function (context) {
  const fn = Symbol('fn')        // 聲明一個獨有的Symbol屬性, 防止fn覆蓋已有屬性
  context = context || window // 若沒有傳入this, 默認綁定window對象
  context.fn = this // 將函數掛載到對象的fn屬性上
  const args = [...arguments].slice(1) // 處理傳入的參數
  const result = context.fn(...args) // 經過對象的屬性調用該方法
  delete context.fn // 刪除該屬性
  return result
}

// 測試
function test(arg1, arg2) {
  console.log(arg1, arg2)
  console.log(this.a, this.b)
}

test.myCall(
  {
    a: 'a',
    b: 'b',
  },
  1,
  2
)

咱們看一下上面的代碼:code

  1. 首先咱們對參數 context 作了兼容處理,不傳值,context 默認值爲 window。
  2. 而後咱們將函數掛載到 context 上面,context.fn = this;
  3. 處理參數,將傳入 myCall 的參數截取,去除第一位,而後轉爲數組;
  4. 調用 context.fn,此時 fn 的 this 指向 context;
  5. 刪除對象上的屬性 delete context.fn
  6. 將結果返回。

實現 apply

apply 和 call 實現相似,只是傳入的參數形式是數組形式,而不是逗號分隔的參數序列。對象

所以,藉助 es6 提供的...運算符,就能夠很方便的實現數組和參數序列的轉化。io

Function.prototype.myApply = function (context) {
  const fn = Symbol('fn')        // 聲明一個獨有的Symbol屬性, 防止fn覆蓋已有屬性
  context = context || window // 若沒有傳入this, 默認綁定window對象
  context.fn = this // 將函數掛載到對象的fn屬性上
  const args = [...arguments].slice(1) // 處理傳入的參數
  const result = context.fn(args) // 經過對象的屬性調用該方法
  delete context.fn // 刪除該屬性
  return result
}

// 測試
function test(arr) {
  console.log(arr)
  console.log(this.a, this.b)
}

test.myApply(
  {
    a: 'a',
    b: 'b',
  },
  [1, 2]
)

實現 bind

在模擬 bind 的實現以前,先看一下 bind 的使用案例:

var obj = { a: 1 }
function bar() {
  console.log(this.a)
}
bar.bind(obj)() //here

bind 函數雖然也能改變 bar 函數的 this,可是改變後,函數並不會執行,只是返回一個新的函數,想執行就得繼續調用,仔細觀察第五行代碼的寫法。

根據上面的使用案例,咱們先實現一個簡單版本的 bind:

Function.prototype.myBind = function (context) {
  return () => {
    // 要用箭頭函數,不然 this 指向錯誤
    return this.call(context)
  }
}
var obj = { a: 1 }
function bar() {
  console.log(this.a)
}
bar.myBind(obj)()

可是這樣比較簡陋,函數的參數一多就不能處理了,以下面這種狀況:

bar.bind(obj, 2)(2)
// or
bar.bind(obj)(2, 2)

爲了兼容 bind 調用時知足參數傳遞的不一樣方式,代碼修改以下:

Function.prototype.myBind = function (ctx, ...arg1) {
  return (...arg2) => {
    return this.call(ctx, ...arg1, ...arg2)
  }
}
//測試代碼
var obj = { a: 1 }
function bar(b, c) {
  console.log(this.a + b + c)
}
bar.myBind(obj)(20, 30)
bar.myBind(obj, 20, 30)()
相關文章
相關標籤/搜索