一文理解变化多端的this🔮🔮🔮

121 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 20 天,点击查看活动详情

相信很多前端人对“this”的指向是很懵逼的,因为this的指向总是变幻莫测,在不同的调用环境中,它的指向总是各不相同。

在面试中,this也是经常考的必考题之一,很多前端老鸟经常会在this这里掉坑。接下来,让我们来一层一层的揭开this指向的面纱。

1. 事件调用环境中的this指向

<div class="box1"></div>

<div class="box2"></div>



<script>

  const box1 = document.querySelector('.box1')

  const box2 = document.querySelector('.box2')



  function event() {

    console.log(this)

  }



  box1.onclick = event  ------>  <div class="box1"></div>

  box2.onclick = event  ------>  <div class="box2"></div>

  event()   ------>  window对象



</script>

从上述实例中可以看出:

box1点击后,event方法中this指向div.class.box1

box2点击后,event方法中this指向div.class.box2

event()直接调用,this指向window对象,严格模式下指向的是undefined

  1. 当事件被动调用时,谁去触发,this就指向谁;
  2. 当时间主动执行时(事件后面加()), this指向的就是window,严格模式下指向的是undefined

2. 全局环境下的this指向

前端全局环境分两部分,浏览器环境node环境

浏览器全局环境

<script>

  var aaaa = 100000

  function aaatestEvent() {

    console.log(this)

  }

  // console.log(this) ------> window对象

  // aaatestEvent() === window.aaatestEvent() ------> window对象

  // aaa = window.aaa

</script>

我相信有很多小伙伴应该试过在script书写的时候直接打印出this的指向,看看this指向的是个什么东东?没错,指向的是window全局对象;我们经常定义的常量和变量也存放在window对象中

node环境

console.log(this)  -----> {}

console.log(this === module.exports)  -----> true

小伙伴从上述实例中可以看出,在node环境中,this指向的是暴露出来的对象

  1. 全局环境中,浏览器环境指向的是window对象
  2. node环境指向的是暴露出来的object对象

3. 函数内部环境中的this指向

在实际开发中,调用函数方法时,为了节约对内存的消耗,函数方法经常被存放于一个object对象中去,有利于方法的管理性和可读性

实例1:

<script>

  var obj = {

    num: 100,

    fn: function () {

      console.log(this)

    }

  }

  // 调用

 obj.fn() ----> this指向obj

  </script>

obj.fn()运行后,this指向的obj,可以发现,函数方法存在对象中的时候,函数内部的this指向的调用它的对象。

实例2:

<script>

  var obj = {

    num: 100,

    fn: function () {

      console.log(this)

    }

  }

  // 调用

 obj.fn() ----> this指向obj

  window.obj.fn() ---> this指向obj

  </script>
var obj1 = {

  num: 100,

  aaa: {

    fn: function () {

      console.log(this)

    }

  }

}



 // 调用

window.obj1.aaa.fn() ---> this指向aaa

通过实例1中的测试,我们稍微总结了下this指向的是调用它的对象;

我们在上面浏览器全局环境中讲过,定义常量和变量会被存放在window对象中,通过window对象输出,我们也能发现obj在window中,可以尝试去执行

window.obj.fn(),我们发现this还是指向obj啊?笔者刚刚不是说this指向的是调用它的对象吗?现在是window在调用obj的fn方法啊?怎么回事?

window.obj1.aaa.fn(),我们发现this指向了aaa啊?

解析:

实例1:obj.fn() fn被obj调用,this指向obj

实例2:window.obj.fn() fn被window调用,this指向obj

window.obj1.aaa.fn() fn被window调用,this指向aaa

实例1:【this指向调用它的对象】

实例2:【函数被多层对象包含,函数被最外层对象调用时,this指向的是它的上一级的对象】

请看下面的测试实例

<script>

  var obj = {

    num: 100,

    aaa: {

      fn: function () {

        console.log(this)

      }

    }

  };



  var abc = obj.aaa.fn;

  window.obj.aaa.fn()    ------> 指向aaa

  abc()      ------>指向window

  abc()等同于window.abc(),此处abc省略了window

</script>

解析上面实例

window.obj.aaa.fn() 就是上面总结的实例2,this指向的是它的上一级的对象

此时假如window.obj.aaa.fn被定义为到了一个abc上,调用abc()就相当于调用window.abc(), 实例1的总结是这样的:this指向调用它的对象,this的输出执行是abc方法被执行了,谁调用了?没错window对象调用了,所以this指向的就是window对象,此处非常的绕,请小伙伴们仔细思考哦!

4.构造函数环境中的this指向

凡事都是有例外的,当我们在写构造函数或者在写面向对象的逻辑中的时候,this的指向情况又不一样了,请看实例:

不含返回值的构造函数

<script>

  function fn() {

    this.num = 10

    console.log(this)

  }



  var obj = new fn();

  console.log(obj)

  /*  * new在此时的作用  *

  * 1. 调用fn这个函数方法

  * 2. 自动创建一个object对象

  * 3. 把创建出来的对象与this进行绑定

  * 4。如果构造函数没有返回值,隐式返回this对象

  * */

</script>

上面的例子可以看出

this返回的东西和fn new出来的实例返回的东西是一样的,这个就是new的魅力

构造函数中,this指向的是新创建出来的并且如果有属性值,进行绑定属性的新建对象

我们再来个复杂的,同时也是更有意思的实例:

<script>

  function fn() {

    this.num = 10

    console.log(this)

  }



  // 此处的num与fn中的num不是同一个东西哦

 fn.num = 20;

  // 每个方法都有自己的原型链,在原型链上定义一个num

 fn.prototype.num = 30;

  // 在原型链上定义一个method的方法

 fn.prototype.method = function () {

    console.log(this);

  }



  var prototype = fn.prototype;   就相当于var prototype = {}

  var method = prototype.method

  // 直接new fn

  new fn()

  new fn().method()

  prototype.method()

  method()

</script>

实例解析:

1.new fn() 刚刚说了,this指向的是新创建出来的并且如果又属性值,进行绑定属性的新建对象 ,所以,此时this输出的值是{num: 10}

2.new fn().method(), new的作用说了,先调用方法,然后创建一个对象,然后进行绑定this,最后输出;

new fn().method()就相当于{}.method(), 空对象{}绑定this下面的num = 10,所以此时this输出的值是{num: 10}

3.函数方法都有原型链,原型链就是一个实例化了空对象{}进行扩展,并绑定原型链上的属性(可以理解为new了一个原型链)

prototype.method()执行中,var prototype = fn.prototype 就相当于 var prototype = {},原型链上含有很多私有方法和定义属性等等,

比如我们定义的fn.prototype.num = 30; fn.prototype.method = function () { console.log(this); }

所以此时this输出的值是{num: 30,......}

4.method()就相当于window.method(),很显然,全局环境中的this指向的是window, 全局window对象没有定义num,所以如果输出this.num就是undefined

这个实例很大程度依赖小伙伴们对 js 基础知识掌握的考验

在没有返回值的构造函数中,this指向的是new实例化创建出来的对象,对象中与函数this进行绑定 (原型链相同原理,参考解析3)

有return返回值的构造函数

看实例:

<script>

  function fn() {

    this.num = 10;

    return ''

  /*    *  '' 空字符串 ---> 10

 *  [] 数组 --->undefined

 *  {} 对象 ---> 10

 *  123 数字 ---> 10

 *  function(){} 方法 ---> undefined

 *  null  ------> 10

 * */ }



  var obj = new fn()

  console.log(obj.num);

  </script>

从上述实例中,我们可以总结出

  1. 构造函数有返回值,当返回值是对象(type of进行类型查看)的时候,this指向的是实例化创建出来的对象(obj)
  2. 当返回值不是对象的时候,保持原本规则不变,
  3. 此处null是一个特例,返回null时,this指向的是实例化创建出来的对象(obj)

5.箭头函数环境中的this指向

经常有this指向发生偏移的时候,有些同伴就说,赶紧用ES6的箭头函数啊?这样this的指向就不会变了,至于为什么不会变,自己都说不出来的情况

接下来,让我们剖析一下箭头箭头函数

<script>

  var box1 = document.querySelector( '.box1' );

  var box2 = document.querySelector( '.box2' );



  box1.onclick = move1;

  box2.onclick = move2;



  function move1() {

    setTimeout(function () {

      console.log(this);

    }, 1000)

  }



  function move2() {

    setTimeout(() => {

      console.log(this);

    }, 1000)

  }</script>

解析:为何在setTimeout中使用function(){}和箭头函数() => {}this的指向不一样呢?

延迟调用的函数在setTimeout中是以入参的形式调用的

  1. 其实如果传入的入参提到外面进行定义,不就是如下面一样嘛,这是不难理解,fn()就是相当于window.fn(),所以this指向window对象
function fn() {

  console.log(this)

}

function move1() {

  setTimeout(fn, 1000)

}

2.入参以箭头函数形式进行程序执行,this输出为调用的节点了,是不是如下面一样了

<script>

  var obj = {

    fn: () => {

      console.log(this)

    }

  }

  // obj是不能形成独立作用域的

  obj.fn()  ------> this指向的是window对象

  </script>

箭头函数中的this指向官方文档指的是上下文环境中的this;

我们理解为:箭头函数本身是没有this和argument的,在箭头函数中调用this实际上就是在调用定义在上一层作用域的this指向;这里强调一下,指的是上一层作用域,因为对象是不能形成独立的作用域的。

有bug?想补充?

感谢大家观看这篇文章,有任何问题或想和我交流,请直接留言,

发现文章有不妥之处,也可指出交流,感谢阅读~

53875b26-8251-4ccc-ad02-70badf65d662.gif