手寫 apply call bind 實現

apply call bind 相關

對於 apply call bind 之前也有寫過一篇很詳細的入門級文章 apply , call, bind 用法和區別

瞭解用法後,最好還是瞭解其實現原理,這樣才能掌握透徹

三者的區別

  1. callapply 都是爲了解決改變 this 的指向。作用都是相同的,只是傳參的方式不同
  2. 除了第一個參數外,call 可以接收一個參數列表,apply 只接受一個參數數組
  3. bind 和其他兩個方法作用也是一致的,只是該方法會返回一個函數。並不會立刻執行

手寫 call

  • 理一理需求:

    • 改變 this 指向。默認指向 window
    • 可以接收多個參數
    • 改變 this 請求後執行該方法
    • 所有的方法都掛載這個 myCall
  • 理一下實現思路:

    • 對於 this。只需要記住,誰調用該方法,this 就指向誰(箭頭函數除外),讓傳入的對象/window 去調用方法即可
    • 接收多參數:arguments或者 es6 新語法:...args
    • 立刻執行函數,灑灑水了
    • 所有的方法都掛載,那就用到原型鏈了,掛載在Function對象上
  • 萬事俱備,開始實現:

Function.prototype.myCall = function(context, ...args) {
  // 默認的window對象
  context = context && typeof context === 'object' ? context : window
  // 防止覆蓋掉原有屬性
  const key = Symbol()
  // 這裏的this爲需要執行的方法
  context[key] = this
  // 方法執行
  const result = context[key](...args)
  delete context[key]
  return result
}
  • 總結一下實現
    • 首先得確保新的指向是一個對象類型object。否則默認全都是 window。當然了 typeof null 也是等於 “object”。所以我們還得先用一個判斷確保 context 存在
    • Symbol 作用:防止屬性名的衝突。爲什麼要這麼做?因爲我們需要在新的對象上放一個屬性,這個屬性就是舊的 this。可怎麼保證新屬性不會和 context 原有屬性衝突呢?那就要用到 Symbol
    • 可能對上面的 context[key] = this 疑惑。這到底是爲何,爲啥要在新的 context 上放舊的 this 對象?後面會有代碼解釋原理
    • 執行方法不存在什麼問題了~
    • 最後要 delete 掉我們新增的屬性,畢竟不能影響原先的 context 對象

小拓展 context[key] = this 是爲何?

理解 context[key] = this 對整個手寫代碼實現有非常重要的意義

首先萬年不變的原理: 誰調用方法,this 就指向誰
那爲何不是context去調用對應方法嗎?
因爲 context 上根本就沒對應的方法

用一個小例子理解這個問題

let data = {
  key: 1,
  name: 'Jioho',
  say: function() {
    console.log(this.name)
  }
}
function fn() {
  console.log(this.name)
}

data.say() // Jioho
fn() // 打印爲空

// 那如果這樣:
data.sayfn = fn
data.sayfn() // Jioho
  • 差距就出現了,只要對應方法在對象裏面,方法就能拿到當前的 this 實例
  • context[key] = this 同理。這時候的 this 還是原函數的 this。原函數中的 this 纔有他本身的方法。所以我們直接把整個 this 賦值給 context。就類似於上面第 16 行代碼一樣
  • 至於我們一開始 key 爲什麼要 Symbol。就是怕重名如果我們第 16 新的屬性不叫 sayfn。叫 say,那麼我們後續的操作就會影響到原先的對象的屬性,這是絕對不能出現的問題!

手寫 apply

如果 手寫 call 理解後 apply 就只是一個傳參的問題

其餘代碼都無需改動,只需要在接收第二個參數的時候是一個數組類型即可

Function.prototype.myApply = function(context, args = []) {
  // 默認的window對象
  context = context && typeof context === 'object' ? context : window
  // 防止覆蓋掉原有屬性
  const key = Symbol()
  // 這裏的this爲需要執行的方法
  context[key] = this
  // 方法執行
  const result = context[key](...args)
  delete context[key]
  return result
}

手寫 bind

  • 理一理需求:

    • 改變 this 指向。默認指向 window
    • 調用 bind 之前的參數,可以傳入多個
    • 返回的函數中,還可以繼續傳參。這個場景還是很實用的
    • 所有的方法都掛載這個 myBind
  • 理一下實現思路:

    • 改變 this 指向就無須多說了,不管是手寫實現,還是用原有的 apply 或者 call 都行
    • 調用 bind 之前可以傳遞多個參數,那還是用到 arguments 或者 ...args 統一接收了
    • bind 需要返回一個函數。並且新的函數中還可以繼續傳參,那就要用到閉包了
    • 所有的方法都掛載,那就用到原型鏈了,掛載在Function對象上
Function.prototype.myBind = function(context, ...args) {
  context = context && typeof context === 'object' ? context : window
  const _self = this
  return function(...args2) {
    return _self.apply(context, [...args, ...args2])
  }
}

// 接下來驗證一下:
var data = { baseCount: 10 }
function add(num1, num2) {
  return this.baseCount + num1 + num2
}

// 調用方法
var addBind = add.myBind(data, 1)
addBind(2) // 13

// 或者這樣寫
add.myBind(data, 1)(2) // 13
  • 總結一下實現
    • 萬年不變的 context 實現
    • 由於有 2 次接收多個參數的方法,我們用到的 ...args 接收的也必定是一個數組了。所以在返回的函數中用 apply 最合適不過了

不要小看 bind 這看似奇怪的寫法,爲啥要分 2 次傳參?後續講到複雜的柯里化,就會發現 bind 現在的魅力所在

原文發佈於:apply call bind 相關 新的博客地址,感興趣的可以看看~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章