Louis's blog
GitHub
GitHub
  • 瀏覽器運作原理
  • 閉包
  • this
  • arguments 和 array-like
  • apply、call、bind
  • 函式程式設計
  • 物件和原型
  • 原型鏈和繼承
  • ES6 和 Class
  • ES6 中新增的數據類型
  • ES6 語法糖
  • var、let、const
  • ES7~ES12
  • Strict Mode
  • 補充
  • Proxy 和 Reflect
  • Promise
  • Iterator
  • 生成器
  • async、await
  • Event loop
  • 錯誤處理
  • 模塊化
  • 套件管理工具
  • JSON
  • WebStorage
  • IndexDB
  • Cookie
  • BOM
  • DOM
  • 防抖(debounce)和節流(throttle)
  • Deep copy
  • 事件總線

apply、call、bind

練習實現 apply、call、bind,並更深刻的了解 this 和在函數中的調用關係。

call 的實現

自定義方法

首先使用 prototype 為函數添加一個名為 mycall 的自定義方法:

Function.prototype.mycall = function (thisArg) {
  console.log('mycall')
}

以上這段程式碼能夠讓我們能夠透過 mycall 這個方法模擬原生的 call 方法,接下來我們只要需要在 mycall 方法中獲取需要被執行的函數。

綁定 this

透過 this 能夠獲取到需要被執行的函數,具體可以參考 JavaScript 中的 this 這篇。

Function.prototype.mycall = function (thisArg) {
  var fn = this
}

接下來就要進行 this 的綁定,參數 thisArg 就代表著要綁定的物件,透過在物件中作用為方法調用可以對函數的 this 進行綁定。

Function.prototype.mycall = function (thisArg) {
  var fn = this
  thisArg.fn = fn
  thisArg.fn()
}

簡單寫一個範例:

Function.prototype.mycall = function (thisArg) {
  var fn = this
  thisArg.fn = fn
  thisArg.fn()
}
function foo() {
  console.log('foo函數被執行了', this)
}
foo.mycall({ name: 'louis' })

console.log 輸出結果為:

foo函數被執行了 {name: 'louis', fn: ƒ}

可以看到 foo 函數中的 this 被指向了對應的物件,物件內部多出了一個 fn 函數是綁定物件調用的結果,這是在 JavaScript 中模擬 call 的副作用,原生的 call 使用 C++進行編寫,就不會出現這個屬性,當然最後也是可以使用 delete 將該 fn 屬性進行刪除,但是打印 this 的時候依然會出現,所以就先不刪了。

邊緣情況(edge case)

接下來我們在對這個函數進行ㄧ些改進,由於使用 thisArg.fn 去綁定函數的關係,所以當傳入的 thisArg 不為物件時就會出錯。

可以透過 Object 這個函數將 string、number 等類型轉化為物件形式:

Function.prototype.mycall = function (thisArg) {
  var fn = this
  // 防止傳入的參數不為物件
  thisArg = Object(thisArg)
  thisArg.fn = fn
  thisArg.fn()
}

這樣就可以傳入字符串:

function foo() {
  console.log('foo函數被執行了', this)
}
foo.mycall('louis')

輸出:

foo函數被執行了 String {'louis', fn: ƒ}

還有一個邊緣情況(edge case)可以進行修正,原生的 call 在傳入 null 或是 undefined 作為參數時,綁定的 this 會是 window。

可以透過簡單的運算子來實現:

Function.prototype.mycall = function (thisArg) {
  var fn = this
  // 假如傳入null或是undefined要綁定window
  thisArg = (thisArg !== undefined && thisArg !== null && Object(thisArg)) || window
  thisArg.fn = fn
  thisArg.fn()
}
function foo() {
  console.log('foo函數被執行了', this)
}
foo.mycall(null)

輸出:

foo函數被執行了 Window {window: Window, self: …}

接收參數

要實現接收參數,可以直接使用 es6 的語法接收所有剩餘參數,並且將函數處理後的結果進行返回,以下為最終的例子:

Function.prototype.mycall = function (thisArg, ...args) {
  var fn = this
  thisArg = (thisArg !== undefined && thisArg !== null && Object(thisArg)) || window
  thisArg.fn = fn
  // 接收參數並返回結果
  return thisArg.fn(...args)
}
function foo(n1, n2) {
  return n1 + n2
}
var result = foo.mycall('louis', 20, 30)
console.log(result)

輸出為:

50

apply 的實現

apply 和 call 的實現方式幾乎相同,差別只在於傳入參數的形式,apply 是以陣列的形式將參數傳入:

foo.apply(obj, [arg1, arg2, arg3])

所以要修改的地方就只有參數的接收,並且對不傳入參數進行處理:

// argArray做為參數不用以剩餘參數的形式接收
Function.prototype.mycall = function (thisArg, argArray) {
  var fn = this
  thisArg = (thisArg !== undefined && thisArg !== null && Object(thisArg)) || window
  thisArg.fn = fn
  // 對不傳入參數進行處理
  argArray = (argArray !== undefined && argArray !== null && argArray) || []
  return thisArg.fn(...argArray)
}
function foo(n1, n2) {
  console.log('foo函數執行', this)
  return n1 + n2
}
var result = foo.myapply('louis', [20, 30])
console.log(result)

輸出的結果為:

foo函數執行 String {'louis', fn: ƒ}
50

bind 的實現

bind 和 call、apply 最大的差別是會返回一個函數,這個返回的函數會綁定傳入的物件作為 this。

Function.prototype.mybind = function (thisArg, ...argArray) {
  var fn = this
  thisArg = (thisArg !== null && thisArg !== undefined && Object(thisArg)) || window

  // bind和call、apply最大的差別是要返回一個函數
  return function (...args) {
    // ...
  }
}

接下來可只要把在 call、apply 中進行綁定 this 操作在返回的函數中實現即可,同時也要注意返回的函數也能夠進行參數的傳遞。

Function.prototype.mybind = function (thisArg, ...argArray) {
  var fn = this
  thisArg = (thisArg !== null && thisArg !== undefined && Object(thisArg)) || window

  return function (...args) {
    // 在返回的函數中進行this綁定
    thisArg.fn = fn
    // 將新函數的參數一起傳入要調用的函數中
    return thisArg.fn(...argArray, ...args)
  }
}
Edit this page
Last Updated:
Contributors: louis61619, louis
Prev
arguments 和 array-like
Next
函式程式設計