彻底搞懂JavaScript的this指向问题

this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。但是即使是非常有经验的 JavaScript 开发者也很难说清它到底指向什么。一、为什么要用this?下面我们来解释一下为什么要使用 this: const me = { name: "xiaoming" }; const you = { name: "xiaohua" }; function identify() { return this.name.
摘要由CSDN通过智能技术生成

this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。但是即使是非常有经验的 JavaScript 开发者也很难说清它到底指向什么。

一、为什么要用this?

下面我们来解释一下为什么要使用 this:

    const me = {
    name: "xiaoming" };
    const you = {
    name: "xiaohua" };
		function identify() {
   
      return this.name.toUpperCase();
    }
    function speak() {
   
      var greeting = "Hello, I'm " + identify.call(this);
      console.log(greeting);
    }
    identify.call(me); // XIAOMING
    identify.call(you); // XIAOHUA 
    speak.call(me); // Hello, 我是 XIAOMING
    speak.call(you); // Hello, 我是 XIAOHUA

这段代码可以在不同的上下文对象(me 和 you)中重复使用函数 identify() 和 speak(), 不用针对每个对象编写不同版本的函数。

如果不使用 this,那就需要给 identify() 和 speak() 显式传入一个上下文对象。

    const me = {
    name: "xiaoming" };
    const you = {
    name: "xiaohua" };
    function identify(context) {
   
      return context.name.toUpperCase();
    }
    function speak(context) {
   
      var greeting = "Hello, I'm " + identify(context);
      console.log(greeting);
    }
    identify(you); // XIAOHUA 
    speak(me); //hello, 我是 XIAOMING

this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计 得更加简洁并且易于复用。

随着你的使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this 则不会这样。

二、误解
指向自身

第一种误解就是this指向函数自身。分析一下下面的代码:

  function foo(num) {
   
    console.log("foo: " + num); // 记录 foo 被调用的次数
    // foo: 6
    // foo: 7
    // foo: 8
    // foo: 9
    console.log(this); // Window
    this.count++;
    console.log(this.count); // -- 第一次为undefined,后续为NaN
    // undefined
    // NaN
    // NaN
    // NaN
  }
  foo.count = 0;
  for (let i = 0; i < 10; i++) {
   
    if (i > 5) {
   
      foo(i);
    }
  }
  // foo 被调用了多少次?
  console.log(foo.count); // 0
  console.log(window.count); // NaN

如果说this指向自身(foo)的话,console.log(foo.count);得到的结果应该是4,结果却是0。

开发者一定会问“如果我增加的 count 属性和预期的不一样,那我增加的是哪个 count ?”实际上,如果他深入探索的话,就会发现这段代码在无意中创建了一个全局变量 count(后续讲解),它的值为 NaN。当然, 如果他发现了这个奇怪的结果,那一定会接着问:“为什么它是全局的,为什么它的值是 NaN 而不是其他更合适的值?”(后续讲解)

三、this到底是什么?

排除了错误理解之后,我们来看看 this 到底是一种什么样的机制。

this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会创建一个活动记录(也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。

所以说,我们要学习如何寻找函数的调用位置,从而判断函数在执行过程中会如何绑定this。

四、调用位置

**调用位置:**调用位置就是函数在代码中被调用的位置(而不是声明的位置)。

**调用栈:**为了到达当前执行位置所调用的所有函数。

下面我们来看看到底什么是调用栈和调用位置:

  function father() {
   
    // 当前调用栈是:father
    // 因此,当前调用位置是全局作用域
    console.log("father");
    son(); // <-- son 的调用位置
  }
  function son() {
   
    // 当前调用栈是 father -> son
    // 因此,当前调用位置在 father 中
    console.log("son");
    grandSon(); // <-- grandSon 的调用位置
  }
  function grandSon() {
   
    // 当前调用栈是 father -> son -> grandSon
    // 因此,当前调用位置在 son 中
    console.log("grandSon");
  }
  father(); // <-- father 的调用位置

注意:我们是如何(从调用栈中)分析出真正的调用位置的,因为它决定了 this 的绑定。

五、绑定规则

我们来看看在函数的执行过程中调用位置如何决定 this 的绑定对象。

你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。我们首先会分别解释这四条规则,然后解释多条规则都可用时它们的优先级如何排列。

1、默认绑定

首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。

**(1)**如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined:

  function foo() {
   
    "use strict"; 
    console.log(this); // undefined
    console.log(this.a); // Uncaught TypeError: Cannot read property 'a' of undefined
  }
  let a = 2;
  foo();

**(2)**如果非严格模式(non-strict mode)下时,默认绑定才能绑定到全局对象Window:

  function foo() {
   
    console.log(this); // Window
    console.log(this.a); // 2
  }
  let a = 2;
  foo();
2、隐式绑定

隐式绑定需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。

分析一下下面的代码:

  function foo() {
   
    console.log(this.a);
  }
  var obj = {
    a: 2, foo: foo };
  obj.foo(); // 2

首先需要注意的是 foo() 的声明方式,及其之后是如何被当作引用属性添加到 obj 中的。但是无论是直接在 obj 中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj 对象。

然而,调用位置会使用 obj 上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥有”或者“包含”它。

对象属性引用链中只有最顶层或者说最后一层会影响调用位置。

举例说明:

  function foo() {
   
    console.log(this.a);
  }
  var obj2 = {
    a: 42, foo: foo };
  var obj1 = {
    a: 2, obj2: obj2 };
  obj1.obj2.foo(); // 42

⭐️隐式丢失

一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。

举例说明:

  function foo() {
   
    console.log(this.a);
  }
  var obj = {
    a: "这是obj对象的a属性", foo: foo };
  var bar = obj.foo; // 指向foo函数自身的指针赋值给了bar
  var a = "这是全局对象的a属性"; //
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值