likes
comments
collection

🐟 🐟包教包会——this

作者站长头像
站长
· 阅读数 11

前言

实不相瞒,之所以前几天写几篇js底层原理的文章,都是因为我在看this这一篇的东西的时候涉及到了执行上下文,作用域链这些概念,然后这些概念自己又忘了。 😕

所以干脆把前面的东西都补起来,顺便以写文章的形式输出一下加深印象。

这里就到了我最初的起点了,this。

又是个老生常谈的问题,但这次我相信无论是多小白,也一定可以看的懂

为什么有this

话不多说,先看代码

var bar = {
    myName:"图图",
    printName: function () {
        console.log(myName)
    }    
}
let myName = "牛爷爷"
bar.printName()

如果感觉打印出来的是图图,那么你肯定就是把作用域链的查找机制加了进来,认为bar对象中,myName在当层printName中查找不到就去找外层。

哈哈哈,大错特错了,说明你对作用域链的概念还很模糊,或者说不熟。

如果赶时间也不要紧,大概就是因为作用域链一般是函数直接的嵌套然后进行一个变量的查找链。但这里的bar可不是函数,而是一个对象哦~ 所以当然不能在对象里面直接找,而是去全局中找,并且找到了牛爷爷。

这种反直觉的机制往往使我们很难受,在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 又搞出来另外一套 this 机制。

所以,在 JavaScript 中可以使用 this 实现在 printName 函数中访问到 bar 对象的 myName 属性了。具体该怎么操作呢?你可以调整 printName 的代码,如下所示:

    printName: function () {
        console.log(this.myName)
    }   

这样打印出来的结果就是图图了~

this是什么

关于 this,我们还是得先从执行上下文说起。在前面几篇文章中,我们提到执行上下文中包含了变量环境、词法环境、外部环境,但其实还有一个 this 没有提及,具体你可以参考下图:

🐟 🐟包教包会——this

从图中可以看出,this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this。

而其中全局执行上下文中的 this函数执行上下文中的 this是我们必须要知道的。

全局执行上下文中的 this

首先我们来看看全局执行上下文中的 this 是什么。

你可以在控制台中输入console.log(this)来打印出来全局执行上下文中的 this,最终输出的是 window 对象。所以你可以得出这样一个结论:全局执行上下文中的 this 是指向 window 对象的。

函数执行上下文中的 this

先看下面这段代码


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

我们在 foo 函数内部打印出来 this 值,执行这段代码,打印出来的也是 window 对象,这说明在默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。

这时你也许会好奇,嗯?怎么指来指去都是window,不能灵活一点吗?

改变this指向

通过函数的 call 方法设置

看下面这段代码

let bar = {
    myName : "图图"
  }
  function foo(){
    this.myName = "牛爷爷"
  }
  foo.call(bar)
  console.log(bar)

这里通过引用foo函数中的call方法,使自己本身中的this指向从window指向了bar,简单理解为this=bar,即

bar.myName = '牛爷爷'

最终改变了bar本身的myName的值。

其实除了 call 方法,你还可以使用 bindapply 方法来设置函数执行上下文中的 this,这里就不再赘述了。

通过对象调用方法设置

其实这就是我们最常见的情况

比如下面这段代码

var myObj = {
    name : "图图", 
    showThis: function(){
      console.log(this)
    }
  }
  myObj.showThis()

在这段代码中,我们定义了一个 myObj 对象,该对象是由一个 name 属性和一个 showThis 方法组成的,然后再通过 myObj 对象来调用 showThis 方法。执行这段代码,你可以看到,最终输出的 this 值是指向 myObj 的。

所以,你可以得出这样的结论:使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的

通过构造函数中设置


function CreateObj(){
  this.name = "图图"
}
var myObj = new CreateObj()

new关键字主要做了以下的工作

1. 创建了一个新的对象myobj
2. 将对象与构造函数通过原型链连接起来
3. 将构造函数的this绑定到新建的对象myobj上
4. 根据构造函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理

这样,我们就通过 new 关键字构建好了一个新对象,并且构造函数中的 this 其实就是新对象本身。

this的缺陷

this这种反直觉的感觉给我们带来了不少的不便,下面咱们就来一起看看那些 this 设计缺陷。

嵌套函数中的 this 不会从外层函数中继承


var myObj = {
  showThis: function(){
    console.log(this)
    function bar(){console.log(this)}
    bar()
  }
}
myObj.showThis()

我们在这段代码的 showThis 方法里面添加了一个 bar 方法,然后接着在 showThis 函数中调用了 bar 函数,那么现在的问题是:bar 函数中的 this 是什么?

如果你是刚接触 JavaScript,那么你可能会很自然地觉得,bar 中的 this 应该和其外层 showThis 函数中的 this 是一致的,都是指向 myObj 对象的,这很符合人的直觉。但实际情况却并非如此,执行这段代码后,你会发现函数 bar 中的 this 指向的是全局 window 对象,而函数 showThis 中的 this 指向的是 myObj 对象。这就是 JavaScript 中非常容易让人迷惑的地方之一,也是很多问题的源头。

我们一般使用 ES6 中的箭头函数来解决这个问题


var myObj = {
    name : "图图", 
    showThis: function(){
      console.log(this)
      var bar = ()=>{
        this.name = "牛爷爷"
        console.log(this)
      }
      bar()
    }
  }
  myObj.showThis()
  console.log(myObj.name)

执行这段代码,你会发现它也输出了我们想要的结果,也就是箭头函数 bar 里面的 this 是指向 myObj 对象的。这是因为 ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数。

普通函数中的 this 默认指向全局对象 window

上面我们已经知道了,在默认情况下调用一个函数,其执行上下文中的 this 是默认指向全局对象window的。

不过这个设计也是一种缺陷,因为在实际工作中,我们并不希望函数执行上下文中的 this 默认指向全局对象。这个问题可以通过设置 JavaScript 的“严格模式”来解决。在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined,这就解决上面的问题了。

小结

如果这篇文章对你有收获的话可以不要吝啬你的点赞哦~ 🤪🤪 往期文章:

参考文章:11 | this:从JavaScript执行上下文的视角讲清楚this

本文正在参加「金石计划 . 瓜分6万现金大奖」