11 | this:从JavaScript执行上下文的视角讲清楚
在对象内部的方法中使用对象内部的属性是一个非常普遍的需求
但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 又搞出来另外一套 this 机制。
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log(myName)
}
}
function foo() {
let myName = "极客时间"
return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()
相信你已经知道了,在 printName 函数里面使用的变量 myName 是属于全局作用域下面的,所以最终打印出来的值都是“极客邦”。这是因为 JavaScript 语言的作用域链是由词法作用域决定的,而词法作用域是由代码结构来确定的。
下来咱们就展开来介绍 this,不过在讲解之前,希望你能区分清楚 作用域链 和 this 是两套不同的系统,它们之间基本没太多联系。在前期明确这点,可以避免你在学习 this 的过程中,和作用域产生一些不必要的关联。
JavaScript 中的 this 是什么
关于 this,我们还是得先从执行上下文说起。在前面几篇文章中,我们提到执行上下文中包含了变量环境、词法环境、外部环境,但其实还有一个 this 没有提及,具体你可以参考下图:
从图中可以看出, this 是和执行上下文绑定 的,也就是说每个执行上下文中都有一个 this。
执行上下文主要分为三种——全局执行上下文、函数执行上下文和 eval 执行上下文
1.当函数作为对象的方法调用时,函数中的 this 就是该对象
- 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;
- 嵌套函数中的 this 不会继承外层函数的 this 值。
- 最后,我们还提了一下箭头函数,因为箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 this。
三种方式来设置函数执行上下文中的 this 值。
1. 通过函数的 call 方法设置
2. 通过对象调用方法设置:使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的。
3. 通过构造函数中设置
function CreateObj(){
this.name = "极客时间"
}
var myObj = new CreateObj()
其实,当执行 new CreateObj() 的时候,JavaScript 引擎做了如下四件事:首先创建了一个空对象 tempObj;接着调用 CreateObj.call 方法,并将 tempObj 作为 call 方法的参数,这样当 CreateObj 的执行上下文创建时,它的 this 就指向了 tempObj 对象;然后执行 CreateObj 函数,此时的 CreateObj 函数执行上下文中的 this 指向了 tempObj 对象;最后返回 tempObj 对象。
代码解释
var tempObj = {}
CreateObj.call(tempObj)
return tempObj
this 的设计缺陷以及应对方案
1. 嵌套函数中的 this 不会从外层函数中继承
var myObj = {
name : "极客时间",
showThis: function(){
console.log(this)
function bar(){console.log(this)} //window
bar()
}
}
myObj.showThis()
函数 bar 中的 this 指向的是全局 window 对象,而函数 showThis 中的 this 指向的是 myObj 对象
解决问题
1声明一个变量 self 用来保存 this,然后在 bar 函数中使用 self,代码如下所示:
这个方法的的本质是把 this 体系转换为了作用域的体系。
第一种是把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数。
var myObj = {
name : "极客时间",
showThis: function(){
console.log(this)
var self = this
function bar(){
self.name = "极客邦"
}
bar()
}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)
2你也可以使用 ES6 中的箭头函数来解决这个问题
这是因为 ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数。
第二种是继续使用 this,但是要把嵌套函数改为箭头函数,因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this。
var myObj = {
name : "极客时间",
showThis: function(){
console.log(this)
var bar = ()=>{
this.name = "极客邦"
console.log(this)
}
bar()
}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)