面向对象编程(OOP)的核心思想,以及在 JavaScript(特别是 ES6)中的具体实现和特性。
一、面向对象的核心思想
1. 基本概念
- 类与对象:类是对现实世界中具有相同特征事物的抽象描述(模板);对象是根据类创建出来的具体实例(具体事物)。一个类可以衍生出无穷无尽的对象。
- 核心目的:面向对象是对具象世界的抽象表达。通过屏蔽不关心的细节和差异,提取事物的共性进行分类,研究其共同的属性和行为,从而形成固定的、丰富的处理方案(即设计模式)。
- 设计 vs 编程:
- 面向对象设计:从现实世界抽象出“类”的思维过程。
- 面向对象编程:将设计转换成具体代码(如 JS 中的类)的实现过程。
- 封装:在 JS 中,把属性和方法写在类里面来描述一系列相同事务的共同特征,这个过程就叫封装。
2. 编程范式对比
- 面向过程:以过程(函数)为中心,关注“怎么做”,将问题拆解为一步步的执行流程。
- 面向对象:以对象(数据和行为)为中心,关注“谁来做”,系统由哪些东西组成,它们有什么状态(属性)以及彼此间的关系。
- 函数式编程:强调一切都是计算,以及计算与计算之间的关系。
二、JavaScript 中的类与成员
1. 类的组成与成员
- 构造函数 (Constructor):描述对象产生时的特征。在创建对象时自动调用,用于初始化。
- 实例成员:属于对象本身。每个对象都有自己独立的副本,互不影响(如:每个学生的姓名)。
- 静态成员 (Static):属于类本身。不需要创建对象就能访问,所有对象共享同一份数据(如:全局配置、计数器)。
2. 访问器(Getter & Setter)
- 作用:让一个方法看起来像属性,避免数据冗余。例如
总价 = 数量 * 单价,总价不需要作为属性存储,而是通过计算得出。 - 实现方式:
- ES5:使用
Object.defineProperty(object, 'total', { get: function () { return this.price * this.count; } })。 - ES6:直接使用语法糖
get 方法名() {}。一旦读取这个属性,就相当于调用了这个方法。
- ES5:使用
三、ES6 类的特性与底层机制
1. 可枚举性 (Enumerable) ES6 的类在设计上对属性的可枚举性做了严格限制,以保持代码的整洁:
- 类的方法:在 ES6 类中定义的方法,默认是不可枚举的(
enumerable: false)。 - 访问器属性 (Getter/Setter):在类中定义的访问器属性也是不可枚举的。
- 表现:在控制台查看对象时,这些属性通常会显示为淡淡的颜色;使用
Object.keys()遍历时会忽略它们。 - ES5 转换注意:如果在 ES5 中手动模拟类的访问器,需要显式加上
enumerable: false来保持与 ES6 一致的行为。
- 表现:在控制台查看对象时,这些属性通常会显示为淡淡的颜色;使用
2. 暂时性死区 (TDZ)
- 定义:在 ES6 中,使用
let、const或class声明变量/类之前,该变量处于不可访问的状态。 - 类的表现:ES6 的类不存在变量提升,且必须通过
new来调用。 - 检测是否使用 New:可以通过判断
this的原型来检测类是否被正确实例化。例如:if (new.target === Product)或检查this的原型链是否指向Product.prototype,以此判断有无使用new。
3. ES6 转 ES5 (Babel 原理简述)
- 核心转换:ES6 的类本质上是 ES5 构造函数的“语法糖”。
- 转换逻辑:
- 类的
constructor转换为 ES5 的构造函数。 - 类的方法转换为添加到
构造函数.prototype上的方法。 - 静态方法转换为直接添加到构造函数上的方法。
- 继承机制通过原型链(
Object.create等)来实现。
- 类的
总结
面向对象不仅仅是写代码的方式,更是一种对现实世界的抽象思维。在 JavaScript 中,我们通过类来封装共同的特征(属性)和行为(方法),利用实例与静态成员区分个体与整体,并通过访问器优雅地处理计算属性。理解 ES6 类底层的不可枚举性和暂时性死区,能帮助我们写出更健壮、更符合语言规范的代码。