从原型到类:JavaScript面向对象编程的终极进化指南

作者:良山有风来日期:2025/10/27

你是不是也曾经被JavaScript的原型链绕得头晕眼花?每次看到[__proto__]([object Object])和prototype就感觉在看天书?别担心,这几乎是每个前端开发者都会经历的阶段。

今天我要带你彻底搞懂JavaScript面向对象编程的进化之路。从令人困惑的原型到优雅的class语法,再到实际项目中的设计模式应用,读完本文,你不仅能理解JS面向对象的本质,还能写出更优雅、更易维护的代码。

原型时代:JavaScript的"上古时期"

在ES6之前,JavaScript面向对象编程全靠原型链。虽然语法看起来有点奇怪,但理解它对我们掌握JS面向对象至关重要。

让我们先看一个最简单的原型继承例子:

1// 构造函数 - 相当于其他语言中的类
2function Animal(name) {
3  this.name = name;
4}
5
6// 通过原型添加方法
7Animal.prototype.speak = function() {
8  console.log(this.name + ' makes a noise.');
9}
10
11// 创建实例
12var dog = new Animal('Dog');
13dog.speak(); // 输出: Dog makes a noise.
14

这里发生了什么?我们用Animal函数创建了一个"类",通过prototype给所有实例共享方法。这样创建的实例都能调用speak方法。

再来看看继承怎么实现:

1// 子类构造函数
2function Dog(name) {
3  // 调用父类构造函数
4  Animal.call(this, name);
5}
6
7// 设置原型链继承
8Dog.prototype = Object.create(Animal.prototype);
9Dog.prototype.constructor = Dog;
10
11// 添加子类特有方法
12Dog.prototype.speak = function() {
13  console.log(this.name + ' barks.');
14}
15
16var myDog = new Dog('Rex');
17myDog.speak(); // 输出: Rex barks.
18

是不是感觉有点繁琐?这就是为什么ES6要引入class语法 - 让面向对象编程变得更直观。

Class时代:ES6带来的语法糖

ES6的class并不是引入了新的面向对象继承模型,而是基于原型的语法糖。但不得不说,这个糖真的很甜!

同样的功能,用class怎么写:

1class Animal {
2  constructor(name) {
3    this.name = name;
4  }
5  
6  speak() {
7    console.log([`${this.name} makes a noise.`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.a.md));
8  }
9}
10
11class Dog extends Animal {
12  constructor(name) {
13    super(name); // 调用父类构造函数
14  }
15  
16  speak() {
17    console.log(`${this.name} barks.`);
18  }
19}
20
21const myDog = new Dog('Rex');
22myDog.speak(); // 输出: Rex barks.
23

代码是不是清晰多了?class语法让我们能够用更接近传统面向对象语言的方式编写代码,大大提高了可读性。

但要注意,class本质上还是基于原型的。我们可以验证一下:

1console.log(typeof Animal); // 输出: function
2console.log(Animal.prototype.speak); // 输出: [Function: speak]
3

看到没?class其实就是构造函数的语法糖,方法还是在prototype上。

封装与私有字段:保护你的数据

面向对象三大特性之一的封装,在JavaScript中经历了很多变化。从最初的命名约定到现在的真正私有字段,让我们来看看进化历程。

早期的做法是用下划线约定:

1class BankAccount {
2  constructor(balance) {
3    this._balance = balance; // 下划线表示"私有"
4  }
5  
6  getBalance() {
7    return this._balance;
8  }
9}
10

但这只是约定,实际上还是可以访问:

1const account = new BankAccount(100);
2console.log(account._balance); // 还是能访问到,不安全
3

ES6之后,我们可以用Symbol实现真正的私有:

1const _balance = Symbol('balance');
2
3class BankAccount {
4  constructor(balance) {
5    this[_balance] = balance;
6  }
7  
8  getBalance() {
9    return this[_balance];
10  }
11}
12
13const account = new BankAccount(100);
14console.log(account[_balance]); // 理论上拿不到,除非拿到Symbol引用
15

最新的ES提案提供了真正的私有字段语法:

1class BankAccount {
2  #balance; // 私有字段
3  
4  constructor(balance) {
5    this.#balance = balance;
6  }
7  
8  getBalance() {
9    return this.#balance;
10  }
11  
12  // 静态私有字段
13  static #bankName = 'MyBank';
14}
15
16const account = new BankAccount(100);
17console.log(account.#balance); // 语法错误:私有字段不能在类外访问
18

现在我们的数据真正安全了!

设计模式实战:用OOP解决复杂问题

理解了基础语法,让我们看看在实际项目中如何运用面向对象思想和设计模式。

单例模式:全局状态管理

单例模式确保一个类只有一个实例,这在管理全局状态时特别有用。

1class AppConfig {
2  static instance = null;
3  
4  constructor() {
5    if (AppConfig.instance) {
6      return AppConfig.instance;
7    }
8    
9    this.theme = 'light';
10    this.language = 'zh-CN';
11    this.apiBaseUrl = 'https://api.example.com';
12    
13    AppConfig.instance = this;
14  }
15  
16  static getInstance() {
17    if (!AppConfig.instance) {
18      AppConfig.instance = new AppConfig();
19    }
20    return AppConfig.instance;
21  }
22  
23  setTheme(theme) {
24    this.theme = theme;
25  }
26}
27
28// 使用
29const config1 = AppConfig.getInstance();
30const config2 = AppConfig.getInstance();
31
32console.log(config1 === config2); // 输出: true - 确实是同一个实例
33

观察者模式:实现事件驱动架构

观察者模式在UI开发中无处不在,让我们自己实现一个简单的事件系统:

1class EventEmitter {
2  constructor() {
3    this.events = {};
4  }
5  
6  // 订阅事件
7  on(eventName, listener) {
8    if (!this.events[eventName]) {
9      this.events[eventName] = [];
10    }
11    this.events[eventName].push(listener);
12    
13    // 返回取消订阅的函数
14    return () => {
15      this.off(eventName, listener);
16    };
17  }
18  
19  // 取消订阅
20  off(eventName, listener) {
21    if (!this.events[eventName]) return;
22    
23    this.events[eventName] = this.events[eventName].filter(
24      l => l !== listener
25    );
26  }
27  
28  // 发布事件
29  emit(eventName, data) {
30    if (!this.events[eventName]) return;
31    
32    this.events[eventName].forEach(listener => {
33      try {
34        listener(data);
35      } catch (error) {
36        console.error(`Error in event listener for ${eventName}:`, error);
37      }
38    });
39  }
40}
41
42// 使用示例
43class User extends EventEmitter {
44  constructor(name) {
45    super();
46    this.name = name;
47  }
48  
49  login() {
50    console.log(`${this.name} logged in`);
51    this.emit('login', { user: this.name, time: new Date() });
52  }
53}
54
55const user = new User('John');
56
57// 订阅登录事件
58const unsubscribe = user.on('login', (data) => {
59  console.log('登录事件触发:', data);
60});
61
62user.login();
63// 输出:
64// John logged in
65// 登录事件触发: { user: 'John', time: ... }
66
67// 取消订阅
68unsubscribe();
69

工厂模式:灵活的对象创建

当创建逻辑比较复杂,或者需要根据不同条件创建不同对象时,工厂模式就派上用场了。

1class Notification {
2  constructor(message) {
3    this.message = message;
4  }
5  
6  send() {
7    throw new Error('send method must be implemented');
8  }
9}
10
11class EmailNotification extends Notification {
12  send() {
13    console.log(`Sending email: ${this.message}`);
14    // 实际的邮件发送逻辑
15    return true;
16  }
17}
18
19class SMSNotification extends Notification {
20  send() {
21    console.log(`Sending SMS: ${this.message}`);
22    // 实际的短信发送逻辑
23    return true;
24  }
25}
26
27class PushNotification extends Notification {
28  send() {
29    console.log(`Sending push: ${this.message}`);
30    // 实际的推送逻辑
31    return true;
32  }
33}
34
35class NotificationFactory {
36  static createNotification(type, message) {
37    switch (type) {
38      case 'email':
39        return new EmailNotification(message);
40      case 'sms':
41        return new SMSNotification(message);
42      case 'push':
43        return new PushNotification(message);
44      default:
45        throw new Error([`Unknown notification type: ${type}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.type.md));
46    }
47  }
48}
49
50// 使用工厂
51const email = NotificationFactory.createNotification('email', 'Hello!');
52email.send(); // 输出: Sending email: Hello!
53
54const sms = NotificationFactory.createNotification('sms', 'Your code is 1234');
55sms.send(); // 输出: Sending SMS: Your code is 1234
56

高级技巧:混入和组合

JavaScript的灵活性让我们可以实现一些在其他语言中比较困难的功能,比如混入模式。

1// 混入函数
2const CanSpeak = (Base) => class extends Base {
3  speak() {
4    console.log(`${this.name} speaks`);
5  }
6};
7
8const CanWalk = (Base) => class extends Base {
9  walk() {
10    console.log(`${this.name} walks`);
11  }
12};
13
14const CanSwim = (Base) => class extends Base {
15  swim() {
16    console.log(`${this.name} swims`);
17  }
18};
19
20// 组合不同的能力
21class Person {
22  constructor(name) {
23    this.name = name;
24  }
25}
26
27// 创建一个会说话和走路的人
28class SpeakingWalkingPerson extends CanWalk(CanSpeak(Person)) {}
29
30// 创建一个会所有技能的人
31class SuperPerson extends CanSwim(CanWalk(CanSpeak(Person))) {}
32
33const john = new SpeakingWalkingPerson('John');
34john.speak(); // John speaks
35john.walk();  // John walks
36
37const superman = new SuperPerson('Superman');
38superman.speak(); // Superman speaks
39superman.walk();  // Superman walks  
40superman.swim();  // Superman swims
41

这种组合的方式让我们可以像搭积木一样构建对象的功能,非常灵活!

性能优化:原型 vs Class

很多人会问,class语法会不会影响性能?让我们实际测试一下:

1// 原型方式
2function ProtoAnimal(name) {
3  this.name = name;
4}
5ProtoAnimal.prototype.speak = function() {
6  return this.name + ' speaks';
7};
8
9// Class方式
10class ClassAnimal {
11  constructor(name) {
12    this.name = name;
13  }
14  speak() {
15    return this.name + ' speaks';
16  }
17}
18
19// 性能测试
20console.time('Proto创建实例');
21for (let i = 0; i < 100000; i++) {
22  new ProtoAnimal('test');
23}
24console.timeEnd('Proto创建实例');
25
26console.time('Class创建实例');
27for (let i = 0; i < 100000; i++) {
28  new ClassAnimal('test');
29}
30console.timeEnd('Class创建实例');
31

在现代JavaScript引擎中,两者的性能差异可以忽略不计。class语法经过优化,在大多数情况下甚至可能略快一些。

实战案例:构建一个简单的UI组件库

让我们用今天学到的知识,构建一个简单的UI组件库:

1// 基础组件类
2class Component {
3  constructor(element) {
4    this.element = element;
5    this.init();
6  }
7  
8  init() {
9    // 初始化逻辑
10    this.bindEvents();
11  }
12  
13  bindEvents() {
14    // 绑定事件 - 由子类实现
15  }
16  
17  show() {
18    this.element.style.display = 'block';
19  }
20  
21  hide() {
22    this.element.style.display = 'none';
23  }
24  
25  // 静态方法用于创建组件
26  static create(selector) {
27    const element = document.querySelector(selector);
28    return new this(element);
29  }
30}
31
32// 按钮组件
33class Button extends Component {
34  bindEvents() {
35    this.element.addEventListener('click', () => {
36      this.onClick();
37    });
38  }
39  
40  onClick() {
41    console.log('Button clicked!');
42    this.emit('click'); // 如果继承了EventEmitter
43  }
44  
45  setText(text) {
46    this.element.textContent = text;
47  }
48}
49
50// 模态框组件
51class Modal extends Component {
52  bindEvents() {
53    // 关闭按钮事件
54    const closeBtn = this.element.querySelector('.close');
55    if (closeBtn) {
56      closeBtn.addEventListener('click', () => {
57        this.hide();
58      });
59    }
60  }
61  
62  setContent(content) {
63    const contentEl = this.element.querySelector('.modal-content');
64    if (contentEl) {
65      contentEl.innerHTML = content;
66    }
67  }
68}
69
70// 使用
71const myButton = Button.create('#myButton');
72const myModal = Modal.create('#myModal');
73
74myButton.setText('点击我');
75myButton.on('click', () => {
76  myModal.setContent('<h2>Hello Modal!</h2>');
77  myModal.show();
78});
79

常见陷阱与最佳实践

在JavaScript面向对象编程中,有一些常见的坑需要注意:

1. 绑定this的问题

1class MyClass {
2  constructor() {
3    this.value = 42;
4  }
5  
6  // 错误:这样会丢失this
7  printValue() {
8    console.log(this.value);
9  }
10}
11
12const instance = new MyClass();
13const func = instance.printValue;
14func(); // TypeError: Cannot read property 'value' of undefined
15
16// 解决方法1:在构造函数中绑定
17class MyClassFixed1 {
18  constructor() {
19    this.value = 42;
20    this.printValue = this.printValue.bind(this);
21  }
22  
23  printValue() {
24    console.log(this.value);
25  }
26}
27
28// 解决方法2:使用箭头函数
29class MyClassFixed2 {
30  constructor() {
31    this.value = 42;
32  }
33  
34  printValue = () => {
35    console.log(this.value);
36  }
37}
38

2. 继承中的super调用

1class Parent {
2  constructor(name) {
3    this.name = name;
4  }
5}
6
7class Child extends Parent {
8  constructor(name, age) {
9    // 必须首先调用super!
10    super(name);
11    this.age = age;
12  }
13}
14

3. 私有字段的兼容性

1// 在生产环境中,如果需要支持旧浏览器,可以考虑使用Babel转译
2// 或者使用传统的闭包方式实现私有性
3
4function createPrivateCounter() {
5  let count = 0; // 真正的私有变量
6  
7  return {
8    increment() {
9      count++;
10      return count;
11    },
12    getCount() {
13      return count;
14    }
15  };
16}
17
18const counter = createPrivateCounter();
19console.log(counter.increment()); // 1
20console.log(counter.count); // undefined - 无法直接访问
21

面向未来的JavaScript OOP

JavaScript的面向对象编程还在不断发展,一些新的特性值得关注:

1. 装饰器提案

1// 目前还是Stage 3提案,但已经在很多项目中使用
2@sealed
3class Person {
4  @readonly
5  name = 'John';
6  
7  @deprecate
8  oldMethod() {
9    // ...
10  }
11}
12

2. 更强大的元编程

1// 使用Proxy实现高级功能
2const createValidator = (target) => {
3  return new Proxy(target, {
4    set(obj, prop, value) {
5      if (prop === 'age' && (value < 0 || value > 150)) {
6        throw new Error('Invalid age');
7      }
8      obj[prop] = value;
9      return true;
10    }
11  });
12};
13
14class Person {
15  constructor() {
16    return createValidator(this);
17  }
18}
19
20const person = new Person();
21person.age = 25; // 正常
22person.age = 200; // 抛出错误
23

总结

JavaScript的面向对象编程经历了一场精彩的进化:从令人困惑的原型链,到优雅的class语法,再到各种设计模式的实践应用。

记住这些关键点:

  • class是语法糖,理解原型链仍然很重要
  • 私有字段让封装更安全
  • 设计模式能解决特定类型的复杂问题
  • 组合优于继承在很多场景下更灵活

面向对象不是银弹,但在构建复杂的前端应用时,良好的OOP设计能显著提高代码的可维护性和可扩展性。

你现在对JavaScript面向对象编程的理解到什么程度了?在实际项目中遇到过哪些OOP的挑战?欢迎在评论区分享你的经验和问题!


从原型到类:JavaScript面向对象编程的终极进化指南》 是转载文章,点击查看原文


相关推荐


Redis(81)Redis的缓存雪崩是什么?
Victor3562025/10/24

缓存雪崩的概念 缓存雪崩(Cache Avalanche)是指在某一时间段内,缓存中的大量数据同时过期,或者由于缓存服务器宕机导致大量请求直接打到数据库,导致数据库瞬时压力剧增,甚至可能导致数据库崩溃。 解决缓存雪崩的方法 为了解决缓存雪崩问题,可以采取以下几种策略: 缓存数据的过期时间设置为随机值:避免在同一时间大量缓存数据同时失效。 加锁或队列:在缓存失效时,通过机制控制对数据库的访问,避免大量请求同时打到数据库。 双写策略:更新缓存的同时也更新数据库,保证数据的一致性。 数据预热:在系统


KuiklyUI利用Kotlin Lambda函数实现声明式UI系统的深入分析
风冷2025/10/22

KuiklyUI利用Kotlin Lambda函数实现声明式UI系统的深入分析 KuiklyUI通过巧妙地利用Kotlin的lambda函数特性,构建了一套灵活、高效的声明式UI系统。本文将深入分析其实现机制和核心技术点。 一、Lambda函数在声明式UI中的核心应用 1. 接收器作用域函数的巧妙运用 KuiklyUI的声明式语法核心基于Kotlin的接收器作用域函数。在按钮组件ButtonView中,我们可以看到典型的实现: class ButtonView : ComposeView<But


面试问题—上家公司的离职原因
mapbar_front2025/10/21

面试结尾HR必问的问题,就是上一家公司的离职原因,作为多年的资深架构师,我做过多次终面,听到过千奇百怪的答案,有的真诚,有的官方,有的遮遮掩掩,有的情绪愤怒,这个问题是有正确答案的,今天就来和你分享一下。 1、真实的离职原因 其实离职无非就是两类原因,一类主动,一类被动。 主动,要么钱少,要么心累,但大多数情况都是钱少心又累。 被动,要么被行情拖累,要么末位淘汰,要么违纪被发现,这个问题只要不回答的稀碎,都不会影响你被录用。 2、避开下面两个错误答案 2.1、 破口大骂前公司前领导 有可能真的是


Redis(73)如何处理Redis分布式锁的死锁问题?
Victor3562025/10/20

处理Redis分布式锁的死锁问题,可以采取以下一些方法: 设置锁的自动过期时间: 使用EX和PX参数设置锁的超时时间,确保锁在持有过久后自动释放,避免死锁。 使用看门狗模式: 通过不断刷新锁的过期时间来维持锁的有效性,防止锁的持有者因为长时间操作而导致锁自动过期。 确保锁的唯一性和原子性: 使用具有唯一标识的锁值来确保释放锁时的准确性,避免因误删导致的死锁问题。 故障恢复机制: 在检测到锁持有者失效后,其他节点可以尝试获取锁,确保系统的高可用性。 下面是一个示


区块链技术的五大应用场景
终端域名2025/10/19

区块链技术凭借其去中心化、不可篡改和透明性等核心优势,已渗透至金融、供应链管理、医疗健康、知识产权保护及公共服务五大领域,成为重构信任机制与提升协作效率的关键技术。以下是对五大应用场景的详细阐述: 一、金融:重塑信任基石 跨境支付与清算 区块链通过分布式账本技术实现跨境交易的实时结算,显著降低传统SWIFT网络的中介成本和时间延迟。例如,Ripple、R3等区块链联盟已推动跨境汇款效率提升至分钟级,将跨国交易成本从每笔26美元降低至15美元。 数字货币与支付结算 央行数字货币(如中国


B站多模态精细画质分析模型在 ICCV2025 大赛获得佳绩
哔哩哔哩技术2025/10/17

前言 暑期,B站多媒体实验室带队参与了 ICCV MIPI (Mobile Intelligent Photography and Imaging) Workshop 的细粒度图像质量定位 (Detailed Image Quality Assessment Track) 国际挑战赛,提出创新的多模态训练策略,将综合指标提升了13.5%,最终获得了第二名的好成绩。本次参赛经历阶段性地验证了实验室在视频质量评价 (Video Quality Assessment,后文统称为 VQA) ,MLLM


【软件测试】性能测试工具 JMeter
清风~徐~来2025/10/16

性能测试工具 JMeter 一. JMeter 下载与环境配置二. JMeter 介绍1. JMeter 基本使用2. JMeter 元件作用域和执行顺序3. JMeter 重点组件(1). 线程组(2). HTTP 请求(3). 查看结果树(4). HTTP 请求默认值(5). HTTP 信息头管理器(6). JSON 提取器(7). 用户定义的变量(8). JSON 断言(9). 同步定时器(10). 事务控制器(11). CSV 数据文件设置(12). HTTP Cookie 管理器


Python 的内置函数 bytes
IMPYLH2025/10/14

Python 内建函数列表 > Python 的内置函数 bytes class bytes(x=b''): ''' 创建 bytes :param x: 要转换的变量 :return: x 转换为 bytes 后的值 ''' Python 的内置函数 bytes 用于创建不可变的字节序列对象。它是 Python 中处理二进制数据的基本类型之一,与 str 类型类似但专门用于表示字节数据而非文本。 bytes 函数有三种主要创建方式: 通过指定


电视盒子助手开心电视助手 v8.0 删除电视内置软件 电视远程控制ADB去除电视广告
2501_929382652025/10/13

电视盒子助手开心电视助手 v8.0 删除电视内置软件 电视远程控制ADB去除电视广告                                       “开心电视助手V8.0最新版”是一款功能强大的安卓电视/电视盒子管理软件,通常运行在Windows电脑上。它通过局域网(Wi-Fi)或USB数据线连接到您的电视或盒子,实现一系列高级管理、调试和控制功能。 它尤其受到开发者、电视发烧友和喜欢折腾智能电视/盒子的用户的欢迎。支持安卓设备的电视 盒子 安卓手机 安卓所有设备 下载地


C# 中 Excel 工作表打印前页面边距的设置方法
缺点内向2025/10/12

C# 中 Excel 工作表打印前页面边距的设置方法 在日常的业务报表生成与文档打印场景中,Excel无疑是不可或缺的工具。然而,当我们需要批量打印或自动化生成Excel报表时,一个常见且令人头疼的问题浮现:如何确保所有打印输出的页面边距一致,避免手动逐一调整的繁琐与低效呢? 本文将探讨在C#环境中,如何通过编程方式精确控制Excel工作表的打印页面边距。在这里,我们将借助第三方库——Spire.XLS for .NET,为您提供一个高效、自动化且易于实现的解决方案,让您的Excel打印设置工作

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0