一、前言                                                             

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

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, ) // 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, )
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, )
// 实际上就是调用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, )
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, )
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, )
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, )
return [[Call]](Function.prototype.call, test, argList)
} Function.prototype.call = function(thisArg){
if ([[IsCallable]](test)) throw new TypeError() var argList = [].slice.call(arguments, )
return [[Call]](test, thisArg, argList)
}

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

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

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

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

Array.prototype.forEach = function(fn, thisArg){
var item
for (var i = , 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的内部原理而已。

尊重原创,转载请注明在:http://www.cnblogs.com/fsjohnhuang/p/4160942.html ^_^肥仔John

七、参考                                           

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

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

Annotated ECMAScript 5.1

JS魔法堂:再次认识Function.prototype.call的更多相关文章

  1. JS魔法堂:jsDeferred源码剖析

    一.前言 最近在研究Promises/A+规范及实现,而Promise/A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(<JavaScript框架设计& ...

  2. JS魔法堂:不完全国际化&本地化手册 之 实战篇

    前言  最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...

  3. JS魔法堂:属性、特性,傻傻分不清楚

    一.前言 或许你和我一样都曾经被下面的代码所困扰 var el = document.getElementById('dummy'); el.hello = "test"; con ...

  4. JS魔法堂:那些困扰你的DOM集合类型

    一.前言 大家先看看下面的js,猜猜结果会怎样吧! 可选答案: ①. 获取id属性值为id的节点元素 ②. 抛namedItem is undefined的异常 var nodes = documen ...

  5. JS魔法堂:追忆那些原始的选择器

    一.前言                                                                                                 ...

  6. JS魔法堂:判断节点位置关系

    一.前言 在polyfill querySelectorAll 和写弹出窗时都需要判断两个节点间的位置关系,通过jQuery我们可以轻松搞定,但原生JS呢?下面我将整理各种判断方法,以供日后查阅. 二 ...

  7. JS魔法堂:LINK元素深入详解

    一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...

  8. JS魔法堂:浏览器模式和文档模式怎么玩?

    一.前言 从IE8开始引入了文档兼容模式的概念,作为开发人员的我们可以在开发人员工具中通过“浏览器模式”和“文档模式”(IE11开始改为“浏览器模式”改成更贴切的“用户代理字符串”)品味一番,它的出现 ...

  9. JS魔法堂:精确判断IE的文档模式by特征嗅探

    一.前言 苦逼的前端攻城狮都深受浏览器兼容之苦,再完成每一项功能前都要左顾右盼,生怕浏览器不支持某个API,生怕原生API内含臭虫因此判断浏览器类型和版本号成了不可绕过的一道关卡,而特征嗅探是继浏览器 ...

  10. JS魔法堂:不完全国际化&本地化手册 之 理論篇

    前言  最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...

随机推荐

  1. Android Scroll分析——滑动效果产生

    相对于在Android2.x版本上出现的长按.点击事件的效果,不得不说,滑动操作具有更好的用户体验.因此,从Android 4.X版本开始,出现了更多滑动操作的效果.越来越多第三方应用模仿这样的效果, ...

  2. ASP.NET 5 入门 (2) – 自定义配置

    ASP.NET 5 入门 (2) – 自定义配置 ASP.NET 5 理解和入门 建立和开发ASP.NET 5 项目 初步理解ASP.NET5的配置 正如我的第一篇文章ASP.NET 5 (vNext ...

  3. 使用NPOI读写Excel、Word

    NPOI 是 POI 项目的 .NET 版本.POI是一个开源的Java读写Excel.WORD等微软OLE2组件文档的项目. 使用 NPOI 你就可以在没有安装 Office 或者相应环境的机器上对 ...

  4. 使用ACE_Get_Opt解析命令行

    当我们用C++开发一些C++控制台小工具时,会需要一些用户输入的参数来决定程序如何工作和执行,而用户输入参数的方式大部分都是采用命令行参数的方式. 比如上一篇文章 玩转Windows服务系列--命令行 ...

  5. Window Ghosting

    最近工作中遇到Window Ghosting这个问题, 感觉挺有意思,这里简单记录下. 在XP时代我们的程序没有响应后只能通过任务管理器强制杀掉,但是Vista之后情况变了, 我们仍然可以拖动失去响应 ...

  6. AWS系列之一 亚马逊云服务概述

    云计算经过这几年的发展,已经不再是是一个高大上的名词,而是已经应用到寻常百姓家的技术.每天如果你和互联网打交道,那么或多或少都会和云扯上关系.gmail.github.各种网盘.GAE.heroku等 ...

  7. AngularJS快速入门指南17:Includes

    使用AngularJS,你可以在HTML中包含其它的HTML文件. 在HTML中包含其它HTML文件? 当前的HTML文档还不支持该功能.不过W3C建议在后续的HTML版本中增加HTML import ...

  8. ios 向工程里添加Fonts

    ios 向工程里添加Fonts 1.网上搜索字体文件(后缀名为.ttf,或.odf),假如你加入的字体为微软雅黑 2.把字体库导入到工程的resouce中 3.然后在你的工程的Info.plist文件 ...

  9. Atitit 理解Monad attilax总结

    Atitit 理解Monad attilax总结 但函数式编程最大的一个问题是,函数是一个数学抽象,在现实世界中不存在,1 那既然这样就够用了,还要 Monad 干嘛?Monad 的作用在这里就体现出 ...

  10. Linux常用命令01

    Linux对于我们来说,就是跑程序的运行平台,简单的来说,就是服务器,自己也没怎么系统的学习Linux的命令,随着项目的需要, 比如要查找日志,哪里出问题了,哪里报错了,因此自己也慢慢地懂一些常用的L ...