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方法,你还可以使用bind和apply方法来设置函数执行上下文中的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 引擎做了如下四件事:
- 首先创建一个空对象
tempObj - 然后调用
CreateObj.call(tempObj)方法,将tempObj作为参数这样当CreateObj函数创建执行上下文时,它的this就指向了tempObj对象 - 执行
CreateObj函数,此时的this指向了tempObj对象 - 最后返回
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。

被折叠的 条评论
为什么被折叠?



