跳转到内容

React 原理

React 15及以前,React 使用的是 “Stack Reconciler”(栈调和器)。当组件状态更新时,React 会立即、同步地从组件树的根部开始,递归地调用每个组件的渲染方法,对比新旧虚拟 DOM(这个过程称为 Diffing),然后一次性将变更应用到真实 DOM。这个过程就像在一个很深的函数调用栈里执行,必须一口气完成。

栈调和器存在如下的几个痛点:

  • 不可中断:一旦开始渲染,就必须执行到底。

  • 阻塞主线程:如果组件树很大,这个计算过程会长时间占用JavaScript主线程。而浏览器的主线程还负责布局、绘制、处理用户输入(点击、滚动等)。结果就是,页面在渲染期间会卡顿、无响应,用户体验非常差。

React Fiber解决了上述痛点,是React v16版本的重大更新,核心思想是将复杂的渲染更新任务拆分成多个小工作单元,并赋予其可中断、可恢复和优先级调度的能力,从而极大地提升用户体验和应用的响应速度。

  1. 中断与恢复:Fiber 支持任务的暂停和恢复,避免长时间占用主线程,提升页面的响应速度。

  2. 优先级管理:不同任务可以分配不同的优先级,高优先级任务(如用户输入)可以优先执行。

  3. 性能优化:通过链表结构和副作用标记,减少不必要的节点遍历,提高渲染效率

Fiber节点的结构大致如下:

// Fiber节点对象伪代码
{
// >>> 1. 实例标识信息 (Instance Identification)
type: Function | String, // 组件类型(函数、类)或HTML标签名('div')
key: null | String, // 同级节点中的唯一标识,用于Diff算法
stateNode: Component | DOMNode, // 对应的类组件实例或DOM节点
// >>> 2. 构成链表树的结构信息 (LinkedList Structure) - 这是实现可中断遍历的关键!
return: Fiber | null, // 指向父Fiber节点(父节点)
child: Fiber | null, // 指向第一个子Fiber节点(第一个子节点)
sibling: Fiber | null, // 指向下一个兄弟Fiber节点(兄弟节点)
// (通过child->sibling->sibling...和return,构成了深度优先遍历的链表结构)
// >>> 3. 副作用相关信息 (Side Effects) - 标记需要做什么工作
flags: Number, // (旧版本为effectTag) 一个二进制数字,标记这个Fiber需要执行的副作用类型(如:Placement-插入,Update-更新,Deletion-删除)
subtreeFlags: Number, // 标记子树中是否存在副作用,避免递归遍历整个子树
deletions: Array<Fiber> | null, // 记录需要被删除的子Fiber
hooks: null | HookLinkedList, // 对于函数组件,存储其hooks链表
// >>> 4. 状态和Props (State and Props)
memoizedProps: any, // 上次渲染时传入的props
pendingProps: any, // 新的、待处理的props
memoizedState: any, // 上次渲染后的state(对于函数组件,是hook链表的状态)
updateQueue: UpdateQueue<any> | null, // 存储来自setState的更新队列
// >>> 5. 用于调度和交替的工作进度信息 (Work-in-Progress)
alternate: Fiber | null, // 指向current树或workInProgress树上对应的节点,用于比较和复用
// current树:当前屏幕上显示内容对应的Fiber树
// workInProgress树:正在内存中构建的、下一次要渲染的Fiber树
}

可以看到,Fiber节点采用链表结构来描述虚拟DOM树。使用链表结构有如下好处:

  1. 实现可中断与恢复:链表结构将树的父子兄弟关系显式地存储为指针。这意味着React可以使用一个简单的while循环来手动模拟深度优先遍历。循环是完全可以控制的

    • 中断:React处理完一个Fiber节点(一个工作单元)后,可以立即检查当前帧是否还有剩余时间。如果没有,直接 break 跳出循环即可中断工作。此时唯一需要保存的“进度”就是一个指向当前节点的指针。

    • 恢复:当浏览器空闲时,React重新进入工作循环。它只需要从上次保存的当前节点指针开始,根据链表指针(child -> sibling -> return)找到下一个要处理的节点,然后继续循环即可。

  2. 支持优先级调度和任务插队: 链表结构使得“任务插队”变得可行。当一个高优先级的更新(如用户输入)到来时,React可以:

    • 中断当前正在进行的低优先级渲染工作(可能正处在链表中间的某个节点)。

    • 直接开始处理高优先级的更新,构建一棵新的、更高优先级的Fiber链表(称为workInProgress tree)。

    等高优先级的更新被提交到DOM后,React可以完全丢弃之前被中断的低优先级的work-in-progress链表,或者重新开始低优先级的渲染,而不会造成任何状态不一致。

在协调/渲染阶段,React做了下面的事情:

  1. 构建WorkInProgress TreeReact会尝试复用现有的Fiber节点,创建一棵新的、代表“工作进度”的Fiber树(workInProgress tree)。这使用了双缓存技术,在内存中完成所有计算,不直接影响屏幕。

  2. 遍历并处理每个Fiber(beginWork):从根节点开始,React会深度优先遍历每个Fiber节点,对每个节点执行的主要工作包括:

    • 更新状态:处理组件状态和属性的更新。

    • 调用渲染方法:对于类组件,调用render方法;对于函数组件,调用函数本身(这就是为什么函数组件每次更新都会执行整个函数体)。

    • Diffing算法:将渲染返回的新子元素(children)与旧的子Fiber进行对比。

    • 标记副作用:根据对比结果,为Fiber节点打上“副作用”(Effect)的标签(使用二进制位flags表示,如: Placement-插入、Update-更新、Deletion-删除),但此时并不执行任何DOM操作。

    • 决定子节点:通过Diffing的结果,决定是复用旧的Fiber、创建新的Fiber还是标记删除。

    • 完成处理 (completeWork):当一个节点的所有子节点都处理完毕后,会回到该节点完成“收尾”工作。例如,为宿主组件(DOM节点)准备props的更新。

当整个workInProgress tree的所有节点都遍历完成后,React得到了一棵完整的、标记了所有变更的新树。React会将这些所有标记了副作用的Fiber节点连接成一个线性链表,称为Effect List(副作用列表)。

在提交更新阶段,React会遍历Effect List,执行DOM增删改、调用生命周期/Effect钩子。(提交更新阶段不可中断)

React通过时间切片的方式去拆分任务,具体工作流程如下:

  • 任务开始:setState触发一个新的渲染任务。

  • 申请时间片:React调度器开始工作,根据任务优先级,向浏览器申请一个时间片(例如5ms)。

  • 循环工作与检查:

    • React 开始循环处理Fiber节点(执行beginWorkcompleteWork)。

    • 每处理完一个Fiber节点,它都会检查当前时间片是否已经耗尽。

  • 决策点:

    • 时间充足:继续处理下一个节点。

    • 时间耗尽:立即中断工作,保存当前进度,并归还主线程。

  • 浏览器接管:浏览器利用这拿回的主线程进行布局、绘制,并响应用户的点击、输入等操作。用户不会感到卡顿。

  • 恢复工作:通过requestIdleCallback(或其polyfill),浏览器在空闲时通知ReactReact再次获取主控制权,从上一次保存的Fiber节点继续处理,申请下一个时间片。

  • 完成:重复步骤 2-6,直到所有 Fiber 节点处理完毕。然后一次性同步地执行提交阶段(commitWork),将最终结果更新到DOM上。

由于requestIdleCallback的兼容性和触发频率问题,React团队自己实现了这部分能力,源码见:https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js。

React 16.x(即React 17之前)的版本中,Fiber架构使用的优先级调度机制是基于ExpirationTime(过期时间) 的模型。ExpirationTime模型的核心理念是:为每个更新任务计算一个唯一的“过期时间戳”,优先级越高的任务,其过期时间戳越小(意味着越紧迫,需要越快执行)。

React 内部定义了不同的优先级等级,例如:

  • ImmediatePriority: 用于需要同步执行的任务。

  • UserBlockingPriority: 用于用户交互(如点击、输入)。

  • NormalPriority: 用于普通的数据更新、网络请求结果。

  • LowPriority: 用于可延迟的任务(如 Offscreen 内容)。

  • IdlePriority: 用于非必要的后台任务(最低优先级)。

当一个更新(如 setState)被触发时,React 会根据其优先级,计算出一个过期时间。计算公式简化后类似于:expirationTime = currentTime + timeout。其中:

  • currentTime: 当前时间(一个从应用加载开始持续递增的毫秒数)。

  • timeout: 一个常量,优先级越高,timeout 值越小。

例如,高优先级任务的timeout可能是100ms,而低优先级任务的timeout可能是5s。这意味着,高优先级任务在currentTime + 100ms后就“过期”了,而低优先级任务则有5s的“缓冲期”。

React调度器始终优先执行队列中expirationTime最小的任务(即最紧迫、最快过期的任务)。在执行一个任务(渲染一棵 Fiber 树)的过程中,React 会不断地检查当前时间:

  • 如果当前时间小于下一个最高优先级任务的expirationTime,说明还有时间,可以继续当前工作。

  • 如果有一个更高优先级(更小expirationTime)的任务被加入队列,或者当前时间已经超过了当前任务的expirationTime(任务已过期),React就会中断当前工作,转而去执行那个更高优先级的或已过期的任务。

使用ExpirationTime模型来调度更新存在如下问题:

  • 饥饿问题(Starvation): 如果一直有高优先级的更新(如用户输入),那么低优先级的更新(如渲染一个大型列表)可能会因为其过期时间戳不断被推后而永远无法被处理。

  • 无法批量处理多个更新: ExpirationTime模型更倾向于处理单个最高优先级的更新,难以将多个同一优先级的更新进行高效的批量处理。

  • 表达能力有限: 它无法同时表示和处理多个不同优先级的更新。调度器一次只能盯着“一个”最快过期的任务。

React 17之后引入了Lanes模型(车道模型)来管理优先级,解决了上述问题,为并发模式的实现奠定了坚实的基础。

想象一条有着多条车道的公路。

  • 每条车道代表一个优先级(如:用户输入、过渡更新、普通更新等)。

  • 一辆车代表一个更新(Update)。

  • 一次渲染任务可以占用一条或多条车道(即处理多个优先级的更新)。

车道有宽度(优先级),也有编号。React 使用一个31位的二进制数来表示这些车道(LaneLanes类型)。因为优先级是二进制位,所以可以非常高效地使用位操作(|, &, ~)来组合、分离和判断优先级。

源码见:https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberLane.js

基于Lanes的优先级机制工作流程如下:

  • Schedule Update: 当发生一个更新时(例如 setStatestartTransition),React会根据更新的来源和上下文,为其分配一个或多个对应的车道。

  • Select Lane: 在每次准备构建新的渲染树(workLoopSyncworkLoopConcurrent)之前,调度器需要决定本次渲染要处理哪些优先级的更新(getNextLanes)。

    • 检查是否有挂起的车道: 查看pendingLanes集合。

    • 选择最高优先级车道: 从pendingLanes中找出优先级最高的车道(数值最小的非零车道)。

    • 进行调度: 这是并发模式的核心。调度器会询问浏览器是否有空余时间。

      • 如果当前是同步任务(如 SyncLane) 或浏览器空闲: 则直接处理选中的车道。

      • 如果浏览器繁忙: 即使有高优先级更新,React也可能中断当前渲染,将主线程交还给浏览器去绘制UI或处理用户输入,以避免页面卡顿。这就是时间切片(Time Slicing)。

      • 对于低优先级更新(如TransitionLane),React会主动yield(让步) 给浏览器,检查是否有更高优先级的更新进来。

  • Render and Interrupt:

    • 渲染过程: React 开始渲染组件树。本次渲染只会处理那些优先级与选中车道相匹配的更新。

    • 中断机制:

      • 在并发渲染过程中,React 会定期中断渲染工作,去检查是否有新的、更高优先级的更新被加入。例如,正在渲染一个低优先级的Transition更新时,用户突然点击了按钮(产生SyncLane更新)。React会立即中断当前的渲染工作,丢弃已经进行的Fiber树构建(因为可能已经过时),然后立即开始一次新的、更高优先级的渲染来处理用户的点击。之前被中断的低优先级渲染会完全作废,然后以最新的状态重新开始。
  • Commit and Cleanup: 当一棵Fiber树的渲染工作(Render Phase)完成,React会将其提交(Commit Phase)到 DOM。提交阶段是同步的、不可中断的,以保证UI的一致性。提交完成后,React会清理已处理的车道:pendingLanes = pendingLanes & ~renderedLanes,将刚刚渲染过的车道从待处理集合中移除。

React Fiber 简介 —— React 背后的算法

React Lanes(泳道)机制

React Diff算法是React中用于比较虚拟DOM树并计算出最小更新操作的一种算法。它的工作流程可以概括为以下几个步骤:

  1. 树的递归比较React会对新旧两棵虚拟DOM树进行递归比较,从根节点开始,逐层比较节点。

  2. 比较规则

    • 节点类型不同:如果节点类型(标签名或组件名)不同,React会直接销毁旧节点及其子节点,创建新节点并替换。

    • 节点类型相同:如果是相同类型的节点,React会更新该节点的属性,然后递归比较其子节点。

  3. 列表节点的比较:当比较一组子节点(如列表)时,React使用Key属性来优化比较过程:会尽量复用具有相同key的节点,而不是直接销毁和创建。如果没有keyReact可能会采用更暴力的方式更新,导致性能下降和不必要的渲染。

  4. Diffing策略

    • 分层比较React只会对同一层次的节点进行比较,不会跨层次比较。如果节点跨层次移动,React会销毁并重新创建。

    • 列表比较(Reconciliation)

      1. 定义两个指针(oldIndexnewIndex)分别指向旧列表和新列表的头部。

      2. 开始遍历,如果新旧列表指针指向的Fiber节点的key相同且类型相同,则视为可复用,指针后移。

      3. 如果遇到不可复用的节点,则停止遍历。

      4. 然后,根据停止后的情况:

        • 如果旧列表指针指向为空,那么新列表指针指向的Fiber节点以及往后的Fiber节点都是待新增的。

        • 如果旧列表指针指向不为空,则:

          a. 将剩余的旧Fiber节点(从oldIndex开始)放入一个以key为键的map中。

          b. 然后从新列表的当前指针(newIndex)开始遍历,对于每个新Fiber节点,从map中查找是否有相同key的旧节点。

          • 如果找到,且类型相同,则复用,并从map中删除。

          • 如果找不到,则说明是新增的。

          c. 最后,map中剩余的旧节点都是待删除的。

在函数组件中,每次调用一个 Hook(比如 useStateuseEffect 等),React 都会将这些 Hook 按照调用的顺序添加到链表中。这个链表存储在组件对应的 Fiber 节点上,每个 Hook 节点都会存储相关的状态和更新队列。

在 Fiber 节点的 memoizedState 属性中,函数组件的 Hooks 以链表形式存储:

Fiber.memoizedState (Hook链表头节点)
Hook (useState #1) Hook (useEffect #2) Hook (useState #3)
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ memoizedState: 0 │ │ memoizedState: ...│ │ memoizedState: '' │
│ queue: UpdateQueue│ │ queue: null │ │ queue: UpdateQueue│
│ next: ———————————————> │ next: ———————————————> │ next: null │
└───────────────────┘ └───────────────────┘ └───────────────────┘
↑ ↑ ↑
| | |
const [count, setCount] useEffect(...) const [text, setText]
= useState(0) = useState('')

每个 Hook 节点包含以下关键属性:

  • memoizedState: 存储 Hook 的当前状态值
  • queue: 更新队列,用于批量处理状态更新(useState、useReducer 等)
  • next: 指向下一个 Hook 节点的指针

基于 React 源码,Hook 的调用机制如下:

// React 内部简化实现
let componentHooks = [];
let currentHookIndex = 0;
function useState(initialState) {
let pair = componentHooks[currentHookIndex];
if (pair) {
// 非首次渲染,返回已存在的状态
currentHookIndex++;
return pair;
}
// 首次渲染,创建新的状态对
pair = [initialState, setState];
function setState(nextState) {
pair[0] = nextState;
// 触发重新渲染
updateDOM();
}
componentHooks[currentHookIndex] = pair;
currentHookIndex++;
return pair;
}

基于链表的设计,React 强制执行以下规则:

1. 顶层调用原则

  • Hook 必须在函数组件的顶层调用,不能在条件、循环或嵌套函数中调用
  • 原因:React 依赖 Hook 的调用顺序来匹配链表中的节点

2. 调用顺序一致性

  • 每次渲染时,Hook 的调用顺序必须保持一致
  • React 通过 currentHookIndex 来追踪当前处理的 Hook 节点

3. 多个 Hook 互不干扰

  • 同一组件中多次调用 useState 会创建不同的链表节点
  • 每个节点维护独立的状态和更新队列

这种设计确保了 Hook 状态的正确管理和组件的可预测行为。

以下是一个展示 Hook 链表工作原理的实际例子:

function Gallery() {
// Hook #1: useState
const [index, setIndex] = useState(0);
// Hook #2: useState
const [showMore, setShowMore] = useState(false);
// Hook #3: useEffect
useEffect(() => {
document.title = `Gallery - Image ${index + 1}`;
}, [index]);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
return (
<div>
<button onClick={handleNextClick}>Next</button>
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
{showMore && <p>Image details...</p>}
</div>
);
}

在这个组件中,React 会创建如下的 Hook 链表:

Fiber.memoizedState
Hook #1 (useState - index)
├─ memoizedState: 0
├─ queue: UpdateQueue
└─ next ──→ Hook #2 (useState - showMore)
├─ memoizedState: false
├─ queue: UpdateQueue
└─ next ──→ Hook #3 (useEffect)
├─ memoizedState: Effect对象
├─ queue: null
└─ next: null

❌ 错误的条件调用:

function BadComponent({ condition }) {
const [count, setCount] = useState(0);
if (condition) {
// 错误:条件调用会破坏链表顺序
const [name, setName] = useState('');
}
const [visible, setVisible] = useState(true);
}

问题分析:

  • 首次渲染时如果 conditiontrue,会创建 3 个 Hook 节点
  • 再次渲染时如果 conditionfalse,只会创建 2 个 Hook 节点
  • React 无法正确匹配链表中的节点,导致状态混乱

✅ 正确的实现:

function GoodComponent({ condition }) {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [visible, setVisible] = useState(true);
// 在渲染逻辑中处理条件
return (
<div>
<p>Count: {count}</p>
{condition && <p>Name: {name}</p>}
{visible && <p>Visible content</p>}
</div>
);
}

这样确保了每次渲染时 Hook 的调用顺序都保持一致,维护了链表结构的稳定性。

React.memo 是一个高阶组件(HOC),用于对函数组件进行浅比较优化,防止不必要的重新渲染。它通过缓存组件的渲染结果,只有当 props 发生变化时才重新渲染组件。

React.memo 的核心原理是浅比较(Shallow Comparison)

  1. 首次渲染:组件正常渲染并缓存结果
  2. 后续渲染:比较新旧 props 是否相同
  3. 相同 props:直接返回缓存的渲染结果,跳过重新渲染
  4. 不同 props:重新渲染组件并更新缓存
// React.memo 的简化实现原理
function memo(Component, areEqual) {
return function MemoizedComponent(props) {
const ref = useRef();
// 首次渲染或 props 发生变化时重新渲染
if (!ref.current || !areEqual(ref.current.props, props)) {
ref.current = {
props,
result: Component(props)
};
}
return ref.current.result;
};
}
// 默认的浅比较函数
function shallowEqual(prevProps, nextProps) {
const keys1 = Object.keys(prevProps);
const keys2 = Object.keys(nextProps);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (prevProps[key] !== nextProps[key]) {
return false;
}
}
return true;
}
比较类型说明示例
基本类型直接值比较stringnumberboolean
引用类型引用地址比较objectarrayfunction
自定义比较使用第二个参数函数memo(Component, customCompare)

React.memo 适用于以下场景:

  1. 纯展示组件:只依赖 props 渲染的组件
  2. 昂贵计算组件:渲染过程包含复杂计算的组件
  3. 频繁更新的父组件:父组件频繁更新但子组件 props 稳定
import { memo } from 'react';
// 普通组件
function Profile({ person }) {
return (
<div>
<h2>{person.name}</h2>
<p>Age: {person.age}</p>
</div>
);
}
// 使用 memo 优化
const MemoizedProfile = memo(Profile);
// 父组件
function App() {
const [count, setCount] = useState(0);
const person = { name: 'John', age: 30 }; // 引用类型 props
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
{/* 每次 count 更新时,Profile 都会重新渲染 */}
<Profile person={person} />
{/* MemoizedProfile 不会重新渲染,因为 person 引用相同 */}
<MemoizedProfile person={person} />
</div>
);
}
import { memo, useMemo } from 'react';
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('John');
const [age, setAge] = useState(30);
// 使用 useMemo 确保 person 对象的引用稳定性
const person = useMemo(() => ({ name, age }), [name, age]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
{/* 现在 MemoizedProfile 只在 name 或 age 变化时重新渲染 */}
<MemoizedProfile person={person} />
</div>
);
}
import { memo } from 'react';
function ExpensiveComponent({ items, threshold }) {
// 昂贵的计算过程
const processedItems = items.filter(item => item.value > threshold);
return (
<ul>
{processedItems.map(item => (
<li key={item.id}>{item.name}: {item.value}</li>
))}
</ul>
);
}
// 自定义比较函数:只关心 items 数组长度和 threshold
const MemoizedExpensiveComponent = memo(ExpensiveComponent, (prevProps, nextProps) => {
return (
prevProps.items.length === nextProps.items.length &&
prevProps.threshold === nextProps.threshold
);
});
// 列表项组件
const ListItem = memo(function ListItem({ item, onDelete }) {
return (
<div>
<span>{item.name}</span>
<button onClick={() => onDelete(item.id)}>删除</button>
</div>
);
});
// 列表组件
function TodoList({ items }) {
const [filter, setFilter] = useState('');
// 使用 useCallback 确保 onDelete 函数引用稳定
const handleDelete = useCallback((id) => {
// 删除逻辑
}, []);
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="筛选..."
/>
{items.map(item => (
<ListItem
key={item.id}
item={item}
onDelete={handleDelete}
/>
))}
</div>
);
}
优化方案适用场景优点缺点使用建议
React.memo函数组件 props 比较简单易用,自动浅比较只能比较 props,无法优化内部状态适用于纯展示组件
useMemo昂贵计算结果缓存精确控制缓存逻辑需要手动管理依赖数组适用于复杂计算场景
useCallback函数引用稳定性防止子组件不必要渲染过度使用可能适得其反配合 memo 使用效果更佳
PureComponent类组件优化自动 props 和 state 比较只适用于类组件类组件的首选优化方案
// ❌ 不必要的 memo 使用
const SimpleText = memo(({ text }) => <span>{text}</span>);
// ✅ 适合使用 memo 的场景
const ExpensiveChart = memo(({ data }) => {
// 复杂的图表渲染逻辑
return <ComplexChart data={data} />;
});
// ❌ 每次渲染都创建新对象
function Parent() {
return <Child config={{ theme: 'dark', size: 'large' }} />;
}
// ✅ 使用 useMemo 或提取到组件外部
const defaultConfig = { theme: 'dark', size: 'large' };
function Parent() {
return <Child config={defaultConfig} />;
}

随着 React Compiler 的发展,手动使用 memouseMemouseCallback 的需求将逐渐减少:

// 传统手动优化方式
const OptimizedComponent = memo(function Component({ data, onClick }) {
const processedData = useMemo(() => expensiveProcessing(data), [data]);
const handleClick = useCallback((item) => onClick(item.id), [onClick]);
return <div>{/* 渲染逻辑 */}</div>;
});
// React Compiler 自动优化后
function Component({ data, onClick }) {
const processedData = expensiveProcessing(data);
const handleClick = (item) => onClick(item.id);
return <div>{/* 渲染逻辑 */}</div>;
}

React.memo 作为 React 性能优化的重要工具,通过浅比较机制有效减少不必要的组件重新渲染,但需要合理使用以避免过度优化带来的负面影响。