实现call
Function.prototype.myCall = function(context){
if(typeof this != 'function')
throw new Error('type Error');
context = context || window//context上下文,指要指向的对象
const args = [...arguments].slice(1);//接收到的参数,因为第一个参数为要改变的this,不加入参数列表,用数组包裹是因为arguments不能作为数组使用slice方法
context.fn = this//把fn函数为this,因为this就是一个函数
let ans = context.fn(...args);//核心代码此时fn为this函数,调用改函数,因为调用者为context,可以改变改函数的this了
delete context.fn//只删除对象的fn函数
return ans;
}
function Fn(name) {
this.name = name;
console.log(this.name);
}
let obj = {
ha: 'uuu'
}
Fn.myCall(obj,'han')
console.log(obj.name);//han
具体步骤可以分为:
- 先判断context,即改变this的对象判断指向是否为空,即window。
- 处理拿过来的参数列表,借用赋值运算符用数组装起来
- 新增context一个函数,该函数的目的是作为中间值存调用者this函数,也就是请求更改this的函数
- 根据call特性,会自动执行一次调用者this函数,并且调用者为context起到改变this指向的目的,再处理下参数的传递即可
实现apply
Function.prototype.myApply = function(context){
if(typeof this != 'function')
throw new Error('type Error');
context = context || window;
context.fn = this
let ans = arguments[1]?context.fn(...arguments[1]):context.fn();//判断是否传值
delete context.fn;
return ans;
}
function fn(name, age) {
this.name = name;
this.age = age;
}
let obj = {
name: 'gogo'
}
fn.myApply(obj,['han',14]);
console.log(obj.name, obj.age);//han 14
与call不同的是apply传输的是数组。核心是想办法把接受到的数组解构出去。此时slice不能用的原因是传入的是arguement中一个是this,一个数组,解构后还是数组,也就是说到时候传过去还是数组,虽然可以接收的时候用赋值运算符解构,但是这显然不是原生的apply的效果
apply实现bind
Function.prototype.myBind = function(context){
context = context || window;//保存住原始的改变this的目标context
let fn = this;//保存初始调用者函数
let args = [...arguments].slice(1)//保存住原始的传过来的参数
return function Fn(){
return fn.apply(this instanceof fn?this:context,args.concat(...arguments))
}
}
其中fn.apply(this instanceof fn?this:context,args.concat(…arguments))
- fn为刚开始的调用者,改变目标函数不能变
- this instanceof fn?this:context:因为bind返回值是一个函数,所以后面可能有调用者,如果没有则更改的this还是前面的context
- args.concat(…arguments)):因为后面的时候还可能传参,一块放进去