前言
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 的描述
中文翻译一下
- 如果不可调用,抛出异常
- 准备一个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 也有提到:
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
函数, 到此也真相了。
其实我依旧不能太释怀, 但是这个解释可以接受,表象也是正确的, 期望掘友们有更合理,更详细的解答。
为什么this
是 String {"b"}
在上一节的分析中,我故意遗漏了Function.prototype.call
的两个note
注意这一句:
两点:
- 如果
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
写在最后
如果你觉得不错,你的一赞一评就是我前行的最大动力。
技术交流群请到 这里来。
或者添加我的微信 dirge-cloud,一起学习。