手写改变this的方法

97 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

我们知道js中this指的是函数运行时所在的环境,我们不同的函数在运行的时候可能有些属性在不同的环境下我们想让其有不同的值,那么这个时候我们就可以通过改变this的指向来改变对应的属性。

let obj={
  age:22,
  test:function(){
    console.log(this.age);  
  }
}
obj.test();
let func = obj.test;
func();

image.png

可以看到我们的test函数在不同的运行环境的时候输出的值是不同的,这里具体输出是啥在把我搞晕的this----希望把你也搞晕有进行详细的讲解。

今天我们要分享的是通过call,bind和apply来进行改变this的实现。

call实现

我们先来看下call是怎么使用的

let obj={
  age:18
}
function test(args1,args2){
  console.log(this.age);
  console.log(args1,args2);
}
test.call(obj,"test1","test2");

image.png

我们可以看到通过call,我们传入一个第一个参数为运行的环境,后面的参数是所要改变的函数所需的参数集,且是要一个一个进行传递。

let obj = {
  age: 18,
};
function test(args1, args2) {
  console.log(this.age);
  console.log(args1, args2);
}
Function.prototype.myCall = function (context, ...args) {
  let _this = context || window;
  _this.func=this;
  let res=context.func(...args);
  delete context.func;
  return res;
};
test.myCall(obj,"test1","test2")

这里我们直接把方法声明在函数的原型上,这样所有的函数实例都可以进行调用,那么调用的时候此时的this就是函数本身,注意call的第一个参数如果没有传入的话,其值就是window,所以我们通过管道运算符来进行赋值,之后我们只要把当前的函数作为上下文对象的属性,这样调用的时候,其内部的this就会指向这个上下文this,函数运行后的返回值我们要在最后进行返回所以先进行暂存,之后由于不能凭空的给对象增加属性,所以最后我们要把刚才增加的属性删除,注意这里还用到了参数的收集语法,这样方便我们对参数进行传递。

apply

同样我们先来看看它是怎么使用的:

let obj = {
  age: 18,
};
function test(args1, args2) {
  console.log(this.age);
  console.log(args1, args2);
}
test.apply(obj,["test1","test2"])

image.png

可以看到其使用方式和call可以说是十分相似的,唯一的不同点就是参数的传递,call是一个个的传递,而apply是传递一个参数的数组,那么我们只要修改一下前面的一点代码就可以了。

let obj = {
  age: 18,
};
function test(args1, args2) {
  console.log(this.age);
  console.log(args1, args2);
}
Function.prototype.myApply = function (context,args) {
  let _this = context || window;
  _this.func=this;
  let res=context.func(...args);
  delete context.func;
  return res;
};
test.myApply(obj,["test1","test2"])

bind

老规矩我们先来看一看它是怎么使用的:

let obj = {
  age: 18,
};
function test(args1, args2) {
  console.log(this.age);
  console.log(args1, args2);
}
let func = test.bind(obj,"test1","test2");
let testObj=new func();
console.log(testObj)
func();

image.png

可以看到这个函数和前面两个的区别就有点大了,首先你调用bind的时候函数并不会马上执行而是返回一个改变了上下文的函数,此时你再执行才会调用该函数,同时既然是函数,那么它就可以作为构造函数,当作为构造函数的时候,你执行new操作的时候其this又指向它的实例化对象,同时注意到bind的第一个参数是上下文对象,而对于其他参数传递它是和call一样的,都是多个进行传递,基于这些知识我们就可以来手写一个bind看看:

let obj = {
  age: 18,
};
function test(args1, args2) {
  console.log(this.age);
  console.log(args1, args2);
}
Function.prototype.myBind = function (context) {
  // 获取参数
  const args = [...arguments].slice(1);
  const fn = this;
  return function Fn() {
    return fn.apply(
      this instanceof Fn ? this : context,
      // 当前的这个 arguments 是指 Fn 的参数
      args.concat(...arguments)
    );
  };
};
let func = test.bind(obj, "test1", "test2");
let testObj = new func();
console.log(testObj);
func();

这里要注意的就是我们通过原型链查找bind返回后的函数执行this是否属于返回函数,来判断是new操作还是直接执行。