ES6类(Classes):语法糖下的原型与继承
引言
嘿,大家好!今天我们要聊一聊ES6中的类(Classes)。如果你觉得类是个高深莫测的概念,那你就大错特错了!其实,ES6的类只是JavaScript原型链和构造函数的一层“语法糖”,它让代码看起来更简洁、更易读。接下来,我会用轻松诙谐的语言,带你深入了解ES6类背后的原型与继承机制。
什么是ES6类?
在ES6之前,JavaScript并没有真正的“类”概念。我们通常使用构造函数和原型链来模拟面向对象编程。ES6引入了class
关键字,虽然它看起来像是其他语言中的类,但实际上它只是对现有机制的封装,简化了代码的编写。
传统方式 vs. ES6类
让我们先看看传统的构造函数和原型链的写法:
// 传统方式
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
const person1 = new Person('Alice');
person1.sayHello(); // Hello, my name is Alice
现在,用ES6类来实现同样的功能:
// ES6类
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}
const person1 = new Person('Alice');
person1.sayHello(); // Hello, my name is Alice
是不是看起来更简洁了?class
关键字让我们不需要显式地使用function
和prototype
,但底层的机制并没有改变。
类的本质:构造函数 + 原型链
ES6类本质上仍然是基于构造函数和原型链的。你可以通过以下代码验证这一点:
console.log(typeof Person); // "function"
console.log(Person === Person.prototype.constructor); // true
这说明Person
类实际上是一个构造函数,而Person.prototype
是它的原型对象。当我们创建一个实例时,new Person()
会调用构造函数,并将实例的__proto__
指向Person.prototype
。
静态方法与静态属性
除了实例方法,ES6类还支持静态方法和静态属性。静态方法不会被实例继承,而是直接属于类本身。静态属性也是类似的概念,但它们是类的属性而不是方法。
class MathUtils {
static add(a, b) {
return a + b;
}
static PI = 3.14; // 静态属性
}
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.PI); // 3.14
getter 和 setter
ES6类还支持get
和set
方法,用于定义属性的访问器。这在处理私有属性或需要对属性进行验证时非常有用。
class Rectangle {
constructor(width, height) {
this._width = width;
this._height = height;
}
get area() {
return this._width * this._height;
}
set width(value) {
if (value > 0) {
this._width = value;
} else {
throw new Error('Width must be positive');
}
}
}
const rect = new Rectangle(4, 5);
console.log(rect.area); // 20
rect.width = 6;
console.log(rect.area); // 30
继承:子类与父类的关系
ES6类的一个重要特性是继承。通过extends
关键字,我们可以轻松地创建子类,并从父类继承属性和方法。子类可以重写父类的方法,也可以调用父类的方法。
基本继承
下面是一个简单的继承示例:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类的构造函数
this.breed = breed;
}
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // Buddy barks.
在这个例子中,Dog
类继承了Animal
类的name
属性和speak
方法。super()
用于调用父类的构造函数,确保子类能够正确初始化父类的属性。
方法重写与super
子类可以重写父类的方法,但有时我们仍然希望调用父类的原始实现。这时可以使用super
关键字来调用父类的方法。
class Cat extends Animal {
constructor(name, color) {
super(name);
this.color = color;
}
speak() {
super.speak(); // 调用父类的speak方法
console.log(`${this.name} meows.`);
}
}
const cat = new Cat('Whiskers', 'gray');
cat.speak();
// Whiskers makes a noise.
// Whiskers meows.
多层继承
ES6类支持多层继承,即子类可以继承自另一个子类。这种结构在复杂的项目中非常常见。
class Bird extends Animal {
constructor(name, wingspan) {
super(name);
this.wingspan = wingspan;
}
fly() {
console.log(`${this.name} flies with a wingspan of ${this.wingspan} meters.`);
}
}
class Penguin extends Bird {
constructor(name, wingspan, habitat) {
super(name, wingspan);
this.habitat = habitat;
}
fly() {
console.log(`${this.name} cannot fly, but it swims in the ${this.habitat}.`);
}
}
const penguin = new Penguin('Pingu', 0.7, 'Antarctica');
penguin.fly(); // Pingu cannot fly, but it swims in the Antarctica.
类的内部工作原理
虽然ES6类看起来像是面向对象编程的“真类”,但它们实际上是基于原型链的。为了更好地理解这一点,我们可以查看类的内部结构。
构造函数与原型
每个类都有一个隐式的构造函数,它可以通过constructor
方法来定义。如果没有显式定义构造函数,JavaScript会提供一个默认的构造函数。
class Vehicle {
// 默认构造函数
}
class Car extends Vehicle {
// 子类的构造函数
constructor(make, model) {
super(); // 调用父类的构造函数
this.make = make;
this.model = model;
}
}
原型链
当你创建一个类的实例时,JavaScript会在实例的__proto__
属性中存储该类的原型对象。这个原型对象又会指向其父类的原型对象,依次类推,直到到达Object.prototype
。
class A {}
class B extends A {}
class C extends B {}
const c = new C();
console.log(C.__proto__ === B); // false
console.log(C.__proto__ === B.prototype); // false
console.log(C.__proto__ === Function.prototype); // true
console.log(c.__proto__ === C.prototype); // true
console.log(c.__proto__.__proto__ === B.prototype); // true
console.log(c.__proto__.__proto__.__proto__ === A.prototype); // true
console.log(c.__proto__.__proto__.__proto__.__proto__ === Object.prototype); // true
instanceof
操作符
instanceof
操作符用于检查一个对象是否是某个类的实例。它会沿着原型链向上查找,直到找到匹配的构造函数。
const a = new A();
const b = new B();
const c = new C();
console.log(c instanceof C); // true
console.log(c instanceof B); // true
console.log(c instanceof A); // true
console.log(c instanceof Object); // true
总结
好了,今天的讲座就到这里!我们讨论了ES6类的基本语法、继承机制以及它们与原型链的关系。虽然ES6类看起来像是面向对象编程的“真类”,但它们实际上是基于JavaScript的构造函数和原型链的语法糖。通过理解这些底层机制,你可以更好地掌握类的工作原理,并写出更加优雅的代码。
最后,引用MDN文档中的一句话:“Class bodies are always executed in strict mode。”这意味着在类中使用的所有代码都会自动启用严格模式,因此你不需要手动添加'use strict';
。
希望这篇文章对你有所帮助,如果你有任何问题,欢迎随时提问!