call, call.call, call.call.call, 你也许还不懂这疯狂的call

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

1.JPG


前言



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 的描述


Function.prototype.call (thisArg , ...args)

When the call method is called on an object func with argument, thisArg and zero or more args, the following steps are taken:

  1. If IsCallable(func) is false, throw a TypeError exception.
  2. Let argList be an empty List.
  3. If this method was called with more than one argument then in left to right order, starting with the second argument, append each argument as the last element of argList.
  4. Perform PrepareForTailCall().
  5. Return Call(func, thisArg, argList).


中文翻译一下


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


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


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


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


The bind() function creates a new bound function, which is an exotic function object (a term from ECMAScript 2015) that wraps the original function object. Calling the bound function generally results in the execution of its wrapped function.


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'
复制代码

Function.prototype.call.call(my, this, "Hello"); means:

Use my as this argument (the function context) for the function that was called. In this case Function.prototype.call was called.

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.


重点标出:


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


NOTE 1: The thisArg value is passed without modification as the this value. This is a change from Edition 3, where an undefined or null thisArg is replaced with the global object and ToObject is applied to all other values and that result is passed as the this value. Even though the thisArg is passed without modification, non-strict functions still perform these transformations upon entry to the function.

NOTE 2: If func is an arrow function or a bound function then the thisArg will be ignored by the function [[Call]] in step 5.


注意这一句:


This is a change from Edition 3, where an undefined or null thisArg is replaced with the global object and ToObject is applied to all other values and that result is passed as the this value


两点:


  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
复制代码


相关文章
|
6月前
|
JavaScript 前端开发
call和apply的区别
call和apply的区别
|
前端开发
【JCEF】关于-1 The query has been canceled或Unexpected call to CefQueryCallback_N::finalize()错误
【JCEF】关于-1 The query has been canceled或Unexpected call to CefQueryCallback_N::finalize()错误
121 0
|
前端开发
手动封装call
手动封装call
69 0
|
JavaScript
深入理解 V8 的 Call Stack
Call Stack(调用栈) 一般指计算机程序执行时子程序之间消息处理的相互调用产生的一些列函数序列,而且几乎所有的计算机程序都依赖于调用栈。
3640 0
CALL
CALL
110 0
|
JavaScript
一步一步实现call和apply方法,超简单!
前言 this 指向问题一直是一个老生常谈的问题了!我们对它可以说是又爱又恨,因为 this 指向常常没有按照我们的想法去指向谁,导致程序无缘出现许多 bug。所以我们常常直接强制改变程序中的 this 指向,我们常用的方法有 bind、apply 和 call,bind 与其它两个稍许不同,所以我们本篇文章专门讲解 call 和 apply 方法,并且手动模拟实现它们。
180 0
一步一步实现call和apply方法,超简单!
|
JavaScript 前端开发
ES6—12:call()方法的应用
ES6—12:call()方法的应用
158 0
ES6—12:call()方法的应用
|
JavaScript
call,apply,方法的使用
//apply和call的使用方法 /* * apply的使用语法 * 函数名字.apply(对象,[参数1,参数2,...]); * 方法名字.apply(对象,[参数1,参数2,.
1005 0