🧠JavaScript 面向对象编程(OOP)详解:进化 - 从对象字面量到 class

51 阅读5分钟

💡 JavaScript 是一门基于对象(Object-based)的语言
它虽不像 Java、C++ 那样是“纯正”的面向对象语言,但通过 原型链(Prototype Chain) 机制,依然实现了 OOP 的三大核心思想:封装、继承、多态
本文将带你从零开始,深入理解 JS 中的 OOP 实现方式。


🔍 一、为什么说 JS 是“基于对象”而非“真正面向对象”?

  • 📦 万物皆对象:字符串、数字等基本类型也有对应的包装对象(如 new String())。
  • 历史原因:早期 JS 没有 class 关键字,直到 ES6 才引入——但它只是 语法糖 🍬。
  • 🔄 底层机制:JS 的 OOP 基于 原型(Prototypal) ,而非传统“类(Class-based)”。

核心概念速览

  • 🗃️ 对象字面量 → 封装数据
  • 🏗️ 构造函数 + new → 创建实例
  • 🔗 prototype → 共享方法与属性
  • 🧬 原型链 → 实现继承

🧱 二、原始模式:对象字面量

最简单的封装方式:

var Cat = {
  name: "",
  color: ""
};

var cat1 = { name: '加菲猫', color: '橘色' };
var cat2 = { name: '黑猫警长', color: '黑色' };

📌 注:Cat 首字母大写是开发约定,表示“类模板”。

❌ 问题:

  • 🔄 代码重复:每个实例都要手写属性
  • 🤝 无关联性:无法判断 cat1cat2 是否同属一类
  • ♻️ 无法复用方法:比如 eat() 要写两遍

🏗️ 三、构造函数模式:封装实例化过程

使用函数作为“构造器”,配合 new

function Cat(name, color) {
  this.name = name;
  this.color = color;
}

const cat1 = new Cat('Tom', '黑色');
const cat2 = new Cat('哆啦A梦', '蓝色');

console.log(cat1.constructor === Cat); // ✅ true
console.log(cat1 instanceof Cat);      // ✅ true

🔧 new 的执行过程(关键!):

  1. new创建一个空Cat对象 {}
  2. 🎯 将 this 指向这个新对象;
  3. 🛠️ 执行构造函数代码(为 this 添加属性);
  4. 📤 返回这个新对象。

⚠️ 注意:若直接调用 Cat()(不加 new),this 会指向全局对象(如 window),造成污染!


🔗 四、原型模式:解决方法重复问题

❌ 问题代码(浪费内存):

function Cat(name, color) {
  this.eat = function() { console.log('吃Jerry'); }; // 每个实例都新建函数!
}

解决方案:使用 prototype 共享方法

function Cat(name, color) {
  this.name = name;
  this.color = color;
}

Cat.prototype.type = '猫科动物';
Cat.prototype.eat = function() {
  console.log('eat Jerry');
};

🧬 原型链关系图解:

cat1.__proto__Cat.prototypeObject.prototypenull
       ↑                ↑
   实例对象        构造函数的原型对象

🔎 四个关键判断(彻底搞懂属性来源)

1️⃣ cat1.constructor === Cat → ✅ true

🧾 含义:这个对象是由哪个构造函数创建的?
🧠 原理cat1 自身没有 constructor,但通过原型链继承自 Cat.prototype.constructor。(超关键)

2️⃣ Cat.prototype.isPrototypeOf(cat1) → ✅ true

🧬 含义Cat.prototype 是不是 cat1 的原型?
🔍 用途:检测“血缘关系”,确认原型链连接。

3️⃣ cat1.hasOwnProperty('name') → ✅ true

🏠 含义namecat1 自己的属性吗?
只查自身,不看原型链。

4️⃣ 'type' in cat1 → ✅ true

🌐 含义cat1 能不能用到 type
查自身 + 整个原型链

📊 对比总结:

方法 / 操作符查找范围用途说明
obj.hasOwnProperty('prop')🏠 仅自身属性判断是不是“自己的”
'prop' in obj🌐 自身 + 原型链判断能不能“用到”这个属性

💬 一句话记住
hasOwnProperty 看“产权证”,in 看“使用权”。


🧬 五、继承:让子类拥有父类的能力

方法1️⃣:借用构造函数(仅继承实例属性)

function Animal() {
  this.species = '动物';
}

function Cat(name, color) {
  Animal.apply(this); // 👈 关键!
  this.name = name;
  this.color = color;
}

🔧 Animal.apply(this)调用 Animal,并让其内部的 this 指向当前 Cat 实例,从而添加 species 属性。

❌ 缺点:

无法继承 Animal.prototype 上的方法!
因为 apply 只执行函数体,没有修改原型链,所以 sayHi() 等方法无法访问。


方法2️⃣:原型链继承(完整继承)→ 组合继承

function Animal() {
  this.species = '动物';
}
Animal.prototype.sayHi = function() {
  console.log('啦啦啦啦啦');
};

function Cat(name, color) {
  Animal.apply(this); // 继承实例属性
  this.name = name;
  this.color = color;
}

// 🔑 关键:让 Cat 的原型 = Animal 的实例
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; // 修复 constructor

🧠 原理:

  • cat.__proto__new Animal()Animal.prototype
  • 于是 cat.sayHi() 可以顺着原型链找到方法!

✅ 这就是经典的 组合继承构造函数(属性)+ 原型链(方法)


🍬 六、ES6 class:语法糖,更清晰的 OOP 写法

什么是“语法糖”(Syntactic Sugar)?

语法糖(Syntactic Sugar)是指编程语言中添加的某种语法,这种语法对语言的功能没有影响,但更方便程序员阅读和编写代码

简单说:

语法糖 = 更甜、更好写的写法,底层其实没变

它就像给苦咖啡加了一勺糖——味道更好了,但咖啡还是那杯咖啡 ☕。

class Animal {
  constructor() {
    this.species = '动物';
  }
  sayHi() {
    console.log('啦啦啦啦啦');
  }
}

class Cat extends Animal {
  constructor(name, color) {
    super(); // 👈 调用父类构造函数
    this.name = name;
    this.color = color;
  }
}

🧩 class 与原型的关系:

cat.__proto__ === Cat.prototype;           // ✅ true
Cat.prototype.constructor === Cat;         // ✅ true
Cat.prototype.__proto__ === Animal.prototype; // ✅ true

💡 extends 本质就是:
Cat.prototype.__proto__ = Animal.prototype


📈 七、JS OOP 的演进路径

阶段方式特点
🟢 初级对象字面量简单但无法复用
🔵 进阶构造函数封装实例化,但方法重复
🟡 成熟原型模式方法共享,节省内存
🔴 完整组合继承构造函数 + 原型链,完整继承
🟣 现代ES6 class语法糖,语义清晰,贴近传统 OOP

✅ 八、最佳实践建议

  1. 🎯 优先使用 class(ES6+) :语义清晰,不易出错;

  2. 🚫 避免在构造函数中定义方法:应放在 prototypeclass 方法中;

  3. 🔁 继承时记得调用 super()

  4. 🧠 理解两个关键属性的区别

    • __proto__实例的原型(指向构造函数的 prototype
    • prototype构造函数的原型对象(用于共享方法)

🌟 结语

JavaScript 的面向对象虽不同于传统语言,但其 灵活、动态、基于原型 的特性,反而赋予了它强大的表达能力。

🧠 记住
“在 JS 中,万物皆对象,而对象皆可继承。”
掌握原型链,是成为高级 JS 开发者的必经之路!


现在,你已经打通了 JS OOP 的任督二脉! 💪