JavaScript 是产生“类”的又是如何演变成“面向对象”的Class语法糖背后隐藏着什么秘密本篇文章将完整梳理 JavaScript 面向对象编程的发展历程。前言为什么JavaScript需要面向对象var name 张三; var age 25; var job 工程师; function sayHello(person) { console.log(你好我是 person.name); }在早期的 JavaScript 代码中我们通常采用的是过程式编程。但随着应用复杂度增加我们需要更好的代码组织方式因此面向对象编程应运而生。工厂模式面向对象的雏形什么是工厂模式工厂模式是最简单的创建对象的方式它就像一个“工厂”一样批量生产对象。我们来看一个简单的工厂模式示例// 创建Person对象的工厂 function createPerson(name, age, job) { // 1. 创建一个新对象 var obj {}; // 2. 添加属性 obj.name name; obj.age age; obj.job job; // 3. 添加方法 obj.sayHello function() { console.log(你好我是 this.name 今年 this.age 岁); }; obj.work function() { console.log(this.name 正在工作 this.job); }; // 4. 返回对象 return obj; } // 使用工厂模式创建对象 var person1 createPerson(张三, 25, 前端工程师); var person2 createPerson(李四, 30, 后端工程师); person1.sayHello(); // 你好我是张三今年25岁 person2.work(); // 李四正在工作后端工程师 console.log(person1.sayHello person2.sayHello); // false但这种方式存在一个问题每个对象都有独立的方法副本浪费内存。工厂模式的优点简单易懂可以创建多个相似对象封装了创建过程工厂模式的缺点无法识别对象类型person1 instanceof createPerson; // false方法重复创建内存浪费构造函数模式引入类型概念什么是构造函数构造函数通过new关键字创建对象解决了工厂模式的类型识别问题。我们来看一个简单的示例// 构造函数模式 function Person(name, age, job) { // 1. 创建一个新对象隐式this {} // 2. 设置原型链隐式this.__proto__ Person.prototype // 3. 添加属性 this.name name; this.age age; this.job job; // 4. 添加方法仍然有问题 this.sayHello function() { console.log(你好我是 this.name); }; // 5. 返回this隐式return this } // 使用new关键字创建对象实例 var person1 new Person(张三, 25, 工程师); var person2 new Person(李四, 30, 设计师); person1.sayHello(); // 你好我是张三 person2.sayHello(); // 你好我是李四 console.log(person1 instanceof Person); // true console.log(person1 instanceof Object); // true console.log(person1.constructor Person); // true console.log(person1.sayHello person2.sayHello); // false从上述代码中我们可以看出构造函数模式中可以识别对象类型了但每个实例仍有独立的方法副本内存浪费问题仍然存在。new操作符的工作原理function myNew(constructor, ...args) { // 1. 创建一个新对象 const obj {}; // 2. 设置原型链将新对象的__proto__指向构造函数的prototype obj.__proto__ constructor.prototype; // const obj Object.create(Constructor.prototype); // 这种写法也是可以的 // 3. 绑定this并执行构造函数 const result constructor.apply(obj, args); // 4. 返回结果如果构造函数返回对象则返回该对象否则返回新对象 return result instanceof Object ? result : obj; }原型模式解决方法共享问题原型模式的处理方法是将方法定义在原型上实现共享。function Person(name, age) { // 属性定义在实例上每个实例独立 this.name name; this.age age; } // 方法定义在原型上所有实例共享 Person.prototype.sayHello function() { console.log(你好我是 this.name 今年 this.age 岁); }; Person.prototype.work function() { console.log(this.name 正在工作); }; // 创建实例 const p1 new Person(张三, 25); const p2 new Person(李四, 30); p1.sayHello(); // 你好我是张三今年25岁 p2.sayHello(); // 你好我是李四今年30岁 // 现在方法是共享的 console.log(p1.sayHello p2.sayHello); // true原型模式带来的问题所有实例都会共享引用类型属性如果在原型上定义引用类型一个数据修改时所有对象对应的数据都会修改所有实例共享相同的原型属性无法动态传递初始化参数组合继承结合构造函数和原型的优点组合继承的实现组合继承的实现使用构造函数定义实例属性使用原型定义共享方法。function Parent(name) { this.name name; this.colors [red, blue]; } Parent.prototype.sayName function() { console.log(this.name); }; function Child(name, age) { Parent.call(this, name); // 第一次调用继承实例属性 this.age age; } Child.prototype new Parent(); // 第二次调用继承原型方法 Child.prototype.constructor Child; // 修复constructor指向组合继承是JavaScript中最常用的继承模式。组合继承的缺点父类构造函数被调用了两次Parent.call(this, name);第一次调用继承实例属性Child.prototype new Parent();第二次调用继承原型方法寄生组合继承最理想的继承方式寄生组合继承的实现function inheritPrototype(child, parent) { // 创建父类原型的副本 const prototype Object.create(parent.prototype); // 修复constructor指向 prototype.constructor child; // 将副本设置为子类的原型 child.prototype prototype; } // 父类 function Animal(name) { this.name name; this.colors [red, blue]; } Animal.prototype.sayName function() { console.log(我是 this.name); }; // 子类 function Dog(name, age) { // 继承实例属性只调用一次父类构造函数 Animal.call(this, name); this.age age; } // 继承原型方法不使用new Parent()避免第二次调用 inheritPrototype(Dog, Animal); // 添加子类特有方法 Dog.prototype.bark function() { console.log(this.name 在叫汪汪); };Class语法ES6的语法糖Class的基本语法class Person { // 构造函数对应ES5的构造函数 constructor(name, age) { // 实例属性 this.name name; this.age age; this._secret 这是我的秘密; // 约定俗成的私有属性 } // 实例方法自动添加到原型上 introduce() { console.log(大家好我是${this.name}今年${this.age}岁); } eat(food) { console.log(${this.name}正在吃${food}); } // getter和setter get secret() { return this._secret; } set secret(value) { this._secret value; } // 静态方法类方法 static createAnonymous() { return new Person(匿名, 0); } }Class语法背后的原型原理Class 语法只是语法糖底层仍然是原型继承其本质是一个函数class Animal { constructor(name) { this.name name; } speak() { console.log(this.name makes a noise.); } } // Class实际上是一个函数 console.log(typeof Animal); // functionextends 继承extends 基本语法在 ES6 的class语法糖中通过extends语法糖实现继承。class Dog extends Animal { constructor(name) { super(name); } speak() { console.log(this.name barks.); } }extends 继承的本质extends继承的本质就等价于ES5的寄生组合继承function AnimalES5(name) { this.name name; } AnimalES5.prototype.speak function() { console.log(this.name makes a noise.); }; function DogES5(name) { AnimalES5.call(this, name); } // 设置原型链 DogES5.prototype Object.create(AnimalES5.prototype); DogES5.prototype.constructor DogES5; DogES5.prototype.speak function() { console.log(this.name barks.); };Class的高级特性类表达式const MyClass class { constructor(value) { this.value value; } getValue() { return this.value; } }; const obj1 new MyClass(42); console.log(obj1.getValue()); // 42私有字段class BankAccount { // 私有字段以#开头 #balance 0; constructor(owner) { this.owner owner; } // 通过公开方法访问私有字段 getBalance() { return this.#balance; } } const account new BankAccount(张三); // console.log(account.#balance); // SyntaxError: 属性 #balance 在类 BankAccount 外部不可访问。 console.log(account.getBalance()); // 500静态块class Config { static dbConfig; static apiConfig; // 静态初始化块 static { console.log(初始化静态配置...); this.dbConfig { host: localhost, port: 3306, username: root }; this.apiConfig { baseUrl: https://api.example.com, timeout: 5000 }; } static getConfig() { return { db: this.dbConfig, api: this.apiConfig }; } }类的访问器属性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 kelvin() { return this.celsius 273.15; } }Mixin模式多重继承的替代方案const FlyMixin (BaseClass) class extends BaseClass { fly() { console.log(${this.name} is flying!); } }; const SwimMixin (BaseClass) class extends BaseClass { swim() { console.log(${this.name} is swimming!); } }; class Animal { constructor(name) { this.name name; } eat() { console.log(${this.name} is eating); } } // 应用Mixin class Duck extends SwimMixin(FlyMixin(Animal)) { quack() { console.log(${this.name} says: Quack!); } }面向对象编程的演进路线工厂模式 → 构造函数模式 → 原型模式 → 组合继承 → 寄生组合继承 → Class语法 ↓ ↓ ↓ ↓ ↓ ↓ 创建对象 识别类型 共享方法 结合优点 最优方案 语法糖结语面向对象编程是 JavaScript 发展的重要里程碑。理解从工厂模式到 Class 语法的演进过程不仅能让我们写出更好的代码还能在遇到问题时快速定位和解决。对于文章中错误的地方或者有任何问题欢迎在评论区留言讨论原文 https://juejin.cn/post/76022596