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


相关文章
|
存储 关系型数据库 MySQL
MySQL数据类型----定点型,数据类型的选择
MySQL数据类型----定点型,数据类型的选择
375 0
|
Java 关系型数据库 数据库连接
探索Java中的MyBatis Plus注解 @DbType:灵活处理数据库类型
在数据库操作中,不同的数据库系统可能具有不同的数据类型,如MySQL、Oracle、SQL Server等,这就需要我们在操作中处理不同的数据库类型。MyBatis Plus作为一款强大的ORM框架,提供了注解 `@DbType`,使得开发者能够更加灵活地处理数据库类型,从而在多数据库支持下轻松切换。本文将详细介绍 `@DbType` 注解的用法及其在持久层开发中的应用。
2347 1
|
1天前
|
数据采集 人工智能 安全
|
11天前
|
云安全 监控 安全
|
2天前
|
自然语言处理 API
万相 Wan2.6 全新升级发布!人人都能当导演的时代来了
通义万相2.6全新升级,支持文生图、图生视频、文生视频,打造电影级创作体验。智能分镜、角色扮演、音画同步,让创意一键成片,大众也能轻松制作高质量短视频。
981 151
|
2天前
|
编解码 人工智能 机器人
通义万相2.6,模型使用指南
智能分镜 | 多镜头叙事 | 支持15秒视频生成 | 高品质声音生成 | 多人稳定对话
|
16天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
1681 8
|
8天前
|
人工智能 自然语言处理 API
一句话生成拓扑图!AI+Draw.io 封神开源组合,工具让你的效率爆炸
一句话生成拓扑图!next-ai-draw-io 结合 AI 与 Draw.io,通过自然语言秒出架构图,支持私有部署、免费大模型接口,彻底解放生产力,绘图效率直接爆炸。
630 152
|
10天前
|
人工智能 安全 前端开发
AgentScope Java v1.0 发布,让 Java 开发者轻松构建企业级 Agentic 应用
AgentScope 重磅发布 Java 版本,拥抱企业开发主流技术栈。
601 15