likes
comments
collection
share

从 JavaScript 执行上下文的视角讲清楚 this

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

前言

在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 有另外一套 this 机制。

this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this。执行上下文主要分为三种—— 全局执行上下文函数执行上下文eval 执行上下文,所以对应的 this 也只有这三种——全局执行上下文中的 this、函数中的 this 和 eval 中的 this。

全局执行上下文中的 this

全局执行上下文中的 this 是指向 window 对象的。这也是 this 和作用域链的 唯一交点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象。

函数执行上下文中的 this

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

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

通常情况下,有下面三种方式来设置函数执行上下文中的 this 值:

1. 通过函数的 call 方法设置

let bar = {
  myName: "极客邦",
  test1: 1,
};
function foo() {
  this.myName = "极客时间";
}
foo.call(bar);
console.log(bar);
console.log(myName);

执行这段代码,你就能发现 foo 函数内部的 this 已经指向了 bar 对象,因为通过打印 bar 对象,可以看出 barmyName 属性已经由“极客邦”变为“极客时间”了,同时在全局执行上下文中打印 myName,JavaScript 引擎提示该变量未定义。其实除了 call 方法,你还可以使用 bindapply 方法来设置函数执行上下文中的 this

2. 通过对象调用方法设置

var myObj = {
  name: "极客时间",
  showThis: function () {
    console.log(this);
  },
};
myObj.showThis();

执行这段代码,你可以看到,最终输出的 this 值是指向 myObj 的。所以,你可以得出这样的结论:使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的。

接下来我们稍微改变下调用方式,把 showThis 赋给一个全局对象,然后再调用该对象,代码如下所示:

var myObj = {
  name: "极客时间",
  showThis: function () {
    this.name = "极客邦";
    console.log(this);
  },
};
var foo = myObj.showThis;
foo();

执行这段代码,你会发现 this 又指向了全局 window 对象。

结论:

  1. 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window

  2. 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。

3. 通过构造函数中设置

function CreateObj() {
  this.name = "极客时间";
}
var myObj = new CreateObj();

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

  1. 首先创建了一个空对象 tempObj

  2. 接着调用 CreateObj.call 方法,并将 tempObj 作为 call 方法的参数,这样当 CreateObj 的执行上下文创建时,它的 this 就指向了 tempObj 对象

  3. 然后执行 CreateObj 函数,此时的 CreateObj 函数执行上下文中的 this 指向了 tempObj 对象

  4. 最后返回 tempObj 对象

var tempObj = {};
CreateObj.call(tempObj);
return tempObj;

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

this 的设计缺陷以及应对方案

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

var myObj = {
  name: "极客时间",
  showThis: function () {
    console.log(this);
    function bar() {
      console.log(this);
    }
    bar();
  },
};
myObj.showThis();

执行这段代码后,会发现函数 bar 中的 this 指向的是全局 window 对象,而函数 showThis 中的 this 指向的是 myObj 对象。

可以通过一个小技巧来解决这个问题,比如在 showThis 函数中声明一个变量 that 用来保存 this,然后在 bar 函数中使用 that。其实,这个方法的的本质是把 this 体系转换为了作用域的体系。

其实,你也可以使用 ES6 中的箭头函数来解决这个问题:

var myObj = {
  name: "极客时间",
  showThis: function () {
    console.log(this);
    var bar = () => {
      console.log(this);
    };
    bar();
  },
};
myObj.showThis();

这是因为 ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数。

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

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

可以通过设置 JavaScript 的 严格模式 来解决(在第一行加上 "use strict";)。在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined