Function.apply.call 特殊用法
最近群里由发了一个JS题目:
const pages = Array.apply(null, {
length: data.pages.length
}).map(Function.call, Number)
由于看不懂,先搜搜谷歌,找到一篇相关的问题:
stack overflow
https://stackoverflow.com/questions/37888470/mapping-array-in-javascript-with-sequential-numbers
// 首先是问题中的代码
let myArray = Array.apply(null, {length: 10}).map(Number.call, Number);
下面是一些伪代码
/**
* 1. Array.apply(null, { length: n })
* Array(n) // makes an array of length n
* Array.apply(null, { length: n }) Array with n indexes, but no elements
*/
[undefined, undefined, ...].map(Number.call, Number)
/**
* 2. map(func, this)
* write out the callback func as an anonymous func
*/
map(function() {
return Number.call.apply(this, arguments)
}, Number)
这里把Number.call 看做一个整体,使用apply传入this和传入函数的参数
Number.call = Function.prototype.call
当看做一个整体时,Number.call 实际上指向 构造函数的call方法,所以实际上Number.call 在这里也可以是Function.call,也可以是Function.prototype.call,回到了最开始讨论的题目
map(function() {
return Function.prototype.call.apply(this, arguments)
}, Number)
到这里,Function.call.apply是一种特殊用法,用于将不确定参数长度传入给里面的this
我们来理解一下这段会如何执行
Function.call.apply(method, arguments)
- 以method为作用对象调用call方法(指定调用者为this, 即method)
- 对于call方法,this才是执行的方法,因此等同于 method.call(),
- 由于执行了apply,入参的集合被指定为arguments,因此等同于 method.call(arguments[0], arguments[1], arguments[2], ...)
如果不理解的话可以想象我们平时使用call 方法,method.call(someThis, arg1, arg2, ...) 对于call而言,method就是调用call时的调用对象,即call执行时的this, 而传入的someThis 是作为method执行时的this使用的
再回到前面 :
map(function() {
return Number.call.apply(this, arguments)
}, Number)
里面的this由于map方法被指定为Number
因此执行下来就是
map(function() {
return Number.call.apply(Number, arguments)
})
又由于Number.call 实际是Function.prototype.call ,
call 方法由apply指定Number为this, arguments为入参后
map(function() {
return Number.call(arguments[0], arguments[1], ...)
})
由于arguments来自map传入函数的入参[element, index, array]
因此Number方法的this指定为element,参数为(index, array)
最后得到Number(index)
let myArray = Array.apply(null, {length: 10}).map(Number.call, Number);
由于前面的分析可以得到Number.call在这里的其实可以是Function.call,也可以是Function.prototype.call
与我们最开始的问题对应,整个过程分析完
参考:
https://segmentfault.com/q/1010000005778821
另外在搜索过程中,了解了Function.prototype.call(apply)内部是怎么执行的
关于Function.prototype.call
https://www.cnblogs.com/fsjohnhuang/p/4160942.html
http://es5.github.io/#x15.3.4.3
// 以下是伪代码 [[ xxx ]] 表示内部方法
// 当有一个方法func调用原型链上的call时
Function.prototype.call = function(thisArg, arg1, args2, ...) {
if (![[isCallable]](func)) { // 这里实际上是检查是否有[[call]]属性
throw new TypeError()
}
var argList = [].slice.call(arguments, 1) // 将入参放到argList
return func.[[Call]](thisArg, argList)
// 调用func的方法[[call]], thisArg (指向call方法指定的this), argList (函数入参)
}
// [[call]] 的调用是执行函数func本身的语句
// 参照:http://es5.github.io/#x13.2.1
Function.prototype.apply = function(thisArg, argArray) {
if (![[isCallable]](func)) {
throw new TypeError()
} //
if (argArray === null || argArray === undefined) {
return [[call]](thisArg, []) // 传空的参数列表
}
if (typeof argArray !== 'object') {
throw new TypeError()
}
let len = argArray.[[GET]].length // argArray[[GET]]方法 获取数组/类数组的长度
let n = ToUnit32(len) // 转换整数
let index = 0
let argList = []
for(index; index < n; index++) {
let indexName = ToString(index)
let nextArg = argArray.[[GET]](indexName) // 获取参数
argList.push(nextArg)
}
// 构建参数
return func.[[Call]](thisArg, argList)
}