在 JavaScript 中,虽然 ES6 引入了 class 关键字让面向对象编程更加直观和易读,但其底层机制仍然是基于原型(prototype)的继承模型。本文将带你一步步理解:
- 如何手动实现类似
new的功能 - ES6 中
class的核心特性 - 如何用 ES5 手动模拟 ES6 的
class和继承机制
通过这篇文章,你不仅能掌握 JavaScript 面向对象的核心原理,还能深入理解类和原型之间的关系。
一、JavaScript 中的 new 运算符详解
✅ new 的作用
当你使用 new 创建一个对象时,JavaScript 引擎会执行以下步骤:
- 创建一个新的空对象。
- 将这个新对象的内部
[[Prototype]](即__proto__)指向构造函数的prototype属性。 - 构造函数被调用,并且
this被绑定到新对象。 - 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象。
🛠️ 手动实现 myNew 函数
我们可以模仿 new 的行为,实现一个自定义的 myNew 函数:
function myNew(constructor, ...args) {
// 1. 创建一个空对象,并继承构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 执行构造函数,并绑定 this 到 obj
const result = constructor.apply(obj, args);
// 3. 如果构造函数返回了一个对象,则返回它;否则返回新对象
return (typeof result === 'object' && result !== null) ? result : obj;
}
✅ 使用示例
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
// 原生 new
const p1 = new Person('Alice', 25);
p1.sayHello(); // 输出: Hello, I'm Alice
// 自定义 myNew
const p2 = myNew(Person, 'Bob', 30);
p2.sayHello(); // 输出: Hello, I'm Bob
🔍 注意事项
Object.create(proto)用于设置原型链。- 构造函数可能返回一个对象(如单例模式),此时应返回该对象。
- 若未显式返回对象,默认返回我们创建的新对象。
二、ES6 中的 class 简介与使用
ES6 的 class 是一种语法糖,本质上是基于原型的封装。它提供了更清晰、更接近传统面向对象语言(如 Java、C++)的写法。
✅ 最简 class 示例
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, I'm ${this.name}`);
}
}
const p = new Person('Alice', 25);
p.sayHello(); // Hello, I'm Alice
🧱 类的组成部分
| 特性 | 示例 |
|---|---|
| 构造函数 | constructor() |
| 实例方法 | 直接写在类体中的方法 |
| 静态方法 | static info() |
| getter/setter | get fullName() / set fullName(value) |
| 私有字段 | #privateField(以 # 开头) |
✅ 继承与 super
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.`);
}
static info() {
console.log("This is a dog class.");
}
#privateData = "Secret Info";
showPrivateData() {
console.log(this.#privateData);
}
}
✅ 使用子类
const d = new Dog("Buddy", "Golden Retriever");
d.speak(); // Buddy barks.
d.showPrivateData(); // Secret Info
Dog.info(); // This is a dog class.
console.log(d instanceof Dog); // true
console.log(d instanceof Animal); // true
三、用 ES5 实现 ES6 的 class 与继承机制
为了更深入地理解 class 的本质,我们可以使用 ES5 来模拟其实现。
🎯 模拟目标
我们将模拟以下 ES6 类结构:
class Animal { /* ... */ }
class Dog extends Animal { /* ... */ }
✅ ES5 实现完整代码
// ========== Animal 构造函数 ==========
function Animal(name) {
if (!(this instanceof Animal)) {
throw new TypeError("Animal must be called with new");
}
this.name = name;
}
Animal.prototype.speak = function () {
console.log(`${this.name} makes a noise.`);
};
// ========== Dog 构造函数 ==========
function Dog(name, breed) {
if (!(this instanceof Dog)) {
throw new TypeError("Dog must be called with new");
}
// 模拟 super()
Animal.call(this, name);
this.breed = breed;
}
// 设置原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 重写 speak 方法
Dog.prototype.speak = function () {
console.log(`${this.name} barks.`);
};
✅ 使用示例
var animal = new Animal("Generic Animal");
animal.speak(); // Generic Animal makes a noise.
var dog = new Dog("Buddy", "Golden Retriever");
dog.speak(); // Buddy barks.
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
🔍 关键点解析
| ES6 语法 | ES5 模拟方式 |
|---|---|
class | 构造函数 + prototype |
extends | Object.create(Parent.prototype) 设置原型链 |
super() | Parent.call(this, ...) 调用父类构造函数 |
method() | 添加到 prototype 上的方法 |
instanceof | 正确设置原型链和 constructor |
四、进阶技巧:封装继承工具函数
为了简化继承逻辑,我们可以封装一个通用的 extend 工具函数:
function extend(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.super = superClass;
}
然后这样使用:
extend(Dog, Animal);
这可以让你更清晰地组织代码结构,尤其是在构建大型应用或库时非常有用。
五、注意事项与限制
- 必须使用
new调用构造函数:否则this会指向全局对象(非严格模式下)或undefined(严格模式下)。 - 不支持私有字段:除非你使用闭包或 Symbol 属性进行封装。
- 静态方法不会自动继承:需要手动绑定。
- getter/setter 需要使用
Object.defineProperty来定义。
六、总结
通过这篇文章,你应该已经掌握了以下几个关键知识点:
✅ 如何手动实现 JavaScript 中的 new
✅ ES6 class 的基本语法与特性
✅ 如何用 ES5 模拟 ES6 的类和继承机制
✅ 如何封装工具函数简化继承逻辑
这些知识不仅有助于你更好地理解 JavaScript 的原型系统,也能帮助你在没有现代语法支持的环境中编写兼容性强的代码。
📚 延伸阅读建议
如果你对下面的内容感兴趣,可以继续学习:
- JavaScript 原型链与继承的深入解析
- 闭包与模块化设计
- ES6+ 新特性(如
Symbol,Proxy,Reflect) - 使用 Babel 编译 ES6+ 代码为 ES5
- 构建自己的类工厂函数或 OOP 库