前言

Function.prototype.call 我想大家都觉得自己很熟悉了,手写也没问题!!
你确认这个问题之前, 首先看看 三千文字,也没写好 Function.prototype.call,

看完,你感觉还OK,那么再看一道题:
请问如下的输出结果

function a(){ 
    console.log(this,'a')
};
function b(){
    console.log(this,'b')
}
a.call.call(b,'b')  

如果,你也清晰的知道,结果,对不起,大佬, 打扰了,我错了!

本文起源:
一个掘友加我微信,私聊问我这个问题,研究后,又请教了 阿宝哥
觉得甚有意思,遂与大家分享!

结果

结果如下: 惊喜还是意外,还是淡定呢?

String {"b"} "b"

再看看如下的代码:2个,3个,4个,更多个的call,输出都会是String {"b"} "b"

function a(){ 
    console.log(this,'a')
};
function b(){
    console.log(this,'b')
}
a.call.call(b,'b')  // String {"b"} "b"
a.call.call.call(b,'b')   // String {"b"} "b"
a.call.call.call.call(b,'b')  // String {"b"} "b"

看完上面,应该有三个疑问?

  1. 为什么被调用的是b函数
  2. 为什么thisString {"b"}
  3. 为什么 2, 3, 4个call的结果一样

结论:
两个以上的call,比如call.call(b, 'b'),你就简单理解为用 b.call('b')

分析

为什么 2, 3, 4个call的结果一样

a.call(b) 最终被调用的是a,
a.call.call(b), 最终被调用的 a.call
a.call.call.call(b), 最终被执行的 a.call.call

看一下引用关系

a.call === Function.protype.call  // true
a.call === a.call.call  // true
a.call === a.call.call.call  // true

基于上述执行分析:
a.call 被调用的是a
a.call.calla.call.call.call 本质没啥区别, 被调用的都是Function.prototype.call

为什么 2, 3, 4个call的结果一样,到此已经真相

为什么被调用的是b函数

看本质就要返璞归真,ES 标准对 Funtion.prototye.call 的描述

中文翻译一下

  1. 如果不可调用,抛出异常
  2. 准备一个argList空数组变量
  3. 把第一个之后的变量按照顺序添加到argList
  4. 返回 Call(functhisArgargList)的结果

这里的Call只不是是一个抽象的定义, 实际上是调用函数内部 [[Call]] 的方法, 其也没有暴露更多的有用的信息。

实际上在这里,我已经停止了思考:

a is a function, then what a.call.call really do? 一文的解释,有提到 Bound Function Exotic Objects , MDN的 Function.prototype.bind 也有提到:

Function.prototype.call 相反,并没有提及!!! 但不排查在调用过程中有生成。

Difference between Function.call, Function.prototype.call, Function.prototype.call.call and Function.prototype.call.call.call 一文的解释,我觉得是比较合理的

function my(p) { console.log(p) }
Function.prototype.call.call(my, this, "Hello"); // output 'Hello'

重点标出:
So, Function.prototype.call would be called with my as its context. Which basically means - it would be the function to be invoked.

It would be called with the following arguments: (this, "Hello"), where this is the context to be set inside the function to be called (in this case it’s my), and the only argument to be passed is "Hello" string

翻译一下:
Function.prototype.call.call(my, this, "Hello")表示: 用my作为上下文调用Function.prototype.call,也就是说my是最终被调用的函数。

my带着这些 (this, "Hello") 被调用, this 作为被调用函数的上下文,此处是作为my函数的上下文, 唯一被传递的参数是 "hello"字符串。

基于这个理解, 我们简单验证一下, 确实是这样的表象

// case 1:
function my(p) { console.log(p) }
Function.prototype.call.call(my, this, "Hello"); // output 'Hello'

// case 2:
function a(){ 
    console.log(this,'a')
};
function b(){
    console.log(this,'b')
}
a.call.call(b,'b')  // String {"b"} "b"

为什么被调用的是b函数, 到此也真相了。

其实我依旧不能太释怀, 但是这个解释可以接受,表象也是正确的, 期望掘友们有更合理,更详细的解答。

为什么thisString {"b"}

在上一节的分析中,我故意遗漏了Function.prototype.call的两个note

注意这一句:

两点:

  1. 如果thisArgundefined 或者null, 会用global object替换
    这里的前提是 非严格模式
"use strict"

function a(m){
    console.log(this, m);  // undefined, 1
}

a.call(undefined, 1)
  1. 其他的所有类型,都会调用 ToObject进行转换
    所以非严格模式下, this肯定是个对象, 看下面的代码:
Object('b') // String {"b"}

note2的 ToObject 就是答案

到此, 为什么thisSting(b) 这个也真相了

万能的函数调用方法

基于Function.prototype.call.call的特性,我们可以封装一个万能函数调用方法

var call = Function.prototype.call.call.bind(Function.prototype.call);

示例

var person = {
    hello() { 
        console.log('hello', this.name) 
    }
}

call(person.hello, {"name": "tom"})  // hello tom

写在最后

如果你觉得不错,你的一赞一评就是我前行的最大动力。

技术交流群请到 这里来
或者添加我的微信 dirge-cloud,一起学习。

引用

04-11 03:54