call, call.call, call.call.call, 你也许还不懂这疯狂的call
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"
看完上面,应该有三个疑问?
- 为什么被调用的是
b
函数 - 为什么
this
是String {"b"}
- 为什么 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.call
和 a.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 objectfunc
with argument,thisArg
and zero or moreargs
, the following steps are taken:
- If IsCallable(func) is false, throw a TypeError exception.
- Let argList be an empty List.
- 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.
- Perform PrepareForTailCall().
- Return Call(func, thisArg, argList).
中文翻译一下
- 如果不可调用,抛出异常
- 准备一个argList空数组变量
- 把第一个之后的变量按照顺序添加到argList
- 返回 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
asthis
argument (the function context) for the function that wascall
ed. In this caseFunction.prototype.call
was called.So,
Function.prototype.call
would be called withmy
as its context. Which basically means - it would be the function to be invoked.It would be called with the following arguments:
(this, "Hello")
, wherethis
is the context to be set inside the function to be called (in this case it'smy
), 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
函数, 到此也真相了。
其实我依旧不能太释怀, 但是这个解释可以接受,表象也是正确的, 期望掘友们有更合理,更详细的解答。
为什么this
是 String {"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 thethisArg
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
两点:
- 如果
thisArg
是undefined
或者null
, 会用global object替换
这里的前提是 非严格模式
"use strict"
function a(m){
console.log(this, m); // undefined, 1
}
a.call(undefined, 1)
- 其他的所有类型,都会调用 ToObject进行转换
所以非严格模式下, this
肯定是个对象, 看下面的代码:
Object('b') // String {"b"}
note2的 ToObject 就是答案
到此, 为什么this
是 Sting(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
写在最后
如果你觉得不错,你的一赞一评就是我前行的最大动力。
作者:云的世界
链接:https://juejin.cn/post/6999781802923524132