JavaScript:實現call、apply和bind方法

實現call方法

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

語法:function.call(thisArg, arg1, arg2, ...)

call()的原理比較簡單,首先要明確函數的this會指向它的直接調用者,我們變更調用者即完成this指向的變更: 

//變更函數調用者示例
function printName() {
    console.log(this.name)
}

// 測試
const person = {
    name: 'Neeky'
}
person.printName = printName   // 變更printName的調用者,將其this指向person
person.printName()       // 'Neeky'

基於以上的代碼原理, 我們能很快的實現一個call()

Function.prototype.myCall = function(thisArg, ...args) {
    thisArg.fn = this              // this指向調用call的對象,即我們要改變this指向的函數
    return thisArg.fn(...args)     // 執行函數並return其執行結果
}

其原理就是,我們爲傳入的thisArg對象新增了一個fn函數,並且通過 thisArg.fn = this 這個操作複製了調用myCall的函數,因爲函數的this指向直接調用者。然後執行該fn函數返回其執行結果。

但是我們還需要處理一些細節:

Function.prototype.myCall = function(thisArg, ...args) {
    const fn = Symbol('fn')        // 聲明一個獨有的Symbol屬性, 防止fn覆蓋已有屬性
    thisArg = thisArg || window    // 若沒有傳入this, 默認綁定window對象
    thisArg[fn] = this              // this指向調用call的對象,即我們要改變this指向的函數
    const result = thisArg[fn](...args)  // 執行當前函數
    delete thisArg[fn]              // 刪除我們聲明的fn屬性
    return result                  // 返回函數執行結果
}

//測試
printName.myCall(person)     // 輸出'Neeky'

實現apply方法

apply() 方法調用一個具有給定this值的函數,以及作爲一個數組(或類似數組對象)提供的參數。
語法:func.apply(thisArg, [argsArray])

apply()call()類似,區別在於call()接收參數列表,而apply()接收一個參數數組,所以我們在call()的實現上簡單改一下入參形式即可

Function.prototype.myApply = function(thisArg, args) {
    const fn = Symbol('fn')        // 聲明一個獨有的Symbol屬性, 防止fn覆蓋已有屬性
    thisArg = thisArg || window    // 若沒有傳入this, 默認綁定window對象
    thisArg[fn] = this              // this指向調用call的對象,即我們要改變this指向的函數
    const result = thisArg[fn](...args)  // 執行當前函數(此處說明一下:雖然apply()接收的是一個數組,但在調用原函數時,依然要展開參數數組。可以對照原生apply(),原函數接收到展開的參數數組)
    delete thisArg[fn]              // 刪除我們聲明的fn屬性
    return result                  // 返回函數執行結果
}

//測試
printName.myApply(person, [])     // 輸出'Neeky'

 實現bind方法

bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定爲 bind() 的第一個參數,而其餘參數將作爲新函數的參數,供調用時使用。
語法: function.bind(thisArg, arg1, arg2, ...)

首先明確bind方法做了什麼事情

  1. bind()除了this還接收其他參數,bind()返回的函數也接收參數,這兩部分的參數都要傳給返回的函數
  2. new會改變this指向:如果bind綁定後的函數被new了,那麼this指向會發生改變,指向當前函數的實例
  3. 沒有保留原函數在原型鏈上的屬性和方法
Function.prototype.myBind = function (thisArg, ...args) {
    var self = this
    // new優先級
    var fbound = function () {
        self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))
    }
    // 繼承原型上的屬性和方法
    fbound.prototype = Object.create(self.prototype);

    return fbound;
}

//測試
const person = { name: 'Neeky' }
function printName() {
    console.log(this.name)
    console.log(arguments)
}

printName.myBind(person, 'a', 'b', 'c')()    //輸出 Neeky ['a', 'b', 'c']

參考文檔:2萬字 | 前端基礎拾遺90問 

 

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