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, ) // 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
最新文章
- VisualSVN-5.1.4补丁原创发布
- 转来的emacs配置文件,自动安装插件
- Hyper-V初涉_虚拟磁盘链错误的解决方案
- 2015 - 准备读书List
- 小数5.2500四舍五入保留1位小数的java算法之一
- PAT (Basic Level) Practise:1040. 有几个PAT
- WCF 之 OperationContract
- 用PHP实现单向链表
- poj 1265 Area(Pick定理)
- EM and GMM(Code)
- no device found for connection ‘ System eth0′问题
- Vue.set() this.$set()引发的视图更新思考
- python爬虫实战:利用scrapy,短短50行代码下载整站短视频
- 使用awk和sed获取文件奇偶数行的方法总结
- 学习Android过程中遇到的未解决问题(个人笔记,细节补充,随时更新)
- Vue2 第三天学习
- Mysql学习---面试基础知识点总结
- 【Treap 例题】神秘岛(island)
- 把默认功能关闭,当做普通IO口使用。
- Hadoop入门学习路线
热门文章
- ffmpeg进行视频转换
- CSS3之绽放的花朵(网页效果--每日一更)
- [后端人员耍前端系列]KnockoutJs篇:使用WebApi+Bootstrap+KnockoutJs打造单页面程序
- [翻译]理解Ruby中的blocks,Procs和lambda
- 《你必须知道的.NET》读书笔记:方法表初窥
- 字体大小自适应纯css解决方案
- [51单片机] 以PWM控制直流电机为例建一个简单的51工程框架
- [FPGA] 2、新建并运行一个工程
- easy-ui 小白进阶史(二):操作数据,easy-ui操作
- IOS 公共类-数字处理