关于this,apply,call,bind(手动实现)

171 阅读4分钟

什么是this

  • this是JavaScript语言中的关键字。也叫全局上下文对象。是在函数运行时自动在内部生成的一个对象

this指向的几种情况

1.在全局运行环境中(在任何函数体的外部),this指向的是全局对象window

    console.log(this === window)  // true
    this.b = "web"
    console.log(window.b) // "web"

2.在函数内部,this指向取决于函数的调用方式

  • 我们来看一段代码
    var name = "资讯";
    var obj = {
      name: "沸点",
      say: function () {
        console.log(this.name);
      },
    };

在上述代码中,我们定义了一个name变量和一个obj对象,对象里有一个say方法,那么我们接下来要通过调用方式的不同看看say方法里面的this.name到底指向的是哪个name

Ⅰ. 直接调用法(这个时候this指向的是该方法的拥有者)

 obj.say() // returns "沸点"

Ⅱ. 普通函数调用法(这个时候this指向的是全局window对象)

    var mySay = obj.say;
    mySay() //  returns "资讯"

Ⅲ. 作为构造函数被调用(这个时候this指向的就是这个新对象)

    var name = "掘金";
    var obj = {
      name: "资讯",
      say: function () {
        this.name = "沸点";
      },
    };
    var osay = new obj.say(); 
   // 这个函数会立即执行,但是执行过后并没有改变全局window的name,所以这时候的this指向的是new实例过后生成的新对象
    console.log(name); // returns '掘金'
    console.log(osay.name); // returns '沸点'
    

3.通过call apply bind进行调用

首先我们要明确,这三个方法都是用来改变this指向的,那么三者有什么区别呢?

  1. call方法接受的第一个参数是this的指向,后面传入的是一个参数列表。当一个参数为null或undefined的时候,表示指向window(在浏览器中),call只是临时改变一次this指向,并立即执行。
    var fn = function (age, sex) {
      console.log(
        "我叫" + this.name + ",我今年" + age + "岁了!" + "我是" + sex
      );
    };

    var obj = {
      name: "二狗子",
    };
    // call 方法接受的第二个参数可以一个一个的传
    fn.call(obj, 24, "男生"); // '我叫二狗子,我今年24岁了!我是男生'
  1. apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),call只是临时改变一次this指向,并立即执行。
    var fn = function (age, sex) {
      console.log(
        "我叫" + this.name + ",我今年" + age + "岁了!" + "我是" + sex
      );
    };
    var obj = {
      name: "二狗子",
    };
    // apply 方法接受的第二个参数是一个数组
    fn.apply(obj, [24, "男生"]);// '我叫二狗子,我今年24岁了!我是男生'
  1. bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
    var fn = function (age, sex) {
      console.log(
        "我叫" + this.name + ",我今年" + age + "岁了!" + "我是" + sex
      );
    };
    var obj = {
      name: "杨泽",
    };
    // 参数可以分多次传入
    const o_bind = fn.bind(obj, 24);
    o_bind("男生"); // '我叫二狗子,我今年24岁了!我是男生'

手动实现call,apply,bind

1.实现call方法

    Function.prototype.myCall = function (thisArg, ...rest) {
      // 对传入的参数进行判断
      if (typeof thisArg === "object") { 
        // 如果传入为null的话就指向window
        thisArg = thisArg || window
      } else {
        // 类型不正确就创建一个新对象
        thisArg = Object.create({});
      }
      // 创建一个唯一的值
      const fn = Symbol("fn");
      // 通过改变调用方式的方法改变this指向
      // this 就是调用myCall方法的函数,thisArg就是this要指向的对象
      thisArg[fn] = this; 
      // 在当前对象的作用域下面执行这个函数,就改变了这个函数的this指向,同时把剩余参数传过去
      const result = thisArg[fn](...rest); 
      delete thisArg[fn]; // 最后把这个属性删除,因为本身这个对象里是不需要这个属性的
      return result
    };
    const obj = {
      name: "二狗子",
    };
    function Fn(age, sex) {
      this.age = age;
      this.sex = sex;
      console.log(this.name, age, sex);  
    }
    Fn.myCall(obj, 24, "男"); // 二狗子 24 男

2.实现apply方法

 Function.prototype.myApply = function (context, args) {
      //  首先判断第一个参数是不是正确的格式
      if (typeof context === "object") {
        context = context || window;
      } else {
        context = Object.create({});
      }
      // 创建一个唯一的值
      const fn = Symbol("fn");
      context[fn] = this;
      const result = context[fn](...args);
      delete context[fn];
      return result;
    };

    const obj = {
      name: "二狗子",
    };
    function Fn(age, sex) {
      this.age = age;
      this.sex = sex;
      console.log(this.name, age, sex); // 二狗子 18 男
    }

    Fn.myApply(obj, [18, "男"]);

3.实现apply方法

    Function.prototype.myBind = function (context, ...args) {
      // 把函数先保存起来
      const me = this;
      // bind不是立即调用,所以需要再返回一个函数
      return function (...arg) {
        // 利用call方法
        return me.call(context, ...args, ...arg);
      };
    };

    const obj = {
      name: "二狗子",
    };
    function Fn(age, sex, address) {
      this.age = age;
      this.sex = sex;
      this.address = address;
      console.log(this.name, age, sex, address); // 二狗子 18 男 山西
    }

    const bindFn = Fn.myBind(obj, 18, "男");
    // 参数可以分为两次传入
    bindFn("山西");

结语

就是单纯的做一些总结和学习,如果您看到有哪不正确或者不完整的还希望能指出来,大家一起学习,共同进步!