JS的this机制

背景

下面这段实例代码

let bar = {
  myname: 'yy',
  printname: function() {
    console.log(myname)
  }
}
let myname = 'qq'
bar.printname()
// 执行结果:qq

在对象内部的方法中使用对象内部的属性是一个非常普遍的需求,但是 JavaScript 的作用域机制并不支持这一点。基于这个需求,JavaScript 又搞出来另外一套 this机制。你需要修改 printname 函数为:

printname: function() {
  console.log(this.myname)
}

这样获取到的值才是 bar 对象内部的属性 myname,结果为 yy

接下来展开介绍 this,作用域链和 this 是两套不同的系统,它们之间基本没太多联系,首先明确这点,以免在学习 this 过程中,无意识的和作用域链产生一些不必要的关联。

JavaScript 中的 this 是什么

this 是和执行上下文绑定的,也就是说每个执行上下文都有一个 this。执行上下文分为三种——全局执行上下文、函数执行上下文以及 eval 执行上下文,对应的 this 也有三种——全局执行上下文的 this、函数中的 this以及 eval 中的 this。重点介绍全局执行上下文中的 this函数执行上下文中的 this

全局执行上下文中的 this

在控制台中输入 console.log(this) 输出 window 对象,所以你可以得出这样一个结论:全局上下文中的 this 是指向 window 对象的。这也是 this 和作用域链的唯一支点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象

函数执行上下文中的 this

执行下面这段示例代码

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

打印出来的也是 window 对象,这说明在默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象。那如何设置这个 this 指向其他对象呢?

通过函数的 call 方法设置 this 指向

let bar = {
  myname: 'yy',
  test1: 1
}
function foo() {
  this.myname = 'qq'
}
// foo()
// console.log(bar) // {myname: 'yy', test1: 1}
// console.log(myname) // qq
foo.call(bar)
console.log(bar) // {myname: 'qq', test1: 1}
console.log(myname) // Uncaught ReferenceError: myname is not defined

执行这段代码输出的结果可以看出,bar 对象的 myname 属性已经变成 qq 了,同时在全局执行上下文中打印 myname JavaScript 引擎会提示该变量未定义。

除了 call 方法,你还可以使用 bindapply 方法来设置函数执行上下文中的 this,它们在使用上有一些区别,可以参考文章 JS 实现 bind

通过对象调用方法设置 this 指向

示例代码1:

var myobj = {
  name: 'yy',
  showthis: function() {
    console.log(this)
  }
}
myobj.showthis() // {name: 'yy', showthis: ƒ}

通过 myobj 对象来调用 showthis 方法,执行后最终输出的 this 值指向的就是对象 myobj

实例代码2:

var myobj = {
  name: 'yy',
  showthis: function() {
    this.name = 'qq'
    console.log(this)
  }
}
var foo = myobj.showthis
foo()

执行这段代码,你会发现 this 又指向了 window 对象。对比以上两段示例代码可以得出这样的结论:

  • 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window 对象
  • 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向该对象本身

通过构造函数中设置 this 指向

function CreateObj() {
  this.name = 'yy'
}
var myobj = new CreateObj()

在执行 new CreateObj() 时,JavaScript 引擎做了如下四件事:

  1. 首先创建一个空对象 tempObj
  2. 然后调用 CreateObj.call(tempObj) 方法,将 tempObj 作为参数这样当 CreateObj 函数创建执行上下文时,它的 this 就指向了 tempObj 对象
  3. 执行 CreateObj 函数,此时的 this 指向了 tempObj 对象
  4. 最后返回 tempObj 对象

用代码来直观演示如下:

var tempObj = {} // 创建
CreateObj.call(tempObj) // 执行(给 tempObj 添加属性和方法)
return tempObj // 返回

this 的设计缺陷及应对方案

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

var myobj = {
  name: 'yy',
  showthis: function() {
    console.log(this)
    function bar() {
      console.log(this)
    }
    bar()
  }
}
myobj.showthis()

执行这段代码,相信你已经知道 showthis 中打印的 this 指向的是 myobj 对象,但是 bar 函数中 this 的指向呢?
其实 bar 函数中的 this 指向的是 window 对象。

从这里可以看出,this 没有作用域的限制,它和变量是不一样的,所以嵌套函数不会从调用它的函数中继承 this

这样会造成很多不符合直觉的代码,要解决这个问题,有以下两种思路

保存 this 为变量

第一种是把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数。上述代码中将 showthis 中定义一个 self = this 变量来保存 this,然后就可以在 bar 函数内部使用 myobj 对象了。就像这样:

不熟悉作用域机制的可以查看这篇 JS作用域链和闭包

var myobj = {
  name: 'yy',
  showthis: function() {
    console.log(this)
    var self = this
    function bar() {
      self.name = 'qq'
    }
    bar()
  }
}
myobj.showthis() // {name: 'yy', showthis: ƒ}
console.log(myobj.name) // qq

这个方法的本质是把 this 体系转换为了作用域的体系

嵌套函数改为箭头函数

第二种是继续使用 this,但是要将嵌套函数改为箭头函数,将上述代码做如下改动:

var myobj = {
  name: 'yy',
  showthis: function() {
    console.log(this)
    var bar = () => {
      this.name = 'qq'
    }
    bar()
  }
}
myobj.showthis() // {name: 'yy', showthis: ƒ}
console.log(myobj.name) // qq

当使用箭头函数来定义函数 bar 时,函数 bar 内的 this 是指向 myobj 对象的。这是因为箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数

普通函数在执行时确定 this 对象,箭头函数是定义时确定 this 对象。关于更多 JS 中函数的 this 指向问题可以参考 JS 中函数的 this 指向

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

调用一个函数时,其执行上下文中的 this 是默认指向全局对象 window 的。但是作为开发者,我们并不希望如此,因为这样会打破数据的边界,造成一些误操作。如果要让函数指向上下文中的 this 指向某个对象,最好的方式是通过 call 方法来显示调用。

这个问题可以通过设置 JavaScript 的“严格模式”来解决,严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值