题目

好的,我们继续。

在上一个练习中,我们深入探讨了 this 的复杂性。你会发现,ES6 引入的 class 语法在很大程度上就是为了简化 this 的使用,并为 JavaScript 提供一个更清晰、更熟悉的面向对象编程(OOP)模型。

class 本质上是 JavaScript 现有原型继承模型的“语法糖”。它没有改变 JavaScript 的底层机制,但提供了一套非常清晰和友好的语法来创建“对象的蓝图”。


练习 09: 对象的蓝图 - class 语法与面向对象编程

在这个练习中,我们将创建一个 BankAccount 类来模拟一个银行账户,它有自己的属性(如余额)和方法(如存款、取款)。然后,我们还会创建一个更具体的 SavingsAccount(储蓄账户)类来继承它。

🎯 学习目标:

  • 学会使用 class 关键字定义一个类。
  • 理解并使用 constructor 方法来初始化对象实例的属性。
  • 在类中定义方法,并理解在这些方法中 this 会自动指向实例对象。
  • 使用 new 关键字来创建类的实例。
  • (进阶) 使用 extends 关键字实现类的继承。
  • (进阶) 在子类中使用 super() 来调用父类的构造函数。

🛠️ 任务:

Part 1: 创建一个基础的 BankAccount

  1. 构造函数 (constructor): 应该接受 ownerName 和一个可选的初始 balance(默认值为 0)作为参数。
  2. deposit(amount) 方法: 增加账户余额,并打印一条确认信息。
  3. withdraw(amount) 方法: 减少账户余额,但需要检查余额是否充足。
  4. balance getter: 创建一个名为 balance 的“计算属性”,用于安全地获取当前余额。

Part 2 (挑战): 创建一个 SavingsAccount 子类

  1. 这个类应该继承BankAccount
  2. 它的 constructor 应该额外接受一个 interestRate (利率) 参数。
  3. 它应该有一个新方法 applyInterest(),用于计算利息并将其存入账户。

📋 初始代码 (09-classes.js):
创建新文件 09-classes.js,并将以下代码复制进去。你的任务是补全所有 // TODO 的部分。

// --- Part 1: 创建一个基础的银行账户类 ---
class BankAccount {// 1. 添加一个 constructor// 它应该接受 ownerName 和 balance (默认值为 0)// 为了演示 getter,我们将实际余额存在一个通常表示“私有”的 _balance 变量上。constructor(ownerName, balance = 0) {// TODO: 在这里设置 ownerName 和 _balance 属性}// 2. 添加 deposit 方法deposit(amount) {// TODO: 增加 _balance 的值,并打印新余额}// 3. 添加 withdraw 方法withdraw(amount) {// TODO: 检查 _balance 是否足够,如果足够则减去金额并打印,否则打印警告}// 4. 使用 getter 来创建一个名为 balance 的只读属性get balance() {// TODO: 返回 _balance 的值}
}// --- 使用 BankAccount 类 ---
console.log("--- 操作基础账户 ---");
const myAccount = new BankAccount("Jack", 500);
console.log(`Account created for ${myAccount.ownerName}.`);
// myAccount.deposit(100);
// myAccount.withdraw(200);
// console.log(`Final balance for Jack: ${myAccount.balance}`); // 注意这里是调用 getter,而不是 myAccount._balance
// myAccount.withdraw(500); // 应该会提示余额不足// --- Part 2: (挑战) 创建一个储蓄账户子类 ---
class SavingsAccount /* TODO: 使用 extends 继承 BankAccount */ {// 5. 添加 constructor// 它应该接受 ownerName, balance, 和 interestRate。constructor(ownerName, balance = 0, interestRate) {// TODO: 使用 super() 调用父类的构造函数来设置 ownerName 和 balance// TODO: 设置自己的 interestRate 属性}// 6. 添加 applyInterest 方法applyInterest() {// TODO: 根据 interestRate 计算利息,然后调用父类的 deposit 方法将利息存入。}
}// --- 使用 SavingsAccount 类 ---
console.log("\n--- 操作储蓄账户 ---");
const mySavings = new SavingsAccount("Jill", 1000, 0.05); // 5% 的利率
console.log(`Savings account created for ${mySavings.ownerName}.`);
// mySavings.applyInterest();
// console.log(`Final savings balance for Jill: ${mySavings.balance}`);
// mySavings.withdraw(1100); // 应该会提示余额不足

✅ 预期输出:

--- 操作基础账户 ---
Account created for Jack.
New balance: 600
New balance: 400
Final balance for Jack: 400
Insufficient funds! Current balance is 400.--- 操作储蓄账户 ---
Savings account created for Jill.
Interest applied. New balance: 1050
Final savings balance for Jill: 1050
Insufficient funds! Current balance is 1050.

完成这个练习后,你将对如何在 JavaScript 中以结构化的方式创建和管理复杂对象有一个非常清晰的认识。这也是通往所有现代前端框架(它们都大量使用基于类的组件)的必经之路。

答案

好的,我们来看这个 class 练习的完整答案。

这部分代码集中体现了面向对象编程(OOP)的核心思想:封装、继承。

参考实现 (09-classes.js)

// --- Part 1: 创建一个基础的银行账户类 ---
class BankAccount {// 1. 构造函数:当使用 new 创建实例时,它会被自动调用constructor(ownerName, balance = 0) {// `this` 指向新创建的实例对象this.ownerName = ownerName;// 使用下划线 `_` 是一种约定,表示这个属性不应该被外部直接访问this._balance = balance; }// 2. deposit 方法:类的方法deposit(amount) {if (amount > 0) {this._balance += amount;console.log(`New balance: ${this._balance}`);} else {console.log("Deposit amount must be positive.");}}// 3. withdraw 方法withdraw(amount) {if (amount > this._balance) {console.log(`Insufficient funds! Current balance is ${this._balance}.`);} else if (amount > 0) {this._balance -= amount;console.log(`New balance: ${this._balance}`);} else {console.log("Withdrawal amount must be positive.");}}// 4. getter:它让我们可以像访问属性一样调用一个函数get balance() {return this._balance;}
}// --- 使用 BankAccount 类 ---
console.log("--- 操作基础账户 ---");
const myAccount = new BankAccount("Jack", 500);
console.log(`Account created for ${myAccount.ownerName}.`);
myAccount.deposit(100);
myAccount.withdraw(200);
// 注意这里我们访问 .balance,而不是 ._balance。它会自动调用 get balance() 方法。
console.log(`Final balance for Jack: ${myAccount.balance}`);
myAccount.withdraw(500);// --- Part 2: (挑战) 创建一个储蓄账户子类 ---
// extends 关键字用于声明一个类是另一个类的子类
class SavingsAccount extends BankAccount {// 5. 子类的构造函数constructor(ownerName, balance = 0, interestRate) {// `super()` 必须在子类构造函数中使用 `this` 之前被调用。// 它会调用父类(BankAccount)的构造函数,并传递参数。super(ownerName, balance); // 这是子类自己独有的属性this.interestRate = interestRate;}// 6. 子类自己独有的方法applyInterest() {const interest = this._balance * this.interestRate;console.log("Interest applied.");// 调用从父类继承来的 deposit 方法this.deposit(interest);}
}// --- 使用 SavingsAccount 类 ---
console.log("\n--- 操作储蓄账户 ---");
const mySavings = new SavingsAccount("Jill", 1000, 0.05); // 5% 的利率
console.log(`Savings account created for ${mySavings.ownerName}.`);
mySavings.applyInterest();
console.log(`Final savings balance for Jill: ${mySavings.balance}`);
mySavings.withdraw(1100);

代码解析

  1. constructor(ownerName, balance = 0)

    • 当你写 new BankAccount(...) 时,constructor 会被自动执行。
    • this.ownerName = ownerName; 这行代码是在新创建的实例对象上设置一个名为 ownerName 的属性。在类的方法中,this 的指向通常就是我们所期望的实例对象,这比之前练习中的情况要简单得多。
  2. get balance()

    • 这是一个 getter。它定义了一个“访问器属性”。从外部看,你可以像读取普通属性一样读取它(myAccount.balance),但实际上,每次读取时都会执行这个函数。这是一种封装的体现——外部只能读取余额,但不能直接设置 _balance 的值。
  3. class SavingsAccount extends BankAccount

    • extends 关键字建立了 SavingsAccountBankAccount 之间的继承关系。
    • SavingsAccount 自动获得了 BankAccount 的所有方法(如 deposit, withdraw)和属性(通过 super 调用设置)。
  4. super(ownerName, balance)

    • 在子类的 constructor 中,super 是一个对父类 constructor 的引用。
    • 必须先调用 super() 来完成父类的初始化工作,然后才能在子类构造函数中使用 this 关键字来添加子类自己的属性(如 this.interestRate)。
  5. 方法继承 (this.deposit(interest))

    • applyInterest 方法中,我们直接调用了 this.deposit()
    • SavingsAccount 本身没有定义 deposit 方法,于是 JavaScript 会沿着原型链向上查找,在父类 BankAccount 中找到了这个方法,并成功调用。这就是继承在起作用。

至此,我们已经完整地学习了 JavaScript 语言从基础到核心的各个重要组成部分:

  • 数据处理(函数式方法)
  • 异步流程(回调、Promise、async/await)
  • 现代语法(解构、展开/剩余)
  • 代码组织(模块化)
  • 面向对象thisclass、继承)

你现在已经拥有了构建复杂 JavaScript 应用程序所需的全部核心知识。无论是去学习 React/Vue/Angular 这样的前端框架,还是使用 Node.js 构建后端服务,这些知识都是不可或缺的基石。

恭喜你完成了这段系统的学习旅程!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/912676.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/912676.shtml
英文地址,请注明出处:http://en.pswp.cn/news/912676.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Airtest的App数据爬取实战:突破传统爬虫的边界

引言:App数据爬取的技术困境 在当今移动优先的时代,App已成为企业核心数据载体,然而​​传统爬虫技术​​在App数据获取上面临三大难题: ​​协议层屏障​​:加密HTTPS、SSL Pinning等技术阻断中间人攻击​​渲染层障碍​​:React Native、Flutter等跨平台框架使DOM解析…

【LeetCode 热题 100】560. 和为 K 的子数组——(解法一)前缀和+暴力

Problem: 560. 和为 K 的子数组 题目:给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。子数组是数组中元素的连续非空序列。 【LeetCode 热题 100】560. 和为 K 的子数组——(解法二)前缀和…

android车载开发之HVAC

目前主要在做车载hvac的开发,主要的一些功能主要是hvac,座椅,香氛,设置等的一些模块,具体模块下,比如 1.空调 ac,智能模式(极速降温,极速采暖,智能除味&…

深度学习 Diffusers 库(自留)

(本文将围绕 安装Diffusers库及其依赖、理解Diffusers核心概念:Pipeline, Model, Scheduler 、使用预训练模型进行推理(文生图、图生图等) 、 自定义模型和调度器 、训练自己的扩散模型(可选,需要大量资源&…

【VPC技术】基础理论篇

文章目录 概述相关基础核心知识软件定义网络SDNOverlay 技术 安全组概述 参考博客 😊点此到文末惊喜↩︎ 概述 相关基础 基本概念 虚拟私有云VPC:是一个隔离的网络环境,每个VPC拥有专属的IP地址范围(CIDR)、路由表、…

在 RK3588 Ubuntu 上编译 eglinfo:全流程实战 + 常见报错修复

dv1/eglinfo 是一个开源的 EGL 信息检测工具,广泛用于 OpenGL ES 图形栈调试、驱动验证和嵌入式平台图形支持排查。在 Rockchip RK3588 上编译该工具可以协助我们确认 EGL DRM 是否配置正确,尤其在无窗口系统(如 eglfs、framebuffer&#xf…

开源推荐:基于前后端分离架构的WMS仓储管理系统

开源推荐:基于前后端分离架构的WMS仓储管理系统 🔥 在线演示地址:https://tob.toolxq.com/wms/wms.html 点击上方链接可直接体验系统功能和界面,无需安装部署 前言 在企业数字化转型的浪潮中,仓储管理系统&#xff08…

Redis中List类型常见的操作命令有哪些?

Redis中List类型是一个字符串列表,这里是一些常见的命令: 1)lpush:将一个或多个值插入到列表头部。列表不存在,一个新的列表会被创建。 2)rpush:将一个或多个值插入到列表尾部。 3)lpop:移除并返回列表头…

mac重复文件清理,摄影师同款清理方案

摄影师小林盯着屏幕上的警告:“存储空间不足”,离截稿只剩3小时。她的MacBook如同塞满回忆的阁楼,128GB的“其他”空间神秘消失。翻看照片库时,她惊讶地发现——同一组西藏雪山照片竟有十几个副本!这是mac重复文件问题…

lua脚本为什么能保证原子性

Redis 处理客户端请求是基于单线程模型的( Redis 6.0 开始引入了多线程处理网络 IO,但命令执行仍然是单线程的)。这意味着,在任意时刻 Redis 只会执行一个命令或脚本。这种单线程特性确保了当 Redis 在执行一个 Lua 脚本时&#x…

爬虫详解:Aipy打造自动抓取代理工具

一、爬虫的本质与核心功能 爬虫是一种通过编写程序自动抓取互联网公开数据的技术工具,其核心流程包括: 模拟浏览器行为:发送 HTTP 请求访问目标网页解析页面结构:提取 HTML/XML 中的关键信息(如文本、链接、图片&…

Leetcode百题斩-栈

终于来到了栈专题,想想之前来阿里的时候就是面试了一道栈最终通过了终面,也是十分怀念了。 739. Daily Temperatures[Medium] 思路:这就是最典型的单调栈问题了。从后向前维护下一个更大值或者下一个更大值的位置。 可以看一下当年面阿里时…

PIXHAWK(ardupilot4.52)NMEA的解析bug

最近在测试过程中发现在椭球高为负的地方,地面站读取GPS_RAW_INT (24)消息中的alt高度竟然是正值。而消息中定义的alt并不是一个unsigned数据,理论上是带有正负符号的。 查看gga的原始信息: $GPGGA,063718.40,3714.8533856,N,11845.9411766,…

Linux容器讲解以及对应软件使用

一、容器基础知识讲解 1.1 微服务的部署策略 部署单体应用意味着运行大型应用的多个相同副本,通常提供若干台(N)服务器(物理机或虚拟 机),在每台服务器上运行若干个(M)应用实例。部…

企业级应用技术-ELK日志分析系统

目录 #1.1ELK平台介绍 1.1.1ELK概述 1.1.2Elasticsearch 1.1.3Logstash 1.1.4Kibana #2.1部署ES群集 2.1.1基本配置 2.1.2安装Elasticsearch 2.1.3安装Logstash 2.1.4Filebeat 2.1.5安装Kibana 1.1ELK平台介绍 1.1.1ELK概述 ELK 是三个开源工具的缩写,分别是Elas…

Shiro漏洞复现

Shiro简介 Apache Shiro是一种功能强大且易于使用的Java安全框架,它执行身份验证、授权、 加密和会话管理,可用于保护任何应用程序的安全。 Shiro提供了应用程序安全性API来执行以下方面: 1.身份验证:证明用户身份,通…

VSCode 中使用 Google Test(GTest)框架测试

VSCode 中使用 Google Test(GTest)框架在 VSCode 中对 C 代码进行测试的示例: 一、Unbutu x86使用gtest 环境配置 安装 GTest :在 Ubuntu 系统中,可以通过命令sudo apt-get install libgtest-dev安装 GTest 库。对于…

【1.6 漫画数据库设计实战 - 从零开始设计高性能数据库】

1.6 漫画数据库设计实战 - 从零开始设计高性能数据库 🎯 学习目标 掌握数据库表结构设计原则理解字段类型选择与优化学会雪花算法ID生成策略掌握索引设计与优化技巧了解分库分表设计方案 📖 故事开始 小明: “老王,我总是不知道怎么设计数…

OSPF虚拟链路术语一览:快速掌握网络路由

大家好,这里是G-LAB IT实验室。今天带大家了解一下OSPF的相关知识! 01 OSPF虚拟链路术语大全 网络架构中,OSPF(开放式最短路径优先)是一种重要的路由协议。通过其链路状态路由机制,OSPF能够有效维护和更新…

oracle常用的函数(一) 之 to_char、to_date

文章目录 前言to_char基本语法格式模型格式模型介绍无FM示例使用FM输出货币负数输出尖括号 将日期格式化将数字格式化为带有货币符号和千位分隔符的格式总结 to_date语法语法示例 戳这里,第二弹 → oracle常用的函数(二) 之 nvl、decode、l…