理解编程范式(前端角度)

作者:颜酱日期:2025/11/12

编程范式(Programming Paradigms)是编程的一种风格或方法,它定义了代码的结构和组织方式。编程范式提供了不同的思考和解决问题的角度,影响着程序员如何编写代码。

常见的前端编程范式包括:

  • 命令式编程(Imperative Programming)
  • 声明式编程(Declarative Programming)
  • 函数式编程(Functional Programming)
  • 面向对象编程(Object-Oriented Programming)

命令式编程(Imperative Programming) - 关注"如何做"(How)

特点:关注"如何做"(How),核心思想是通过一系列明确的指令(如变量赋值、循环、条件判断)来改变程序的状态,逐步引导计算机完成任务。它强调 “过程” 和 “状态变化”,就像给计算机一步步下达操作命令。

然后,看看下面的代码属于命令式编程么?

1const isEven = (number) => number % 2 === 0;
2const square = (number) => number * number;
3const add = (a, b) => a + b;
4function sumOfEvenSquares(numbers) {
5  // 声明变量保存中间状态(总和)
6  let sum = 0;
7  // 循环遍历数组,(明确的步骤:逐个检查元素,并进行处理)
8  numbers.forEach((number) => {
9    // 条件判断,筛选偶数
10    if (isEven(number)) {
11      // 修改状态(总和)
12      sum += square(number);
13    }
14  });
15  // 返回结果
16  return sum;
17}
18
19// 使用
20console.log(sumOfEvenSquares([1, 2, 3, 4, 5, 6])); // 56
21

答案:上面是命令式编程。

以前我粗浅的以为,看到forEach就是声明式编程,看到提取函数以为是函数式编程,其实还是命令式编程,因为sum在被显示的修改。

其整个流程依然是 “初始化 sum → 循环数组 → 逐个判断 → 累加偶数 → 返回结果”

重新梳理下命令式编程的特点:

  • 步骤化指令(关注步骤,明确每一步的执行过程)
  • 依赖显式状态修改(状态在显示的修改,比如sum在显示的被修改)
  • 控制流由开发者主导(开发者控制程序的执行流程,比如for循环、if判断等)

场景和优缺点

  • 适合场景:适合简单、步骤明确的逻辑的场景
  • 优势:直观、可控性强
  • 缺点:复杂逻辑下代码容易冗长(如多层嵌套循环)

类比炒西红柿炒鸡蛋

你手里有一份《番茄炒蛋盖浇饭步骤说明书》,严格按步骤执行:

  • 拿出 2 个番茄,洗净,切成小块(明确 “拿、洗、切” 的动作);
  • 拿出 2 个鸡蛋,打入碗中,用筷子搅拌均匀(明确 “打、搅” 的动作); 开火,锅烧热后倒入 2 勺油(明确 “开火、加热、倒油”);
  • 倒入鸡蛋液,翻炒至凝固,盛出备用(明确 “倒、炒、盛”);
  • 锅里再倒 1 勺油,放入番茄块,翻炒出汁(明确 “倒、放、炒”);
  • 倒入炒好的鸡蛋,加 1 勺盐、半勺糖,翻炒均匀(明确 “倒、加、炒”);
  • 拿出一碗米饭,盛在盘子里,把炒好的番茄炒蛋浇在米饭上(明确 “拿、盛、浇”)。

你自己控制整个流程,自己把控每一步的执行过程。

但是注意,每个步骤有可能会涉及其他的范式。

声明式编程(Declarative Programming) - 关注"做什么"(What)

特点:其实这个是相对于命令式编程来说的,命令式编程是关注"如何做"(How),描述具体的实现步骤,而声明式编程是关注"做什么"(What),开发者只需描述 “想要的结果 / 目标”,而无需编写具体的执行步骤、控制流(循环、分支)或手动管理状态,具体的实现细节(如遍历、状态维护)由语言或框架自动完成。

看下同样的功能,使用声明式编程的代码:

1const isEven = (n) => n % 2 === 0;
2const square = (n) => n * n;
3const sum = (acc, curr) => acc + curr;
4
5const sumOfEvenSquares = (numbers) =>
6  numbers
7    .filter(isEven) // 描述:筛选偶数(不关心如何遍历)
8    .map(square) // 描述:计算平方(不关心如何处理)
9    .reduce(sum, 0); // 描述:累加求和(不关心如何维护状态)
10
11// 使用
12console.log(sumOfEvenSquares([1, 2, 3, 4, 5, 6])); // 56
13

看着好像也是一步步的,但这些 “步骤” 是 “目标的分解”,而非 “执行的指令”。

第一步:我需要“筛选出偶数”这个结果,而不是具体的如何遍历、如何判断偶数。 第二步:我需要“计算平方”这个结果,而不是具体的如何计算平方。 第三步:我需要“累加求和”这个结果,而不是具体的如何累加求和。

每一步都是对 “中间结果” 的描述,而非 “如何实现这个中间结果” 的指令。你不需要告诉计算机 “如何筛选偶数”(是否用 for 循环、forEach 还是其他方式),filter 内部已经封装了遍历逻辑。 同样,你不需要手动维护 “筛选后的数组”“平方后的数组” 这些中间状态,它们由函数自动生成并传递。也就是底层的遍历和状态维护完全封装。

看下sql的例子加深理解:

1-- 目标:查询“年龄大于18的用户姓名”,不关心数据库如何检索
2SELECT name FROM users WHERE age > 18;
3-- 命令式思路会是:“打开数据库→遍历所有用户→判断年龄→收集姓名”,而 SQL 直接描述结果。
4

场景和优缺点

  • 适合场景:数据查询、复杂计算逻辑
  • 优势:可读性高、可维护性强、抽象程度高
  • 缺点:可能产生中间数组,内存占用较大

注意,声明式是一个 “大范式”,包含多个具体的子范式,覆盖不同场景,常见的子范式包括:

  • 函数式编程(FP):用纯函数组合描述计算,无副作用、不可变数据(如 JavaScript 的 filter+map+reduce、Haskell)。
  • 逻辑编程:通过 “事实 + 规则” 推导结果,而非步骤(如 Prolog)。
  • 数据查询语言:描述需要的数据,而非获取数据的过程(如 SQL、GraphQL)。
  • 响应式编程:基于数据流和变化传播处理异步(如 RxJS)。
  • 标记语言:描述 “内容结构”,而非 “渲染步骤”(如 HTML、XML)。

这些子范式的共性是 “声明目标,不写步骤”,差异是 “目标类型不同”(计算、推理、数据、异步、结构)。

类比去餐馆点炒西红柿炒鸡蛋

你去餐馆,直接跟服务员说:“我要一份番茄炒蛋盖浇饭,米饭要软硬适中,番茄要炒出汁,少盐少糖。” 你完全不用管:厨师是先炒鸡蛋还是先炒番茄、用多少油、炒多久 —— 只需要告诉 “最终需求”,厨师会自己处理所有步骤。

函数式编程(Functional Programming)- 函数

特点:一种以 “函数” 为核心的声明式编程范式,其核心思想是将计算过程视为 “纯函数的组合”,强调无副作用、不可变数据、函数是 “第一公民” 等特性,通过函数的嵌套和组合来解决问题,而非通过修改状态执行步骤

注意,函数式编程是声明式编程的子范式,所以如果是函数式编程的,一定属于声明式编程。反过来不一定成立。

所以声明式编程里面的求和代码,也属于函数式编程。

函数式编程的核心原则

  • 纯函数(相同输入必产相同输出;不修改函数外部的任何状态(如全局变量、参数对象、DOM 等),也不依赖外部状态的变化)
  • 不可变数据(数据一旦创建就不可修改,只能通过创建新的数据来修改)
  • 函数是 “第一公民”(函数可以作为参数传递、可以作为返回值返回、可以作为变量赋值)

函数式编程的常见工具

  • 高阶函数(如 map、filter、reduce、flatMap等,用于组合数据处理逻辑)
  • 柯里化(currying,将多参数函数转化为一系列单参数函数的过程,便于复用和组合)
  • 函数组合(compose,将多个函数组合成一个函数,执行顺序从右到左或者从右到左)

场景和优缺点

  • 适合场景:适合数据处理、逻辑处理、函数组合的场景
  • 优势:代码可测试性强、易于并行化、函数可复用、减少 bug
  • 缺点:学习曲线陡峭、可能产生性能开销、不适合所有场景

拆解React的核心设计,加深函数式编程的理解

React并非 “纯函数式框架”,而是选择性吸收了函数式编程(FP)的核心思想,并结合前端开发的实际场景(如 UI 状态管理、组件复用、副作用处理)进行落地。其核心目的是:通过 FP 的特性(纯函数、不可变数据、无副作用)解决前端开发的经典痛点(如状态混乱、组件复用复杂、调试困难),让代码更可预测、可维护。

从 React 的核心设计和实践出发,拆解它如何 “吸收 FP 思想”,从而加深对函数式编程的理解。

用 “纯函数组件” 替代 “类组件”(React 16.8+ Hooks 时代)

函数式编程的核心是 “纯函数”,React 把这个思想落地为 函数组件(Function Component)+ Hooks,替代了早期的类组件(Class Component)。

React 函数组件本质是一个 “输入 → 输出” 的纯函数:

  • 输入:props(父组件传递的参数)+ state(组件内部状态,通过 Hooks 管理);
  • 输出:UI 描述(JSX);
  • 无副作用(理想状态):组件本身只负责 “根据输入渲染 UI”,不直接操作 DOM、不修改外部状态、不发起网络请求(这些都属于副作用,交给专门的 Hooks 处理)。
1// 纯函数组件:输入 props(name),输出 UI,无副作用
2function Greeting({ name }) {
3  return <h1>Hello, {name}!</h1>;
4}
5
6// 调用时,相同输入必然产生相同输出(可预测)
7<Greeting name="小明" /> // 输出 <h1>Hello, 小明!</h1>
8<Greeting name="小红" /> // 输出 <h1>Hello, 小红!</h1>
9

之前的:

1// 类组件:依赖可变的 this,状态修改隐含副作用
2class Greeting extends React.Component {
3  render() {
4    return <h1>Hello, {this.props.name}!</h1>; // this.props 是可变的(父组件更新时变化)
5  }
6}
7

相比之下,函数组件的优势很明显:

  • 可预测性:纯函数组件 “输入定,输出定”,无需担心内部隐藏状态导致的 UI 错乱,调试时只需关注 props 和 state;
  • 简洁性:摆脱 this 绑定、生命周期钩子(如 componentDidMount)的复杂逻辑,代码更短、可读性更高;
  • 复用性:纯函数组件更容易通过组合(而非继承)复用(如自定义 Hooks)。

“不可变数据” 管理状态(避免隐性副作用)

函数式编程强调 “不可变数据”(数据创建后不修改,而是返回新副本),React 把这个思想贯穿到 状态更新(setState/useState) 和 Props 传递 中。

React 的状态(state)本质是 “不可变的”:你不能直接修改状态对象 / 数组,必须返回一个新的副本,React 才会感知到状态变化并重新渲染。

1function TodoList() {
2  // 初始化状态:todo 列表(数组)
3  const [todos, setTodos] = React.useState([{ id: 1, text: '学习 FP' }]);
4
5  // 添加新 todo:返回新数组(不修改原数组)
6  const addTodo = (text) => {
7    const newTodo = { id: Date.now(), text };
8    // 正确:用扩展运算符创建新数组(不可变)
9    setTodos([...todos, newTodo]);
10    // 错误:直接修改原数组(React 无法感知变化)
11    // todos.push(newTodo);
12  };
13
14  return (
15    <div>
16      {todos.map((todo) => (
17        <p key={todo.id}>{todo.text}</p>
18      ))}
19      <button onClick={() => addTodo('学习 React FP')}>添加</button>
20    </div>
21  );
22}
23

这种设计的优势:

  • 避免隐性副作用:直接修改原状态会导致 “状态变化不可追踪”(比如不知道什么时候、谁修改了状态),纯函数组件的 “输入 → 输出” 逻辑被打破;
  • React 渲染优化:React 通过 “浅比较” 判断状态 / Props 是否变化(比如比较数组引用是否改变),如果直接修改原数据,引用不变,React 会误以为状态没变化,不重新渲染;
  • 符合 FP 思想:不可变数据让状态变化 “可预测、可回溯”,比如 Redux(React 生态的状态管理库)的核心就是 “不可变状态树”。

“副作用隔离”(用 Hooks 分离纯逻辑与副作用)

函数式编程强调 “纯函数无副作用”,但前端开发无法避免副作用(如网络请求、DOM 操作、定时器)。React 没有禁止副作用,而是通过 Hooks(如 useEffect、useCallback) 将副作用与纯渲染逻辑 “隔离”,让组件主体保持纯函数特性(组件主题(函数执行部分)只负责 “根据状态渲染 UI”(纯逻辑),副作用(如网络请求、DOM 操作、定时器)由专门的 Hooks 处理)。

1function UserProfile({ userId }) {
2  const [user, setUser] = React.useState(null);
3
4  // 副作用:发起网络请求(隔离在 useEffect 中)
5  React.useEffect(() => {
6    // 网络请求是副作用(依赖外部环境,有不确定性)
7    fetch(`/api/user/${userId}`)
8      .then((res) => res.json())
9      .then((data) => setUser(data));
10  }, [userId]); // 依赖项:只有 userId 变化时,才重新执行副作用
11
12  // 组件主体:纯逻辑(根据 user 状态渲染 UI)
13  if (!user) return <div>加载中...</div>;
14  return <div>姓名:{user.name}</div>;
15}
16

这样设计的优势:

  • 组件职责单一:组件主体只关心 “渲染 UI”,副作用交给专门的 Hooks 处理,符合 “单一职责原则”;
  • 副作用可控制:useEffect 通过 “依赖项数组” 控制副作用的执行时机(如组件挂载时、依赖变化时),避免 “副作用泛滥”;
  • 符合 FP 思想:纯函数负责 “计算”(渲染 UI),副作用负责 “与外部交互”,两者分离,代码更易维护。

“函数组合” 实现组件复用(替代继承)

函数式编程强调 “用函数组合替代继承”,React 把这个思想落地为 组件组合(Composition) 和 自定义 Hooks,替代了类组件的继承(如 extends React.Component)。

组件组合(Composition)通过 “组件嵌套” 和 “Props 传递” 实现复用,而非继承:

1// 通用组件:Button(纯函数组件)
2function Button({ children, onClick }) {
3  return (
4    <button style={{ color: 'red' }} onClick={onClick}>
5      {children}
6    </button>
7  );
8}
9
10// 业务组件:LoginButton(组合 Button 实现复用)
11function LoginButton() {
12  const handleLogin = () => console.log('登录');
13  // 组合 Button,传递 Props,无需继承
14  return <Button onClick={handleLogin}>登录</Button>;
15}
16
17// 业务组件:LogoutButton(组合 Button 实现复用)
18function LogoutButton() {
19  const handleLogout = () => console.log('退出');
20  return <Button onClick={handleLogout}>退出</Button>;
21}
22

把可复用的逻辑(纯逻辑 + 副作用)封装成自定义 Hooks,通过 “函数调用” 复用,而非组件继承:

1// 自定义 Hooks:封装“获取用户数据”的复用逻辑(纯逻辑 + 副作用)
2function useUser(userId) {
3  const [user, setUser] = React.useState(null);
4  React.useEffect(() => {
5    fetch(`/api/user/${userId}`)
6      .then((res) => res.json())
7      .then((data) => setUser(data));
8  }, [userId]);
9  return user; // 返回结果,供组件使用
10}
11
12// 组件 A:复用 useUser 逻辑
13function UserProfileA({ userId }) {
14  const user = useUser(userId); // 函数调用,复用逻辑
15  if (!user) return <div>加载中...</div>;
16  return <div>姓名:{user.name}</div>;
17}
18
19// 组件 B:复用 useUser 逻辑
20function UserCard({ userId }) {
21  const user = useUser(userId); // 函数调用,复用逻辑
22  if (!user) return <div>加载中...</div>;
23  return <div>卡片:{user.name}</div>;
24}
25

这样的设计优势:

  • 灵活性更高:继承会导致 “组件耦合”(子类依赖父类的实现),而组合和自定义 Hooks 是 “松耦合”(组件 / 逻辑通过参数传递,不依赖内部实现);
  • 符合 FP 思想:自定义 Hooks 本质是 “函数组合”,把复杂逻辑拆解成小的、可复用的函数,与函数式编程 “用纯函数组合描述计算” 的思想一致。

总结:React 吸收 FP 思想的核心价值React 采用函数式编程思想,不是为了 “赶潮流”,而是为了解决前端开发的实际问题:

  • 可预测性:纯函数组件 + 不可变数据,让 UI 渲染 “输入定,输出定”,减少状态混乱;
  • 可维护性:副作用隔离 + 函数组合,让代码职责清晰、易于复用和调试;
  • 性能优化:不可变数据 + 浅比较,让 React 渲染优化更高效;
  • 简洁性:摆脱类组件的 this 和生命周期,代码更短、学习成本更低。

面向对象编程(Object-Oriented Programming) - 对象

特点:一种以 “对象” 为核心的编程范式,核心思想是将现实世界中的实体抽象为 “对象”—— 每个对象包含 “描述实体的属性(数据)” 和 “实体能执行的行为(方法)”,通过对象间的交互(调用方法、传递数据)完成复杂功能。

简单说,OOP 就像 “搭积木”:把复杂系统拆分成一个个独立的 “积木块(对象)”,每个积木块有自己的 “样子(属性)” 和 “功能(方法)”,再通过积木块的组合拼接,搭建出完整的系统。

仍然是求和的例子,用面向对象编程的方式实现:

1// 类方式
2class NumberProcessor {
3  constructor(numbers) {
4    this.numbers = numbers;
5  }
6
7  filterEven() {
8    this.numbers = this.numbers.filter((n) => n % 2 === 0);
9    return this;
10  }
11
12  square() {
13    this.numbers = this.numbers.map((n) => n * n);
14    return this;
15  }
16
17  sum() {
18    return this.numbers.reduce((sum, n) => sum + n, 0);
19  }
20
21  sumOfEvenSquares() {
22    return new NumberProcessor(this.numbers).filterEven().square().sum();
23  }
24}
25
26// 使用
27const processor = new NumberProcessor([1, 2, 3, 4, 5, 6]);
28console.log(processor.sumOfEvenSquares()); // 56
29
30// 或者使用对象字面量
31const numberUtils = {
32  isEven: (n) => n % 2 === 0,
33  square: (n) => n * n,
34  sum: (a, b) => a + b,
35
36  sumOfEvenSquares(numbers) {
37    return numbers.filter(this.isEven).map(this.square).reduce(this.sum, 0);
38  },
39};
40
41console.log(numberUtils.sumOfEvenSquares([1, 2, 3, 4, 5, 6])); // 56
42

当然一般计算,不会想到用面向对象编程的方式实现,但这里就反应了面向对象编程的核心思想:把一切东西都抽象为对象。

OOP的四大核心特性

  • 封装(Encapsulation):将数据(属性)和行为(方法)封装在一起,形成一个独立的 “对象”,对外隐藏内部实现细节,只暴露必要的接口(方法);
  • 继承(Inheritance):通过 “类” 和 “对象” 的继承关系,实现代码复用和扩展;
  • 多态(Polymorphism):通过 “方法重载” 和 “接口抽象”,实现 “同名不同实现” 的灵活性;
  • 抽象(Abstraction):通过 “接口” 和 “抽象类”,实现 “高内聚、低耦合” 的模块化设计。
1// 父类:Animal
2// 将getName方法封装在父类中,对外隐藏内部实现细节,只暴露必要的接口(方法),这就是封装的体现
3class Animal {
4  constructor(name) {
5    this.name = name;
6  }
7  getName() {
8    return this.name;
9  }
10
11  // 父类方法(统一接口)
12  makeSound() {
13    console.log('动物发出声音');
14  }
15}
16
17// 子类:Dog 继承了父类的getName方法,这就是继承的体现
18class Dog extends Animal {
19  // 重写父类的makeSound方法,这就是多态的体现
20  makeSound() {
21    console.log(`${this.name}:汪汪汪!`);
22  }
23}
24
25// 子类:Cat(重写 makeSound) 这就是多态的体现
26class Cat extends Animal {
27  makeSound() {
28    console.log(`${this.name}:喵喵喵!`);
29  }
30}
31
32// 统一调用逻辑(不关心具体是哪个子类)
33function animalSound(animal) {
34  // 这就是多态的体现,不同对象调用同一方法,结果不同
35  animal.makeSound(); // 同一方法调用,不同行为
36}
37
38// 多态体现:不同对象调用同一方法,结果不同
39const dog = new Dog('旺财');
40const cat = new Cat('咪宝');
41animalSound(dog); // 旺财:汪汪汪!
42animalSound(cat); // 咪宝:喵喵喵!
43
44// 抽象就是你思考到核心的属性和方法,什么适合封装成一个父类,什么适合封装成一个子类,这就是抽象的体现
45

OOP的适用场景和优劣势

  • 适用场景:模拟现实世界的复杂实体(如 “用户、订单、商品” 等业务对象);大型复杂系统(如管理系统、游戏、框架),需要清晰的模块划分和复用
  • 优势:可读性高(代码结构贴近现实世界),易于维护和拓展(内部实现细节对外隐藏,继承和多态让代码更灵活),模块化清晰(高内聚、低耦合)
  • 劣势:可能过度设计(如果一个系统很小,却要使用OOP,可能会过度设计),性能开销(继承和多态会有一定的性能开销),继承可能导致紧耦合(如果一个系统很大,却要使用OOP,可能会导致代码过于复杂,难以维护)

范式对比总结

范式关注点适用场景代码风格
命令式如何做性能敏感、底层操作循环、条件语句
声明式做什么数据处理、UI 构建链式调用、表达式
函数式函数组合数据处理、数学计算纯函数、不可变数据
面向对象对象和类大型系统、GUI 应用类、继承、封装

实际应用建议

实际项目中通常混合使用多种范式,不同场景适合不同范式。

来一个综合使用的例子: 假设需实现一个 “电商订单处理流程”,包含以下功能:

  1. 定义订单实体(含商品、金额、状态等)。
  2. 筛选符合条件的订单(如 “已付款且金额> 100 元”)。
  3. 计算订单总金额(含折扣:满 200 减 20)。
  4. 输出处理结果。
1// 1. 面向对象编程(OOP):定义订单实体(封装属性和行为)
2class Order {
3  constructor(id, products, isPaid) {
4    this.id = id;
5    this.products = products; // 商品列表({name: string, price: number})
6    this.isPaid = isPaid; // 是否付款
7  }
8
9  // 计算订单原始总金额(封装行为)
10  getTotalPrice() {
11    return this.products.reduce((sum, p) => sum + p.price, 0);
12  }
13}
14
15// 2. 函数式编程(FP):纯函数处理数据(不可变、无副作用)
16// 纯函数:计算折扣后金额
17const calculateDiscount = (total) => (total >= 200 ? total - 20 : total);
18
19// 纯函数:筛选符合条件的订单(已付款且原始金额>100)
20const filterValidOrders = (orders) =>
21  orders.filter((order) => order.isPaid && order.getTotalPrice() > 100);
22
23// 3. 声明式编程:描述目标(筛选→计算折扣→汇总),隐藏步骤
24const processOrders = (orders) =>
25  filterValidOrders(orders)
26    .map((order) => ({
27      id: order.id,
28      originalPrice: order.getTotalPrice(),
29      discountedPrice: calculateDiscount(order.getTotalPrice()),
30    }))
31    .reduce(
32      (summary, item) => {
33        summary.totalOriginal += item.originalPrice;
34        summary.totalDiscounted += item.discountedPrice;
35        summary.details.push(item);
36        return summary;
37      },
38      { totalOriginal: 0, totalDiscounted: 0, details: [] },
39    );
40
41// 4. 命令式编程:执行流程并输出结果(显式步骤)
42function main() {
43  // 初始化订单(命令式步骤:创建对象)
44  const orders = [
45    new Order(1, [{ name: '书', price: 50 }], true), // 金额50(不满足>100)
46    new Order(2, [{ name: '手机', price: 250 }], true), // 金额250(满足)
47    new Order(3, [{ name: '耳机', price: 150 }], false), // 未付款(不满足)
48    new Order(
49      4,
50      [
51        { name: '键盘', price: 120 },
52        { name: '鼠标', price: 90 },
53      ],
54      true,
55    ), // 金额210(满足)
56  ];
57
58  // 处理订单(调用声明式/函数式逻辑)
59  const result = processOrders(orders);
60
61  // 命令式输出(显式步骤:循环打印)
62  console.log('有效订单处理结果:');
63  for (let i = 0; i < result.details.length; i++) {
64    // 命令式循环
65    const item = result.details[i];
66    console.log([`订单${item.id}:原价${item.originalPrice}元,折扣后${item.discountedPrice}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md));
67  }
68  console.log(`总计:原价${result.totalOriginal}元,折扣后${result.totalDiscounted}`);
69}
70
71// 执行主函数
72main();
73

main 函数的核心是 “流程控制”,通过明确的步骤、显式的循环和指令,一步步引导计算机完成 “从初始化到输出” 的全流程,完全符合命令式编程 “关注如何做、步骤化执行” 的本质。

虽然内部调用了 processOrders(声明式 + 函数式),但这并不影响 main 本身是命令式 —— 相当于 “命令式的流程” 中,调用了一个 “声明式的工具” 来完成某个子任务。


理解编程范式(前端角度)》 是转载文章,点击查看原文


相关推荐


为什么在 JavaScript 中 NaN !== NaN?背后藏着 40 年的技术故事
冴羽2025/11/10

1. 前言 初学 JavaScript 的时候,经常会遇到一些令人困惑的现象,比如: console.log(NaN === NaN); // false console.log(NaN !== NaN); // true 为什么一个值会不等于它自己呢? 今天,我们就来深入探究这个问题。 2. NaN 的本质:一个特殊的“数字” NaN 其实是 Not a Number 的缩写,表示它不是一个数字。但 NaN 的类型却是 number console.log(typeof NaN); // "


自定义instanceof运算符行为API: Symbol.hasInstance
桜吹雪2025/11/8

今天翻zod的源码,发现有个之前没见过的,应该也没听说的API:Symbol.hasInstance export /*@__NO_SIDE_EFFECTS__*/ function $constructor<T extends ZodTrait, D = T["_zod"]["def"]>( name: string, initializer: (inst: T, def: D) => void, params?: { Parent?: typeof Class } ): $con


Bash 的 md5sum 命令
hubenchang05152025/11/6

#Bash 的 md5sum 命令 md5sum [OPTION]... [FILE]... 功能 计算或校验 MD5 值。 类型 可执行文件(/usr/bin/md5sum),属于 coreutils。 参数 OPTION 选项: -b, --binary - 以二进制模式读取文件;类 UNIX 系统下始终是二进制模式 -c, --check - 从文件中读取 MD5 值进行校验 --tag- 生成 BSD 风格的输出 -t, --text - 以文本模式读取文件;类 UNIX 系统下不


虚拟机的未来:云计算与边缘计算的核心引擎(一)
jiushun_suanli2025/11/1

虚拟机定义与核心原理 虚拟机(VM)是指通过软件模拟实现的完整计算机系统,具有与物理计算机相同的功能。VM可以运行自己的操作系统和应用程序,就像独立的物理机器一样,但实际上是在共享的物理硬件资源上运行。 硬件虚拟化技术 硬件虚拟化是通过虚拟化层(hypervisor)在物理硬件和虚拟机之间建立抽象层,主要包括两种类型: 全虚拟化(Full Virtualization): 无需修改客户操作系统通过二进制翻译技术(如VMware的ESXi)或硬件辅助虚拟化(Intel VT-x/A


力扣热题100(前10道题目)
少年姜太公2025/10/30

前言 算法题几乎是面试必考的,许多同学一看到算法题就是一个头两个大,所以笔者这次准备把力扣热题100写成文章与jym一起学习,估计会分为10篇文章来写。在这个过程中会分享一些自己刷题的想法和思路,让大家能够轻松看懂,这些题目我会采用js来写,有看不懂js的同学可以看个思路然后换成自己熟悉的语言去写🔥 LeetCode 热题 HOT 100 在每道题目之前我都会把对应的题目链接贴出来,方便大家可以看完我的解法再去力扣上刷题,而且这些题目我会尽可能多种解法去写,大家可以参考一下。 160. 相交链


从原型到类:JavaScript面向对象编程的终极进化指南
良山有风来2025/10/27

你是不是也曾经被JavaScript的原型链绕得头晕眼花?每次看到__proto__和prototype就感觉在看天书?别担心,这几乎是每个前端开发者都会经历的阶段。 今天我要带你彻底搞懂JavaScript面向对象编程的进化之路。从令人困惑的原型到优雅的class语法,再到实际项目中的设计模式应用,读完本文,你不仅能理解JS面向对象的本质,还能写出更优雅、更易维护的代码。 原型时代:JavaScript的"上古时期" 在ES6之前,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参数设置锁的超时时间,确保锁在持有过久后自动释放,避免死锁。 使用看门狗模式: 通过不断刷新锁的过期时间来维持锁的有效性,防止锁的持有者因为长时间操作而导致锁自动过期。 确保锁的唯一性和原子性: 使用具有唯一标识的锁值来确保释放锁时的准确性,避免因误删导致的死锁问题。 故障恢复机制: 在检测到锁持有者失效后,其他节点可以尝试获取锁,确保系统的高可用性。 下面是一个示

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读