深入理解this的指向

前言: 在学习javascript的过程中,this的指向一直是一个令人头疼的问题,很多新手包括我自己在内一开始都会对this的指向有这样那样的误解,本文是我在读了《你所不知道的JavaScript 上卷》后,对this的指向有了深一层次的理解,故写下这篇文章,希望能帮助更多人理解this的指向问题。


目录

1.为什么要使用this

2.this的指向

2.1this的四条指向规则

2.2 规则的优先级

2.3 判断this(ES5)

2.4绑定例外

3.箭头函数的this


1.为什么要使用this

在JavaScript的使用过程种,“类”一直是很重要的概念(实际上js中并不存在类),那么如何实现一个“类”呢?如果不使用this的话,那你的代码可能是这样的:

 function teacher(name, age) {  //创建教师类
    var tc = {};
    tc.name = name;
    tc.age = age;
    return tc;
  }

  function student(name,age) {   //创建学生类
    var sd = {};
    sd.name = name;
    sd.age =age;
    return sd; 
  }

  //实例化
  var teacher1 = teacher('wws', 30);      
  var student1 = student('frx', 18);

这样写有什么不好吗?嗯。。。好像看起来没啥问题,可是你怎么区分一个对象到底是老师还是学生呢?另外,如果我想teacher类新增一个方法,难道我是给每个实例都添加一个方法吗,还是给又重新对teacher类进行修改?毫无疑问,这些都是挺令人烦燥的事情,那有什么办法可以解决这个问题吗?有!使用this就是一个很好的办法。

下面是改进的代码:

 function teacher(name, age) {  //创建教师类
    this.name = name;
    this.age = age;
  }

  function student(name,age) {   //创建学生类
    this.name = name;
    this.age = age;
  }

  //实例化
  var teacher1 = new teacher('wws', 30);      
  var student1 = new student('frx', 18);

  //给teacher类新增一个方法
  teacher.prototype.teach = function() {
    console.log('teach');
  }

也许你看不懂这段代码,这里使用原型和原型链的相关的知识,你目前只要知道this有这个好处就行了,下面我们来细说this这个似乎是很深奥的问题。


2.this的指向

2.1this的四条指向规则

(1)默认绑定 

当函数独立调用时,即没有任何的前缀,this指向window,即全局对象。在严格模式下,this指向undefined。

      function foo() {
        console.log(this.a);
      }

      var a = "window";

      foo();     //输出: window

(2)隐式绑定

当通过某个对象进行调用时,this指向该对象。当函数调用时有多个前缀时,this指向最靠近函数的那个前缀所代表的对象。

      function foo() {
        console.log(this.a);
      }

      var a = "window";
      var obj = {
        a: "obj",
        fn: foo
      }

      foo();     //输出: window
      obj.fn();  //输出: obj
      window.obj.fn()  //输出:obj

其实本质上foo也可以认为是通过window对象调用,这样理解规则一也许会更加容易。

(3)显示绑定(即apply,call,bind的使用)

JavaScript中存在一些方法可以改变this的指向,比如call(...),apply(...),bind(...)。下面我会细说这三个方法:

call方法和apply方法在改变this的指向上作用时一致的,只不过它们的使用方法有所不同而已,但是这两个函数的第一个参数都是一个对象,这个就是给this准备的,在调用函数时将其绑定到this上。 下面是call方法和apply方法的使用方法:

call(obj:需要绑定到的对象,arg1:传入到函数的第一个参数,arg2:传入到函数的第二个参数,...)

apply(obj:需要绑定到的对象,array: 传入函数的参数数组)

举个栗子(注意看函数使用方法):

      function  foo(name) {
        console.log(name);
        console.log(this.age);
      }

      var age = 30;
      var person = {
        age: 18
      }

      foo('wws');  //输出: wws  30
      foo.call(person, 'frx');   //输出: frx 18
      foo.apply(person, ['frx']);  //输出: frx 18

你肯定觉得奇怪,bind呢?别急,bind就是我接下来要说的硬绑定。硬绑定也是显式绑定的一种,但是当使用硬式绑定后,this的指向就不能通过call,apply,bind进行重复绑定了。那么bind的使用方法是什么?其实它和call的使用方式是一样的,但它又有所不同,使用call和apply的函数会立即执行,但bind返回的是一个新的函数,返回的函数不能够再使用apply,call,bind来显式绑定this的指向了。

bind(obj:需要绑定到的对象,arg1:传入到函数的第一个参数,arg2:传入到函数的第二个参数,...)

说到这,你是不是又有点懵了?没关系,继续看下一个栗子:

      function  foo() {
        console.log(this.name);
      }

      var name = 'window';
      var person1 = {
        name: 'person1'
      };
      var person2 = {
        name : 'person2'
      } 

      var bar = foo.bind(person1);    //使用bind进行绑定

      bar();    //person1
      //尝试使用bind重新绑定bar中this的指向(严格模式会报错)
      bar.bind(person2)();   //person1

  可以看到我们尝试使用bind对已经硬绑定的函数进行绑定,但返回值仍然是person1,所以再次强调,使用硬绑定的函数的this的指向不能够再使用apply,call,bind来进行重新绑定。

(4)new绑定

        除了上述的3中绑定,我们使用new操作符时也会更改this的指向,因为new操作符的过程如下:

        ①    新创建一个对象。

        ②    将该对象的原型修改为函数的portotype属性(注意prototype和[[prototype]]不是同一个东西)。

        ③    将函数调用的this绑定到这个新创建的对象上。

        ④    如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新创建的对象。

废话少说,上代码:

      function Foo(name) {
        this.name = name;
      }

      var person = new Foo('frx');    //使用new操作符更改this的指向
      console.log(person.name);

2.2 规则的优先级

介绍完上面的四条规则,也许你会有疑惑,如果一个函数同时使用了bind,有使用了new操作符,那么this的指向该是什么样子的呢?接下来我们就讲述规则的优先级。

毫无疑问,默认绑定的优先级是四条规则中最低的,我们先不考虑它。

隐式绑定和显式绑定谁的优先级高的呢,也许你已经猜到了,但实践是检验真理的唯一标准嘛!我们还是来比较一下吧!

      function foo() {
        console.log(this.name);
      }

      const person1 = {
         name: 'frx',
         foo: foo
      }

      const person2 = {
        name: 'gp'
      }

      person1.foo();     //frx
      person1.foo.call(person2);  //gp

可以看到我们是call显示绑定后,this的指向由person1变成了person2,所以显示绑定的优先级比隐式绑定的优先级高

那么new绑定和隐式绑定谁的优先级高呢,我们再来比较一下:


      function foo(name) {
        this.name = name;
      }

      const obj = {
        foo: foo
      }

      obj.foo('frx');
      console.log(obj.name);  //frx

      var bar = new obj.foo('gp');
      console.log(obj.name);  //frx
      console.log(bar.name);  //gp

可以看到,我们先调用了obj.foo(),因为this指向obj,所以该方法给obj添加了一个新的属性name,接着我们使用new绑定,发现foo中的this已经被修改,所以bar.name就是我们传进去的参数,如果this仍然指向obj的话,obj.name应该被修改为gp。故new绑定的优先级比隐式绑定的优先级更高

好了,最后一个问题new绑定和显示绑定谁的优先级更高?由于new不能和apply/call一起使用,所以我们使用硬绑定bind来和new绑定进行比较。

      function foo(name) {
        this.name = name;
      }

      const person1 = {};

      //使用bind绑定
      const bar = foo.bind(person1);
      bar('frx');
      console.log(person1.name);  //frx
      //使用new绑定
      var person2 = new foo('gp');
      console.log(person1.name);  //frx 
      console.log(person2.name);  //gp

可以看到,我们先使用了bind对foo进行硬绑定到person1上,接着使用new绑定,如果new绑定的优先级比显示绑定的优先级低的话,person1.name应该被更改为gp,所以new绑定的优先级比显示绑定的优先级高

2.3 判断this(ES5)

判断this的指向的顺序:

(1)函数是否是在new中调用?如果是,this指向新创建的对象。

(2)函数是否使用了call、apply或硬式绑定?如果是,this指向指定的对象。

(3)函数是否通过某个对象调用(隐式绑定,即在某个上下文中调用),如果是,this指向那个上下文对象。

(4)如果都不是,在严格模式下,this指向undefined,非严格模式下指向window。

不过凡事总有例外,this的指向也一样。

2.4绑定例外

当我们使用显示绑定时,如果我们绑定的对象是null或者undefined的话,那么这个参数会被忽略,并且会将this绑定到window上。如:

      function foo() {
        console.log(this.a);
      }

      var a = 'window';

      foo.call(null);   //window

3.箭头函数的this

箭头函数的this会根据当前词法作用域来决定,也就是说箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。比如:

      function foo() {
        return () => {
          console.log(this.a);
        }
      }

      var a = 'window'
      var obj = {
        a: 'obj'
      }

      foo.call(obj)();  //obj
      foo()();          //window

上述箭头函数的this就根据foo中的this的指向确定的,foo中this指向谁,那么箭头函数中的this就指向谁,所以它和下面这段代码是等价的。

      function foo() {
        var that = this;
        return function() {
          console.log(that.a);
        }
      }

      var a = 'window'
      var obj = {
        a: 'obj'
      }

      foo.call(obj)();  //obj
      foo()();          //window

好了,相信你读到这对this的指向已经有了更深层次的理解了吧,如果有用的话帮忙点个赞哦o(* ̄▽ ̄*)ブ!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值