专栏首页偏前端工程师的驿站JS魔法堂:再次认识Function.prototype.call

JS魔法堂:再次认识Function.prototype.call

一、前言                                                             

  大家先预计一下以下四个函数调用的结果吧!

var test = function(){
  console.log('hello world')
  return 'fsjohnhuang'
}
test.call() // ①
Function.prototype.call(test) // ②
Function.prototype.call.call(test) // ③
Function.prototype.call.call(Function.prototype.call, test) // ④

  揭晓:①、③和④. 控制台显示hello world,并返回fsjohnhuang。②. 返回undefined且不会调用test函数;

  那到底是啥回事呢?下面将一一道来。

二、从常用的call函数说起                                                        

  还是通过代码说事吧

var test2 = function(){
  console.log(this)
  return 'fsjohnhuang'
}
test2() // 控制台显示window对象信息,返回值为fsjohnhuang
test2.call({msg: 'hello world'}) // 控制台显示{msg:'hello world'}对象信息,返回值为fsjohnhuang

  test2.call实际上是调用 Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] ) ,而其作用我想大家都了解的,但其内部的工作原理是怎样的呢? 这时我们可以参考ECMAScript5.1语言规范。以下是参照规范的伪代码(各浏览器的具体实现均不尽相同)

Function.prototype.call = function(thisArg, arg1, arg2, ...) {
  /*** 注意:this指向调用call的那个对象或函数 ***/

  // 1. 调用内部的IsCallable(this)检查是否可调用,返回false则抛TypeError
  if (![[IsCallable]](this)) throw new TypeError()
  
  // 2. 创建一个空列表
  // 3. 将arg1及后面的入参保存到argList中
  var argList = [].slice.call(arguments, 1)

  // 4. 调用内部的[[Call]]函数
  return [[Call]](this, thisArg, argList)
}

  那现在我们可以分析一下 ①test.call() ,并以其为基础去理解后续的内容。它内部实现的伪代码如下:

test.call = function(thisArg, arg1, arg2, ...){
  if (![[IsCallable]](test)) throw new TypeError()

  var argList = [].slice.call(arguments, 1)
  return [[Call]](test, thisArg, argList)
}

  下面我们再来分析② Function.prototype.call(test) ,伪代码如下:

Function.prototype.call = function(test, arg1, arg2, ...){
  /***  Function.prototype是一个function Empty(){}函数  ***/

  if (![[IsCallable]](Function.prototype)) throw new TypeError()

  var argList = [].slice.call(arguments, 1)
  // 实际上就是调用Empty函数而已,那返回undefined是理所当然的
  return [[Call]](Function.prototype, test, argList)
}

三、Function.prototype.call.call内部究竟又干嘛了?                                       

  有了上面的基础那么Function.prototype.call.call就不难理解了。就是以最后一个call函数的thisArg作为Function.prototype.call的this值啦!伪代码如下:

// test作为thisArg传入
Function.prototype.call.call = function(test, arg1, arg2,...){
  if ([[IsCallable]](Function.prototype.call)) throw new TypeError()
  
  var argList = [].slice.call(arguments, 1)
  return [[Call]](Function.prototype.call, test, argList)
}

// test作为函数的this值
// 注意:入参thisArg的值为Function.prototype.call.call的入参arg1
Function.prototype.call = function(thisArg, arg1, arg2,...){
  if ([[IsCallable]](test)) throw new TypeError()

  var argList = [].slice.call(arguments, 1)
  return [[Call]](test, thisArg, argList)
}

四、见鬼的合体技——Function.prototype.call.call(Function.prototype.call, test) 

  看伪代码理解吧!

// test作为arg1传入
Function.prototype.call.call = function(Function.prototype.call, test){
  if ([[IsCallable]](Function.prototype.call)) throw new TypeError()
  
  var argList = [].slice.call(arguments, 1)
  return [[Call]](Function.prototype.call, Function.prototype.call, argList)
}

Function.prototype.call = function(test){
  if ([[IsCallable]](Function.prototype.call)) throw new TypeError()

  var argList = [].slice.call(arguments, 1)
  return [[Call]](Function.prototype.call, test, argList)
}

Function.prototype.call = function(thisArg){
  if ([[IsCallable]](test)) throw new TypeError()

  var argList = [].slice.call(arguments, 1)
  return [[Call]](test, thisArg, argList)
}

  这种合体技不就是比第三节的多了一个步吗?有必有吗?  

五、新玩法——遍历执行函数数组                            

Array.prototype.resolve = function(){
  this.forEach(Function.prototype.call, Function.prototype.call)
}
var cbs = [function(){console.log(1)}, function(){console.log(2)}]
cbs.resolve() 
// 控制台输出
// 1
// 2

   这是为什么呢?那先要看看 Array.prototype.forEach(fn, thisArg) 的内部实现了,伪代码如下:

Array.prototype.forEach = function(fn, thisArg){
  var item
  for (var i = 0, len = this.length; i < len; ++i){
    item = this[i]
    fn.call(thisArg, item, i, this)
  }
}

   大家再自行将编写 Function.prototype.call.call(Function.prototype.call, item, i,this) 的伪代码就明白了

六、总结                                          

  在项目中关于Function.prototype.call.call的用法确实少见,而且性能不高,本篇仅仅出于学习的目的,只希望再深入了解一下Function.prototype.call的内部原理而已。

七、参考                                           

在JavaScript的Array数组中调用一组Function方法

  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

Annotated ECMAScript 5.1

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CSS魔法堂:再次认识font

    一、前言                                 文字承载着站点内涵,而良好的字体、排版则为用户提供舒适的阅读体验。本文打算对字体稍微深...

    ^_^肥仔John
  • JS魔法堂:再识instanceof

    一、Breif                                  大家都知道instanceof一般就是用来检查A对象是否为B类或子类的实例。那...

    ^_^肥仔John
  • JS魔法堂:再识Number type

    Brief                                   本来只打算理解JS中0.1 + 0.2 == 0.300000000000000...

    ^_^肥仔John
  • JS魔法堂:再识Bitwise Operation & Bitwise Shift

    Brief                                 linkFly的《JavaScript-如果...没有方法》中提及如何手写Math....

    ^_^肥仔John
  • JS魔法堂:再识IE的内存泄露

    一、前言                               IE6~8除了不遵守W3C标准和各种诡异外,我想最让人诟病的应该是内存泄露的问题了。这阵子...

    ^_^肥仔John
  • CSS3魔法堂:认识@font-face和Font Icon

    一、前言                                过去我们总通过图片来美化站点的LOGO、标题、图标等,而现在我们可以通过@font-f...

    ^_^肥仔John
  • Vim魔法堂:认识快捷键绑定

    Brief                               习惯在VS上按<F5>来编译运行程序,刚用上VIM上就觉得无比的麻烦,而随着对VIM的学...

    ^_^肥仔John
  • JS读书心得:《JavaScript框架设计》——第12章 异步处理

    一、何为异步                                 执行任务的过程可以被分为发起和执行两个部分。 同步执行模式:任务发起后必须等待直...

    ^_^肥仔John
  • JS魔法堂:再识ASCII实体、符号实体和字符实体

    一、前言                                            相信大家都熟悉通过字符实体 &nbsp; 来实现多个连续空格的输...

    ^_^肥仔John
  • JS魔法堂:追忆那些原始的选择器

    一、前言                                                                            ...

    ^_^肥仔John
  • CSS魔法堂:重新认识Box Model、IFC、BFC和Collapsing margins

    前言                                   盒子模型作为CSS基础中的基础,曾一度以为掌握了IE和W3C标准下的块级盒子模型即可,...

    ^_^肥仔John
  • 划重点:js中的this、call、apply

    2、作为普通函数调用时,函数中的this指向全局对象,在浏览器环境中,指向的就是全局对象window

    前端_AWhile
  • JS魔法堂:doctype我们应该了解的基础知识

    一、前言                              什么是doctype?其实我们一直使用,却很少停下来看清楚它到底是什么,对网页有什么作用。本...

    ^_^肥仔John
  • 关于JS中this指向问题的探究

    本篇文章的所有例子来源都是《JS设计模式与开发实践》这本书,写这篇文章之前也去查阅了很多关于this指向问题的探讨,包括但不仅仅有像阮一峰老师,还有很多的博主的...

    何处锦绣不灰堆
  • 由浅入深的前端面试题 和矫情的“浪漫主义”诗句

    好吧,我承认太标题党了,这篇文章是通过一道前端面试题引出的纯技术讨论。我先要矫情无比的从中外诗歌说起。 传统的佛学经典里,被世人熟知的有这样一句话:“一花一世界...

    用户1667431
  • 给 Number 打 Call

    最近“打 call”这个词突然流行起来,我想到 call 在 JavaScript中可是个重要的方法,那么能不能用 JavaScript也来玩一把打 call ...

    IMWeb前端团队
  • CSS魔法堂:小结一下Box Model与Positioning Scheme

    前言  对于Box Model和Positioning Scheme中3种定位模式的细节,已经通过以下几篇文章记录了我对其的理解和思考。 《CSS魔法堂:重新...

    ^_^肥仔John
  • 只有JS基础扎实的攻城狮,才解得出的“密码”

    HTML5学堂-码匠:虽然很不想面对,但是明天,的确是节后工作的开始,今天就一边解密一边“适应”一下代码吧! Tips:如下的四道题目,是基于几家公司的面试真题...

    HTML5学堂
  • JavaScript 基础语法

    讲法声明 - 很重要,请先查看 关于JavaScript的讲法,我们采取的方式与HTML&CSS类似,先在课程中带大家,以“实现页面小功能”为目的,一步一步的进...

    HTML5学堂

扫码关注云+社区

领取腾讯云代金券