这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战。
前言
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,一起学习。
引用
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
 
                     
     
        
       
        
       
        
       
        
       
    
发表评论
还没有评论,快来抢沙发吧!