问题复现
有这么一道题:
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
方法是哪里来的?
首先call
是Function
基类原型上的方法,也就是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)
实例分析就是:
- 根据
key_1
,我们知道所以,fn1.call.call(fn2)
的第一个call
是Function.prototype.call
,第二个call
是通过Function.prototype.call
的原型链找到的。 根据上面得到的信息,我们把可以把
fn1.call.call(fn2)
看做(fn1.call).call(fn2)
。结合key_2
我们可以知道,第二个call
起了这些作用:- 首选明确这个
call
的this_caller
是fn1.call
fn1.call
里面的this
指向变成了fn2
fn1.call
的变成了fn2
3.整体来看,就是
fn1.call.call(fn2)
变成了fn2()
- 首选明确这个
上面的问题关键在于:fn1
里面虽然没有this
,不受call
改变this指向的影响,但是fn1.call
里面有this
,受call
改变this
指向的影响。
如果能够理解这一点的话,那这个问题就比较好理解了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。