MDN对call方法的定义:
call()方法使用一个指定的 this值和单独给出的一个或多个参数来调用一个函数。
var foo = {
value: 1
};
function bar(){
console.log(this.value)
};
bar.call(foo); // 1
这段代码一共做了两件事情:
- call方法改变了this的指向,指向了foo
- 执行了bar函数
我们可以尝试着改变一下foo对象:
var foo = {
value: 1,
bar: function(){
console.log(this.value)
}
};
foo.bar(); // 1
这个时候的this就指向了foo,但是这样foo对象就会多了bar这个属性,因此,我们要删除这个属性,与之前foo对象保持一致。
这样,实现call方法可分为三个步骤:
- 将目标函数设置为对象的属性
- 执行这个函数
- 删除这个函数
// 1
foo.fn = bar
// 2
foo.fn()
// 3
delete foo.fn
根据这个步骤我们可以试着写一下call方法
Function.prototype.call2 = function(context){
context.fn = this
context.fn()
delete context.fn
}
用这个方法测试一下,能看到打印出来的结果依然是1。
那么下一个要解决的问题是:当无法得知传入的参数个数时,要怎么处理呢?
我们知道,arguments是一个对应于传递给函数参数的类数组对象(具有length属性,不具有数组的方法)。这样,我们就可以从argument是对象中取值,取出第二个到最后的一个参数,然后放进一个数组里。
var args = []
for(var i = 1;i < arguments.length;i++){
args.push('arguments[' + i + ']')
}
解决了不定长的问题,就要把参数放到要执行的函数参数里面去。
eval('context.fn(' + args + ')')
这样args就会自动调用Array.toString()这个方法。结合参数调整一下我们写的call方法。
Function.prototype.call2 = function(context){
context.fn = this
var args = []
for(var i = 0;i < arguments.length;i++){
args.push('arguments[' + i + ']')
}
eval('context.fn' + args + ')')
delete context.fn;
}
再次进行测试,也能验证没有问题。
还有最后一点需要考虑的问题,就是this为null的时候,指向window,以及需要返回值。
Function.prototype.call2 = function(context){
var context = context || window;
context.fn = this
var args = []
for(var i = 0;i < arguments.length;i++){
args.push('arguments[' + i + ']')
}
var result = eval('context.fn' + args + ')')
delete context.fn
return result
}
至此,就完成了call的模拟实现。
apply方法跟call方法类似
Function.prototype.apply = function(context,arr){
var context = Object(context) || window
context.fn = this
var result
if(!arr){
result = context.fn()
} else {
var args = []
for(var i = 0;i < arguments.length;i++){
args.push('arr[' + i + ']')
}
result = eval('context.fn(' + args + ')' )
}
delete context.fn
return result
}