js代码08

题目

好的,我们正式进入 JavaScript 的另一个深水区,这也是面向对象编程的基石:this 关键字

this 是 JavaScript 中最强大、最灵活,也最容易引起困惑的概念之一。但别担心,它的行为遵循一套清晰的规则。一旦你理解了这些规则,就能完全驾驭它。


练习 08: this 的指向 - 解开JS中最微妙的谜题

核心法则: 在学习 this 之前,请先记住这条黄金法则:

this 的值取决于函数被调用时的“执行上下文”(即函数是如何被调动的),而不是函数被定义时的位置。

学习目标:

  • 理解方法调用中的 this(隐式绑定)。
  • 理解独立函数调用中的 this(默认绑定),以及 this“丢失”的常见场景。
  • 理解箭头函数this 的特殊行为(词法绑定)。

️ 任务:
这个练习非常特别。你的任务不是编写代码,而是预测下面代码片段中每一行 console.log 的输出结果。

请在运行代码之前,仔细阅读代码和注释,然后根据你对 this 规则的理解,在心中或纸上写下你认为的答案。这个“预测”的过程本身就是最有价值的学习环节。

预测代码 (08-this-keyword.js):
创建文件 08-this-keyword.js,将以下代码复制进去。

// 在全局作用域下,严格模式(模块默认就是)下 this 是 undefined
// 非严格模式下是 window 对象。我们假设在模块环境中。
console.log("全局 this:", this); 

const user = {
  name: "Alex",
  skills: ['React', 'Node', 'CSS'],
  
  // 场景1: 作为对象的方法被调用
  greet() {
    console.log(`你好,我是 ${this.name}`);
  },

  // 场景2: 箭头函数作为对象的方法
  showSkills: () => {
    // 箭头函数没有自己的 this,它会捕获其定义时所在作用域的 this
    console.log(`我的技能: ${this.name} 知道吗?`, this);
  },
  
  // 场景3: 方法内部的普通函数
  describe() {
    console.log(`${this.name} 在描述自己...`);
    
    function innerFunction() {
      // 这是一个独立的函数调用,它的 this 指向什么?
      console.log(`在 innerFunction 中,this.name 是 ${this.name}`, this);
    }
    
    innerFunction();
  }
};


// --- 开始预测 ---

console.log("--- 调用 1: 方法调用 ---");
// 问: 下面这行会输出什么?
user.greet();


console.log("\n--- 调用 2: '丢失 this' 的情况 ---");
// 我们把 user.greet 方法赋值给一个独立的变量
const standaloneGreet = user.greet;
// 问: 现在这样调用,会输出什么?为什么?
standaloneGreet();


console.log("\n--- 调用 3: 箭头函数的 this ---");
// 问: 调用箭头函数方法,会输出什么?
user.showSkills();


console.log("\n--- 调用 4: 嵌套函数的 this ---");
// 问: 调用 describe 方法后,其内部的 innerFunction 会输出什么?
user.describe();


你的预测

请写下你对4次调用的预测结果:

  • 调用 1 (user.greet()):
  • 调用 2 (standaloneGreet()):
  • 调用 3 (user.showSkills()):
  • 调用 4 (user.describe() 内部的 innerFunction):

这是一个纯粹的“概念检查”练习。挑战在于应用我们刚刚讨论的规则。不要急着运行代码!当你预测完毕,准备好对答案并看详细解析时,就告诉我。

答案

好的,我们来核对答案并深入解析其中的奥秘。这是一个坎,迈过去之后,你对 JavaScript 的理解会上升一个全新的层次。


✅ 实际运行结果

全局 this: undefined

--- 调用 1: 方法调用 ---
你好,我是 Alex。

--- 调用 2: '丢失 this' 的情况 ---
TypeError: Cannot read properties of undefined (reading 'name')

--- 调用 3: 箭头函数的 this ---
我的技能: undefined 知道吗? undefined

--- 调用 4: 嵌套函数的 this ---
Alex 在描述自己...
TypeError: Cannot read properties of undefined (reading 'name')

你的预测和这个结果相符吗?无论是否相符,我们一起来看看到底是为什么。


答案解析

调用 1: user.greet()
  • 输出: 你好,我是 Alex。
  • 规则: 隐式绑定 (Implicit Binding)
  • 解释: 这是最直观的一种情况。当函数作为对象的一个方法被调用时(注意调用时的 user. 这个点),this 就被“绑定”到了这个点前面的对象上。所以,在 greet 函数内部,this 就是 user 对象。因此 this.name 就是 'Alex'
调用 2: standaloneGreet()
  • 输出: TypeError: Cannot read properties of undefined (reading 'name')
  • 规则: 默认绑定 (Default Binding)
  • 解释: 这是 this 最常见的“陷阱”。当我们执行 const standaloneGreet = user.greet; 时,我们只是把 greet 函数本身赋值给了 standaloneGreet 这个变量,它与 user 对象的关联就此断开了。
    随后,我们调用 standaloneGreet() 时,它是一个独立的、没有任何上下文对象的函数调用。在这种情况下,默认绑定规则生效:
    • 严格模式 (Strict Mode) 下(ES6 模块默认就是严格模式),this 会被设为 undefined
    • 因此,执行 this.name 就变成了 undefined.name,从而抛出 TypeError
调用 3: user.showSkills()
  • 输出: 我的技能: undefined 知道吗? undefined
  • 规则: 箭头函数的词法this (Lexical this)
  • 解释: 箭头函数是 this 规则里的一个“异类”。它没有自己的 this。它会像普通变量一样,捕获其定义时所在作用域this
    • showSkills: () => { ... } 这个箭头函数是在全局(或模块顶层)作用域中定义的(作为 user 对象字面量的一部分)。
    • 在我们的模块环境中,顶层作用域的 thisundefined
    • 所以,showSkills 内部的 this 就永远是 undefined,无论它将来如何被调用。因此 this.name 就是 undefined.name,结果为 undefined
调用 4: user.describe()
  • 输出:
    1. Alex 在描述自己...
    2. TypeError: Cannot read properties of undefined (reading 'name')
  • 规则: 隐式绑定默认绑定 的结合
  • 解释:
    1. user.describe() 的调用符合隐式绑定规则,所以 describe 方法内部的 thisuser 对象。因此第一个 console.log 成功输出了 Alex 在描述自己...
    2. 但是,在 describe 方法内部,innerFunction() 是作为一个独立的、没有任何上下文的函数被调用的。这和“调用2”的情况完全一样!因此,innerFunction 内部的 this 遵循默认绑定规则,值为 undefined,从而在访问 this.name 时抛出 TypeError

如何“修正”丢失的 this

既然我们知道了问题所在,那么如何解决“调用2”和“调用4”中的问题呢?

  1. 使用 .bind() (显式绑定)
    .bind() 方法可以创建一个新函数,并永久地将 this 绑定到指定的对象上。

    // 修正调用2
    const standaloneGreet = user.greet.bind(user);
    standaloneGreet(); // 输出: 你好,我是 Alex。
    
  2. 使用箭头函数 (利用词法this)
    在 ES6 之后,解决嵌套函数 this 问题的最佳方法就是使用箭头函数,因为它能自动捕获我们想要的 this

    // 修正调用4
    describe() {
      console.log(`${this.name} 在描述自己...`);
      const innerFunction = () => { // 改为箭头函数!
        // 它会自动捕获外层 describe 方法的 this,也就是 user 对象
        console.log(`在 innerFunction 中,this.name 是 ${this.name}`);
      }
      innerFunction();
    }
    // user.describe() 会输出:
    // Alex 在描述自己...
    // 在 innerFunction 中,this.name 是 Alex
    

总结

this 的指向是一个纯粹的规则问题,记住“函数是如何被调用的”这个核心,就能解决大部分问题。

我们已经攻克了 this 这个难关。下一个练习,我们将学习 class 语法,它是 ES6 提供的、用于创建对象的“语法糖”,能帮助我们更优雅地组织代码和处理 this,让你更接近传统的面向对象编程。

准备好后,我们就可以开始了!

你可能感兴趣的:(vue,javascript,开发语言,ecmascript)