1
头图

问题复现

有这么一道题:

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指向的影响。
如果能够理解这一点的话,那这个问题就比较好理解了。

参考文档


aqiongbei
2k 声望281 粉丝

人生路上,你走的每一步都算数