分享一个基础面试题---手写call
- 手写call笔记
- 第一步
- 第二步
- 第三步
手写call笔记
call():在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。
let foo = {value:1
};
function bar(){console.log(this.value);
}
bar.call(foo);//1
注意两点:
- call改变了this的指向,指向到foo;
- bar 函数执行了;
第一步
上述方式等同于:
let foo = {value:1;bar:function(){console.log(this.value)}
};
foo.bar();//1
这个时候this就指向了foo,但是这样却给foo对象本身添加了一个属性,所以我们用delete再删除它即可。
所以我们模拟的步骤可以分为:
- 将函数设为对象的属性;
- 执行该函数;
- 删除该函数;
以上个例子为例,就是:
//第一步
//fn 是对象的属性名,反正最后也要删除它,所以起什么名字都可以。
foo.fn = bar
//第二步
foo.fn()
//第三步
delete foo.fn
根据上述思路,提供一版:
//第一版
//将foo作为context参数传递
Function.prototype.call2 = function(context){//首先要获取调用call的函数,用this可以获取//将函数设为对象的属性context.fn = this;//执行该函数context.fn();//删除该函数delete context.fn;
}
第二步
call除了可以指定this,还可以指定参数
var foo = {value:1
};
function bar(name,age){console.log(name);console.log(age);console.log(this.value);
}
bar.call(foo,'ken',18);
可以从Arguments对象中取值,取出第二个到最后一个参数,然后放到一个数组里。
上述代码的Arguments中取第二个到最后一个参数。
//以上个例子为例,此时的arguments为:
// arguments = {
// 0:foo,
// 1:'ken',
// 2:18,
// length:3
//}
//因为arguments是类数组对象,所以可以用for循环
var args = [];
for(var i = 1,len = arguments.length;i<len;i++){args.push('arguments['+i+']');
}
//执行后args 为["arguments[1]","arguments[2]","arguments[3]"]
接下来使用eval拼接成一个函数
eval('context.fn('+args+')')
考虑到目前大部分浏览器在console中限制eval的执行,也可以使用rest
此处代码为:
//第二版
Function.prototype.call2 = function(context){context.fn = this;let arg = [...arguments].slice(1);context.fn(...arg);delete context.fn;
}
//测试一下
var foo = {value:1
};
function bar(name,age){console.log(name);console.log(age);console.log(this.value);
}
bar.call2(foo,'ken',18)
//ken
//18
//1
第三步
this
参数可以传null
,当为null
的时候,视为指向window
举个例子:
var value = 1;
function bar(){console.log(this.value);
}
bar.call(null);//1
- 针对函数,可以实现返回值
var obj = {value:1
};
function bar(name,age){return{value:this.value,name:name,age:age}
}
console.log(bar.call(obj,'ken',18));
//Object{
// value:1,
// name:'ken',
// age:18
//}
第三版:
Function.prototype.call3=function(context){//1.this为null,也可以写为=>context ?? window 或者 context=context??windowvar context = context || window;context.fn = this;let arg = [...arguments].slice(1)let result = context.fn(...arg)delete context.fn//2.有返回值return result
}
//测试一下
var value = 2;
var obj = {value:1
}
function bar(name,age){console.log(this.value);return{value:this.value,name:name,age:age}
}
bar.call3(null);//2
console.log(bar.call3(obj,'ken',18));//1
//Object{
// value:1,
// name:'ken',
// age:18
//}
这边给出的简化写法:
Function.prototype.call3 = function(context,..args){//判断是否是undefined和nullif (typeof context === 'undefined' || context === null){context = window}//每个从Symbol()返回的symbol值都是唯一的let fnSymbol = Symbol()context[fnSymbol] = this//入参...args === [...arguments].slice(1);let fn = context[fnSymbol](...args)//删除目的是不污染原来数据delete context[fnSymbol]return fn
}
可能会有新手宝宝们看完觉得还是很晦涩,可以动手写一遍试试,不行再多写两遍,好记性不如烂键盘,脑子再快也不如肌肉记忆哈哈哈~
当然也可以留言讨论啦~