回顾一下官方call的用法:
var value = 'v in window';
function func() {
console.log(this.value);
}
var obj = {
value: 'v in obj'
};
func(); //v in window
func.call(obj); // v in obj
call函数在这里修改了func运行时this的指向,相当于console.log(obj.value),所以输出的就是obj内部的value了。
根据这个原理,自己分装一个call:
var value = 'v in window';
function func() {
console.log(this.value);
}
var obj = {
value: 'v in obj'
};
Function.prototype.call2 = function (obj) {
obj.fn = this;
obj.fn();
delete obj.fn; //记住最后要删除掉临时添加的方法,否则obj就无缘无故多了个fn
};
func(); //v in window
func.call2(obj); // v in obj
call2方法里的this是指func这个函数,因为是func调用的,obj.fn = this给传进来的obj对象克隆了一个func这个方法,让obj再调用这个方法,此时是obj调用这个方法,就相当于console.log(obj.value),达到了call的效果。(记得最后要delete掉obj临时添加的fn)
官方call是可以传参数的,现在处理一下传参的问题:
var value = 'v in window';
function func() {
arguments = [].splice.call(arguments, 0); //这一句是把参数转成数组,因为arguments不是数组,输出的时候容易观察
console.log(arguments);
console.log(this.value);
}
var obj = {
value: 'v in obj'
};
Function.prototype.call2 = function (obj) {
obj.fn = this;
obj.fn();
delete obj.fn; //记住最后要删除掉临时添加的方法,否则obj就无缘无故多了个fn
};
func(1,2,3);
//[1,2,3]
//v in window
func.call2(obj);
//[]
// v in obj
首先现在call方法里接收参数:
var args = []; //存放参数的数组
for(var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']'); //这样存进去是有原因的,后面讲
}
做一个循环把参数存进args数组,因为auguments的第一个参数是obj,所以直接从i=1开始遍历,存放好的数组结构是这样的:
console.log(args); //["arguments[1]", "arguments[2]", "arguments[3]"]
这是因为我们平时传参的习惯是这样的:
func(1,2,3);
那么我们在call2方法里要做到的效果是这样的:
正确姿势:
obj.fn(arguments[1], arguments[2], arguments[3]); //传参代码正确姿势
错误姿势1:
obj.fn(args.join(',')); //obj.fn('arguments[1], arguments[2], arguments[3]')
对比正确姿势后你会发现,其实你传的只是一个字符串:‘arguments[1], arguments[2], arguments[3]’
错误姿势2:
obj.fn(args); //相当于obj.fn([1,2,3]),和正确的obj(1,2,3)对比一下就知道错哪了
所以这里要用到eval函数,eval函数可以把string字符串里的内容运行,例如:
var str = 'console.log(1)';
eval(str); //输出1
console.log(1); //输出1
那么把obj.fn()和arguments[1], arguments[2], arguments[3]拼接一下就可以变成正确姿势了。
由于+号拼接自动会将arr类型的进行toString(),‘obj.fn(’+args+’)‘里面的args数组[“arguments[1]”, “arguments[2]”, “arguments[3]”]会自动变成’arguments[1],arguments[2],arguments[3]’,最后就变成了:
'obj.fn(arguments[1], arguments[2], arguments[3])' //一个字符串
最后用eval函数运行一下这个string字符串,就得到了这个效果:
obj.fn(arguments[1], arguments[2], arguments[3])
完整代码:
var value = 'v in window';
function func() {
arguments = [].splice.call(arguments, 0);
console.log(arguments);
console.log(this.value);
}
var obj = {
value: 'v in obj'
};
Function.prototype.call2 = function (obj) {
var args = [];
for(var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
obj.fn = this;
eval('obj.fn('+args+')');
delete obj.fn;
};
func(1,2,3);
//[1,2,3]
//v in window
func.call2(obj,1,2,3);
//[1,2,3]
// v in obj
当参数为null时,官方call会返回将this变成window:
var value = 'v in window';
function func() {
console.log(this.value);
}
var obj = {
value: 'v in obj'
};
func(); //v in window
func.call(null); //v in window
需要修改一下call2函数,当obj为null时obj=window:
Function.prototype.call2 = function (obj) {
var obj = obj || window;
var args = [];
for(var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
obj.fn = this;
eval('obj.fn('+args+')');
delete obj.fn;
};
最后处理一下call2返回值的问题,官方call是有返回值的:
var value = 'v in window';
function func() {
return this.value;
}
var obj = {
value: 'v in obj'
};
Function.prototype.call2 = function (obj) {
var obj = obj || window;
var args = [];
for(var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
obj.fn = this;
eval('obj.fn('+args+')');
delete obj.fn;
};
console.log(func.call(obj)); //v in obj
console.log(func.call2(obj)); //undifined
这是因为call2函数中并没有return,这里定义一个result用来接收返回值再返回去。
最终代码:
Function.prototype.call2 = function (obj) {
var obj = obj || window;
var args = [];
for(var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
obj.fn = this;
var result = eval('obj.fn('+args+')');
delete obj.fn;
return result;
};