博客 / 詳情

返回

如何理解fn1.call.call(fn2)的結果

問題復現

有這麼一道題:

function fn1(){ 
    console.log(`fn1 ${this}`)
} 
function fn2(){ 
    console.log(`fn2 ${this}`)
} 
fn1.call(fn2);// fn1 function fn2(){console.log(`fn2 ${this}`)}
fn1.call.call(fn2);// fn2 [object Window]
fn1.call.call.call.call(fn2);// fn2 [object Window]

問題分析

關於call我們一般知道這些:

  • 主要作用是改變函數的this指向
  • 傳參形式類似function.call(thisArg, arg1, arg2, ...)
  • 執行步驟

    • 改變調用函數的this指向
    • 傳參並將調用函數執行

所以,對於

fn1.call(fn2); // fn1 function fn2(){console.log(`fn2 ${this}`)}

這樣的輸出並不會使我們感到意外,根據上面的已知信息,這段代碼的執行邏輯如下:
fn1中的this指向fn2然後執行fn1
而對於

fn1.call.call(fn2);// fn2 [object Window]

這個樣的輸出就會讓人困惑了,那麼我們該如何理解呢?

問題解決

要理解這個輸出我們需要知道這兩點:

key_1: call方法是哪裏來的?

首先callFunction基類原型上的方法,也就是Function.prototype.call
所以fn1.call.call(fn2)相當於Function.prototype.call.call(fn2)

key_2: call的執行過程是什麼樣的

用偽代碼表示,call的執行過程大概是這樣(不考慮傳參的情況)的

Function.prototype.call = function (context) {
    // 1. 首先明確第一個this,因為這是個原型上的方法,是公共的方法
    // 所以我們需要知道是誰在調用call這個方法,誰在調用這個this就是誰
    // 我給這裏的this起個別名就是this_caller
    // 2. 改變this_caller中的this指向context
    // 3. 執行this_caller()
    this();
}

知道這兩點之後,結合fn1.call.call(fn2)實例分析就是:

  1. 根據key_1,我們知道所以,fn1.call.call(fn2)的第一個callFunction.prototype.call,第二個call是通過Function.prototype.call的原型鏈找到的。
  2. 根據上面得到的信息,我們把可以把fn1.call.call(fn2)看做(fn1.call).call(fn2)。結合key_2我們可以知道,第二個call起了這些作用:

    1. 首選明確這個callthis_callerfn1.call
    2. fn1.call裏面的this指向變成了fn2
    3. fn1.call的變成了fn2

    3.整體來看,就是fn1.call.call(fn2)變成了fn2()

上面的問題關鍵在於:
fn1裏面雖然沒有this,不受call改變this指向的影響,但是fn1.call裏面有this,受call改變this指向的影響。
如果能夠理解這一點的話,那這個問題就比較好理解了。

參考文檔

  • JavaScript中的call、apply、bind深入理解
  • JavaScript中關於call函數的一道面試題
  • Function.prototype.call()
user avatar esunr 頭像 _raymond 頭像
2 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.