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

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

JavaScript 是一门基于对象(Object-based) 的语言------你几乎接触到的一切都是对象,甚至连 1、'hello' 这样的原始值,在需要时也会被自动包装成 Number、String 对象。然而,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 的灵魂。

相关推荐
hhhjhl6 小时前
Protobuf.js 跨端三方库仓颉适配全流程
javascript·网络·udp
Flynt9 小时前
我把热榜那篇"禁用 axios"逐条实测了一遍:结论大体对,坑在它没讲的地方
前端·javascript·axios
slongzhang_10 小时前
linux 递归删除源码文件|递归删除目录下 *.js,但排除 *.min.js
linux·开发语言·javascript
liyi_hz200813 小时前
O2OA(翱途) V10.1 即将发布|功能再升级,安全再加固,协同更高效!
java·javascript·windows·开源软件
志尊宝14 小时前
AI原生前端开发(029):AI辅助的文档自动生成
javascript·人工智能·ai-native
一个游离的指针14 小时前
ES6新特性:let、const、块级作用域
开发语言·javascript·es6
Hilaku15 小时前
删了一个没用的 console.log,生产环境直接崩了!
前端·javascript·程序员
真上帝的左手15 小时前
2. 编程语言-JavaScript
开发语言·javascript
2601_9498477515 小时前
Form-data 跨端三方库仓颉适配全流程
前端·javascript·flutter
zyseo816 小时前
谷歌SEO 内容更新焕新流程:让老页面重新起量
java·前端·javascript