作为一门多范式编程语言,JavaScript 同时支持函数式编程(FP)和面向对象编程(OOP)两种核心编程范式。这两种范式并非对立关系,而是各有侧重、可互补使用的编程思想。本文将深入解析这两种编程范式的核心思想、实现方式,并通过实例对比其应用场景。
一、面向对象编程(OOP):以”对象”为中心的世界
1. 核心思想
面向对象编程以对象为基本单元,将数据(属性)和操作数据的方法封装在一起,通过封装、继承、多态三大特性组织代码,模拟现实世界的实体和交互。
在 JavaScript 中,OOP 基于原型(Prototype)实现,ES6 引入的 class 语法糖让 OOP 写法更贴近传统面向对象语言(如 Java)。
2. 核心特性解析
(1)封装:隐藏内部实现,暴露公共接口
封装的目的是将对象的状态和行为绑定,同时控制外部对内部数据的访问权限。
// ES6 class 实现封装
class User {
// 构造函数:初始化对象属性
constructor(name, age) {
this.name = name; // 公共属性
this._age = age; // 约定俗成的私有属性(下划线命名)
}
// 公共方法:暴露给外部的接口
getInfo() {
return `姓名:${this.name},年龄:${this._age}`;
}
// 访问器方法:控制属性的读取和修改
get age() {
return this._age;
}
set age(newAge) {
if (newAge >= 0 && newAge <= 120) {
this._age = newAge;
} else {
throw new Error('年龄必须在 0-120 之间');
}
}
}
// 使用示例
const user = new User('张三', 25);
console.log(user.getInfo()); // 姓名:张三,年龄:25
user.age = 30; // 通过 setter 修改属性
console.log(user.age); // 30
user.age = 150; // 抛出错误:年龄必须在 0-120 之间
(2)继承:复用已有类的属性和方法
继承允许子类复用父类的代码,同时可扩展自身特性。JavaScript 中通过原型链实现继承,ES6 的 extends 简化了继承写法。
// 父类
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} 正在进食`);
}
}
// 子类继承父类
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
// 重写父类方法(多态)
eat() {
console.log(`${this.name}(${this.breed})正在吃狗粮`);
}
// 子类扩展方法
bark() {
console.log(`${this.name} 汪汪叫`);
}
}
// 使用示例
const goldenRetriever = new Dog('旺财', '金毛');
goldenRetriever.eat(); // 旺财(金毛)正在吃狗粮
goldenRetriever.bark(); // 旺财 汪汪叫
(3)多态:同一行为的不同表现形式
多态允许不同对象对同一方法做出不同响应,核心是”重写”和”重载”(JavaScript 无原生重载,可通过参数处理模拟)。上面的 Dog.eat() 就是典型的多态实现。
3. OOP 典型应用场景
- 模拟现实世界实体(如用户、订单、商品)
- 大型应用的模块划分(如组件化开发)
- 需要维护状态的场景(如表单对象、游戏角色)
二、函数式编程(FP):以”函数”为核心的思维
1. 核心思想
函数式编程将计算视为函数的组合,强调纯函数、不可变数据、无副作用,避免状态修改和可变数据,核心是”做什么”而非”怎么做”。
JavaScript 中函数是一等公民(可作为参数、返回值、赋值给变量),这为函数式编程提供了天然支持。
2. 核心概念解析
(1)纯函数:无副作用的函数
纯函数满足两个条件:
– 相同输入始终返回相同输出(无外部状态依赖)
– 不修改外部状态(无副作用,如修改全局变量、DOM、网络请求)
// 纯函数:仅依赖输入,无副作用
function add(a, b) {
return a + b;
}
// 非纯函数:依赖外部变量,结果不可预测
let base = 10;
function addWithBase(num) {
return num + base;
}
// 非纯函数:修改外部状态(副作用)
function updateUserAge(user, newAge) {
user.age = newAge; // 修改了传入的对象
return user;
}
// 纯函数替代方案:返回新对象,不修改原对象
function updateUserAgePure(user, newAge) {
return { ...user, age: newAge }; // 解构创建新对象
}
// 使用示例
const user = { name: '李四', age: 28 };
const newUser = updateUserAgePure(user, 29);
console.log(user.age); // 28(原对象未变)
console.log(newUser.age); // 29(新对象)
(2)不可变数据:数据一旦创建就不可修改
JavaScript 中原始类型(字符串、数字、布尔)本身不可变,引用类型(对象、数组)需通过创建新值实现不可变。
// 错误:直接修改数组(可变)
const arr = [1, 2, 3];
arr.push(4); // 修改原数组
// 正确:创建新数组(不可变)
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // 解构创建新数组
console.log(arr); // [1,2,3]
console.log(newArr); // [1,2,3,4]
// 对象不可变示例
const obj = { a: 1 };
const newObj = { ...obj, b: 2 }; // 解构创建新对象
(3)高阶函数:操作函数的函数
高阶函数满足以下任一条件:
– 接收一个或多个函数作为参数
– 返回一个新函数
JavaScript 内置的 map、filter、reduce 都是典型的高阶函数:
// 示例:使用高阶函数处理数组
const numbers = [1, 2, 3, 4, 5];
// map:遍历数组,返回新数组(纯函数)
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2,4,6,8,10]
// filter:过滤数组,返回新数组(纯函数)
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2,4]
// reduce:归约数组,返回单一值(纯函数)
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
// 自定义高阶函数
function withLog(fn) {
return function(...args) {
console.log(`调用函数,参数:${args.join(',')}`);
const result = fn(...args);
console.log(`函数返回值:${result}`);
return result;
};
}
// 使用自定义高阶函数
const loggedAdd = withLog(add);
loggedAdd(2, 3);
// 输出:
// 调用函数,参数:2,3
// 函数返回值:5
(4)函数组合:将多个函数组合成新函数
函数组合是函数式编程的核心,将多个简单函数组合成复杂逻辑:
// 定义基础函数
const add1 = num => num + 1;
const multiply2 = num => num * 2;
const square = num => num * num;
// 函数组合:从右到左执行
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
// 组合新函数:先加1,再乘2,最后平方
const calculate = compose(square, multiply2, add1);
// 使用示例
console.log(calculate(3)); // ((3+1)*2)^2 = 64
3. FP 典型应用场景
- 数据处理和转换(如数组过滤、映射、归约)
- 异步编程(如 Promise、async/await 本质是函数式思想)
- 无状态的业务逻辑(如工具函数、计算函数)
- 高并发、高可靠性场景(无状态易并行)
三、两种范式的对比与融合
1. 核心差异对比
| 维度 | 面向对象编程(OOP) | 函数式编程(FP) |
|---|---|---|
| 核心单元 | 对象(数据+方法) | 函数(纯函数、高阶函数) |
| 状态管理 | 允许状态可变(对象属性) | 强调状态不可变(纯函数) |
| 代码组织 | 封装、继承、多态 | 函数组合、柯里化、管道 |
| 关注点 | 怎么做(过程) | 做什么(结果) |
| 副作用 | 允许(如修改对象属性) | 避免(纯函数无副作用) |
2. 并非对立:JavaScript 中的范式融合
在实际开发中,纯 OOP 或纯 FP 都很少见,更多是混合使用:
// 融合示例:面向对象封装 + 函数式处理
class ShoppingCart {
constructor() {
this.items = []; // 内部状态
}
// 纯函数:计算总价(无副作用)
calculateTotal() {
return this.items.reduce((total, item) => total + item.price * item.quantity, 0);
}
// 函数式思想:添加商品时返回新状态(不可变)
addItem(item) {
this.items = [...this.items, item]; // 创建新数组,不直接修改原数组
return this;
}
// 高阶函数:过滤商品
filterItems(predicate) {
return this.items.filter(predicate);
}
}
// 使用示例
const cart = new ShoppingCart();
cart.addItem({ name: '手机', price: 2999, quantity: 1 });
cart.addItem({ name: '耳机', price: 199, quantity: 2 });
console.log(cart.calculateTotal()); // 2999 + 199*2 = 3397
const expensiveItems = cart.filterItems(item => item.price > 1000);
console.log(expensiveItems); // [{ name: '手机', price: 2999, quantity: 1 }]
四、如何选择合适的范式?
- 选 OOP:需要模拟实体、维护状态、强调模块化和复用(如 UI 组件、业务实体)
- 选 FP:需要数据处理、无状态逻辑、强调可预测性和可测试性(如工具函数、数据转换)
- 混合使用:大多数业务场景,用 OOP 封装实体和状态,用 FP 处理数据和逻辑
总结
- 面向对象编程以对象为核心,通过封装、继承、多态组织代码,适合模拟实体和维护状态;
- 函数式编程以纯函数为核心,强调不可变数据和无副作用,适合数据处理和无状态逻辑;
- JavaScript 作为多范式语言,无需拘泥于单一范式,应根据场景灵活融合 OOP 和 FP 的优势,写出更优雅、可维护的代码。
掌握两种范式的核心思想,不仅能提升代码质量,更能拓宽编程思维——这也是成为优秀 JavaScript 开发者的关键。




