JavaScript 面向对象编程(OOP):从原始模式到原型继承

JavaScript 面向对象编程(OOP):从原始模式到原型继承

本文系统梳理 JavaScript 中面向对象编程的核心思想与实现方式,涵盖封装、原型链、继承等关键概念,并对比 ES5 与 ES6 的不同写法。适合初学者理解 JS OOP 的底层机制,也帮助进阶开发者巩固基础。


一、JavaScript 是"基于对象"的语言

JavaScript 虽然常被归类为"面向对象语言",但它没有传统意义上的类(Class) (ES6 之前)。它是一种 基于原型(Prototype-based) 的语言,几乎所有值(包括基本类型)在运行时都能表现为对象。

  • 早期 JS 没有 class 关键字,但可以通过函数模拟"构造器"。
  • ES6 引入的 class 只是语法糖,底层依然依赖原型链机制。

二、生成实例对象的原始模式

最朴素的方式是使用对象字面量

css 复制代码
var cat1 = {
  name: "加菲猫",
  color: "橘色"
};
var cat2 = {
  name: "coke",
  color: "黑色"
};

问题

  • 代码重复;
  • 无法批量创建;
  • 实例之间无关联,难以判断"是否属于同一类"。

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

通过函数 + new 关键字实现"类"的效果:

ini 复制代码
function Cat(name, color) {
  this.name = name;
  this.color = color;
}

const cat1 = new Cat('加菲猫', '橘色');
const cat2 = new Cat('coke', '黑色');

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

instanceof:

  • 作用 :检测构造函数的 prototype 是否出现在对象的原型链上。
  • 返回值:布尔值。
  • 用途:判断对象是否为某构造函数的实例。

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

  1. 创建一个空对象 {}
  2. 将该对象的 __proto__ 指向 Cat.prototype
  3. 执行构造函数,this 指向新对象;
  4. 返回该对象(若构造函数无显式返回)。

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


四、原型(Prototype)模式:解决方法重复问题

如果在构造函数中定义方法,每个实例都会拥有独立副本,浪费内存:

ini 复制代码
function Cat(name, color) {
  this.name = name;
  this.color = color;
  // ❌ 每个实例都新建一个 eat 函数
  this.eat = function() { console.log('吃鱼'); };
}

解决方案:将公共属性/方法挂载到 prototype

ini 复制代码
function Cat(name, color) {
  this.name = name;
  this.color = color;
}

Cat.prototype.type = '猫';
Cat.prototype.eat = function() {
  console.log('喜欢 eat jerry');
};

const cat1 = new Cat('tom', '黑色');
const cat2 = new Cat('加菲猫', '橘色');

console.log(cat1.type); // '猫'
cat1.eat(); // 共享方法

原型链查找机制:

  • 访问属性时,先查实例自身(hasOwnPropertytrue);
  • 若未找到,则沿 __proto__ 向上查找 prototype
  • 直到 Object.prototype,最终为 null
arduino 复制代码
console.log(cat1.hasOwnProperty('name')); // true(自有属性)
console.log(cat1.hasOwnProperty('type')); // false(来自 prototype)
console.log('type' in cat1); // true(可访问,无论来源)

Object.prototype.hasOwnProperty(prop):

  • 作用 :判断对象自身(不包括原型链)是否拥有指定属性。
  • 返回值:布尔值。
  • 用途:区分自有属性和继承属性。

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

1. 借用构造函数(仅继承实例属性)

ini 复制代码
function Animal() {
  this.species = '动物';
}

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

apply:

  • 作用 :调用一个函数,并显示指定其内部 this 的值,同时传入参数数组(或类数组)。
  • 常用于:在子类构造函数中"借用"父类构造函数,实现属性继承。

✅ 优点:可传承,支持多继承。

❌ 缺点:无法继承父类原型上的方法 (如 Animal.prototype.sayHi)。


2. 原型链继承(继承原型方法)

ini 复制代码
function Animal() {
  this.species = '动物';
}
Animal.prototype.sayHi = function() {
  console.log('你好,我是动物');
};

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

// 关键:让 Cat.prototype 指向 Animal 实例
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; // 修复 constructor 指向

const cat = new Cat('加菲猫', '橘色');
cat.sayHi(); // ✅ 成功继承原型方法

这是经典的 组合继承(Combination Inheritance)

  • 构造函数继承属性(支持传参);
  • 原型链继承方法(共享、高效)。

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

javascript 复制代码
class Animal {
  constructor() {
    this.species = '动物';
  }
  sayHi() {
    console.log('你好,我是动物');
  }
}

class Cat extends Animal {
  constructor(name, color) {
    super(); // 等价于 Animal.call(this)
    this.name = name;
    this.color = color;
  }
  eat() {
    console.log('喜欢 eat jerry');
  }
}

const cat1 = new Cat('tom', '黑色');
cat1.sayHi(); // 继承自 Animal
cat1.eat();   // 自身方法

注意:

  • class 本质仍是基于原型;
  • super() 必须在子类 constructor 中调用;
  • 方法自动绑定到 prototype,无需手动设置。

七、总结:JS OOP 的演进路径

方式 特点 适用场景
对象字面量 简单直接 单例、配置对象
构造函数 封装实例化 需要多个相似对象
原型模式 共享方法,节省内存 大量实例 + 公共行为
组合继承 属性 + 方法完整继承 传统 ES5 继承方案
ES6 Class 语法简洁,语义清晰 现代项目首选

八、延伸思考

  • 为什么 JS 不直接用类?

    因其动态性与灵活性,原型链更适合运行时修改行为(如 monkey patching)。

  • __proto__ vs prototype

    • prototype函数的属性,用于构建原型链;
    • __proto__对象 的内部属性,指向其构造函数的 prototype
  • 如何判断继承关系?

    javascript 复制代码
    cat1 instanceof Cat;      // true
    Cat.prototype.isPrototypeOf(cat1); // true

📌 记住 :无论用 function 还是 class,JavaScript 的 OOP 核心始终是 原型链 。理解 prototype__proto__constructor 的关系,是掌握 JS 面向对象的关键。


参考资料

相关推荐
ywf121510 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
恋猫de小郭10 小时前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
hpoenixf16 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特16 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷17 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian17 小时前
前端node常用配置
前端
华洛18 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq18 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A19 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常19 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端