JavaScript: JavaScript Class 与模块
随着项目变大,你需要的不再是零散的函数和变量,而是有组织的代码结构。Class 提供了面向对象的写法,Module 提供了文件级的代码隔离——两者结合,就是现代 JS 项目的基石。
1. Class 语法
Class 是创建对象的模板,是构造函数的语法糖。
HTML
<script>
class 类名 {
constructor(参数) {
this.属性 = 参数;
}
方法() {
// ...
}
}
</script>
▶ 示例
JAVASCRIPT
// 本节代码示例
const message = "你好,世界!";
console.log(message);
这是最简单的示例,后续章节会详细解释。### ▶ 示例:创建 Student 类
HTML
<div id="output" style="padding: 10px; border: 1px solid #ccc;"></div>
<script>
const output = document.getElementById('output');
class Student {
constructor(name, age, grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
introduce() {
return '我叫' + this.name + ',今年' + this.age + '岁,' + this.grade + '年级。';
}
study(subject) {
return this.name + '正在学习' + subject + '。';
}
}
const s1 = new Student('小明', 12, '六');
const s2 = new Student('小红', 11, '五');
output.textContent = s1.introduce() + '\n' + s2.introduce() + '\n' + s1.study('数学');
</script>
💡 提示:
class 本质上就是构造函数 + 原型的语法糖。new Student() 的效果和以前 new function Student() 一样,但写起来更清晰,读起来更直观。
2. 属性和方法
(1) 实例属性和方法
在 constructor 中用 this.xxx 定义的属性和方法属于实例——每个对象各有一份。
(2) 静态方法
用 static 关键字定义的方法属于类本身,不属于实例。通过 类名.方法名() 调用。
(3) getter 和 setter
用 get 和 set 关键字定义"虚拟属性"——读写时自动执行函数。
HTML
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc;"></div>
<script>
const output = document.getElementById('output');
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return this.firstName + ' ' + this.lastName;
}
set fullName(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
}
const p = new Person('张', '三');
output.textContent = 'fullName: ' + p.fullName + '\n';
p.fullName = '李 四';
output.textContent += '修改后: ' + p.fullName;
</script>
▶ 示例:静态方法与 getter/setter
HTML
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc;"></div>
<script>
const output = document.getElementById('output');
class Circle {
static count = 0;
constructor(radius) {
this.radius = radius;
Circle.count++;
}
get area() {
return Math.PI * this.radius * this.radius;
}
get diameter() {
return this.radius * 2;
}
set diameter(value) {
this.radius = value / 2;
}
static createUnit() {
return new Circle(1);
}
}
const c1 = new Circle(5);
const c2 = new Circle(10);
const c3 = Circle.createUnit();
output.textContent = '半径5的圆:\n';
output.textContent += ' 面积: ' + c1.area.toFixed(2) + '\n';
output.textContent += ' 直径: ' + c1.diameter + '\n';
output.textContent += ' 修改直径为20:\n';
c1.diameter = 20;
output.textContent += ' 新半径: ' + c1.radius + '\n\n';
output.textContent += '创建的圆总数: ' + Circle.count + '\n';
output.textContent += '单位圆半径: ' + c3.radius;
</script>
3. 继承
extends 让子类继承父类的属性和方法,super 调用父类的构造函数或方法。
▶ 示例:继承演示
HTML
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc;"></div>
<script>
const output = document.getElementById('output');
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
return this.name + '说: ' + this.sound;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name, '汪汪');
this.breed = breed;
}
fetch(item) {
return this.name + '捡回了' + item;
}
}
class Cat extends Animal {
constructor(name, indoor) {
super(name, '喵喵');
this.indoor = indoor;
}
purr() {
return this.name + '在打呼噜...';
}
}
const dog = new Dog('旺财', '柴犬');
const cat = new Cat('咪咪', true);
output.textContent = dog.speak() + '\n';
output.textContent += dog.fetch('飞盘') + '\n';
output.textContent += cat.speak() + '\n';
output.textContent += cat.purr();
</script>
💡 提示: 子类
constructor 中必须先调用 super() 才能使用 this——因为父类还没初始化,子类的 this 就不存在。这是初学继承最容易忘的规则。
4. ES 模块
模块是 JS 的代码组织单位。一个模块就是一个文件,模块内的变量默认是"私有的",只有通过 export 导出的才能被外部使用。
(1) 命名导出 / 导入
HTML
<script>
// math.js
export const PI = 3.14;
export function add(a, b) { return a + b; }
// app.js
import { PI, add } from './math.js';
</script>
(2) 默认导出 / 导入
HTML
<script>
// logger.js
export default function log(msg) { console.log(msg); }
// app.js
import log from './logger.js';
</script>
(3) 在 HTML 中使用模块
script 标签加 type="module" 属性即可。
HTML
<script type="module">
import { add } from './math.js';
console.log(add(1, 2));
</script>
⚠️ 注意: 模块文件会被浏览器缓存,且受同源策略限制。本地文件直接打开 HTML 可能无法加载模块,需要通过 HTTP 服务器访问。
▶ 示例:单文件模拟模块(inline module)
实际项目中模块应该拆分成独立文件。这里为了在单个 HTML 文件中演示,使用 type="module" 的 script 标签模拟。
HTML
<div id="output" style="padding: 10px; border: 1px solid #ccc;"></div>
<script type="module">
const output = document.getElementById('output');
const calculator = {
add(a, b) { return a + b; },
subtract(a, b) { return a - b; },
multiply(a, b) { return a * b; },
divide(a, b) { return b !== 0 ? a / b : '不能除以0'; }
};
const formatter = {
currency(value) { return '¥' + value.toFixed(2); },
percent(value) { return (value * 100).toFixed(1) + '%'; }
};
const r1 = calculator.add(10, 20);
const r2 = calculator.multiply(5, 4);
const r3 = calculator.divide(10, 3);
output.textContent = '10 + 20 = ' + r1 + '\n';
output.textContent += '5 × 4 = ' + r2 + '\n';
output.textContent += '10 ÷ 3 = ' + formatter.currency(r3) + '\n';
output.textContent += '0.85 → ' + formatter.percent(0.85);
// 实际项目中应该这样拆分:
// calculator.js → export { calculator }
// formatter.js → export { formatter }
// main.js → import { calculator } from './calculator.js'
// import { formatter } from './formatter.js'
</script>
❓ 常见问题
Q Class 和构造函数有什么本质区别?
A 没有本质区别,Class 是语法糖。但 Class 有几个特点:必须用
new 调用(不能当普通函数执行)、方法不可枚举、默认严格模式。写法上 Class 更清晰,推荐使用。Q
import 和 require 有什么区别?A
import 是 ES 模块语法,编译时静态分析;require 是 CommonJS 语法(Node.js),运行时动态加载。浏览器原生只支持 ES 模块。import 必须在顶层,require 可以在任何位置。Q 默认导出和命名导出能混用吗?
A 可以。一个模块可以有一个默认导出和多个命名导出:
export default App; export const utils = {};。导入时:import App, { utils } from './module.js'。📖 小节
class是构造函数的语法糖,constructor是初始化方法,方法定义在原型上static方法定义在类上而非实例上,适合工具方法和工厂方法get/set定义虚拟属性,可以拦截读写、做验证或计算extends实现继承,子类constructor中必须先调用super()- ES 模块通过
export/import实现代码隔离和复用,type="module"是启用条件 - 模块的好处:代码按功能组织、按需加载、避免全局命名污染
📝 作业
- 基础:创建一个
Rectangle类,有width和height属性,getArea()和getPerimeter()方法。 - 进阶:让
Rectangle类增加get area()getter 和set area(value)setter(设置面积时按比例调整宽高)。再创建Square子类继承Rectangle。 - 挑战:设计一个
EventManager类,实现on(event, callback)、off(event, callback)、emit(event, data)三个方法,模拟简易事件系统(提示:用对象存储事件到回调数组的映射)。