前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >给 Number 打 Call

给 Number 打 Call

作者头像
IMWeb前端团队
发布2019-12-03 16:00:46
4150
发布2019-12-03 16:00:46
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队

本文作者:IMWeb elvin 原文出处:IMWeb社区 未经同意,禁止转载

最近“打 call”这个词突然流行起来,我想到 call 在 JavaScript中可是个重要的方法,那么能不能用 JavaScript也来玩一把打 call 呢?于是我在 Number 上实验了下,写出来的代码让我自己都一脸懵逼了,大家能猜到下面这段代码的输出结果吗?

代码语言:javascript
复制
Number.call(Number, 1, 2, 3);
Number.call.call(Number, 1, 2, 3);
Number.call.call.call(Number, 1, 2, 3);
Number.call === Number.call.call;
Number.call === Number.call.call.call;

结果初探

打 call 的结果
打 call 的结果

不知道大家有没有猜中运行结果呢?反正我看到这个结果的时候是崩溃的(内心活动:为什么相等的三个函数,对相同的参数,返回的结果却不一样??不愧是 JavaScript!)不过虽然结果看起来违反直觉,但是从上述代码中还是可以得出两个好消息的:

  1. Number.call(Number, 1, 2, 3) 这段代码的运行结果是符合预期的,它其实就是将 Number 中的 this 指向 Number,然后执行 Number(1, 2, 3),和直接执行 Number(1, 2, 3) 作用相同,效果就是对传入的第一个参数尝试转化为数字并返回,我们可以通过下列代码验证一下:
代码语言:javascript
复制
Number.call(Number, 1, 2, 3); // output is 1
Number(1, 2, 3); // output is 1

Number.call(Number, '1', '2', '3'); // output is 1
Number('1', '2', '3'); // output is  1

Number.call(Number, 'do', 'call'); // output is NaN(Not a Number)
Number('do', 'call'); // output is NaN(Not a Number)
  1. Number.callNumber.call.callNumber.call.call.call 用全等符号的判断结果是 true,这说明它们是同一个函数, 那么它们到底是指向哪一个函数呢?仔细想一下,Number 是函数,Number.call 是函数,Number.call.call 也是函数,那么访问一个函数的 call 方法会不会就是访问 Function 原型上的 call 方法呢?同样地,我们通过下列代码验证一下:
代码语言:javascript
复制
typeof Number;  // output is "function"
typeof Number.call; // output is "function"
typeof Number.call.call; // output is "function"

Number.call === Function.prototype.call; // output is true
Number.call.call === Function.prototype.call; // output is true
Number.call.call.call === Function.prototype.call; // output is true

关于原型链更深入的知识,可以参考我之前写的博客 - __proto__ 与 prototype 的关系

虽然有了一些收获,但是为什么对于相同的函数 Function.prototype.call,输入相同的参数 (Number, 1, 2, 3),有时候得到的结果是 1,有时候得到结果却是 2 呢?

深入探究

既然已经定位到了 Function.prototype.call 这里,那么我们可以自己写一个 myCall 函数来代替 call 函数,从而进一步地探究这个函数内部发生了什么。不要被自己写一个 call 函数吓到,毕竟它的作用很简单:将调用者函数中的 this 改为传入的第一个参数,然后再依次传入后面的参数并执行函数。所以我们可以先编写一个这样的函数:

代码语言:javascript
复制
Function.prototype.myCall = function myCall(obj, ...args) {
  console.log(`this is ${this.name}, obj is ${obj}, arguments are ${args}`);
  console.log('------------------');
  const newFunc = this.bind(obj);
  return newFunc(...args);
}

在上面的代码中,关键在于 const newFunc = this.bind(obj);,这里的 this 指的就是调用者函数,然后通过 this.bind(obj) 将调用者函数中的 this(注意:这个 this 是调用者函数中的 this,而不是 myCall 中的 this) 变为了 obj。

上面只是为了探究问题而实现的简单版 call 函数,实际上 call 函数还会对传入的第一个参数做校验和替换,例如传入基本数据类型(primitive values)时会被转化为对象,不过这些与本次问题无关,可以不用关注 ^_^

然后让我们来运行一下 myCall,看看结果如何:

myCall 执行结果
myCall 执行结果

运行的结果和原生的 call 函数相同,说明这个简单版的 myCall 函数实现了目标。在上图中,绿块和蓝块的输出相同,可以把它们俩儿归为一类,所以接下来主要对红块和绿块中的结果进行分析。

Number.myCall

从红块中,我们可以看出函数调用只发生了一次,且 this 为 Number,obj 为 Number,arguments 为 1、2 和 3,所以 Number.myCall(Number, 1, 2, 3) 其实相当于调用了 Number.bind(Number)(1, 2, 3),所以最后结果为 1,这与我们在第一节得出的结论相同。

Number.myCall.myCall

从绿块中,我们可以看出函数调用发生了两次:

  1. 对于第一次调用,将输出的 this、obj 和 arguments 带入到 myCall 中,可以发现其实是执行了 myCall.bind(Number)(1, 2, 3)
  2. 对于第二次调用,同样将输出的 this、obj 和 arguments 带入到 myCall 中,可以发现其实是执行了 Number.bind(1)(2, 3),所以结果与执行 Number(2, 3)相同,也就是 2。

———————————— 前方高能预警!!会比较绕!!请准备!请准备!————————————

对比 Number.myCallNumber.myCall.myCall 的第一次调用,可以发现其实 obj 和 arguments 都没有变化,只是 this 的指向发生了变化:对于 Number.myCall 而言,myCall 的调用者是 Number,所以 this 指向了 Number;对于 Number.myCall.myCall 而言, 最后一个 myCall 的调用者是 Number.myCall,所以 this 指向了 myCall。再进一步,我们看看 Number.myCall.myCall.myCall,最后一个 myCall 的调用者是 Number.myCall.myCall,也就是 Funct.prototype.myCall,所以 this 指向了 myCall,和 Number.myCall.myCall 调用时 this 指向相同,所以输出的结果也相同,都是 2。

———————————————————— 预警结束~~~ ————————————————————

所以说呀,尽管 Number.myCall 和 Number.myCall.myCall 是同一个函数,而且传入的参数也相同,但是由于函数内部的 this 指向不同,导致了函数最后的执行结果不同。这就是基于 “原型链” 的动态语言 JavaScript的“神奇”之处,在基于类的语言如 C++、Java 中可是难以碰到这等好玩的事情呢~

写在最后

如果读者坚持看到了这里而且还没有晕的话,我真心地佩服你 ?? 想当初我看到这个执行结果,到最后弄明白的一个多小时里,我是一直处于懵逼的状态中。这里特别感谢和我一起探究的 avwo(他出品的代理工具 whistle 广受好评,大家可以试试~) 和提供文章题目的 litten

最后为了避免大家觉得我是疯子居然写出这种代码来,我在这里说明一下,之所以研究这个是因为看到下面一段代码,大家可以猜猜它的作用是什么(应该有不少人都见过~):

代码语言:javascript
复制
function arrayGenerator(length) {
  return Array.apply(null, { length: length }).map(Number.call, Number)
}

博客原文链接

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-12-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结果初探
  • 深入探究
    • Number.myCall
      • Number.myCall.myCall
      • 写在最后
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档