好的,我们来梳理一下 JavaScript 面向对象(OOP)的发展路径。这是一个从简单模仿到拥有专用语法,但始终根植于原型机制的演变过程。
阶段一:原始时期 - 万物皆对象与简单模仿 (ES3 之前)
-
核心思想: JavaScript 从一开始就设计成一种基于对象的语言(但不是严格意义上的类继承)。对象字面量 ({}) 是创建对象的便捷方式,用于组织数据和功能。
-
早期实践:
-
对象字面量: 最基本的形式,直接创建包含数据和函数的对象。
jsvar person = { name: "Brendan", greet: function() { console.log("Hello, I'm " + this.name); } }; person.greet();
-
工厂函数: 创建返回对象的函数,试图模拟类的创建过程,但实例之间没有明确的类型关系。
jsfunction createPerson(name) { var obj = {}; // 创建一个新对象 obj.name = name; obj.greet = function() { console.log("Hello, I'm " + this.name); }; return obj; // 返回对象 } var person1 = createPerson("Alice"); var person2 = createPerson("Bob"); // 问题:person1 和 person2 没有共享 greet 方法,浪费内存,且无法识别它们是同一"类型" // console.log(person1 instanceof createPerson); // 错误或 false
-
-
局限性: 缺乏代码复用(方法无法共享)、没有清晰的类型标识(instanceof 无效)、继承实现困难且不规范。
阶段二:构造函数 + 原型模式 (ES3/ES5 - 经典的"类"模拟)
-
核心思想: 利用 JavaScript 函数可以作为构造函数(使用 new 调用)以及每个函数都有一个 prototype 属性(指向一个对象)的特性,来模拟类和继承。这是 ES6 class 出现前最主流、最标准的 OOP 实现方式。
-
关键技术:
- 构造函数 (Constructor Functions): 用于定义实例自身的属性(通常通过 this 赋值)。
- 原型对象 (prototype): 用于定义共享的方法和属性,所有实例通过原型链访问这些成员,实现方法复用。
- new 操作符: 负责创建实例、链接原型、绑定 this 并执行构造函数。
- 原型链 (Prototype Chain): 实现继承的基础机制。
js// 构造函数 function Person(name) { this.name = name; // 实例属性 } // 原型方法 (共享) Person.prototype.greet = function() { console.log("Hello, I'm " + this.name); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); person1.greet(); console.log(person1.greet === person2.greet); // true (方法共享) console.log(person1 instanceof Person); // true (类型识别)
-
继承的实现 (组合继承 - Constructor Stealing + Prototypal Inheritance):
- 在子构造函数中通过 Parent.call(this, ...) 或 Parent.apply(this, ...) 借用父构造函数来继承实例属性。
- 通过 Child.prototype = Object.create(Parent.prototype) (或早期 Child.prototype = new Parent(),但有副作用)来继承原型方法,建立原型链。
- 修复 Child.prototype.constructor 指向。
jsfunction Student(name, grade) { Person.call(this, name); // 继承实例属性 this.grade = grade; } // 继承原型方法 Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; // 修复 constructor Student.prototype.study = function() { console.log(this.name + " is studying."); }; var student1 = new Student("Charlie", 9); student1.greet(); // 继承自 Person student1.study(); // 自身方法 console.log(student1 instanceof Student); // true console.log(student1 instanceof Person); // true
-
优点: 实现了方法复用、类型识别和相对规范的继承模式。
-
缺点: 语法相对繁琐,尤其是继承的实现需要多个步骤,不够直观,this 的指向问题有时也令人困惑。
阶段三:ES6 class 语法糖 (ES2015 - 更清晰的面向对象语法)
-
核心思想: 提供一套更接近传统面向对象语言(如 Java, C++)的 class 语法,让开发者更容易地定义类、构造函数、方法和实现继承。重要的是,class 仅仅是语法糖,其底层实现仍然是基于原型和构造函数的。
-
关键特性:
- class 关键字: 定义类。
- constructor 方法: 类(构造函数)的特殊方法,用于初始化实例。
- 类方法定义: 直接在 class 内部定义方法,无需写 prototype。
- extends 关键字: 用于实现类继承,简化了原型链的设置。
- super 关键字: 用于调用父类的构造函数 (super()) 或父类的方法 (super.methodName())。
- static 关键字: 定义静态方法或属性(属于类本身,而非实例)。
- Getter/Setter: 更方便地定义访问器属性。
jsclass Person { constructor(name) { this.name = name; } greet() { console.log(`Hello, I'm ${this.name}`); } } class Student extends Person { constructor(name, grade) { super(name); // 调用父类 constructor this.grade = grade; } study() { console.log(`${this.name} is studying.`); } // 重写父类方法,并调用父类版本 greet() { super.greet(); console.log(`I am in grade ${this.grade}.`) } } let student1 = new Student("Diana", 10); student1.greet(); student1.study(); console.log(student1 instanceof Student); // true console.log(student1 instanceof Person); // true
-
优点: 语法简洁、清晰、易于理解和维护,更符合传统 OOP 开发者的习惯。
-
缺点: 仍然是语法糖,需要理解其原型本质才能深入排查问题;this 的指向规则依然存在(尤其在方法作为回调传递时)。
阶段四:现代增强与完善 (ES2015 之后)
-
核心思想: 在 class 语法的基础上,进一步完善面向对象的特性,特别是封装和初始化方面。
-
关键特性:
-
Public/Private Class Fields (ES2022):
- Public Fields: myField = 1; 直接在类体中定义实例属性,简化 constructor 中的赋值。
- Private Fields (#): #privateField = 2; 提供了真正的私有成员(属性和方法),只能在类的内部访问,增强了封装性。
jsclass Counter { #count = 0; // 私有字段 increment() { this.#count++; } getCount() { return this.#count; } } const c = new Counter(); c.increment(); console.log(c.getCount()); // 1 // console.log(c.#count); // SyntaxError: Private field '#count' must be declared in an enclosing class
-
Static Public/Private Fields and Methods: 对静态成员也提供了 public/private 支持。
-
Static Initialization Blocks (static {}): 允许在类定义时执行更复杂的静态初始化逻辑。
-
-
优点: 提供了更完善的封装机制(真私有),代码结构更清晰。
-
趋势: JavaScript 的 OOP 越来越向提供更健壮、更符合开发者预期的特性方向发展,同时保持其动态和灵活的本质。
总结:
JavaScript 的面向对象发展路径体现了从模仿 到规范 再到语法优化的过程:
- 早期: 自由但混乱的对象创建和基础功能组合。
- ES3/ES5: 确立了基于构造函数 + 原型的核心模式,模拟了类和继承,成为事实标准。
- ES6: 引入 class 语法糖 ,极大改善了开发体验,使代码更易读写,但底层仍然是原型。
- 现代: 通过私有字段等特性,进一步增强了类的封装能力和语法便利性。
理解这个发展路径,特别是原型机制作为贯穿始终的核心,对于深入掌握 JavaScript 面向对象至关重要。开发者现在可以根据需要选择使用更现代的 class 语法,但了解其背后的原型原理有助于更好地理解语言行为和解决复杂问题。
参考内容
B站 coderwhy官方账号