天天看點

詳解 javascript 中的 call / apply / bind

作者:我的代碼果然有問題

前言

javascript 中 call / apply / bind 這三個方法是日常工作中比較實用的改變函數 this 指向的方法,很友善,但由于涉及到對 this 的了解,同時也是很容易出錯的方法,也是面試官喜歡問的考題之一,今天就和大家一起好好聊一聊這三個方法

call / apply / bind 的差別

我們先簡單的過一下他們之間的異同點,有個簡單概念之後再詳細分析

共同點

他們的功能一緻,都是用來改變函數的 this 指向

// 文法
函數.call(thisArg, arg1, arg2, ...)  
函數.apply(thisArg, [argsArray])
函數.bind(thisArg, [argsArray])             

不同點

  • call / apply 可以立即執行;bind 不會立即執行,而是傳回一個函數,可以在需要的時候再執行
  • 參數不同:apply 第二個參數是數組;call 和 bind 有多個參數需要用逗号隔開挨個寫

簡單應用場景

  • 改寫 this 指向,讓目标對象使用一些方法
const person = {
    name: '江',
    say: function () {
        console.log(this.name)
    }
}

person.say()  // 江

const obj = {
    name: '李'
}

person.say.call(obj)    // 李
person.say.apply(obj)   // 李
person.say.bind(obj)()  // 李           
  • 借用方法
const numbers = [5, 458 , 120 , -215 ]; 
const maxInNumbers = Math.max.apply(Math, numbers),   //458
      maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458           

number 本身沒有 max 方法,但是 Math 有,我們就可以借助 call 或者 apply 使用其方法

手寫 call

Function.prototype.myCall = function (thisArg, ...argsArray) {
    // 判斷 thisArg 是否 null / undefine
    if (thisArg === null || thisArg === undefined) {
        thisArg = window
    }

    console.log(this) // 這裡的 this 是調用的函數
    thisArg.fn = this
    thisArg.fn(...argsArray)
}           

手寫 apply

Function.prototype.myApply = function (thisArg, argsArray) {
    // 判斷 thisArg 是否 null / undefine
    if (thisArg === null || thisArg === undefined) {
        thisArg = window
    }

    console.log(this) // 這裡的 this 是調用的函數
    thisArg.fn = this
    thisArg.fn(...argsArray)
}           

手寫 bind

Function.prototype.myBind = function (thisArg, argsArray) {
    // 判斷 thisArg 是否 null / undefine
    if (thisArg === null || thisArg === undefined) {
        thisArg = window
    }

    console.log(this) // 這裡的 this 是調用的函數
    thisArg.fn = this
    return function () {
        thisArg.fn(...argsArray)
    }
}           

使用一下我們手寫的三個方法,看是否滿足我們的要求

const person = {
    name: '江',
    say: function (word) {
        console.log(this.name + ":" + word)
    }
}

person.say('你好')  // 江:你好

const obj = {
    name: '李'
}

person.say.myCall(obj, 'hello')     // 李:hello
person.say.myApply(obj, ['hello'])   // 李:hello
person.say.myBind(obj, ['hello'])()   // 李:hello           

可以看到可以按期望輸出結果了~

總結

對于改寫 this 指向的實作,關鍵點是要了解:

  • 我們自定義的方法,必須是挂載在 Function 這個個構造函數上的,這是一個大前提,因為隻有這樣,我們在任意方法上使用 . 才能找到我們的方法屬性(其實就是原型鍊)
  • 還有就是對目前函數裡 this 的了解,這個場景可以簡單了解為 this 是誰調用就指向誰,那麼
  • 函數.myCall() 就很清晰了,是函數調用了 myCall,是以 myCall 方法裡的 this 指向的就是函數
  • 了解了以上兩點,就隻要把 this(想執行的方法) 挂到我們的目标對象thisArg上面就可以了