call方法是javascript中很常用的一个方法,其定义是:
TIP
call方法调用一个函数,其具有一个指定的this值和分别地提供的参数
简单来说,call方法可以在指定函数this的情况下调用这个函数,其余的参数和返回值部分不受影响。举个栗子:
var a = 1;
var obj = {
a: 2
}
function fun(name, age){
console.log(this.a);
return {
name: name,
age: age
}
}
fun("zhangsan",18); //打印1,返回 {name:"zhangsan", age:18}
fun.call(obj, "zhangsan", 18); //打印2,返回 {name:"zhangsan", age:18}
总结一下:
- 函数执行了
- this指向第一个参数
- 其他的参数照常传递
- 能有返回值
- 补充一点,第一个参数为null时,this默认执行window
下面我们就根据上面的特点,一步一步来模拟实现call
还是以上面的函数 fun 和对象 obj 为例
# 第一版
先来实现前两个功能,想指定函数的this,我们可以通过对象调用的方式,也就是obj.fun(),这时候fun中的this就指向obj了,我们来实现第一版代码:
Function.prototype.myCall = function(context) {
//首先获取函数fun,这里可以通过this来获取,obj也就是参数context啦
context.fn = this;
//将fn设为obj的一个方法
context.fn();
//莫名其妙给obj添加了一个方法总归是不好的,调用完成之后记得删掉这个方法
delete context.fn;
}
想通过obj.fun()的方式调用,把fun设置为obj的一个方法就好啦,记得调用之后删除。下面我们来测试一下
fun.myCall(context); //打印2
看到打印出2的时候还是很高兴的,终于走出第一步了,但是别高兴的太早了,下面还有好几步。
# 第二版
再来解决传参数和返回值的问题,参数是不固定的,但是我们有arguments对象,可以通过arguments获取参数,然后依次传给fun,来看代码
对
arguments对象不熟悉的可以另外查资料学习,这里就不展开啦。
Function.prototype.myCall=function(context){
context.fn = this;
var args = []; //用来存储参数
for(var i = 1,length = arguments.length;i < length;i++){
args.push(arguments[i]);
}
//注意arguments的第一个参数是指定this的对象,从第二个参数开始才是传给fun的函数,所以从1开始循环
var result = context.fn(...args); //接收返回值
delete context.fn;
return result;
}
调用试试:
var result = fun.myCall(obj, "zhangsan", 18); //打印2
console.log(result); //打印{name:"zhangsan", age:18}
又实现了,但是这里有个问题,在myCall中调用fn的时候,我们给函数穿参数的方式使用的是es6的展开运算符... ,这里考虑到兼容性,我们尽量使用老一点的方式,这里推荐使用eval函数,此时代码是这样的
Function.prototype.myCall = function(context) {
context.fn = this;
var args = [];
for(var i = 1,length = arguments.length;i < length;i++){
args.push('arguments[' + i + ']');
}
var result = eval('context.fn('+args+')');
delete context.fn;
return result;
}
# 最终版
还剩最后一个个功能,第一个参数为null时,this默认指向window,这个就很好实现啦,只需要判断一下context的值,为null时,让它默认为window,来看代码
Function.prototype.myCall = function(context) {
var context = context || window;
context.fn = this;
var args = [];
for(var i = 1,length = arguments.length;i < length;i++){
args.push(arguments[i]);
}
var result = context.fn(...args);
delete context.fn;
return result;
}
调用一下:
var result = fun.myCall(context,"zhangsan", 18); //打印2
console.log(result); //打印{name:"zhangsan",age:18}
var result = fun.myCall(null,"zhangsan", 18); //打印1
console.log(result); //打印{name:"zhangsan",age:18}
搞定!
# apply
apply和call功能一样,只是call传给函数的参数使用的是列表的形式,使用逗号隔开。而apply的第二个参数是参数数组,直接把参数放在数组里。
//call
fun.call(obj,arg1,arg2,arg3......)
//apply
fun.apply(obj,[arg1,arg2,arg3......])
模拟实现apply的思路和call一样,这里就不再分析一遍了,大家可以仿照思路自己试试实现,我直接贴出代码
Function.prototype.apply = function (context, arr) {
var context = context || window;
context.fn = this;
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
var result = eval('context.fn(' + args + ')')
delete context.fn
return result;
}
← new的模拟实现 debonce的模拟实现 →