各位观众老爷们,大家好!今天咱们不聊风花雪月,也不谈人生理想,就来聊聊JavaScript里class
这个“假正经”的东西。为什么说它假正经呢?因为JavaScript本质上还是基于原型的,class
只不过是语法糖,让你写起来更像传统的面向对象语言,但骨子里还是那套原型链的玩法。
好了,废话不多说,咱们这就开讲!
一、class
登场:不再是原型链的“祖传秘方”
在class
出现之前,JavaScript里实现面向对象,那可是个体力活。你得手动构建原型链,搞清楚构造函数、prototype
、__proto__
这些弯弯绕绕,一不小心就掉进坑里。
// 传统方式:构造函数 + 原型链
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}, and I'm ${this.age} years old.`);
};
const john = new Person("John", 30);
john.sayHello(); // 输出: Hello, my name is John, and I'm 30 years old.
看到没?是不是觉得有点繁琐?特别是当你要实现继承的时候,那代码更是绕得像毛线团。
现在,class
来了!它可以让你用更简洁、更易读的方式定义类,就像你在Java、C++里做的那样。
// class 语法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name}, and I'm ${this.age} years old.`);
}
}
const jane = new Person("Jane", 25);
jane.sayHello(); // 输出: Hello, my name is Jane, and I'm 25 years old.
是不是清爽多了?这就是class
的魅力所在。它把复杂的原型链操作隐藏起来,让你专注于类的定义和使用。
二、class
的基本结构:构造函数、方法和属性
一个class
通常包含以下几个部分:
constructor
(构造函数): 这是创建对象时调用的函数,用于初始化对象的属性。如果没有显式定义constructor
,JavaScript会自动创建一个默认的构造函数。- 方法 (Methods): 类中定义的函数,用于实现对象的行为。
- 属性 (Properties): 对象的状态,存储在对象的属性中。
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
getPerimeter() {
return 2 * (this.width + this.height);
}
}
const rect = new Rectangle(10, 5);
console.log(`Area: ${rect.getArea()}`); // 输出: Area: 50
console.log(`Perimeter: ${rect.getPerimeter()}`); // 输出: Perimeter: 30
在这个例子中,Rectangle
类有width
和height
两个属性,以及getArea
和getPerimeter
两个方法。
三、class
的继承:子类继承父类的衣钵
继承是面向对象编程的一个重要概念。它允许你创建一个新的类(子类),继承已有类(父类)的属性和方法,并在此基础上进行扩展。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log("Generic animal sound");
}
}
class Dog extends Animal {
constructor(name, breed) {
// 调用父类的构造函数
super(name);
this.breed = breed;
}
speak() {
console.log("Woof!");
}
fetch() {
console.log("Fetching the ball!");
}
}
const dog = new Dog("Buddy", "Golden Retriever");
console.log(`Dog's name: ${dog.name}`); // 输出: Dog's name: Buddy
dog.speak(); // 输出: Woof!
dog.fetch(); // 输出: Fetching the ball!
const animal = new Animal("Generic Animal");
animal.speak(); // 输出: Generic animal sound
这里,Dog
类继承了Animal
类,并扩展了自己的属性breed
和方法fetch
。Dog
类还重写了speak
方法,实现了自己的行为。
注意super()
的用法。super()
用于调用父类的构造函数,确保父类的属性也能被正确初始化。如果没有调用 super()
,在子类的构造函数中 this
关键字不能被使用,会报错。
四、static
静态方法和属性:类级别的“共享资源”
静态方法和属性是属于类本身的,而不是属于类的实例。它们可以通过类名直接访问,而不需要创建类的实例。
class MathUtils {
static PI = 3.14159;
static calculateCircleArea(radius) {
return MathUtils.PI * radius * radius;
}
}
console.log(`PI: ${MathUtils.PI}`); // 输出: PI: 3.14159
console.log(`Area of circle with radius 5: ${MathUtils.calculateCircleArea(5)}`); // 输出: Area of circle with radius 5: 78.53975
在这个例子中,PI
是静态属性,calculateCircleArea
是静态方法。它们都属于MathUtils
类,可以直接通过MathUtils.PI
和MathUtils.calculateCircleArea()
访问。
使用场景:
- 常量: 静态属性可以用来存储常量,例如上面的
PI
。 - 工具函数: 静态方法可以用来实现工具函数,例如上面的
calculateCircleArea
。 - 单例模式: 静态属性可以用来实现单例模式,确保类只有一个实例。
五、getter
和 setter
:属性访问的“守门员”
getter
和setter
允许你控制属性的访问和修改。它们可以让你在访问或修改属性时执行一些额外的操作,例如验证数据、计算值等。
class Temperature {
constructor(celsius) {
this._celsius = celsius;
}
get fahrenheit() {
return this._celsius * 1.8 + 32;
}
set fahrenheit(value) {
this._celsius = (value - 32) / 1.8;
}
get celsius() {
return this._celsius;
}
set celsius(value) {
if (value < -273.15) {
throw new Error("Temperature cannot be below absolute zero.");
}
this._celsius = value;
}
}
const temp = new Temperature(25);
console.log(`Celsius: ${temp.celsius}`); // 输出: Celsius: 25
console.log(`Fahrenheit: ${temp.fahrenheit}`); // 输出: Fahrenheit: 77
temp.fahrenheit = 68;
console.log(`Celsius: ${temp.celsius}`); // 输出: Celsius: 20
try {
temp.celsius = -300;
} catch (e) {
console.error(e.message); // 输出: Temperature cannot be below absolute zero.
}
在这个例子中,fahrenheit
是一个只读属性,因为它只有getter
没有setter
。celsius
属性具有getter和setter,并且setter对输入值进行了验证。
注意,这里使用了_celsius
来存储实际的摄氏度值。这是一种常见的约定,表示这个属性是私有的,不应该直接访问。当然,JavaScript并没有真正的私有属性(直到ES2022引入了私有字段),这只是一种建议。
六、extends
的多重继承?想多了!
JavaScript的class
只支持单继承,也就是说一个类只能继承一个父类。如果你想实现多重继承的效果,可以考虑使用组合(Composition)的方式,将多个类的功能组合到一个类中。
七、class
的本质:语法糖,糖衣炮弹?
前面已经说过了,class
本质上是语法糖,它只是让你的代码看起来更像面向对象,但底层仍然是基于原型链的。
class MyClass {
constructor(value) {
this.value = value;
}
getValue() {
return this.value;
}
}
console.log(typeof MyClass); // 输出: function
console.log(MyClass.prototype.constructor === MyClass); // 输出: true
可以看到,MyClass
的类型是function
,也就是说class
实际上就是一个函数。而MyClass.prototype.constructor
指向的也是MyClass
本身。
八、关于私有属性,ES2022的救星
在ES2022之前,JavaScript并没有真正的私有属性。通常的做法是使用下划线_
来命名私有属性,但这只是一种约定,并不能阻止外部访问。
ES2022引入了私有字段,使用#
符号来声明。私有字段只能在类的内部访问,外部无法访问。
class Counter {
#count = 0;
increment() {
this.#count++;
}
decrement() {
this.#count--;
}
getCount() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 输出: 2
// console.log(counter.#count); // 报错: Private field '#count' must be declared in an enclosing class
九、总结:class
的优缺点
特性 | 优点 | 缺点 |
---|---|---|
语法 | 更简洁、易读,更接近传统的面向对象语言。 | 本质上是语法糖,对原型链的理解仍然是必要的。 |
继承 | 提供了extends 关键字,方便实现继承关系。 |
只支持单继承,多重继承需要使用组合的方式实现。 |
静态方法/属性 | 方便定义类级别的共享资源。 | |
getter /setter |
可以控制属性的访问和修改,实现数据验证和计算。 | |
私有属性 | ES2022引入了私有字段,可以真正实现属性的私有化。 | 在ES2022之前,只能使用约定来模拟私有属性。 |
总的来说,class
是JavaScript面向对象编程的一个重要改进。它让代码更易读、易维护,也更符合传统的面向对象编程习惯。虽然它只是语法糖,但它降低了学习和使用的门槛,让更多人可以轻松地编写面向对象的JavaScript代码。
十、最后的彩蛋:class
的一些“坑”
this
指向: 在class
的方法中,this
的指向可能会让你感到困惑。你需要小心处理this
的绑定,特别是当你在回调函数中使用方法时。可以使用bind
、call
、apply
或者箭头函数来解决this
的指向问题。new
关键字: 使用class
创建对象必须使用new
关键字。如果你忘记了new
,会抛出一个错误。class
声明不会提升: 与函数声明不同,class
声明不会被提升到代码的顶部。你必须在使用class
之前先声明它。
好了,今天的讲座就到这里。希望大家通过今天的学习,能够更好地理解和使用JavaScript的class
语法,写出更优雅、更健壮的代码!记住,class
只是工具,关键在于你如何运用它,设计出优秀的面向对象程序。 祝大家编程愉快!