手写 call、apply、bind 函数
call
var name = 'waq'; var obj = { name: 'goodLuck' } function sayName(){ console.log(this.name); } //1、改变this指向函数 Function.prototype.myCall = function (context){ context = context || window; var args = [...arguments].slice(1); //第一个参数为context context.fn = this; //因为call的调用方式形如:sayName.call(obj),因此此时call方法的this指向为sayName,因此context.fn = this即为context.fn = sayName var result = context.fn(...args);
delete context.fn; return result; } //2、验证 //没有改变this指向时 sayName(); //waq //改变this指向后 sayName.myCall(obj); //goodLuck
- 首先 context 为可选参数,如果不传的话默认上下文是 window
- 因为 call 可以传入多个参数作为调用函数的参数,所以将参数单独抽取出来
- 接下来给 context 创建一个 fn 属性,并将值设置为需要调用的函数
- 删除对象上的函数,释放内存空间。返回结果
apply
apply 和 call 实现类似,不同的是参数处理,apply 传入的是数组。
Function.prototype.myApply = function (context){ context = context || window; context.fn = this; var result; if (arguments[1]){ result = context.fn(...arguments[1]); }else{ result = context.fn(); } delete context.fn; return result; }
bind
bind 会创建一个新函数,不会立即执行。bind 后面传入的这个参数列表可以分多次传入,call 和 apply 则必须一次性传入所有参数。
Function.prototype.myBind = function (context){ context = context || window; //返回一个绑定this的函数,这里我们需要保存this,具体保存的this如同call let self = this; let args = [...arguments].slice(1); //返回一个函数 return function () { let newArgs = [...arguments]; return self.apply(context, args.concat(newArgs)); } }
return function
是因为 bind
返回的是一个函数,并且这个函数不会执行,需要我们再次调用,那么当我们调用的时候,我们依旧可以对这个函数进行传递参数,即为支持柯里化形式传参,如下:
bind 可以实现类似这样的代码 f.bind(obj,1)(2)
所以需要在返回的函数中声明一个空的数组接收调用 bind
函数返回的函数时传递的参数,之后对两次的参数使用concat()
方法进行连接,调用ES5中的apply
方法,于是就有了这样的实现 args.concat(newArgs).