
本文旨在为有c++++背景的开发者阐明javascript中创建自定义对象类型的方法。通过深入讲解es6 class,我们将学习如何定义具有特定数据结构的模板,并创建多个实例,从而有效管理和组织应用程序数据,实现类似c++结构体的功能。
在C++等强类型语言中,我们经常使用结构体(struct)来定义一个自定义的数据类型,它封装了一组相关的数据成员,然后可以基于这个结构体创建多个实例。对于初次接触JavaScript的开发者而言,如何在动态类型的JavaScript中实现类似的功能,即定义一个具有明确结构并能被多次实例化的“对象类型”,常常会感到困惑。Google People API中的Person和Name对象便是这种需求的典型例子,它们拥有预定义的结构,并能被大量使用。在JavaScript中,ES6引入的class语法正是解决这一问题的现代且标准的方法。
ES6 Class:定义你的对象蓝图
JavaScript中的class提供了一种更清晰、更简洁的方式来创建构造函数和处理原型继承。它本质上是现有原型继承模式的语法糖,但其语法结构与传统面向对象语言(如C++、Java)的类更为相似,使得开发者更容易理解和使用。一个类可以被看作是创建对象的蓝图或模板。
1. 定义一个基本对象类型
让我们以People API中的Name对象为例,它可能包含givenName和familyName等属性。在JavaScript中,我们可以这样定义一个Name类:
class Name {
/**
* 构造函数用于初始化新创建的Name对象。
* @param {string} givenName - 名。
* @param {string} familyName - 姓。
*/
constructor(givenName, familyName) {
this.givenName = givenName;
this.familyName = familyName;
}
/**
* 获取完整的姓名。
* @returns {string} 完整的姓名字符串。
*/
getFullName() {
return `${this.givenName} ${this.familyName}`;
}
}- class Name { ... }: 定义了一个名为Name的类。按照惯例,类名通常使用大驼峰命名法(PascalCase)。
- constructor(...): 这是一个特殊的方法,当使用new关键字创建类的新实例时会自动调用。它负责初始化新对象的属性。this关键字在这里指代新创建的实例。
- getFullName(): 这是一个类的方法。所有由该类创建的实例都将共享这个方法,但每个实例的数据(this.givenName, this.familyName)是独立的。
2. 创建包含其他对象类型的复杂对象
现在,我们考虑Person对象,它可能包含一个Name对象的数组,以及其他如emailAddresses等信息。我们可以利用之前定义的Name类来构建Person类:
立即学习“Java免费学习笔记(深入)”;
Dbsite企业网站管理系统V1.5.0 秉承"大道至简 邦达天下"的设计理念,以灵巧、简单的架构模式构建本管理系统。可根据需求可配置多种类型数据库(当前压缩包支持Access).系统是对多年企业网站设计经验的总结。特别适合于中小型企业网站建设使用。压缩包内包含通用企业网站模板一套,可以用来了解系统标签和设计网站使用。QQ技术交流群:115197646 系统特点:1.数据与页
class Person {
/**
* 构造函数用于初始化新创建的Person对象。
* @param {Array} names - 包含Name对象的数组。
* @param {Array} emailAddresses - 电子邮件地址数组。
*/
constructor(names, emailAddresses = []) {
// 确保names是一个数组,并且其中的元素是Name的实例
if (!Array.isArray(names) || !names.every(name => name instanceof Name)) {
throw new Error("Names must be an array of Name instances.");
}
this.names = names;
this.emailAddresses = emailAddresses;
}
/**
* 获取此人的主要姓名。
* @returns {string} 主要姓名(通常是第一个Name对象的完整姓名)。
*/
getPrimaryName() {
if (this.names.length > 0) {
return this.names[0].getFullName();
}
return "Unknown";
}
/**
* 添加一个电子邮件地址。
* @param {string} email - 要添加的电子邮件地址。
*/
addEmail(email) {
if (email && !this.emailAddresses.includes(email)) {
this.emailAddresses.push(email);
}
}
} 在这个Person类中:
- constructor接受一个names数组,我们可以在这里加入一些简单的验证,确保传入的是Name类的实例。
- this.names属性将存储一个或多个Name类的实例。这完美地模拟了C++中结构体成员可以是另一个结构体类型的情况。
实例化对象:从蓝图到实体
定义了类之后,我们就可以使用new关键字来创建这些类的具体实例。每个实例都是一个独立的对象,但它们都遵循类定义的结构和行为。
// 1. 创建 Name 类的实例
const primaryName = new Name("John", "Doe");
const maidenName = new Name("Jane", "Smith");
console.log(primaryName.getFullName()); // 输出: John Doe
// 2. 创建 Person 类的实例
const johnDoe = new Person(
[primaryName, maidenName], // 传入 Name 实例数组
["john.doe@example.com"]
);
console.log(johnDoe.getPrimaryName()); // 输出: John Doe
console.log(johnDoe.emailAddresses); // 输出: ["john.doe@example.com"]
johnDoe.addEmail("john.d@work.com");
console.log(johnDoe.emailAddresses); // 输出: ["john.doe@example.com", "john.d@work.com"]
// 我们可以创建另一个 Person 实例,它与 johnDoe 是独立的
const janeSmith = new Person(
[new Name("Jane", "Smith")], // 直接在构造函数中创建 Name 实例
["jane.smith@example.com"]
);
console.log(janeSmith.getPrimaryName()); // 输出: Jane Smith通过上述代码,我们可以看到:
- new Name(...)和new Person(...)创建了遵循各自类结构的新对象。
- 每个实例都有自己的属性值,即使它们共享相同的方法。
- Person对象成功地包含了Name对象的实例,实现了嵌套结构。
注意事项与最佳实践
- 类名约定:JavaScript社区普遍遵循大驼峰命名法(PascalCase)来命名类,例如Person、Name。
- 构造函数:constructor方法是类中唯一一个特殊的方法,用于创建和初始化类的一个对象。如果你的类不需要任何初始化参数,可以省略它,JavaScript会提供一个默认的空构造函数。
- this关键字:在类的方法和构造函数中,this指向当前实例。
- 数据验证:在构造函数中进行输入验证是一个好习惯,可以确保创建的对象始终处于有效状态,如Person类中对names数组的检查。
- 封装性:虽然JavaScript目前没有像C++那样严格的public、private、protected修饰符,但可以通过约定(如前缀_表示私有属性)或ES2022引入的私有类字段(#privateField)来实现一定程度的封装。
- 原型链:了解类是基于原型继承的语法糖对于深入理解JavaScript至关重要。类的所有方法都会被添加到其原型上,从而被所有实例共享,节省内存。
总结
对于有C++结构体背景的开发者而言,JavaScript的ES6 class是创建自定义对象类型、定义数据结构并实例化多个独立对象的最佳实践。它提供了清晰、易读的语法,能够有效组织代码,实现复杂的数据模型,并且与现代JavaScript生态系统完美融合。通过使用类,你可以像处理C++结构体一样,定义具有特定属性和行为的模板,进而构建出健壮且可维护的应用程序。









