从字面量到原型链:JavaScript 面向对象的完整进化史

从字面量到原型链:JavaScript 面向对象的完整进化史

JavaScript 是一门基于对象(Object-based) 的语言------你几乎接触到的一切都是对象,甚至连 1'hello' 这样的原始值,在需要时也会被自动包装成 NumberString 对象。然而,JavaScript 并不是传统意义上的"面向对象语言"(如 Java 或 C++),它没有类(class)的概念(直到 ES6 才引入语法糖形式的 class),其核心机制是原型(Prototype)

那么,在没有 class 的年代,我们如何用 JavaScript 实现面向对象编程(OOP)?本文将带你从最原始的对象字面量出发,逐步演进到成熟的原型继承模式,理解 JS OOP 的本质。


一、起点:对象字面量(Object Literal)

最简单的创建对象方式:

javascript 复制代码
const cat1 = {
  name: '小白',
  color: '白色',
  type: '猫科动物',
  eat() {
    console.log('喜欢 Jerry');
  }
};

const cat2 = {
  name: '小黑',
  color: '黑色',
  type: '猫科动物',
  eat() {
    console.log('喜欢 Jerry');
  }
};

优点 :简单直观。

缺点

  • 代码重复;
  • 无法批量生成相似对象;
  • 实例之间没有"类型"关联,无法判断 cat1 是否属于"猫"。

这只是"数据容器",不是"类"的实例。


二、进化:构造函数模式(Constructor Pattern)

为了解决复用问题,我们封装一个函数来生成对象:

ini 复制代码
function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.type = '猫科动物';
  this.eat = function() {
    console.log('喜欢 Jerry');
  };
}

const cat1 = new Cat('小白', '白色');
const cat2 = new Cat('小黑', '黑色');

new 调用时发生了什么?

  1. 创建一个空对象 {}
  2. this 指向这个空对象;
  3. 执行函数体,给 this 添加属性和方法;
  4. 返回这个新对象(除非显式返回其他对象)。

优点

  • 可批量创建实例;
  • 实例可通过 instanceof Cat 判断类型;
  • 实例间有了"关系"。

致命缺点

  • 每个实例都拥有自己的一份 eat 方法 → 内存浪费
  • 公共属性(如 type)也无法共享。

三、优化:原型模式(Prototype Pattern)

JavaScript 的核心思想:把不变的、公共的部分放到原型上

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

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

const cat1 = new Cat('小白', '白色');
const cat2 = new Cat('小黑', '黑色');

console.log(cat1.eat === cat2.eat); // true ✅ 共享同一个方法

原型链机制

  • 每个函数都有一个 prototype 属性(指向原型对象);
  • 每个实例都有一个内部链接 [[Prototype]](在浏览器中可通过 __proto__ 访问),指向其构造函数的 prototype
  • 当访问属性时,JS 会先查实例自身,再沿原型链向上查找。

优点

  • 方法和公共属性共享,节省内存;
  • 支持动态扩展:Cat.prototype.sleep = ... 所有实例立即可用。

四、进阶:继承(Inheritance)

OOP 的三大特性之一是继承 。在 JS 中,如何让 Cat 继承 Animal

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

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

function Cat(name, color) {
  // 关键:让 Animal 的 this 指向当前 Cat 实例
  Animal.apply(this, arguments);
  this.name = name;
  this.color = color;
}
  • Animal.apply(this)借用父类构造函数 ,将 species 添加到子类实例上。
  • ✅ 继承了实例属性
  • 未继承原型方法 (如 Animal.prototype.move)。

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

javascript 复制代码
// 让 Cat.prototype 的原型指向 Animal.prototype
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat; // 修复 constructor 指向

// 补充 Cat 自己的方法
Cat.prototype.purr = function() {
  console.log('呼噜呼噜~');
};

现在,Cat 实例既能访问 species(来自 Animal 构造函数),也能调用 move()(来自 Animal.prototype)。


五、现代写法:ES6 class(语法糖)

ES6 引入了 class 语法,但底层仍是原型:

javascript 复制代码
class Animal {
  constructor() {
    this.species = '动物';
  }
  move() {
    console.log('I can move!');
  }
}

class Cat extends Animal {
  constructor(name, color) {
    super(); // 等价于 Animal.apply(this)
    this.name = name;
    this.color = color;
  }
  purr() {
    console.log('呼噜呼噜~');
  }
}
  • extends → 设置原型链;
  • super() → 借用父类构造函数;
  • 一切更清晰,但本质仍是原型继承

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

阶段 方式 核心思想 缺陷
1 对象字面量 直接写对象 无法复用,无类型关系
2 构造函数 封装生成过程 方法不共享,内存浪费
3 原型模式 公共部分放原型 属性/方法分离,需手动管理
4 原型继承 apply + Object.create 写法繁琐,易出错
5 ES6 class 语法糖封装 底层仍是原型,需理解本质

💡 关键认知

JavaScript 的 OOP 不是"类继承",而是"对象委托 "------通过原型链,对象可以委托给另一个对象处理自己不会的事情。

掌握从字面量到原型继承的全过程,你才算真正理解了 JavaScript 面向对象的精髓。即使今天使用 class,也要明白:class 只是原型的语法糖,而原型才是 JS OOP 的灵魂

相关推荐
izx8881 小时前
JavaScript 面向对象编程(OOP):从原始模式到原型继承
前端·javascript
我血条子呢1 小时前
【Vite】离线打包@iconify/vue的图标
前端·javascript·vue.js
皮蛋瘦肉粥_1212 小时前
pink老师-js基础-day4
javascript
岁月宁静2 小时前
从 JavaScript 到 Python:前端工程师的完全转换指南
前端·javascript·python
特严赤傲2 小时前
在 Vue 中 v-for的写法对语法高亮的影响
前端·javascript·vue.js
Nan_Shu_6142 小时前
熟悉RuoYi-Vue-Plus-前端 (1)
前端·javascript·vue.js
梦6502 小时前
react日历组件
前端·javascript·react.js
网络点点滴2 小时前
Vue3路由params参数
前端·javascript·vue.js
只与明月听3 小时前
一次uniapp问题排查
前端·javascript·vue.js