阅读 100
JS从执行上下文了解this

JS从执行上下文了解this

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

为什么要有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()
复制代码

按照常理来说,大多数语言在执行bar.printName()的时候会输出bar对象中的myName而不是全局的myName.而JavaScript却输出的是全局中的变量,这是因为JavaScript语言的作用域链是由词法作用域决定的,而词法作用域是由代码结构决定的

对象内部的方法要使用对象内部的属性是一个普遍的需求但是JavaScript的作用域机制并不支持这一点基于这个原因,JavaScript又有一套this机制来处理

如何让对象中的函数能读取到对象中的属性呢,改造一下上述代码

var bar = {
    myName:"time.geekbang.com",
    printName: function () {
        console.log(this.myName)
    }    
}
let myName = '全局作用域'
bar.printName()
复制代码

这样就可以获取到了。

首先你需要明白一件事,就是作用域链和this是两套不同的系统,两者之间没有太多的联系

this是什么?

之前有说执行上下文中包括变量环境,词法环境和outer,其实还包括了this。

image-20210808154914549

this和执行上下文是绑定的,每个执行上下文中都有一个this。

执行上下文包括三种:

  • 全局执行上下文
  • 函数执行上下文
  • eval执行上下文

因此对应的this就有三种

  • 全局执行上下文中的this
  • 函数执行上下文中的this
  • eval中的this

eval用的不多,主要用到的是全局执行上下文中的this和函数执行上下文中的this

全局执行上下文中的this

全局执行上下文中的this是什么,可以在控制台中打印一下

console.log(this) 
复制代码

实际输出的就是window对象,结论就是全局执行上下文中的this是指向window对象的,这也是this和作用域链唯一焦点,作用域链最底端包含了window对象,全局执行上下文中的this也是指向window对象。

函数执行上下文中的this

function foo(){
  console.log(this)
}
foo()
复制代码

在foo函数内部打印出this值,发现函数执行上下文和全局执行上下文中的this都是指向window对象的。那可以设置函数执行上下文的this指向其他对象呢?是可以的,有三种方法可以用来设置函数执行上下文的this指向。

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

可以通过call方法来设置函数的this指向,并立刻执行该函数,比如以下代码

function foo(){
  this.name = "foo"
}
fooObj = {
  name:"fooObj"
}
foo.call(fooObj)
console.log(fooObj) //{name: "foo"}
复制代码

一般来说这个this是指向window的,可是call函数改变了this的指向将函数foo的this指向到了fooObj,从而使fooObj的name属性值从fooObj变为了foo。

通过对象调用方法设置this指向当前对象
var Obj = {
  name:"Obj",
  getThis:function(){
    console.log(this)
  }
}
Obj.getThis() //{name: "Obj", getDetail: ƒ}
复制代码

可以看到这样输出的this是指向当前对象的,可以得出一个结论,使用对象来调用其内部方法,这个方法的this是指向对象本身的

也可以认为是JavaScript引擎在执行myObject.showThis的时候将其转换成了

obj.getThis.call(obj)
复制代码

那如果将对象中的函数赋给一个全局变量会怎么样。

var Obj = {
  name:"Obj",
  getThis:function(){
    console.log(this)
  }
}
var foo =  Obj.getThis
foo()
复制代码

此时获取到的又是window对象了。

有两个结论

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

也就是说如果是在全局执行一个函数,那这个函数this就指向全局,但如果是对象里面,那这个this就指向对象,只和这个函数执行的调用者有关。

通过构造函数中设置

比如说new一个对象

function Person(){
  this.name='P'
}
var p1 = new Person()
console.log(p1) //Person {name: "P"}
复制代码

你知道JavaScript引擎在new一个对象的过程中做了什么吗?

  1. 首先先创建一个空对象p1
  2. 调用Person.call(p1),这样当Person的执行上下文创建的时候this就指向空对象p1
  3. 执行Person方法,因为此时this指向p1,因此p1的name='p'
  4. 返回p1这个对象

this的设计缺陷

1.嵌套函数的this不会继承外层函数的this

var Obj = {
  name:"Obj",
  getThis:function(){
    console.log(this)
    function bar(){
      console.log(this)
    }
    bar()
  }
}
Obj.getThis()//{name: "Obj", getThis: ƒ}
             //Window ...
复制代码

加工了一下上面的代码,在getThis中添加了一个方法bar,这个bar的this指向是什么?你可能会觉得这个this指向的是Obj,可是实际上函数bar的this指向的是全局的window对象,而getThis的this指向的是Obj。这是JavaScript中很让人迷惑的事情,也是很多问题的源头。

如何解决这样的问题呢?怎么让bar中可以有变量指向Obj?

一种方法可以将getThis中的this存储到一个self变量然后放到bar中
var Obj = {
  name:"Obj",
  getThis:function(){
    console.log(this)
    self = this;
    function bar(){
      self.name='bar'
      console.log(self)
    }
    bar()
  }
}
Obj.getThis()//{name: "Obj", getThis: ƒ}
             //{name: "bar", getThis: ƒ}
复制代码

此时的bar的self指向了Obj,修改self中的name,让Obj也改变了,这个方法的本质是把this体系转换成了作用域体系。因为执行到bar的时候,bar会沿着作用域链找self然后找到getThis上的self,又因为getThis中将this赋值给了self,所以就相当于bar取到了getThis的this,这个this指向就是Obj。

另一种方法是用ES6中的箭头函数来解决这个问题
var myObj = {
  name : "Obj", 
  showThis: function(){
    console.log(this)
    var bar = ()=>{
      this.name = "bar"
      console.log(this)
    }
    bar()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)
复制代码

这样同样达到了我们想要的嵌套函数获取到最外层对象的目的。这是因为ES6中的箭头函数并不会创建执行上下文,也就代表着箭头函数没有自己的this,箭头函数的this会从其外部函数获取。

想要让嵌套函数获取最外层对象,总结就是两种方式

  • 可以将外层函数的this保存成变量,让嵌套的函数获取该变量
  • 可以将嵌套函数写成箭头函数,让其去获取外层函数的this

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

上文有

function foo(){
  console.log(this)
}
foo()
复制代码

就是这样会输出window对象。

在实际工作中,我们并不希望函数的执行上下文中的this默认指向全局对象,这样会造成一些误操作。如果要让函数执行上下文中的this指向某个对象,最好的方式是通过call方法来调用

在JavaScript严格模式下也可以解决,默认执行一个函数,其函数执行上下文中的this值是undefined

"use strict";
function foo(){
  console.log(this)
}
foo() //undefined
复制代码

一个问题,想实现updateInfo可以改变userInfo,这段代码有什么问题

let userInfo = {
  name:"jack.ma",
  age:13,
  sex:male,
  updateInfo:function(){
    //模拟xmlhttprequest请求延时
    setTimeout(function(){
      this.name = "pony.ma"
      this.age = 39
      this.sex = female
    },100)
  }
}
​
userInfo.updateInfo()
复制代码

问题在于setTimeout里的回调函数还是嵌套函数,需要改成箭头函数,这样才能找到正确的this值。

学自:time.geekbang.org/column/arti…

文章分类
前端
文章标签