<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>冰河末日的博客</title>
  
  
  <link href="https://liaojunhao.github.io/atom.xml" rel="self"/>
  
  <link href="https://liaojunhao.github.io/"/>
  <updated>2026-09-07T06:34:25.348Z</updated>
  <id>https://liaojunhao.github.io/</id>
  
  <author>
    <name>andy</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Vue3 编译器核心逻辑：从 template 到 render 函数</title>
    <link href="https://liaojunhao.github.io/2026/05/07/vue3-compiler-core-logic/"/>
    <id>https://liaojunhao.github.io/2026/05/07/vue3-compiler-core-logic/</id>
    <published>2026-05-07T02:30:00.000Z</published>
    <updated>2026-09-07T06:34:25.348Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Vue 的编译器（Compiler）是一个非常有趣却又常被忽视的模块。我们在 <code>.vue</code> 文件里写的 <code>&lt;template&gt;</code>，最终是怎么变成页面上的 DOM 的？这背后就是编译器的功劳。</p><p>本文基于 Vue 3.2 最新源码，剖析 Vue 编译器从 <strong>template → render 函数</strong> 的完整流程，揭示 AST、Transform、CodeGen 的核心原理。</p><hr><h2 id="一、什么是-DSL-编译器"><a href="#一、什么是-DSL-编译器" class="headerlink" title="一、什么是 DSL 编译器"></a>一、什么是 DSL 编译器</h2><h3 id="1-1-通用编译器-vs-DSL-编译器"><a href="#1-1-通用编译器-vs-DSL-编译器" class="headerlink" title="1.1 通用编译器 vs DSL 编译器"></a>1.1 通用编译器 vs DSL 编译器</h3><p>编译器本质上是一种”翻译器”：<strong>把 A 语言翻译成 B 语言</strong>。</p><p>构建一个 Java 或 JavaScript 通用编译器是非常复杂的，涉及词法分析、语法分析、优化、目标代码生成等环节。</p><p>但 Vue 需要的不是通用编译器，而是一个<strong>领域特定语言（DSL）编译器</strong>：</p><blockquote><p><strong>把 template 模板，编译成 render 函数</strong></p></blockquote><p>这个任务的复杂度比通用编译器低得多，但已经足够支撑 Vue 模板系统的核心能力。</p><h3 id="1-2-编译器入口"><a href="#1-2-编译器入口" class="headerlink" title="1.2 编译器入口"></a>1.2 编译器入口</h3><p>在 Vue 源码 <code>packages/compiler-dom/src/index.ts</code> 中，<code>compile</code> 方法最终调用 <code>baseCompile</code>：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// packages/compiler-core/src/compile.ts</span><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">baseCompile</span><span class="token punctuation">(</span>  template<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> RootNode<span class="token punctuation">,</span>  options<span class="token operator">:</span> CompilerOptions <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token operator">:</span> CodegenResult <span class="token punctuation">&#123;</span>  <span class="token comment">// 1. parse：将模板字符串解析为 AST</span>  <span class="token keyword">const</span> ast <span class="token operator">=</span> <span class="token function">isString</span><span class="token punctuation">(</span>template<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">baseParse</span><span class="token punctuation">(</span>template<span class="token punctuation">,</span> options<span class="token punctuation">)</span> <span class="token operator">:</span> template  <span class="token comment">// 2. transform：将 AST 转换为 JavaScript AST</span>  <span class="token function">transform</span><span class="token punctuation">(</span>ast<span class="token punctuation">,</span> <span class="token function">extend</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> options<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>    prefixIdentifiers<span class="token punctuation">,</span>    nodeTransforms<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>nodeTransforms<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>nodeTransforms <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>    directiveTransforms<span class="token operator">:</span> <span class="token function">extend</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> directiveTransforms<span class="token punctuation">,</span> options<span class="token punctuation">.</span>directiveTransforms <span class="token operator">||</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token comment">// 3. generate：根据 JavaScript AST 生成 render 函数</span>  <span class="token keyword">return</span> <span class="token function">generate</span><span class="token punctuation">(</span>ast<span class="token punctuation">,</span> <span class="token function">extend</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> options<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> prefixIdentifiers <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>编译器只做三件事：<strong>Parse → Transform → Generate</strong>，简洁而优雅。</p><hr><h2 id="二、Parse：模板解析为-AST"><a href="#二、Parse：模板解析为-AST" class="headerlink" title="二、Parse：模板解析为 AST"></a>二、Parse：模板解析为 AST</h2><h3 id="2-1-什么是-AST"><a href="#2-1-什么是-AST" class="headerlink" title="2.1 什么是 AST"></a>2.1 什么是 AST</h3><p>AST（Abstract Syntax Tree，抽象语法树）是一个用来<strong>描述模板结构的 JavaScript 对象</strong>。</p><p>以如下模板为例：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>isShow<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>hello world<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>解析后生成的 AST 核心结构如下：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>  <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token comment">// NodeTypes.ROOT — 根节点</span>  <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">[</span>    <span class="token punctuation">&#123;</span>      <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token comment">// NodeTypes.ELEMENT — 元素节点</span>      <span class="token literal-property property">tag</span><span class="token operator">:</span> <span class="token string">'div'</span><span class="token punctuation">,</span>      <span class="token literal-property property">tagType</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>      <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">[</span>        <span class="token punctuation">&#123;</span>          <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token comment">// NodeTypes.DIRECTIVE — 指令节点</span>          <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'if'</span><span class="token punctuation">,</span>          <span class="token literal-property property">exp</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>            <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token comment">// NodeTypes.SIMPLE_EXPRESSION — 简单表达式</span>            <span class="token literal-property property">content</span><span class="token operator">:</span> <span class="token string">'isShow'</span><span class="token punctuation">,</span>            <span class="token literal-property property">isStatic</span><span class="token operator">:</span> <span class="token boolean">false</span>          <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">]</span><span class="token punctuation">,</span>      <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">[</span>        <span class="token punctuation">&#123;</span>          <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token comment">// 嵌套的 p 元素</span>          <span class="token literal-property property">tag</span><span class="token operator">:</span> <span class="token string">'p'</span><span class="token punctuation">,</span>          <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">[</span>            <span class="token punctuation">&#123;</span>              <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token comment">// NodeTypes.ATTRIBUTE — 属性节点</span>              <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'class'</span><span class="token punctuation">,</span>              <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token literal-property property">content</span><span class="token operator">:</span> <span class="token string">'title'</span> <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span>          <span class="token punctuation">]</span><span class="token punctuation">,</span>          <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">[</span>            <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token literal-property property">content</span><span class="token operator">:</span> <span class="token string">'hello world'</span> <span class="token punctuation">&#125;</span> <span class="token comment">// 文本节点</span>          <span class="token punctuation">]</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">]</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-2-AST-节点类型一览"><a href="#2-2-AST-节点类型一览" class="headerlink" title="2.2 AST 节点类型一览"></a>2.2 AST 节点类型一览</h3><p>Vue 编译器定义了以下几种核心节点类型：</p><table><thead><tr><th>type</th><th>名称</th><th>说明</th></tr></thead><tbody><tr><td>0</td><td>ROOT</td><td>根节点</td></tr><tr><td>1</td><td>ELEMENT</td><td>DOM 元素节点</td></tr><tr><td>2</td><td>TEXT</td><td>文本节点</td></tr><tr><td>6</td><td>ATTRIBUTE</td><td>HTML 属性（如 <code>class</code>、<code>id</code>）</td></tr><tr><td>7</td><td>DIRECTIVE</td><td>指令（如 <code>v-if</code>、<code>v-for</code>）</td></tr><tr><td>4</td><td>SIMPLE_EXPRESSION</td><td>简单表达式</td></tr></tbody></table><p>指令节点（type: 7）下的表达式类型更丰富，包括：</p><ul><li><code>SIMPLE_EXPRESSION</code> — 简单表达式</li><li><code>JS_CALL_EXPRESSION</code> — JS 调用表达式</li><li><code>JS_OBJECT_EXPRESSION</code> — JS 对象表达式</li><li><code>JS_CONDITIONAL_EXPRESSION</code> — 条件表达式</li><li><code>JS_ARRAY_EXPRESSION</code> — 数组表达式</li><li><code>JS_FUNCTION_EXPRESSION</code> — 函数表达式</li></ul><h3 id="2-3-AST-的核心价值"><a href="#2-3-AST-的核心价值" class="headerlink" title="2.3 AST 的核心价值"></a>2.3 AST 的核心价值</h3><p>AST 本质上只是一个<strong>结构化的 JS 对象</strong>，它做到了：</p><ul><li>用 <code>type</code> 区分节点类型（元素、文本、指令……）</li><li>用 <code>children</code> 递归表达 DOM 树结构</li><li>用 <code>loc</code> 记录每个节点在源码中的位置（行、列、偏移量）</li><li>将 <code>v-if</code>、<code>v-for</code> 等指令也纳入 AST 统一管理</li></ul><p>这样一来，模板的<strong>所有信息都被结构化</strong>了，后续处理只需要遍历和操作这个对象即可。</p><hr><h2 id="三、Transform：AST-转化为-JavaScript-AST"><a href="#三、Transform：AST-转化为-JavaScript-AST" class="headerlink" title="三、Transform：AST 转化为 JavaScript AST"></a>三、Transform：AST 转化为 JavaScript AST</h2><h3 id="3-1-为什么要转换？"><a href="#3-1-为什么要转换？" class="headerlink" title="3.1 为什么要转换？"></a>3.1 为什么要转换？</h3><p>AST 描述的是模板的结构，但还<strong>不能直接用来生成 render 函数</strong>。因为 render 函数本质上是 JavaScript 代码，不是 HTML 结构。</p><p>所以需要第二步：<strong>将模板 AST 转换为 JavaScript AST</strong>。</p><h3 id="3-2-核心产物：codegenNode"><a href="#3-2-核心产物：codegenNode" class="headerlink" title="3.2 核心产物：codegenNode"></a>3.2 核心产物：codegenNode</h3><p>转换后最关键的产物是 <code>codegenNode</code>（代码生成节点），它是 render 函数的具体”配方”。</p><p>还是上面的例子，转换后在根节点和元素节点上都多了一个 <code>codegenNode</code> 属性：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>  <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>  <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token punctuation">]</span><span class="token punctuation">,</span>  <span class="token literal-property property">codegenNode</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">13</span><span class="token punctuation">,</span>              <span class="token comment">// NodeTypes.VNODE_CALL</span>    <span class="token literal-property property">tag</span><span class="token operator">:</span> <span class="token string">'"div"'</span><span class="token punctuation">,</span>          <span class="token comment">// 标签名（字符串字面量）</span>    <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>            <span class="token comment">// 子节点</span>      <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span>             <span class="token comment">// NodeTypes.TEXT</span>      <span class="token literal-property property">content</span><span class="token operator">:</span> <span class="token string">'hello world'</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token literal-property property">isBlock</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>         <span class="token comment">// 是否为块级元素</span>    <span class="token literal-property property">isComponent</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>    <span class="token comment">// 是否为组件</span>    <span class="token literal-property property">disableTracking</span><span class="token operator">:</span> <span class="token boolean">false</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>codegenNode</code> 告诉代码生成器：当前节点要调用 <code>createElementBlock(&quot;div&quot;, null, ...)</code> 来创建 VNode。这就是从模板到 render 函数的关键桥梁。</p><h3 id="3-3-Transform-的处理逻辑"><a href="#3-3-Transform-的处理逻辑" class="headerlink" title="3.3 Transform 的处理逻辑"></a>3.3 Transform 的处理逻辑</h3><p>Transform 阶段会注册一系列 <code>nodeTransforms</code>，每个 transform 负责特定节点类型的转换：</p><ul><li><code>transformElement</code> — 处理元素节点，生成 VNode 调用</li><li><code>transformText</code> — 处理文本节点</li><li><code>transformIf</code> — 处理 <code>v-if</code>，注入条件分支逻辑</li><li><code>transformFor</code> — 处理 <code>v-for</code>，注入循环逻辑</li><li><code>transformOn</code> — 处理 <code>@click</code> 等事件绑定</li><li><code>bindDirectiveTransforms</code> — 处理各种指令的绑定</li></ul><p>每个 transform 都是一个纯函数，接收 AST 节点，修改或替换节点的 <code>codegenNode</code>。Transform 链式执行，最终把整个模板 AST 改造成 JavaScript 可执行的形态。</p><hr><h2 id="四、Generate：生成-render-函数"><a href="#四、Generate：生成-render-函数" class="headerlink" title="四、Generate：生成 render 函数"></a>四、Generate：生成 render 函数</h2><h3 id="4-1-render-函数长什么样"><a href="#4-1-render-函数长什么样" class="headerlink" title="4.1 render 函数长什么样"></a>4.1 render 函数长什么样</h3><p>最终生成的 render 函数如下：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token parameter">_ctx<span class="token punctuation">,</span> _cache</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">with</span> <span class="token punctuation">(</span>_ctx<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">openBlock</span><span class="token operator">:</span> _openBlock<span class="token punctuation">,</span> <span class="token literal-property property">createElementBlock</span><span class="token operator">:</span> _createElementBlock <span class="token punctuation">&#125;</span> <span class="token operator">=</span> _Vue    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token function">_openBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">_createElementBlock</span><span class="token punctuation">(</span><span class="token string">"div"</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token string">"hello world"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>注意</strong>：<code>with</code> 语句在现代代码中不推荐使用，Vue 编译器用它来简化模板中对响应式变量的访问。</p></blockquote><h3 id="4-2-with-ctx-的作用"><a href="#4-2-with-ctx-的作用" class="headerlink" title="4.2 with (_ctx) 的作用"></a>4.2 <code>with (_ctx)</code> 的作用</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">with</span> <span class="token punctuation">(</span>_ctx<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> openBlock<span class="token punctuation">,</span> createElementBlock <span class="token punctuation">&#125;</span> <span class="token operator">=</span> _Vue  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token function">openBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">createElementBlock</span><span class="token punctuation">(</span><span class="token string">"div"</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token string">"hello world"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><code>_ctx</code> 就是组件实例（this）。<code>with (_ctx)</code> 让 render 函数内部可以直接访问 <code>data</code>、<code>props</code> 等响应式变量，而不用写 <code>this.xxx</code>。</p><p>去掉 <code>with</code> 的等价写法：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token parameter">_ctx<span class="token punctuation">,</span> _cache</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> openBlock<span class="token punctuation">,</span> createElementBlock <span class="token punctuation">&#125;</span> <span class="token operator">=</span> _ctx<span class="token punctuation">.</span>$Vue  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token function">openBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">createElementBlock</span><span class="token punctuation">(</span><span class="token string">"div"</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token string">"hello world"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-3-openBlock-和-createElementBlock"><a href="#4-3-openBlock-和-createElementBlock" class="headerlink" title="4.3 openBlock 和 createElementBlock"></a>4.3 openBlock 和 createElementBlock</h3><p>这是 Vue3 引入的<strong>Block（块）概念</strong>的核心：</p><ul><li><strong><code>openBlock()</code></strong> — 打开一个新的动态节点收集区，记录后续创建的所有动态节点</li><li><strong><code>createElementBlock(&quot;div&quot;, null, &quot;hello world&quot;)</code></strong> — 创建一个块级 VNode</li></ul><p>为什么要分两步？Vue3 会在运行时<strong>跳过静态节点，只比较动态节点</strong>。<code>openBlock</code> 开启了一个”动态节点收集器”，所有动态部分（<code>&#123;&#123; message &#125;&#125;</code>、动态 class、动态指令等）都会被收集进来，后续 patch 时只比较这些动态节点，大幅提升 Diff 效率。</p><h3 id="4-4-render-函数的执行结果"><a href="#4-4-render-函数的执行结果" class="headerlink" title="4.4 render 函数的执行结果"></a>4.4 render 函数的执行结果</h3><p>render 函数最终返回一个 VNode：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 等价于</span><span class="token function">h</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">,</span> <span class="token string">'hello world'</span><span class="token punctuation">)</span><span class="token comment">// 更精确的等价于</span><span class="token function">createVNode</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token string">'hello world'</span><span class="token punctuation">)</span><span class="token comment">// 或 Vue3 优化版本</span><span class="token function">createElementBlock</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token string">'hello world'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>render 函数 → VNode → render(vnode, container) → 真实 DOM，这便是 Vue 模板渲染的完整链路。</p><hr><h2 id="五、完整编译流程回顾"><a href="#五、完整编译流程回顾" class="headerlink" title="五、完整编译流程回顾"></a>五、完整编译流程回顾</h2><pre class="line-numbers language-none"><code class="language-none">┌─────────────┐│  template   ││  模板字符串  │└──────┬──────┘       │  ① Parse（baseParse）       ▼┌─────────────┐│     AST     │   ← 模板的结构化表示│ 抽象语法树   │└──────┬──────┘       │  ② Transform（nodeTransforms 链）       ▼┌──────────────────┐│ JavaScript AST  │   ← 多了 codegenNode│  + codegenNode  │└──────┬──────────┘       │  ③ Generate（codegen）       ▼┌─────────────┐│ render 函数  │   ← 最终产物└──────┬──────┘       │  ④ runtime 执行       ▼┌─────────────┐│ 真实 DOM    │└─────────────┘<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="六、面试高频问题"><a href="#六、面试高频问题" class="headerlink" title="六、面试高频问题"></a>六、面试高频问题</h2><p><strong>Q：Vue 编译器的 parse 用的是什么算法？</strong></p><p>A：Vue 使用<strong>状态机</strong>进行词法分析和语法解析。Vue3 的 <code>baseParse</code> 基于一个状态机逐字符扫描模板，根据当前状态和输入字符决定跳转逻辑，最终输出 AST。状态机比正则匹配更可控、不易出现歧义。</p><p><strong>Q：Vue2 和 Vue3 的编译策略有什么区别？</strong></p><p>A：Vue2 是<strong>完整编译</strong>，模板 → render 函数在构建时完成，运行时直接执行。Vue3 引入了<strong>Block + 动态节点收集</strong>机制，通过 <code>openBlock</code> 收集动态子节点，运行时只需比较动态部分，Diff 效率更高。同时 Vue3 支持<strong>动态组件（Teleport、Suspense）</strong>，编译策略也做了相应适配。</p><p><strong>Q：为什么需要 <code>with</code> 语句？</strong></p><p>A：<code>with (_ctx)</code> 省略了每个变量的 <code>this.</code> 前缀。如果不用 with，模板里的 <code>&#123;&#123; message &#125;&#125;</code> 就要写成 <code>this.message</code>，代码膨胀且可读性差。Vue3 在运行时规避了 with 的严格模式限制（在 <code>with</code> 外做了 <code>with</code> 隔离），所以可以放心使用。</p><p><strong>Q：codegenNode 的 <code>type: 13</code> 是什么？</strong></p><p>A：<code>type: 13</code> 对应 <code>NodeTypes.VNODE_CALL</code>，表示”调用 createElementBlock / createVNode 创建 VNode”。还有 <code>type: 12</code>（<code>TEXT_CALL</code>，文本节点）和 <code>type: 14</code>（<code>COMMENT_CALL</code>，注释节点）等。</p><hr><p><em>本文基于 Vue 3.2.0 源码分析，源码路径：<code>packages/compiler-core/</code></em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;Vue 的编译器（Compiler）是一个非常有趣却又常被忽视的模块。我们在 &lt;code&gt;.vue&lt;/code&gt; 文件里写的 &lt;code&gt;&amp;</summary>
      
    
    
    
    <category term="Vue" scheme="https://liaojunhao.github.io/categories/Vue/"/>
    
    
    <category term="面试" scheme="https://liaojunhao.github.io/tags/%E9%9D%A2%E8%AF%95/"/>
    
    <category term="源码" scheme="https://liaojunhao.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
    <category term="编译器" scheme="https://liaojunhao.github.io/tags/%E7%BC%96%E8%AF%91%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>Vue3 Diff 算法详解：从原理到源码实现</title>
    <link href="https://liaojunhao.github.io/2026/05/06/vue3-diff-algorithm-deep-dive/"/>
    <id>https://liaojunhao.github.io/2026/05/06/vue3-diff-algorithm-deep-dive/</id>
    <published>2026-05-06T09:30:00.000Z</published>
    <updated>2026-09-07T06:34:26.267Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Diff 算法是 Virtual DOM 的核心，也是前端面试中的高频难点。本文基于 Vue 3.2 最新源码，手把手分析 Diff 的五大步骤，以及最长递增子序列在其中扮演的关键角色。</p><hr><h2 id="一、Diff-算法的触发场景"><a href="#一、Diff-算法的触发场景" class="headerlink" title="一、Diff 算法的触发场景"></a>一、Diff 算法的触发场景</h2><p>很多人以为「DOM 更新就会触发 Diff」，这是一个误区。</p><p><strong>Diff 只在”一组 DOM”发生变化时被触发</strong>——即 <code>v-for</code> 循环渲染多个节点时，数据变化导致旧 VNode 数组需要更新为新 VNode 数组的场景。</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item in arr<span class="token punctuation">"</span></span> <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item.id<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>      &#123;&#123; item.title &#125;&#125;    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>onChange<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>修改<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">&#123;</span>  <span class="token function">data</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">arr</span><span class="token operator">:</span> <span class="token punctuation">[</span>        <span class="token punctuation">&#123;</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'1'</span><span class="token punctuation">,</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'a'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>        <span class="token punctuation">&#123;</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'2'</span><span class="token punctuation">,</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'b'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>        <span class="token punctuation">&#123;</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'3'</span><span class="token punctuation">,</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'c'</span> <span class="token punctuation">&#125;</span>      <span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token literal-property property">methods</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token function">onChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>arr<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'4'</span><span class="token punctuation">,</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'d'</span> <span class="token punctuation">&#125;</span>      <span class="token comment">// 触发 Diff：一组 li 需要更新</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="二、key-的作用与-isSameVNodeType"><a href="#二、key-的作用与-isSameVNodeType" class="headerlink" title="二、key 的作用与 isSameVNodeType"></a>二、key 的作用与 isSameVNodeType</h2><h3 id="2-1-为什么需要-key"><a href="#2-1-为什么需要-key" class="headerlink" title="2.1 为什么需要 key"></a>2.1 为什么需要 key</h3><p>上面的例子中，点击修改后，只有第三个 <code>li</code> 重新渲染了，前两个没有变化。Vue 是怎么知道的？</p><p>答案在于 <code>isSameVNodeType</code> 方法：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// packages/runtime-core/src/vnode.ts</span><span class="token keyword">function</span> <span class="token function">isSameVNodeType</span><span class="token punctuation">(</span>n1<span class="token operator">:</span> VNode<span class="token punctuation">,</span> n2<span class="token operator">:</span> VNode<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> n1<span class="token punctuation">.</span>type <span class="token operator">===</span> n2<span class="token punctuation">.</span>type <span class="token operator">&amp;&amp;</span> n1<span class="token punctuation">.</span>key <span class="token operator">===</span> n2<span class="token punctuation">.</span>key<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li><strong>type</strong>：VNode 节点类型（<code>&#39;li&#39;</code>、<code>&#39;div&#39;</code>、<code>&#39;comment&#39;</code>、组件实例等）</li><li><strong>key</strong>：<code>v-for</code> 绑定的唯一标识</li></ul><p>当 <code>type</code> 和 <code>key</code> 都相同时，两个 VNode 被认为是”相同的”，只需 <code>patch</code> 打补丁，无需重新创建 DOM。</p><h3 id="2-2-key-的重要性"><a href="#2-2-key-的重要性" class="headerlink" title="2.2 key 的重要性"></a>2.2 key 的重要性</h3><p>没有 key 或 key 不稳定，会导致 Vue 无法正确复用 DOM 节点，引发性能问题和状态错乱：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token comment">&lt;!-- ❌ 不推荐：用 index 作为 key --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>(item, index) in arr<span class="token punctuation">"</span></span> <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>index<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token comment">&lt;!-- ✅ 推荐：用唯一 id 作为 key --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item in arr<span class="token punctuation">"</span></span> <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item.id<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="三、Diff-算法的五大步骤"><a href="#三、Diff-算法的五大步骤" class="headerlink" title="三、Diff 算法的五大步骤"></a>三、Diff 算法的五大步骤</h2><p>Vue3 的 <code>patchKeyedChildren</code> 将 Diff 分为 5 步：</p><table><thead><tr><th>步骤</th><th>名称</th><th>作用</th></tr></thead><tbody><tr><td>1</td><td>sync from start</td><td>自前向后对比，相同节点直接 patch</td></tr><tr><td>2</td><td>sync from end</td><td>自后向前对比，相同节点 patch</td></tr><tr><td>3</td><td>common sequence + mount</td><td>新节点多于旧节点 → 挂载新增</td></tr><tr><td>4</td><td>common sequence + unmount</td><td>旧节点多于新节点 → 卸载多余</td></tr><tr><td>5</td><td>unknown sequence</td><td>乱序（最复杂，涉及移动）</td></tr></tbody></table><p>前四步处理”新、旧节点数量相等或简单增删”的场景，第五步处理<strong>乱序</strong>——这是真正考验算法功力的地方。</p><hr><h2 id="四、前四步详解"><a href="#四、前四步详解" class="headerlink" title="四、前四步详解"></a>四、前四步详解</h2><h3 id="4-1-步骤一：sync-from-start（自前向后）"><a href="#4-1-步骤一：sync-from-start（自前向后）" class="headerlink" title="4.1 步骤一：sync from start（自前向后）"></a>4.1 步骤一：sync from start（自前向后）</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">while</span> <span class="token punctuation">(</span>i <span class="token operator">&lt;=</span> oldChildrenEnd <span class="token operator">&amp;&amp;</span> i <span class="token operator">&lt;=</span> newChildrenEnd<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> oldVNode <span class="token operator">=</span> oldChildren<span class="token punctuation">[</span>i<span class="token punctuation">]</span>  <span class="token keyword">const</span> newVNode <span class="token operator">=</span> <span class="token function">normalizeVNode</span><span class="token punctuation">(</span>newChildren<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isSameVNodeType</span><span class="token punctuation">(</span>oldVNode<span class="token punctuation">,</span> newVNode<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">patch</span><span class="token punctuation">(</span>oldVNode<span class="token punctuation">,</span> newVNode<span class="token punctuation">,</span> container<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token comment">// 相同 → 打补丁</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">break</span> <span class="token comment">// 不同 → 停止</span>  <span class="token punctuation">&#125;</span>  i<span class="token operator">++</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>从头部开始，依次对比相同下标的 VNode。相同则 patch，不同则退出。</p><h3 id="4-2-步骤二：sync-from-end（自后向前）"><a href="#4-2-步骤二：sync-from-end（自后向前）" class="headerlink" title="4.2 步骤二：sync from end（自后向前）"></a>4.2 步骤二：sync from end（自后向前）</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">while</span> <span class="token punctuation">(</span>i <span class="token operator">&lt;=</span> oldChildrenEnd <span class="token operator">&amp;&amp;</span> i <span class="token operator">&lt;=</span> newChildrenEnd<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> oldVNode <span class="token operator">=</span> oldChildren<span class="token punctuation">[</span>oldChildrenEnd<span class="token punctuation">]</span>  <span class="token keyword">const</span> newVNode <span class="token operator">=</span> <span class="token function">normalizeVNode</span><span class="token punctuation">(</span>newChildrenEnd<span class="token punctuation">)</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isSameVNodeType</span><span class="token punctuation">(</span>oldVNode<span class="token punctuation">,</span> newVNode<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">patch</span><span class="token punctuation">(</span>oldVNode<span class="token punctuation">,</span> newVNode<span class="token punctuation">,</span> container<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">break</span>  <span class="token punctuation">&#125;</span>  oldChildrenEnd<span class="token operator">--</span>  newChildrenEnd<span class="token operator">--</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>逻辑与步骤一相反，从尾部开始对比。两者结合可以快速处理”首尾相同、中间变化”的场景。</p><h3 id="4-3-步骤三：新节点多于旧节点"><a href="#4-3-步骤三：新节点多于旧节点" class="headerlink" title="4.3 步骤三：新节点多于旧节点"></a>4.3 步骤三：新节点多于旧节点</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">></span> oldChildrenEnd<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">&lt;=</span> newChildrenEnd<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> nextPos <span class="token operator">=</span> newChildrenEnd <span class="token operator">+</span> <span class="token number">1</span>    <span class="token keyword">const</span> anchor <span class="token operator">=</span> nextPos <span class="token operator">&lt;</span> newChildrenLength       <span class="token operator">?</span> newChildren<span class="token punctuation">[</span>nextPos<span class="token punctuation">]</span><span class="token punctuation">.</span>el       <span class="token operator">:</span> parentAnchor        <span class="token keyword">while</span> <span class="token punctuation">(</span>i <span class="token operator">&lt;=</span> newChildrenEnd<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">patch</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token function">normalizeVNode</span><span class="token punctuation">(</span>newChildren<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> container<span class="token punctuation">,</span> anchor<span class="token punctuation">)</span>      i<span class="token operator">++</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>通过 <code>arr.push()</code> 添加新元素时，多出的节点位于尾部；通过 <code>arr.unshift()</code> 添加时，多出的节点位于头部。核心是找到正确的锚点（anchor）来插入新增 DOM。</p><h3 id="4-4-步骤四：旧节点多于新节点"><a href="#4-4-步骤四：旧节点多于新节点" class="headerlink" title="4.4 步骤四：旧节点多于新节点"></a>4.4 步骤四：旧节点多于新节点</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">></span> newChildrenEnd<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">while</span> <span class="token punctuation">(</span>i <span class="token operator">&lt;=</span> oldChildrenEnd<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">unmount</span><span class="token punctuation">(</span>oldChildren<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// 直接卸载</span>    i<span class="token operator">++</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>通过 <code>arr.pop()</code> 或 <code>arr.shift()</code> 删除元素时，只需逐个卸载多余节点。</p><hr><h2 id="五、最长递增子序列（核心前置知识）"><a href="#五、最长递增子序列（核心前置知识）" class="headerlink" title="五、最长递增子序列（核心前置知识）"></a>五、最长递增子序列（核心前置知识）</h2><p>第五步乱序 Diff 中，最长递增子序列（Longest Increasing Subsequence，简称 LIS）是关键优化手段。</p><h3 id="5-1-什么是-LIS"><a href="#5-1-什么是-LIS" class="headerlink" title="5.1 什么是 LIS"></a>5.1 什么是 LIS</h3><p>在一个数值序列中，找出一个子序列，使其元素依次递增，且长度尽可能长。</p><pre class="line-numbers language-none"><code class="language-none">旧节点: 1, 2, 3, 4, 5, 6新节点: 1, 3, 2, 4, 6, 5可能的递增子序列：  1 → 3 → 6        (长度 3)  1 → 2 → 4 → 6    (长度 4) ← 这就是最长递增子序列<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-2-LIS-在-Diff-中的作用"><a href="#5-2-LIS-在-Diff-中的作用" class="headerlink" title="5.2 LIS 在 Diff 中的作用"></a>5.2 LIS 在 Diff 中的作用</h3><p>Diff 的本质操作是：<strong>新增、删除、打补丁、移动</strong>。</p><p>对于乱序场景，如果直接逐个移动，复杂度是 O(n)。但利用 LIS，我们可以<strong>只移动”不在递增子序列中”的节点</strong>，大幅减少移动次数：</p><pre class="line-numbers language-none"><code class="language-none">递增子序列: 1 → 2 → 4 → 6（4个节点无需移动）需要移动的: 3 和 5（仅需 2 次移动）对比：若没有 LIS 策略，需要移动 4 次才能完成转换<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-3-Vue3-中-getSequence-的实现"><a href="#5-3-Vue3-中-getSequence-的实现" class="headerlink" title="5.3 Vue3 中 getSequence 的实现"></a>5.3 Vue3 中 getSequence 的实现</h3><p>Vue3 使用”贪心 + 二分查找”实现 LIS，时间复杂度 O(n log n)：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// packages/runtime-core/src/renderer.ts</span><span class="token keyword">function</span> <span class="token function">getSequence</span><span class="token punctuation">(</span>arr<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> p <span class="token operator">=</span> arr<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token comment">// 回溯数组</span>  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>          <span class="token comment">// 存储下标</span>  <span class="token keyword">let</span> i<span class="token punctuation">,</span> j<span class="token punctuation">,</span> u<span class="token punctuation">,</span> v<span class="token punctuation">,</span> c    <span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> arrI <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>arrI <span class="token operator">!==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      j <span class="token operator">=</span> result<span class="token punctuation">[</span>result<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span>            <span class="token comment">// arr[j] &lt; arrI → 找到更大的序列，直接加入</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>arr<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">&lt;</span> arrI<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> j        result<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span>        <span class="token keyword">continue</span>      <span class="token punctuation">&#125;</span>            <span class="token comment">// 二分查找：找最小的且大于 arrI 的元素位置</span>      u <span class="token operator">=</span> <span class="token number">0</span>      v <span class="token operator">=</span> result<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span>      <span class="token keyword">while</span> <span class="token punctuation">(</span>u <span class="token operator">&lt;</span> v<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        c <span class="token operator">=</span> <span class="token punctuation">(</span>u <span class="token operator">+</span> v<span class="token punctuation">)</span> <span class="token operator">>></span> <span class="token number">1</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>arr<span class="token punctuation">[</span>result<span class="token punctuation">[</span>c<span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&lt;</span> arrI<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          u <span class="token operator">=</span> c <span class="token operator">+</span> <span class="token number">1</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>          v <span class="token operator">=</span> c        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>arrI <span class="token operator">&lt;</span> arr<span class="token punctuation">[</span>result<span class="token punctuation">[</span>u<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>u <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          p<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> result<span class="token punctuation">[</span>u <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span>        <span class="token punctuation">&#125;</span>        result<span class="token punctuation">[</span>u<span class="token punctuation">]</span> <span class="token operator">=</span> i      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>    <span class="token comment">// 回溯得到最终下标</span>  u <span class="token operator">=</span> result<span class="token punctuation">.</span>length  v <span class="token operator">=</span> result<span class="token punctuation">[</span>u <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span>  <span class="token keyword">while</span> <span class="token punctuation">(</span>u<span class="token operator">--</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    result<span class="token punctuation">[</span>u<span class="token punctuation">]</span> <span class="token operator">=</span> v    v <span class="token operator">=</span> p<span class="token punctuation">[</span>v<span class="token punctuation">]</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> result<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>核心思想</strong>：</p><ul><li>遍历数组，维护一个递增的结果序列</li><li>用二分查找快速找到”第一个大于等于当前值”的位置</li><li>最终回溯得到完整的递增下标序列</li></ul><hr><h2 id="六、第五步详解：乱序-Diff"><a href="#六、第五步详解：乱序-Diff" class="headerlink" title="六、第五步详解：乱序 Diff"></a>六、第五步详解：乱序 Diff</h2><p>这是整个 Diff 算法最复杂的部分，源码约 150 行，分三大块：</p><h3 id="6-1-构建-keyToNewIndexMap"><a href="#6-1-构建-keyToNewIndexMap" class="headerlink" title="6.1 构建 keyToNewIndexMap"></a>6.1 构建 keyToNewIndexMap</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">const</span> keyToNewIndexMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> s2<span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> e2<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> nextChild <span class="token operator">=</span> <span class="token function">normalizeVNode</span><span class="token punctuation">(</span>newChildren<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>nextChild<span class="token punctuation">.</span>key <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    keyToNewIndexMap<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>nextChild<span class="token punctuation">.</span>key<span class="token punctuation">,</span> i<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>建立一个 <code>Map&lt;key, newIndex&gt;</code>，快速查找新节点在哪个位置。</p><h3 id="6-2-遍历旧节点，patch-或-unmount"><a href="#6-2-遍历旧节点，patch-或-unmount" class="headerlink" title="6.2 遍历旧节点，patch 或 unmount"></a>6.2 遍历旧节点，patch 或 unmount</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">const</span> newIndexToOldIndexMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token builtin">Array</span></span><span class="token punctuation">(</span>toBePatched<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> s1<span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> e1<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> prevChild <span class="token operator">=</span> oldChildren<span class="token punctuation">[</span>i<span class="token punctuation">]</span>    <span class="token comment">// 超出新节点数量 → 直接卸载</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>patched <span class="token operator">>=</span> toBePatched<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">unmount</span><span class="token punctuation">(</span>prevChild<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">)</span>    <span class="token keyword">continue</span>  <span class="token punctuation">&#125;</span>    <span class="token comment">// 通过 key 查找新节点位置</span>  <span class="token keyword">let</span> newIndex <span class="token operator">=</span> keyToNewIndexMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>prevChild<span class="token punctuation">.</span>key<span class="token punctuation">)</span>    <span class="token comment">// 没找到 → 旧节点没有对应的新节点，卸载</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>newIndex <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">unmount</span><span class="token punctuation">(</span>prevChild<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 记录：新节点 index 对应的旧节点 index（+1，0 代表新增）</span>    newIndexToOldIndexMap<span class="token punctuation">[</span>newIndex <span class="token operator">-</span> s2<span class="token punctuation">]</span> <span class="token operator">=</span> i <span class="token operator">+</span> <span class="token number">1</span>        <span class="token comment">// 判断是否需要移动</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>newIndex <span class="token operator">>=</span> maxNewIndexSoFar<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      maxNewIndexSoFar <span class="token operator">=</span> newIndex    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>      moved <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token comment">// 出现了"逆序"，需要移动</span>    <span class="token punctuation">&#125;</span>        <span class="token function">patch</span><span class="token punctuation">(</span>prevChild<span class="token punctuation">,</span> newChildren<span class="token punctuation">[</span>newIndex<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">)</span>    patched<span class="token operator">++</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>核心：遍历旧节点，找到对应的新节点位置，记录到 <code>newIndexToOldIndexMap</code>，并判断是否需要移动。</p><h3 id="6-3-处理移动与挂载"><a href="#6-3-处理移动与挂载" class="headerlink" title="6.3 处理移动与挂载"></a>6.3 处理移动与挂载</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">const</span> increasingNewIndexSequence <span class="token operator">=</span> moved   <span class="token operator">?</span> <span class="token function">getSequence</span><span class="token punctuation">(</span>newIndexToOldIndexMap<span class="token punctuation">)</span>   <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>j <span class="token operator">=</span> increasingNewIndexSequence<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> toBePatched <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> nextIndex <span class="token operator">=</span> s2 <span class="token operator">+</span> i  <span class="token keyword">const</span> nextChild <span class="token operator">=</span> newChildren<span class="token punctuation">[</span>nextIndex<span class="token punctuation">]</span>  <span class="token keyword">const</span> anchor <span class="token operator">=</span> nextIndex <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">&lt;</span> l2     <span class="token operator">?</span> newChildren<span class="token punctuation">[</span>nextIndex <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>el     <span class="token operator">:</span> parentAnchor    <span class="token comment">// 新节点没有对应的旧节点 → 挂载</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>newIndexToOldIndexMap<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">patch</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> nextChild<span class="token punctuation">,</span> container<span class="token punctuation">,</span> anchor<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 需要移动</span>  <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>moved<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 不在最长递增子序列中 → 移动</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>j <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> i <span class="token operator">!==</span> increasingNewIndexSequence<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">move</span><span class="token punctuation">(</span>nextChild<span class="token punctuation">,</span> container<span class="token punctuation">,</span> anchor<span class="token punctuation">,</span> MoveType<span class="token punctuation">.</span><span class="token constant">REORDER</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>      j<span class="token operator">--</span> <span class="token comment">// 在递增子序列中，不移动</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>为什么倒序处理？</strong> 倒序可以用已处理好的节点作为锚点（anchor），避免每次都查找插入位置。</p><hr><h2 id="七、完整流程总结"><a href="#七、完整流程总结" class="headerlink" title="七、完整流程总结"></a>七、完整流程总结</h2><pre class="line-numbers language-none"><code class="language-none">旧: [A, B, C, D, E]    新: [A, C, B, F, D]<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>步骤一</strong>：A vs A → patch，B vs C → 不同，停止<br><strong>步骤二</strong>：E vs D → 不同，停止<br><strong>步骤三</strong>：无新节点多<br><strong>步骤四</strong>：无旧节点多<br><strong>步骤五</strong>（乱序）：</p><ol><li>构建 keyToNewIndexMap</li><li>遍历旧节点对比新节点位置</li><li>计算 LIS，确定最小移动次数</li><li>移动 + 挂载</li></ol><p><strong>移动次数 = 新节点数 - LIS 长度</strong></p><hr><h2 id="八、面试高频问题"><a href="#八、面试高频问题" class="headerlink" title="八、面试高频问题"></a>八、面试高频问题</h2><p><strong>Q：为什么 Vue3 推荐用唯一 id 作为 key，而不用 index？</strong></p><p>A：用 index 作为 key 时，如果数据中间插入或删除，index 对应的节点会变化，导致 Vue 无法正确复用 DOM，所有节点都需要重新渲染，严重的会引发状态错乱。用唯一 id 可以确保 VNode 身份稳定，精准复用。</p><p><strong>Q：Diff 算法的时间复杂度是多少？</strong></p><p>A：Vue3 Diff 整体是 O(n)。前四步是 O(n)，第五步乱序因为有 LIS 优化，从暴力 O(n²) 降到 O(n log n)。</p><p><strong>Q：没有 key 会怎样？</strong></p><p>A：Vue 会退化为”就地复用”策略，按位置逐一对比，无法正确复用节点，行为不可控，容易出 bug。</p><hr><p><em>本文基于 Vue 3.2.0 源码分析，如有问题欢迎交流探讨。</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;Diff 算法是 Virtual DOM 的核心，也是前端面试中的高频难点。本文基于 Vue 3.2 最新源码，手把手分析 Diff 的五大</summary>
      
    
    
    
    <category term="Vue" scheme="https://liaojunhao.github.io/categories/Vue/"/>
    
    
    <category term="面试" scheme="https://liaojunhao.github.io/tags/%E9%9D%A2%E8%AF%95/"/>
    
    <category term="源码" scheme="https://liaojunhao.github.io/tags/%E6%BA%90%E7%A0%81/"/>
    
    <category term="Diff" scheme="https://liaojunhao.github.io/tags/Diff/"/>
    
  </entry>
  
  <entry>
    <title>fabricjs：透明图片轮廓描边：从像素操作到实时渲染</title>
    <link href="https://liaojunhao.github.io/2026/04/01/%E9%80%8F%E6%98%8E%E5%9B%BE%E7%89%87%E8%BD%AE%E5%BB%93%E6%8F%8F%E8%BE%B9%EF%BC%9A%E4%BB%8E%E5%83%8F%E7%B4%A0%E6%93%8D%E4%BD%9C%E5%88%B0%E5%AE%9E%E6%97%B6%E6%B8%B2%E6%9F%93/"/>
    <id>https://liaojunhao.github.io/2026/04/01/%E9%80%8F%E6%98%8E%E5%9B%BE%E7%89%87%E8%BD%AE%E5%BB%93%E6%8F%8F%E8%BE%B9%EF%BC%9A%E4%BB%8E%E5%83%8F%E7%B4%A0%E6%93%8D%E4%BD%9C%E5%88%B0%E5%AE%9E%E6%97%B6%E6%B8%B2%E6%9F%93/</id>
    <published>2026-04-01T05:18:00.000Z</published>
    <updated>2026-09-07T06:34:35.912Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在 Canvas 图形编辑器中，如何为任意形状的透明 PNG 图片添加「沿轮廓走」的描边效果？本文从零开始，用通俗的语言带你理解整个实现原理。</p></blockquote><h2 id="🎯-先看效果"><a href="#🎯-先看效果" class="headerlink" title="🎯 先看效果"></a>🎯 先看效果</h2><p>想象一下这样的场景：你在做一个海报编辑器，用户拖入一张透明背景的卡通角色 PNG，想给它加一圈白色描边——就像贴纸上那种经典的白色轮廓。</p><pre class="line-numbers language-none"><code class="language-none">┌────────────────────────────────────┐│                                    ││    ╭──╮       ╭─────╮             ││   ╭╯🐱╰╮  →  ╭╯ ░🐱░ ╰╮  ← 描边  ││   ╰────╯     ╰───────╯           ││                                    ││   原始透明图片    添加轮廓描边后     │└────────────────────────────────────┘<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果用 Canvas 2D / fabric.js 的原生 <code>stroke</code> 属性，只能画出一个<strong>矩形边框</strong>——它根本不认识你图片的轮廓。我们需要自己动手，「教」计算机看懂图片的形状。</p><h2 id="❓-为什么原生描边不行？"><a href="#❓-为什么原生描边不行？" class="headerlink" title="❓ 为什么原生描边不行？"></a>❓ 为什么原生描边不行？</h2><p>fabric.js（底层是 Canvas 2D）对图片的描边，本质上就是在图片的<strong>矩形边界</strong>上画一圈：</p><pre class="line-numbers language-none"><code class="language-none">┌────────────────┐│  ┌──────────┐  │  ← fabric 原生 stroke│  │ 🐱       │  │     只能画矩形框│  │          │  ││  └──────────┘  │└────────────────┘<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但我们想要的是这种效果——<strong>紧贴图片内容轮廓</strong>的描边：</p><pre class="line-numbers language-none"><code class="language-none">  ╭───╮ ╭╯   ╰╮╭╯ 🐱  ╰╮   ← 我们想要的：沿轮廓描边╰╮     ╭╯ ╰╮   ╭╯  ╰───╯<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Canvas 2D API 没有提供这样的能力。所以我们得<strong>深入像素层面</strong>，自己算出描边区域。</p><h2 id="🧠-核心思路：5-步走"><a href="#🧠-核心思路：5-步走" class="headerlink" title="🧠 核心思路：5 步走"></a>🧠 核心思路：5 步走</h2><p>整个算法可以概括为 5 步（别急，后面会一步步展开）：</p><pre class="line-numbers language-none"><code class="language-none">原始图片  │  ▼① 读取像素 alpha 通道 → 建立「二值化蒙版」  │  ▼② 用「膨胀算法」将蒙版向外扩展 N 像素  │  ▼③ 膨胀蒙版 - 原始蒙版 &#x3D; 描边区域  │  ▼④ 将描边颜色填充到描边区域 → 生成「描边图层」  │  ▼⑤ 重写 fabric 的渲染方法：先画描边图层，再画原图<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接下来我们一步步拆解。</p><hr><h2 id="Step-1：读取-Alpha-通道-→-二值化蒙版"><a href="#Step-1：读取-Alpha-通道-→-二值化蒙版" class="headerlink" title="Step 1：读取 Alpha 通道 → 二值化蒙版"></a>Step 1：读取 Alpha 通道 → 二值化蒙版</h2><h3 id="什么是-Alpha-通道？"><a href="#什么是-Alpha-通道？" class="headerlink" title="什么是 Alpha 通道？"></a>什么是 Alpha 通道？</h3><p>每个像素有 4 个通道：R（红）、G（绿）、B（蓝）、A（Alpha/透明度）。</p><ul><li><code>A = 255</code> → 完全不透明</li><li><code>A = 0</code> → 完全透明</li><li><code>A = 128</code> → 半透明</li></ul><p>对于透明 PNG，背景区域的 A 值是 0，有内容的区域 A 值 &gt; 0。</p><h3 id="二值化：非黑即白"><a href="#二值化：非黑即白" class="headerlink" title="二值化：非黑即白"></a>二值化：非黑即白</h3><p>我们设一个<strong>阈值</strong>（比如 10），把所有像素分成两类：</p><pre class="line-numbers language-none"><code class="language-none">如果 alpha &gt; 10  →  标记为 1（不透明，属于图片内容）如果 alpha ≤ 10  →  标记为 0（透明，属于背景）<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>这样就得到一张「蒙版」——一个只有 0 和 1 的二维数组：</p><pre class="line-numbers language-none"><code class="language-none">原始图片的 alpha:              二值化蒙版:┌───────────────┐             ┌───────────────┐│ 0  0  0  0  0 │             │ 0  0  0  0  0 ││ 0  0 200 0  0 │      →      │ 0  0  1  0  0 ││ 0 180 255 190 0│             │ 0  1  1  1  0 ││ 0  0 210 0  0 │             │ 0  0  1  0  0 ││ 0  0  0  0  0 │             │ 0  0  0  0  0 │└───────────────┘             └───────────────┘<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>代码实现：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// 读取图片的原始像素数据</span><span class="token keyword">function</span> <span class="token function">getImagePixelData</span><span class="token punctuation">(</span>image<span class="token operator">:</span> FabricImage<span class="token punctuation">)</span><span class="token operator">:</span> ImageData <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> element <span class="token operator">=</span> image<span class="token punctuation">.</span><span class="token function">getElement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> width <span class="token operator">=</span> element<span class="token punctuation">.</span>naturalWidth <span class="token operator">||</span> element<span class="token punctuation">.</span>width<span class="token punctuation">;</span>  <span class="token keyword">const</span> height <span class="token operator">=</span> element<span class="token punctuation">.</span>naturalHeight <span class="token operator">||</span> element<span class="token punctuation">.</span>height<span class="token punctuation">;</span>  <span class="token comment">// 创建临时 canvas，把图片画上去，再读取像素</span>  <span class="token keyword">const</span> tempCanvas <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"canvas"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  tempCanvas<span class="token punctuation">.</span>width <span class="token operator">=</span> width<span class="token punctuation">;</span>  tempCanvas<span class="token punctuation">.</span>height <span class="token operator">=</span> height<span class="token punctuation">;</span>  <span class="token keyword">const</span> ctx <span class="token operator">=</span> tempCanvas<span class="token punctuation">.</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token string">"2d"</span><span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">;</span>  ctx<span class="token punctuation">.</span><span class="token function">drawImage</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> width<span class="token punctuation">,</span> height<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> ctx<span class="token punctuation">.</span><span class="token function">getImageData</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> width<span class="token punctuation">,</span> height<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>💡 <strong>为什么要临时 Canvas？</strong> 因为浏览器不允许直接读取 <code>&lt;img&gt;</code> 标签的像素数据，必须先画到 Canvas 上，再用 <code>getImageData</code> 提取。</p></blockquote><p>然后提取 alpha 通道，建立蒙版：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">const</span> expandedMask <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span>expandedWidth <span class="token operator">*</span> expandedHeight<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> y <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> y <span class="token operator">&lt;</span> height<span class="token punctuation">;</span> y<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> x <span class="token operator">&lt;</span> width<span class="token punctuation">;</span> x<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> srcIdx <span class="token operator">=</span> y <span class="token operator">*</span> width <span class="token operator">+</span> x<span class="token punctuation">;</span>    <span class="token comment">// 每个像素 4 个字节 [R, G, B, A]，我们只看第 4 个（index + 3）</span>    expandedMask<span class="token punctuation">[</span>dstIdx<span class="token punctuation">]</span> <span class="token operator">=</span> data<span class="token punctuation">[</span>srcIdx <span class="token operator">*</span> <span class="token number">4</span> <span class="token operator">+</span> <span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">></span> alphaThreshold <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>🎯 注意：这里我们在一个<strong>比原图大一圈</strong>的画布上建蒙版（四周各留 <code>strokeWidth</code> 的 padding），给描边预留空间。后面会解释为什么。</p></blockquote><hr><h2 id="Step-2：膨胀算法（Dilation）——让蒙版「胖一圈」"><a href="#Step-2：膨胀算法（Dilation）——让蒙版「胖一圈」" class="headerlink" title="Step 2：膨胀算法（Dilation）——让蒙版「胖一圈」"></a>Step 2：膨胀算法（Dilation）——让蒙版「胖一圈」</h2><p>这是整个算法的<strong>核心中的核心</strong>。</p><h3 id="膨胀是什么意思？"><a href="#膨胀是什么意思？" class="headerlink" title="膨胀是什么意思？"></a>膨胀是什么意思？</h3><p>想象蒙版上每个值为 1 的像素都变成一个<strong>小圆饼</strong>，半径就是描边宽度。所有小圆饼覆盖到的区域，就是膨胀后的蒙版。</p><pre class="line-numbers language-none"><code class="language-none">膨胀前（原始蒙版）:          膨胀后（半径&#x3D;1）:┌───────────┐               ┌───────────┐│ 0 0 0 0 0 │               │ 0 0 1 0 0 ││ 0 0 1 0 0 │               │ 0 1 1 1 0 ││ 0 1 1 1 0 │      →        │ 1 1 1 1 1 ││ 0 0 1 0 0 │               │ 0 1 1 1 0 ││ 0 0 0 0 0 │               │ 0 0 1 0 0 │└───────────┘               └───────────┘         ↑ 比原来大了一圈！<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>用更直观的比喻：<strong>就像往面团上滴了一滴墨水，墨水会往四周渗透 N 毫米</strong>。</p><h3 id="方案一：暴力但精确（适合小半径-≤-10px）"><a href="#方案一：暴力但精确（适合小半径-≤-10px）" class="headerlink" title="方案一：暴力但精确（适合小半径 ≤ 10px）"></a>方案一：暴力但精确（适合小半径 ≤ 10px）</h3><p>对于每个透明像素（值为 0），我们检查它周围一个圆形范围内，有没有任何一个不透明像素。如果有，这个点就被「膨胀」了：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">dilateMask</span><span class="token punctuation">(</span>mask<span class="token punctuation">,</span> width<span class="token punctuation">,</span> height<span class="token punctuation">,</span> radius<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 1. 预计算圆形范围内的所有偏移量</span>  <span class="token keyword">const</span> offsets <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> dy <span class="token operator">=</span> <span class="token operator">-</span>radius<span class="token punctuation">;</span> dy <span class="token operator">&lt;=</span> radius<span class="token punctuation">;</span> dy<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> dx <span class="token operator">=</span> <span class="token operator">-</span>radius<span class="token punctuation">;</span> dx <span class="token operator">&lt;=</span> radius<span class="token punctuation">;</span> dx<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>dx <span class="token operator">*</span> dx <span class="token operator">+</span> dy <span class="token operator">*</span> dy <span class="token operator">&lt;=</span> radius <span class="token operator">*</span> radius<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// 圆形判断</span>        offsets<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> dx<span class="token punctuation">,</span> dy <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 2. 对每个透明像素，检查圆形邻域</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> y <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> y <span class="token operator">&lt;</span> height<span class="token punctuation">;</span> y<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> x <span class="token operator">&lt;</span> width<span class="token punctuation">;</span> x<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>mask<span class="token punctuation">[</span>y <span class="token operator">*</span> width <span class="token operator">+</span> x<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        dilated<span class="token punctuation">[</span>y <span class="token operator">*</span> width <span class="token operator">+</span> x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 本来就不透明，保持</span>        <span class="token keyword">continue</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token comment">// 看看周围有没有不透明的邻居</span>      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">&#123;</span> dx<span class="token punctuation">,</span> dy <span class="token punctuation">&#125;</span> <span class="token keyword">of</span> offsets<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>mask<span class="token punctuation">[</span><span class="token punctuation">(</span>y <span class="token operator">+</span> dy<span class="token punctuation">)</span> <span class="token operator">*</span> width <span class="token operator">+</span> <span class="token punctuation">(</span>x <span class="token operator">+</span> dx<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          dilated<span class="token punctuation">[</span>y <span class="token operator">*</span> width <span class="token operator">+</span> x<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 有！膨胀它</span>          <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这个方案直观易懂，但时间复杂度是 <code>O(W × H × R²)</code>。当 <code>R = 10</code> 时，圆形范围内约有 314 个像素点，还可以接受。但 <code>R = 50</code> 时就变成 7,854 个点——太慢了。</p><h3 id="方案二：距离变换（适合大半径-gt-10px）"><a href="#方案二：距离变换（适合大半径-gt-10px）" class="headerlink" title="方案二：距离变换（适合大半径 &gt; 10px）"></a>方案二：距离变换（适合大半径 &gt; 10px）</h3><p>这是一个巧妙的优化：与其「每个点检查一个圆形邻域」，不如先算出<strong>每个透明像素到最近的不透明像素的距离</strong>。然后距离 ≤ radius 的，就是膨胀区域。</p><pre class="line-numbers language-none"><code class="language-none">原始蒙版:             距离场:              膨胀（radius&#x3D;2）:0 0 0 0 0            3 2 1 2 3            0 1 1 1 00 0 1 0 0     →      2 1 0 1 2     →      1 1 1 1 10 1 1 1 0            1 0 0 0 1            1 1 1 1 10 0 1 0 0            2 1 0 1 2            1 1 1 1 10 0 0 0 0            3 2 1 2 3            0 1 1 1 0<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>距离变换只需要<strong>4 次全图扫描</strong>（上 → 下、下 → 上 + 两次对角线扫描），复杂度是 <code>O(W × H)</code>，跟图片大小成正比，与描边宽度无关！</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">dilateMaskFast</span><span class="token punctuation">(</span>mask<span class="token punctuation">,</span> width<span class="token punctuation">,</span> height<span class="token punctuation">,</span> radius<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> dist <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Float32Array</span><span class="token punctuation">(</span>width <span class="token operator">*</span> height<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 初始化：不透明=0，透明=∞</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> width <span class="token operator">*</span> height<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    dist<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> mask<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token number">1</span> <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token number">Infinity</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 前向扫描：上→下、左→右</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> y <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> y <span class="token operator">&lt;</span> height<span class="token punctuation">;</span> y<span class="token operator">++</span><span class="token punctuation">)</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> x <span class="token operator">&lt;</span> width<span class="token punctuation">;</span> x<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> dist<span class="token punctuation">[</span>idx<span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>dist<span class="token punctuation">[</span>idx<span class="token punctuation">]</span><span class="token punctuation">,</span> dist<span class="token punctuation">[</span>idx <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>y <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> dist<span class="token punctuation">[</span>idx<span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>dist<span class="token punctuation">[</span>idx<span class="token punctuation">]</span><span class="token punctuation">,</span> dist<span class="token punctuation">[</span>上方<span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token comment">// 后向扫描：下→上、右→左</span>  <span class="token comment">// ...（类似，只是方向反过来）</span>  <span class="token comment">// 对角线扫描（提高精度，1.414 ≈ √2）</span>  <span class="token comment">// ...</span>  <span class="token comment">// 距离 ≤ radius 的像素就是膨胀区域</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> width <span class="token operator">*</span> height<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    dilated<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> dist<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">&lt;=</span> radius <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>🏎️ <strong>性能对比</strong>：对于 1000×1000 的图片，<code>radius = 20</code>：</p><ul><li>暴力方案：需要检查 ~12 亿次（1000² × 1,256）</li><li>距离变换：只需要扫描 ~400 万次（1000² × 4 遍）</li></ul><p>快了 <strong>300 倍</strong>。</p></blockquote><p>我们的实现中，根据描边宽度自动选择算法：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">const</span> dilatedMask <span class="token operator">=</span>  strokeWidth <span class="token operator">&lt;=</span> <span class="token number">10</span>    <span class="token operator">?</span> <span class="token function">dilateMask</span><span class="token punctuation">(</span><span class="token operator">...</span><span class="token punctuation">)</span>     <span class="token comment">// 小半径用精确圆形</span>    <span class="token operator">:</span> <span class="token function">dilateMaskFast</span><span class="token punctuation">(</span><span class="token operator">...</span><span class="token punctuation">)</span> <span class="token comment">// 大半径用距离变换</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="Step-3：膨胀蒙版-原始蒙版-描边区域"><a href="#Step-3：膨胀蒙版-原始蒙版-描边区域" class="headerlink" title="Step 3：膨胀蒙版 - 原始蒙版 = 描边区域"></a>Step 3：膨胀蒙版 - 原始蒙版 = 描边区域</h2><p>这步最简单——做一次「减法」：</p><pre class="line-numbers language-none"><code class="language-none">膨胀蒙版:                原始蒙版:                描边区域:0 0 1 0 0               0 0 0 0 0               0 0 1 0 00 1 1 1 0               0 0 1 0 0               0 1 0 1 01 1 1 1 1      —        0 1 1 1 0      &#x3D;        1 0 0 0 10 1 1 1 0               0 0 1 0 0               0 1 0 1 00 0 1 0 0               0 0 0 0 0               0 0 1 0 0膨胀了的                 原来的图片                只有描边的整个范围                 内容区域                  环形区域 ✨<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>代码就一行：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript">strokeMask<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> dilatedMask<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token number">1</span> <span class="token operator">&amp;&amp;</span> expandedMask<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>翻译成人话：<strong>膨胀后有、原来没有的像素 = 描边像素</strong>。</p><hr><h2 id="Step-4：生成描边图层"><a href="#Step-4：生成描边图层" class="headerlink" title="Step 4：生成描边图层"></a>Step 4：生成描边图层</h2><p>现在我们知道了哪些像素需要画描边，直接把描边颜色填进去：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">const</span> <span class="token punctuation">&#123;</span> r<span class="token punctuation">,</span> g<span class="token punctuation">,</span> b<span class="token punctuation">,</span> a <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token function">parseColor</span><span class="token punctuation">(</span>strokeColor<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 比如 "red" → &#123;r:255, g:0, b:0, a:255&#125;</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> totalPixels<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>strokeMask<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    outputData<span class="token punctuation">.</span>data<span class="token punctuation">[</span>i <span class="token operator">*</span> <span class="token number">4</span><span class="token punctuation">]</span> <span class="token operator">=</span> r<span class="token punctuation">;</span> <span class="token comment">// 红</span>    outputData<span class="token punctuation">.</span>data<span class="token punctuation">[</span>i <span class="token operator">*</span> <span class="token number">4</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> g<span class="token punctuation">;</span> <span class="token comment">// 绿</span>    outputData<span class="token punctuation">.</span>data<span class="token punctuation">[</span>i <span class="token operator">*</span> <span class="token number">4</span> <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> b<span class="token punctuation">;</span> <span class="token comment">// 蓝</span>    outputData<span class="token punctuation">.</span>data<span class="token punctuation">[</span>i <span class="token operator">*</span> <span class="token number">4</span> <span class="token operator">+</span> <span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">=</span> a<span class="token punctuation">;</span> <span class="token comment">// 透明度</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 非描边区域保持 [0,0,0,0] = 完全透明</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这样我们就得到了一张<strong>只有描边、其余透明</strong>的图片：</p><pre class="line-numbers language-none"><code class="language-none">┌───────────────┐│               ││   ░ ░ ░ ░    │   ░ &#x3D; 描边像素（红色）│  ░         ░  │   · &#x3D; 透明│  ░   🐱    ░  ││  ░         ░  ││   ░ ░ ░ ░    ││               │└───────────────┘<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>💡 <strong>为什么描边 Canvas 比原图大？</strong> 因为描边是向外扩展的！如果不加 padding，描边会超出画布被截断。所以我们在 Step 1 就预留了 <code>strokeWidth</code> 的边距。</p></blockquote><hr><h2 id="Step-5：重写渲染方法——在原图下面垫一层描边"><a href="#Step-5：重写渲染方法——在原图下面垫一层描边" class="headerlink" title="Step 5：重写渲染方法——在原图下面垫一层描边"></a>Step 5：重写渲染方法——在原图下面垫一层描边</h2><p>最后一步是「组装」。fabric.js 渲染图片时会调用 <code>image._render(ctx)</code> 方法。我们把这个方法「劫持」一下：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">patchImageRender</span><span class="token punctuation">(</span>image<span class="token operator">:</span> FabricImage<span class="token punctuation">,</span> options<span class="token operator">:</span> ImageStrokeOptions<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 保存原始 _render（只保存一次）</span>  <span class="token keyword">const</span> originalRender <span class="token operator">=</span> image<span class="token punctuation">.</span><span class="token function">_render</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 重写 _render</span>  image<span class="token punctuation">.</span><span class="token function-variable function">_render</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>ctx<span class="token operator">:</span> CanvasRenderingContext2D<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// ① 先画描边图层（在原图下面）</span>    ctx<span class="token punctuation">.</span><span class="token function">drawImage</span><span class="token punctuation">(</span>      strokeCanvas<span class="token punctuation">,</span> <span class="token comment">// 我们生成的描边图</span>      <span class="token operator">-</span>width <span class="token operator">/</span> <span class="token number">2</span> <span class="token operator">-</span> padding<span class="token punctuation">,</span> <span class="token comment">// 居中偏移 + 描边扩展</span>      <span class="token operator">-</span>height <span class="token operator">/</span> <span class="token number">2</span> <span class="token operator">-</span> padding<span class="token punctuation">,</span>      width <span class="token operator">+</span> padding <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">,</span>      height <span class="token operator">+</span> padding <span class="token operator">*</span> <span class="token number">2</span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// ② 再画原图（覆盖在描边上面）</span>    <span class="token function">originalRender</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> ctx<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>渲染顺序很重要——先描边、再原图：</p><pre class="line-numbers language-none"><code class="language-none"> 第 1 层（先画）         第 2 层（后画）         最终效果┌──────────────┐      ┌──────────────┐      ┌──────────────┐│              │      │              │      │              ││  ░░░░░░░░░  │      │              │      │  ░░░░░░░░░  ││ ░░░░░░░░░░░ │  +   │   🐱🐱🐱    │  &#x3D;   │ ░░🐱🐱🐱░░ ││ ░░░░░░░░░░░ │      │   🐱🐱🐱    │      │ ░░🐱🐱🐱░░ ││  ░░░░░░░░░  │      │              │      │  ░░░░░░░░░  ││              │      │              │      │              │└──────────────┘      └──────────────┘      └──────────────┘   描边图层              原图                  叠加效果 ✨<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="⚡-性能优化：缓存机制"><a href="#⚡-性能优化：缓存机制" class="headerlink" title="⚡ 性能优化：缓存机制"></a>⚡ 性能优化：缓存机制</h2><p>像素级操作很耗时（对一张 1000×1000 的图片，要处理 100 万个像素）。如果每帧都重新计算，滚动和缩放画布时会卡成 PPT。</p><p>解决方案：<strong>用 WeakMap 缓存描边 Canvas</strong>。</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">const</span> strokeCacheMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WeakMap<span class="token operator">&lt;</span>  FabricImage<span class="token punctuation">,</span>  <span class="token punctuation">&#123;</span>    canvas<span class="token operator">:</span> HTMLCanvasElement<span class="token punctuation">;</span>    optionsKey<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token comment">// "red_6_10" 这样的格式</span>  <span class="token punctuation">&#125;</span><span class="token operator">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>只有当描边参数（颜色、宽度、阈值）发生变化时，才重新生成：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">const</span> cacheKey <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>options<span class="token punctuation">.</span>color<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>options<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>options<span class="token punctuation">.</span>alphaThreshold<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><span class="token keyword">const</span> existing <span class="token operator">=</span> strokeCacheMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 参数没变 → 直接复用缓存</span><span class="token keyword">if</span> <span class="token punctuation">(</span>existing <span class="token operator">&amp;&amp;</span> existing<span class="token punctuation">.</span>optionsKey <span class="token operator">===</span> cacheKey<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span><span class="token comment">// 参数变了 → 重新生成</span><span class="token keyword">const</span> strokeCanvas <span class="token operator">=</span> <span class="token function">generateStrokeCanvas</span><span class="token punctuation">(</span><span class="token operator">...</span><span class="token punctuation">)</span><span class="token punctuation">;</span>strokeCacheMap<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>image<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> canvas<span class="token operator">:</span> strokeCanvas<span class="token punctuation">,</span> optionsKey<span class="token operator">:</span> cacheKey <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>🗑️ <strong>为什么用 WeakMap？</strong> 当图片对象被删除（没有其他引用）时，WeakMap 会自动释放缓存内存，不需要手动清理。</p></blockquote><hr><h2 id="🏗️-架构设计：不动-fabric-的奶酪"><a href="#🏗️-架构设计：不动-fabric-的奶酪" class="headerlink" title="🏗️ 架构设计：不动 fabric 的奶酪"></a>🏗️ 架构设计：不动 fabric 的奶酪</h2><p>一个很重要的设计决策：<strong>我们不设置 fabric 的原生 <code>stroke</code> / <code>strokeWidth</code> 属性</strong>。</p><p>为什么？三个原因：</p><h3 id="1-Bounding-Box-错位"><a href="#1-Bounding-Box-错位" class="headerlink" title="1. Bounding Box 错位"></a>1. Bounding Box 错位</h3><p>fabric 会把 <code>strokeWidth</code> 计入 bounding box 计算。如果设置了 <code>strokeWidth: 10</code>，选中框会向外扩 5px——但我们的轮廓描边是不规则形状，和矩形扩展完全对不上。</p><h3 id="2-原生矩形描边干扰"><a href="#2-原生矩形描边干扰" class="headerlink" title="2. 原生矩形描边干扰"></a>2. 原生矩形描边干扰</h3><p>设了 <code>stroke</code> 之后，fabric 会画一个矩形边框。我们需要额外 hack <code>_renderPaintInOrder</code> 方法来阻止它——增加了复杂度和不稳定性。</p><h3 id="3-序列化不兼容"><a href="#3-序列化不兼容" class="headerlink" title="3. 序列化不兼容"></a>3. 序列化不兼容</h3><p>fabric 的 <code>stroke</code> 是给矩形/圆形/多边形设计的，我们的轮廓描边跟它完全是两个概念。混在一起会让 JSON 保存/恢复变得混乱。</p><p>所以我们的做法是：</p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// 描边配置存在自定义属性里</span><span class="token punctuation">(</span>img <span class="token keyword">as</span> <span class="token builtin">any</span><span class="token punctuation">)</span><span class="token punctuation">.</span>__strokeOptions <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  enabled<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>  color<span class="token operator">:</span> <span class="token string">"red"</span><span class="token punctuation">,</span>  width<span class="token operator">:</span> <span class="token number">6</span><span class="token punctuation">,</span>  alphaThreshold<span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token comment">// 清掉 fabric 原生描边</span>img<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> stroke<span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span> strokeWidth<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>__strokeOptions</code> 会被注册到 <code>PROPERTIES_TO_INCLUDE</code> 中，fabric 的 <code>toJSON()</code> 和 <code>loadFromJSON()</code> 会自动处理它的序列化与恢复。</p><hr><h2 id="📦-完整流程图"><a href="#📦-完整流程图" class="headerlink" title="📦 完整流程图"></a>📦 完整流程图</h2><p>把整个系统串起来看：</p><pre class="line-numbers language-none"><code class="language-none">用户操作                    系统内部  │  ├── 拖入一张 PNG ──────→ object:added 事件触发  │                         │  │                         ├── autoMode 开启？  │                         │   ├── 有 __strokeOptions → 恢复已保存的配置  │                         │   ├── 有原生 stroke → 转换为轮廓描边  │                         │   └── 都没有 → 跳过，保持原样  │                         │  │                         └── 执行描边流程：  │                             ① getImagePixelData → 读取像素  │                             ② 二值化 → 膨胀 → 做差 → 生成描边 Canvas  │                             ③ patchImageRender → 重写 _render  │                             ④ strokeCacheMap 缓存结果  │  ├── 调整描边颜色 ────→ 清除缓存 → 重新生成描边 Canvas → re-render  │  ├── 调整描边宽度 ────→ 同上  │  ├── 关闭描边 ────────→ __strokeOptions.enabled &#x3D; false  │                      恢复原始 _render  │  ├── 保存&#x2F;导出 ────→ __strokeOptions 随 JSON 序列化  │                    导出时：新建 StaticCanvas → loadFromJSON → 重建描边 → toBlob  │  └── 重新加载 ────→ loadFromJSON 恢复 __strokeOptions                      restoreStrokesFromCanvas() → 重建所有描边<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="🔑-关键代码一览"><a href="#🔑-关键代码一览" class="headerlink" title="🔑 关键代码一览"></a>🔑 关键代码一览</h2><h3 id="配置接口"><a href="#配置接口" class="headerlink" title="配置接口"></a>配置接口</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token keyword">interface</span> <span class="token class-name">ImageStrokeOptions</span> <span class="token punctuation">&#123;</span>  enabled<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token comment">// 是否启用</span>  color<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token comment">// 描边颜色，如 'red', 'rgba(0,0,0,1)'</span>  width<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token comment">// 描边宽度（像素）</span>  alphaThreshold<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token comment">// alpha 阈值，默认 10</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="使用-API"><a href="#使用-API" class="headerlink" title="使用 API"></a>使用 API</h3><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// 给选中图片添加轮廓描边</span>editor<span class="token punctuation">.</span>imageStrokeHandler<span class="token punctuation">.</span><span class="token function">applyStroke</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  color<span class="token operator">:</span> <span class="token string">"white"</span><span class="token punctuation">,</span>  width<span class="token operator">:</span> <span class="token number">6</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 更新颜色</span>editor<span class="token punctuation">.</span>imageStrokeHandler<span class="token punctuation">.</span><span class="token function">updateStrokeColor</span><span class="token punctuation">(</span><span class="token string">"#FF0000"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 更新宽度</span>editor<span class="token punctuation">.</span>imageStrokeHandler<span class="token punctuation">.</span><span class="token function">updateStrokeWidth</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 移除描边</span>editor<span class="token punctuation">.</span>imageStrokeHandler<span class="token punctuation">.</span><span class="token function">removeStroke</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 读取当前配置</span><span class="token keyword">const</span> opts <span class="token operator">=</span> editor<span class="token punctuation">.</span>imageStrokeHandler<span class="token punctuation">.</span><span class="token function">getStrokeOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// → &#123; enabled: true, color: 'white', width: 6, alphaThreshold: 10 &#125;</span><span class="token comment">// 启用自动模式：所有新加入的图片自动描边</span>editor<span class="token punctuation">.</span>imageStrokeHandler<span class="token punctuation">.</span><span class="token function">enableAutoMode</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> color<span class="token operator">:</span> <span class="token string">"black"</span><span class="token punctuation">,</span> width<span class="token operator">:</span> <span class="token number">4</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="📝-小结"><a href="#📝-小结" class="headerlink" title="📝 小结"></a>📝 小结</h2><table><thead><tr><th align="center">步骤</th><th>做什么</th><th>关键技术</th></tr></thead><tbody><tr><td align="center">1</td><td>读取 alpha 通道 → 二值化蒙版</td><td><code>getImageData</code> + 阈值判断</td></tr><tr><td align="center">2</td><td>蒙版膨胀 N 像素</td><td>圆形膨胀（暴力）/ 距离变换（EDT）</td></tr><tr><td align="center">3</td><td>膨胀 - 原始 = 描边区域</td><td>集合差运算</td></tr><tr><td align="center">4</td><td>描边区域填色 → 描边 Canvas</td><td><code>putImageData</code></td></tr><tr><td align="center">5</td><td>重写 <code>_render</code>：先描边后原图</td><td>Monkey Patch + WeakMap 缓存</td></tr></tbody></table><p>核心思想其实很简单：<strong>先找到图片的「边界」在哪里（alpha 通道），然后把边界向外「膨胀」一圈，膨胀出来的那个环就是描边</strong>。</p><p>整个方案的优势：</p><ul><li>✅ 完全不依赖 fabric 原生 stroke，不影响 bounding box</li><li>✅ 支持任意形状的透明图片</li><li>✅ 自动选择最优算法（小半径精确、大半径快速）</li><li>✅ WeakMap 缓存，参数不变不重复计算</li><li>✅ 配置可序列化，JSON 导入导出无缝恢复</li><li>✅ 描边在原图下方，不遮挡图片内容</li></ul><p>希望这篇文章对你有帮助！如果你也在做 Canvas 图形编辑器，不妨试试这个方案 🎨</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;在 Canvas 图形编辑器中，如何为任意形状的透明 PNG 图片添加「沿轮廓走」的描边效果？本文从零开始，用通俗的语言带你理解整个实现原理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;🎯-先看效果&quot;&gt;&lt;a href=&quot;#🎯-先看效果</summary>
      
    
    
    
    <category term="fabricjs系列" scheme="https://liaojunhao.github.io/categories/fabricjs%E7%B3%BB%E5%88%97/"/>
    
    
    <category term="Canvas" scheme="https://liaojunhao.github.io/tags/Canvas/"/>
    
    <category term="fabric.js" scheme="https://liaojunhao.github.io/tags/fabric-js/"/>
    
  </entry>
  
  <entry>
    <title>前端面试高频场景题精讲：10 个你必须掌握的设计问题</title>
    <link href="https://liaojunhao.github.io/2026/03/24/frontend-interview-scene-questions/"/>
    <id>https://liaojunhao.github.io/2026/03/24/frontend-interview-scene-questions/</id>
    <published>2026-03-24T06:41:00.000Z</published>
    <updated>2026-09-07T06:34:33.804Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前端面试高频场景题精讲：10-个你必须掌握的设计问题"><a href="#前端面试高频场景题精讲：10-个你必须掌握的设计问题" class="headerlink" title="前端面试高频场景题精讲：10 个你必须掌握的设计问题"></a>前端面试高频场景题精讲：10 个你必须掌握的设计问题</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>场景题是前端面试中最能拉开差距的题型。它不考死记硬背，而是考察你在真实业务中的<strong>系统设计能力、性能意识和工程经验</strong>。本文精选 10 道高频场景题，从问题本质出发，给出完整的分析思路和代码实现。</p><hr><h2 id="如何实现一个准确的前端倒计时？"><a href="#如何实现一个准确的前端倒计时？" class="headerlink" title="如何实现一个准确的前端倒计时？"></a>如何实现一个准确的前端倒计时？</h2><h3 id="问题本质"><a href="#问题本质" class="headerlink" title="问题本质"></a>问题本质</h3><p><code>setInterval(fn, 1000)</code> 并不保证每 1000ms 准时执行。JS 是单线程的，遇到大量计算、页面渲染、长任务时，定时器会被延迟。页面切到后台，浏览器还会主动降频定时器。</p><p><strong>结果</strong>：本该 1 秒减一次，实际可能 1.2 秒甚至 2 秒才执行一次，倒计时越来越慢。</p><h3 id="正确设计思路"><a href="#正确设计思路" class="headerlink" title="正确设计思路"></a>正确设计思路</h3><ul><li><strong>以时间戳差值为核心</strong>，而不是依赖 <code>setInterval</code> 的执行次数</li><li><code>setInterval</code> 只作为 UI 刷新工具，每次执行时重新计算剩余时间</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span><span class="token parameter">durationMs<span class="token punctuation">,</span> onTick<span class="token punctuation">,</span> onEnd</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> endTime <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> durationMs<span class="token punctuation">;</span>  <span class="token keyword">const</span> timer <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> remain <span class="token operator">=</span> endTime <span class="token operator">-</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>remain <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">clearInterval</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token function">onTick</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      onEnd<span class="token operator">?.</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token function">onTick</span><span class="token punctuation">(</span>remain<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 刷新频率可以更高，保证精度</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearInterval</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 返回清理函数</span><span class="token punctuation">&#125;</span><span class="token comment">// 使用示例</span><span class="token keyword">const</span> stop <span class="token operator">=</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span>  <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">,</span>  <span class="token punctuation">(</span><span class="token parameter">remain</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> seconds <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">ceil</span><span class="token punctuation">(</span>remain <span class="token operator">/</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"timer"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>seconds<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">秒</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"倒计时结束"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="进阶：页面切后台后恢复"><a href="#进阶：页面切后台后恢复" class="headerlink" title="进阶：页面切后台后恢复"></a>进阶：页面切后台后恢复</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">document<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"visibilitychange"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>document<span class="token punctuation">.</span>visibilityState <span class="token operator">===</span> <span class="token string">"visible"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 页面重新可见，立即刷新一次，避免显示旧数据</span>    <span class="token keyword">const</span> remain <span class="token operator">=</span> endTime <span class="token operator">-</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">onTick</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> remain<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="如何设计精准的秒杀倒计时？"><a href="#如何设计精准的秒杀倒计时？" class="headerlink" title="如何设计精准的秒杀倒计时？"></a>如何设计精准的秒杀倒计时？</h2><h3 id="问题本质-1"><a href="#问题本质-1" class="headerlink" title="问题本质"></a>问题本质</h3><p>普通倒计时的问题是依赖本地时间，而用户电脑时间可能不准（快几分钟或慢几分钟），导致倒计时提前或延迟结束。</p><h3 id="正确设计思路-1"><a href="#正确设计思路-1" class="headerlink" title="正确设计思路"></a>正确设计思路</h3><ol><li><strong>以服务器时间为基准</strong>，获取一次服务端当前时间</li><li>**计算本地时间与服务器时间的差值 <code>diff</code>**，后续用 <code>Date.now() + diff</code> 代替服务器时间</li><li><strong>防止前端篡改</strong>：真正是否可支付由后端控制，前端倒计时只是展示</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">initSeckillCountdown</span><span class="token punctuation">(</span><span class="token parameter">activityStartTime</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 1. 获取服务器时间</span>  <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> serverTime <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/server-time"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token operator">=></span> r<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 2. 计算时间差（本地时间 - 服务器时间）</span>  <span class="token keyword">const</span> diff <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> serverTime<span class="token punctuation">;</span>  <span class="token comment">// 3. 倒计时刷新</span>  <span class="token keyword">const</span> timer <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 用修正后的时间计算剩余时间</span>    <span class="token keyword">const</span> correctedNow <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> diff<span class="token punctuation">;</span>    <span class="token keyword">const</span> remain <span class="token operator">=</span> activityStartTime <span class="token operator">-</span> correctedNow<span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>remain <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">clearInterval</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token function">showBuyButton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 展示购买按钮</span>      <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token function">updateCountdownUI</span><span class="token punctuation">(</span>remain<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">updateCountdownUI</span><span class="token punctuation">(</span><span class="token parameter">remain</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> hours <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>remain <span class="token operator">/</span> <span class="token number">3600000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> minutes <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span><span class="token punctuation">(</span>remain <span class="token operator">%</span> <span class="token number">3600000</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">60000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> seconds <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span><span class="token punctuation">(</span>remain <span class="token operator">%</span> <span class="token number">60000</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 更新 DOM...</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="Web-管理系统越用越慢，如何排查？"><a href="#Web-管理系统越用越慢，如何排查？" class="headerlink" title="Web 管理系统越用越慢，如何排查？"></a>Web 管理系统越用越慢，如何排查？</h2><h3 id="排查思路：先定位，再优化"><a href="#排查思路：先定位，再优化" class="headerlink" title="排查思路：先定位，再优化"></a>排查思路：先定位，再优化</h3><pre class="line-numbers language-none"><code class="language-none">越用越慢 → 三个方向├── 网络层：接口响应变慢？├── 前端层：JS 执行慢？内存泄漏？└── 后端层：数据库查询慢？<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="使用-Chrome-DevTools-定位"><a href="#使用-Chrome-DevTools-定位" class="headerlink" title="使用 Chrome DevTools 定位"></a>使用 Chrome DevTools 定位</h3><table><thead><tr><th>工具</th><th>排查内容</th></tr></thead><tbody><tr><td><strong>Network</strong></td><td>接口 TTFB、响应时间是否变长</td></tr><tr><td><strong>Performance</strong></td><td>JS 执行耗时、渲染帧率</td></tr><tr><td><strong>Memory</strong></td><td>内存是否持续增长（泄漏）</td></tr><tr><td><strong>Lighthouse</strong></td><td>整体性能评分和优化建议</td></tr></tbody></table><h3 id="前端常见问题与解决方案"><a href="#前端常见问题与解决方案" class="headerlink" title="前端常见问题与解决方案"></a>前端常见问题与解决方案</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ❌ 问题 1：大列表一次性渲染</span><span class="token comment">// 一次渲染 10000 条数据，DOM 节点过多</span>list<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> el <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"div"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  el<span class="token punctuation">.</span>textContent <span class="token operator">=</span> item<span class="token punctuation">.</span>name<span class="token punctuation">;</span>  container<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// ✅ 解决：虚拟列表（只渲染可视区域）</span><span class="token comment">// 使用 vue-virtual-scroller / react-window 等库</span><span class="token comment">// ❌ 问题 2：事件监听未释放（内存泄漏）</span><span class="token keyword">function</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"resize"</span><span class="token punctuation">,</span> handleResize<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 组件销毁时忘记移除</span><span class="token punctuation">&#125;</span><span class="token comment">// ✅ 解决：组件销毁时清理</span><span class="token function">onUnmounted</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">"resize"</span><span class="token punctuation">,</span> handleResize<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// ❌ 问题 3：定时器未清理</span><span class="token keyword">function</span> <span class="token function">startPolling</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">setInterval</span><span class="token punctuation">(</span>fetchData<span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 每次进入页面都新增一个定时器</span><span class="token punctuation">&#125;</span><span class="token comment">// ✅ 解决：保存引用并清理</span><span class="token keyword">let</span> timer <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">startPolling</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>timer<span class="token punctuation">)</span> <span class="token function">clearInterval</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">;</span>  timer <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span>fetchData<span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="后端返回几万条数据，前端表格如何展示？"><a href="#后端返回几万条数据，前端表格如何展示？" class="headerlink" title="后端返回几万条数据，前端表格如何展示？"></a>后端返回几万条数据，前端表格如何展示？</h2><h3 id="核心原则：不一次性渲染所有-DOM"><a href="#核心原则：不一次性渲染所有-DOM" class="headerlink" title="核心原则：不一次性渲染所有 DOM"></a>核心原则：不一次性渲染所有 DOM</h3><p>一次性渲染 5 万条数据会创建大量 DOM 节点，导致浏览器卡死。</p><h3 id="方案一：虚拟列表（推荐）"><a href="#方案一：虚拟列表（推荐）" class="headerlink" title="方案一：虚拟列表（推荐）"></a>方案一：虚拟列表（推荐）</h3><p>只渲染可视区域内的行，滚动时动态替换内容：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">VirtualList</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">&#123;</span> container<span class="token punctuation">,</span> itemHeight<span class="token punctuation">,</span> data<span class="token punctuation">,</span> renderItem <span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>container <span class="token operator">=</span> container<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>itemHeight <span class="token operator">=</span> itemHeight<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>data <span class="token operator">=</span> data<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>renderItem <span class="token operator">=</span> renderItem<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> totalHeight <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>data<span class="token punctuation">.</span>length <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>itemHeight<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>container<span class="token punctuation">.</span>style<span class="token punctuation">.</span>position <span class="token operator">=</span> <span class="token string">"relative"</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>container<span class="token punctuation">.</span>style<span class="token punctuation">.</span>height <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>totalHeight<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">px</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>visibleContainer <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"div"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>visibleContainer<span class="token punctuation">.</span>style<span class="token punctuation">.</span>position <span class="token operator">=</span> <span class="token string">"absolute"</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>visibleContainer<span class="token punctuation">.</span>style<span class="token punctuation">.</span>width <span class="token operator">=</span> <span class="token string">"100%"</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>container<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>visibleContainer<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>container<span class="token punctuation">.</span>parentElement<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"scroll"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span>      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> scrollTop <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>container<span class="token punctuation">.</span>parentElement<span class="token punctuation">.</span>scrollTop<span class="token punctuation">;</span>    <span class="token keyword">const</span> viewHeight <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>container<span class="token punctuation">.</span>parentElement<span class="token punctuation">.</span>clientHeight<span class="token punctuation">;</span>    <span class="token keyword">const</span> startIndex <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>scrollTop <span class="token operator">/</span> <span class="token keyword">this</span><span class="token punctuation">.</span>itemHeight<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> endIndex <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>      startIndex <span class="token operator">+</span> Math<span class="token punctuation">.</span><span class="token function">ceil</span><span class="token punctuation">(</span>viewHeight <span class="token operator">/</span> <span class="token keyword">this</span><span class="token punctuation">.</span>itemHeight<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>data<span class="token punctuation">.</span>length    <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>visibleContainer<span class="token punctuation">.</span>style<span class="token punctuation">.</span>top <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>startIndex <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>itemHeight<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">px</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>visibleContainer<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> startIndex<span class="token punctuation">;</span> i <span class="token operator">&lt;</span> endIndex<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> el <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">renderItem</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>data<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>visibleContainer<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="方案二：Web-Worker-处理数据"><a href="#方案二：Web-Worker-处理数据" class="headerlink" title="方案二：Web Worker 处理数据"></a>方案二：Web Worker 处理数据</h3><p>数据量大时，数据处理（排序、过滤）放到 Worker 中，避免阻塞主线程：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// worker.js</span>self<span class="token punctuation">.</span><span class="token function-variable function">onmessage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">&#123;</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span> list<span class="token punctuation">,</span> keyword <span class="token punctuation">&#125;</span> <span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> result <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span>name<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>keyword<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  self<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token comment">// main.js</span><span class="token keyword">const</span> worker <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Worker</span><span class="token punctuation">(</span><span class="token string">"./worker.js"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>worker<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">list</span><span class="token operator">:</span> bigData<span class="token punctuation">,</span> <span class="token literal-property property">keyword</span><span class="token operator">:</span> <span class="token string">"搜索词"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>worker<span class="token punctuation">.</span><span class="token function-variable function">onmessage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">&#123;</span> data <span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token function">renderTable</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="H5-瀑布流在低端安卓机和弱网下如何优化？"><a href="#H5-瀑布流在低端安卓机和弱网下如何优化？" class="headerlink" title="H5 瀑布流在低端安卓机和弱网下如何优化？"></a>H5 瀑布流在低端安卓机和弱网下如何优化？</h2><h3 id="优化策略全景"><a href="#优化策略全景" class="headerlink" title="优化策略全景"></a>优化策略全景</h3><pre class="line-numbers language-none"><code class="language-none">优化方向├── 图片资源优化（最关键）│   ├── WebP&#x2F;AVIF 格式│   ├── 多尺寸响应式图片（srcset）│   └── 低清图占位（LQIP）├── 网络加载优化│   ├── IntersectionObserver 懒加载│   ├── 分批请求（分页加载）│   └── 请求失败自动重试├── 渲染优化│   ├── 虚拟瀑布流│   └── 使用 transform 代替 top&#x2F;left└── 容错降级    ├── 骨架屏占位    └── 弱网自动切换简化模式<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 图片懒加载（IntersectionObserver）</span><span class="token keyword">const</span> observer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">IntersectionObserver</span><span class="token punctuation">(</span>  <span class="token punctuation">(</span><span class="token parameter">entries</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    entries<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">entry</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>entry<span class="token punctuation">.</span>isIntersecting<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">const</span> img <span class="token operator">=</span> entry<span class="token punctuation">.</span>target<span class="token punctuation">;</span>        img<span class="token punctuation">.</span>src <span class="token operator">=</span> img<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>src<span class="token punctuation">;</span>        img<span class="token punctuation">.</span><span class="token function">removeAttribute</span><span class="token punctuation">(</span><span class="token string">"data-src"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        observer<span class="token punctuation">.</span><span class="token function">unobserve</span><span class="token punctuation">(</span>img<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#123;</span>    <span class="token literal-property property">rootMargin</span><span class="token operator">:</span> <span class="token string">"200px 0px"</span><span class="token punctuation">,</span> <span class="token comment">// 提前 200px 开始加载</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">"img[data-src]"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">img</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  observer<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>img<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 弱网检测降级</span><span class="token keyword">const</span> connection <span class="token operator">=</span> navigator<span class="token punctuation">.</span>connection<span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>connection<span class="token operator">?.</span>effectiveType <span class="token operator">===</span> <span class="token string">"2g"</span> <span class="token operator">||</span> connection<span class="token operator">?.</span>saveData<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 切换到简化模式：小图、无动画</span>  document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"lite-mode"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="如何设计一个支持任意内容的单选框组件？"><a href="#如何设计一个支持任意内容的单选框组件？" class="headerlink" title="如何设计一个支持任意内容的单选框组件？"></a>如何设计一个支持任意内容的单选框组件？</h2><h3 id="设计思路"><a href="#设计思路" class="headerlink" title="设计思路"></a>设计思路</h3><p>使用 <code>RadioGroup + RadioItem</code> 组合组件，通过 <code>slot</code> 支持任意内容，用 <code>provide/inject</code> 传递状态：</p><pre class="line-numbers language-vue" data-language="vue"><code class="language-vue">&lt;!-- RadioGroup.vue --&gt;&lt;template&gt;  &lt;div class&#x3D;&quot;radio-group&quot; role&#x3D;&quot;radiogroup&quot;&gt;    &lt;slot &#x2F;&gt;  &lt;&#x2F;div&gt;&lt;&#x2F;template&gt;&lt;script setup&gt;import &#123; provide, toRefs &#125; from &quot;vue&quot;;const props &#x3D; defineProps(&#123;  modelValue: &#123; type: [String, Number], required: true &#125;,&#125;);const emit &#x3D; defineEmits([&quot;update:modelValue&quot;]);provide(&quot;radioGroup&quot;, &#123;  ...toRefs(props),  onChange: (val) &#x3D;&gt; emit(&quot;update:modelValue&quot;, val),&#125;);&lt;&#x2F;script&gt;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-vue" data-language="vue"><code class="language-vue">&lt;!-- RadioItem.vue --&gt;&lt;template&gt;  &lt;div    class&#x3D;&quot;radio-item&quot;    :class&#x3D;&quot;&#123; &#39;is-checked&#39;: isChecked &#125;&quot;    role&#x3D;&quot;radio&quot;    :aria-checked&#x3D;&quot;isChecked&quot;    :tabindex&#x3D;&quot;0&quot;    @click&#x3D;&quot;select&quot;    @keydown.space.prevent&#x3D;&quot;select&quot;  &gt;    &lt;span class&#x3D;&quot;radio-indicator&quot; &#x2F;&gt;    &lt;slot &#x2F;&gt;  &lt;&#x2F;div&gt;&lt;&#x2F;template&gt;&lt;script setup&gt;import &#123; inject, computed &#125; from &quot;vue&quot;;const props &#x3D; defineProps(&#123;  value: &#123; type: [String, Number], required: true &#125;,  disabled: &#123; type: Boolean, default: false &#125;,&#125;);const &#123; modelValue, onChange &#125; &#x3D; inject(&quot;radioGroup&quot;);const isChecked &#x3D; computed(() &#x3D;&gt; modelValue.value &#x3D;&#x3D;&#x3D; props.value);const select &#x3D; () &#x3D;&gt; &#123;  if (!props.disabled) onChange(props.value);&#125;;&lt;&#x2F;script&gt;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="如何排查网页白屏问题？"><a href="#如何排查网页白屏问题？" class="headerlink" title="如何排查网页白屏问题？"></a>如何排查网页白屏问题？</h2><h3 id="排查流程"><a href="#排查流程" class="headerlink" title="排查流程"></a>排查流程</h3><pre class="line-numbers language-none"><code class="language-none">白屏├── 打开控制台，看 Console 有没有 JS 报错│   └── 有报错 → 定位报错代码，修复├── 看 Network，资源是否加载成功│   ├── HTML&#x2F;JS&#x2F;CSS 404 → 检查部署配置、CDN│   └── 接口 500&#x2F;超时 → 后端问题，前端做容错└── 看 DOM 结构，body 是否为空    ├── 空 → JS 执行报错，框架未挂载    └── 有内容但不显示 → CSS 问题（display:none、z-index 等）<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="工程化预防方案"><a href="#工程化预防方案" class="headerlink" title="工程化预防方案"></a>工程化预防方案</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 1. 全局错误监控</span>window<span class="token punctuation">.</span><span class="token function-variable function">onerror</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">message<span class="token punctuation">,</span> source<span class="token punctuation">,</span> lineno<span class="token punctuation">,</span> colno<span class="token punctuation">,</span> error</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token function">reportError</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"js_error"</span><span class="token punctuation">,</span> message<span class="token punctuation">,</span> source<span class="token punctuation">,</span> lineno<span class="token punctuation">,</span> error <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"unhandledrejection"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token function">reportError</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"promise_error"</span><span class="token punctuation">,</span> <span class="token literal-property property">reason</span><span class="token operator">:</span> event<span class="token punctuation">.</span>reason <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 2. 资源加载失败监控</span>window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span>  <span class="token string">"error"</span><span class="token punctuation">,</span>  <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>target <span class="token operator">!==</span> window<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">reportError</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"resource_error"</span><span class="token punctuation">,</span>        <span class="token literal-property property">src</span><span class="token operator">:</span> event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>src <span class="token operator">||</span> event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>href<span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 3. React ErrorBoundary</span><span class="token keyword">class</span> <span class="token class-name">ErrorBoundary</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  state <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">hasError</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token keyword">static</span> <span class="token function">getDerivedStateFromError</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">hasError</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">componentDidCatch</span><span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> info</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">reportError</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"render_error"</span><span class="token punctuation">,</span> error<span class="token punctuation">,</span> info <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>hasError<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>页面出错了，请刷新重试<span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>children<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="如何启动一个新的前端项目？"><a href="#如何启动一个新的前端项目？" class="headerlink" title="如何启动一个新的前端项目？"></a>如何启动一个新的前端项目？</h2><h3 id="完整流程"><a href="#完整流程" class="headerlink" title="完整流程"></a>完整流程</h3><p><strong>第一步：需求对齐</strong></p><ul><li>产品形态（后台系统 / C 端 / 小程序）</li><li>用户量和性能要求（是否需要 SSR/SEO）</li><li>工期（决定技术方案保守程度）</li></ul><p><strong>第二步：技术选型</strong></p><table><thead><tr><th>维度</th><th>选项</th></tr></thead><tbody><tr><td>框架</td><td>Vue 3 / React 18 / Next.js</td></tr><tr><td>语言</td><td>TypeScript（强烈推荐）</td></tr><tr><td>UI 库</td><td>Ant Design / Element Plus</td></tr><tr><td>构建工具</td><td>Vite</td></tr><tr><td>状态管理</td><td>Pinia / Zustand</td></tr></tbody></table><p><strong>第三步：工程化搭建</strong></p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 代码规范</span><span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">-D</span> eslint prettier eslint-config-prettier<span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">-D</span> husky lint-staged  <span class="token comment"># 提交前自动检查</span><span class="token comment"># 目录结构</span>src/├── api/          <span class="token comment"># 接口层，按模块拆分</span>├── components/   <span class="token comment"># 通用组件</span>├── views/        <span class="token comment"># 页面组件</span>├── hooks/        <span class="token comment"># 复用逻辑</span>├── stores/       <span class="token comment"># 状态管理</span>├── router/       <span class="token comment"># 路由 + 权限守卫</span>└── utils/        <span class="token comment"># 工具函数</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>第四步：基础设施</strong></p><pre class="line-numbers language-typescript" data-language="typescript"><code class="language-typescript"><span class="token comment">// Axios 统一封装</span><span class="token keyword">const</span> request <span class="token operator">=</span> axios<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> baseURL<span class="token operator">:</span> <span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">VITE_API_URL</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>request<span class="token punctuation">.</span>interceptors<span class="token punctuation">.</span>request<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  config<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>Authorization <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">getToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>  <span class="token keyword">return</span> config<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>request<span class="token punctuation">.</span>interceptors<span class="token punctuation">.</span>response<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>  <span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token operator">=></span> res<span class="token punctuation">.</span>data<span class="token punctuation">,</span>  <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>response<span class="token operator">?.</span>status <span class="token operator">===</span> <span class="token number">401</span><span class="token punctuation">)</span> <span class="token function">logout</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="如何实现前端线上监控？"><a href="#如何实现前端线上监控？" class="headerlink" title="如何实现前端线上监控？"></a>如何实现前端线上监控？</h2><h3 id="三步走：采集-→-上报-→-分析"><a href="#三步走：采集-→-上报-→-分析" class="headerlink" title="三步走：采集 → 上报 → 分析"></a>三步走：采集 → 上报 → 分析</h3><p><strong>采集</strong></p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Monitor</span> <span class="token punctuation">&#123;</span>  <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">watchJsError</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">watchResourceError</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">watchPerformance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">watchApiError</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">watchJsError</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    window<span class="token punctuation">.</span><span class="token function-variable function">onerror</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">msg<span class="token punctuation">,</span> url<span class="token punctuation">,</span> line<span class="token punctuation">,</span> col<span class="token punctuation">,</span> error</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"js_error"</span><span class="token punctuation">,</span> msg<span class="token punctuation">,</span> url<span class="token punctuation">,</span> line<span class="token punctuation">,</span> <span class="token literal-property property">stack</span><span class="token operator">:</span> error<span class="token operator">?.</span>stack <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"unhandledrejection"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"promise_error"</span><span class="token punctuation">,</span> <span class="token literal-property property">reason</span><span class="token operator">:</span> <span class="token function">String</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>reason<span class="token punctuation">)</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">watchPerformance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"load"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> domContentLoadedEventEnd<span class="token punctuation">,</span> navigationStart<span class="token punctuation">,</span> loadEventEnd <span class="token punctuation">&#125;</span> <span class="token operator">=</span>        performance<span class="token punctuation">.</span>timing<span class="token punctuation">;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"performance"</span><span class="token punctuation">,</span>        <span class="token literal-property property">dclTime</span><span class="token operator">:</span> domContentLoadedEventEnd <span class="token operator">-</span> navigationStart<span class="token punctuation">,</span>        <span class="token literal-property property">loadTime</span><span class="token operator">:</span> loadEventEnd <span class="token operator">-</span> navigationStart<span class="token punctuation">,</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">report</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token punctuation">&#123;</span>      <span class="token operator">...</span>data<span class="token punctuation">,</span>      <span class="token literal-property property">url</span><span class="token operator">:</span> location<span class="token punctuation">.</span>href<span class="token punctuation">,</span>      <span class="token literal-property property">userAgent</span><span class="token operator">:</span> navigator<span class="token punctuation">.</span>userAgent<span class="token punctuation">,</span>      <span class="token literal-property property">timestamp</span><span class="token operator">:</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token comment">// 使用 sendBeacon 保证页面关闭时也能上报</span>    navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"/api/monitor"</span><span class="token punctuation">,</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>排查线上报错</strong></p><ol><li>通过 source map 将压缩代码映射回源码</li><li>按错误类型聚合，找出高频错误</li><li>结合用户行为路径复现问题</li><li>本地开发环境复现并修复</li></ol><hr><h2 id="百万人同时抢一个商品，如何判断第一个？"><a href="#百万人同时抢一个商品，如何判断第一个？" class="headerlink" title="百万人同时抢一个商品，如何判断第一个？"></a>百万人同时抢一个商品，如何判断第一个？</h2><h3 id="核心在后端，前端只负责展示"><a href="#核心在后端，前端只负责展示" class="headerlink" title="核心在后端，前端只负责展示"></a>核心在后端，前端只负责展示</h3><p>前端能做的：</p><ul><li>按钮防抖/防重复点击</li><li>请求发出后立即禁用按钮</li><li>展示排队/结果状态</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 前端防重复提交</span><span class="token keyword">let</span> isSubmitting <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">handleBuy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>isSubmitting<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>  isSubmitting <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>  buyBtn<span class="token punctuation">.</span>disabled <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>  <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/seckill"</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">"POST"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> success<span class="token punctuation">,</span> message <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token keyword">await</span> result<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">showResult</span><span class="token punctuation">(</span>success <span class="token operator">?</span> <span class="token string">"抢购成功！"</span> <span class="token operator">:</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>    isSubmitting <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    buyBtn<span class="token punctuation">.</span>disabled <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>后端核心方案（Redis 原子操作）：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># Redis SETNX：只有第一个写入的用户才能成功</span>SETNX product:1:winner userId<span class="token comment"># 返回 1 → 抢购成功</span><span class="token comment"># 返回 0 → 已被抢走</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>题目</th><th>核心考点</th></tr></thead><tbody><tr><td>准确倒计时</td><td>时间戳差值，不依赖定时器次数</td></tr><tr><td>秒杀倒计时</td><td>服务器时间校准，防篡改</td></tr><tr><td>系统越来越慢</td><td>DevTools 定位，内存泄漏排查</td></tr><tr><td>几万条数据展示</td><td>虚拟列表，Web Worker</td></tr><tr><td>瀑布流低端机优化</td><td>懒加载，降级策略，虚拟列表</td></tr><tr><td>单选框组件设计</td><td>slot + provide/inject 组合组件</td></tr><tr><td>白屏排查</td><td>系统化排查流程，ErrorBoundary</td></tr><tr><td>新项目启动</td><td>需求对齐，技术选型，工程化</td></tr><tr><td>前端监控</td><td>采集、上报、source map 还原</td></tr><tr><td>百万并发抢购</td><td>前端防重，后端 Redis 原子操作</td></tr></tbody></table><p>场景题没有标准答案，面试官考察的是你的<strong>思考过程</strong>：能否快速定位问题本质，给出合理的设计方案，并考虑到边界情况和工程实践。</p><hr><blockquote><p><strong>前端面试系列持续更新中</strong>，下一篇将深入讲解 <strong>前端性能优化完全指南</strong>，敬请关注。</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;前端面试高频场景题精讲：10-个你必须掌握的设计问题&quot;&gt;&lt;a href=&quot;#前端面试高频场景题精讲：10-个你必须掌握的设计问题&quot; class=&quot;headerlink&quot; title=&quot;前端面试高频场景题精讲：10 个你必须掌握的设计问题&quot;&gt;&lt;/a&gt;前端面试高频场景</summary>
      
    
    
    
    <category term="前端面试系列" scheme="https://liaojunhao.github.io/categories/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%B3%BB%E5%88%97/"/>
    
    
    <category term="性能优化" scheme="https://liaojunhao.github.io/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    <category term="面试" scheme="https://liaojunhao.github.io/tags/%E9%9D%A2%E8%AF%95/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 异步编程完全指南：从回调到 async/await</title>
    <link href="https://liaojunhao.github.io/2026/03/23/js-async-complete-guide/"/>
    <id>https://liaojunhao.github.io/2026/03/23/js-async-complete-guide/</id>
    <published>2026-03-23T06:52:00.000Z</published>
    <updated>2026-09-07T06:34:30.341Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>异步编程是 JavaScript 的核心特性，也是最容易让初学者困惑的概念。从最初的回调函数，到 Promise，再到现代的 async/await，JavaScript 的异步编程模式经历了多次演进。本文将从<strong>事件循环的底层机制</strong>出发，系统地讲解 JavaScript 异步编程的全貌。</p><hr><h2 id="1-同步-vs-异步：本质区别"><a href="#1-同步-vs-异步：本质区别" class="headerlink" title="1. 同步 vs 异步：本质区别"></a>1. 同步 vs 异步：本质区别</h2><h3 id="1-1-同步执行"><a href="#1-1-同步执行" class="headerlink" title="1.1 同步执行"></a>1.1 同步执行</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3 — 立即得到结果</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>同步代码<strong>阻塞式执行</strong>：每一行代码必须等待前一行完成才能执行。</p><h3 id="1-2-异步执行"><a href="#1-2-异步执行" class="headerlink" title="1.2 异步执行"></a>1.2 异步执行</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token parameter">callback</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token function">callback</span><span class="token punctuation">(</span><span class="token string">"data"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"start"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1秒后输出 'data'</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"end"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 输出顺序：start → end → data</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>异步代码<strong>非阻塞式执行</strong>：某些操作可以在后台进行，不会阻塞主线程。</p><hr><h2 id="2-事件循环（Event-Loop）：异步的心脏"><a href="#2-事件循环（Event-Loop）：异步的心脏" class="headerlink" title="2. 事件循环（Event Loop）：异步的心脏"></a>2. 事件循环（Event Loop）：异步的心脏</h2><h3 id="2-1-JavaScript-运行时模型"><a href="#2-1-JavaScript-运行时模型" class="headerlink" title="2.1 JavaScript 运行时模型"></a>2.1 JavaScript 运行时模型</h3><p>JavaScript 是<strong>单线程</strong>的，但通过事件循环机制实现了异步能力：</p><pre class="line-numbers language-none"><code class="language-none">┌─────────────────────────────────────┐│      JavaScript 引擎                 ││  ┌──────────────────────────────┐   ││  │   Call Stack（调用栈）        │   ││  │  执行同步代码                  │   ││  └──────────────────────────────┘   ││  ┌──────────────────────────────┐   ││  │   Heap（堆）                  │   ││  │  存储对象和变量                │   ││  └──────────────────────────────┘   │└─────────────────────────────────────┘         ↓ 异步操作 ↓┌─────────────────────────────────────┐│      Web APIs &#x2F; Node.js APIs         ││  setTimeout, fetch, fs.readFile...   │└─────────────────────────────────────┘         ↓ 完成 ↓┌─────────────────────────────────────┐│   Callback Queue（回调队列）         ││  Microtask Queue（微任务队列）       ││  Macrotask Queue（宏任务队列）       │└─────────────────────────────────────┘         ↓ Event Loop ↓    检查 Call Stack 是否为空    是 → 从队列取任务执行<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-2-事件循环的执行顺序"><a href="#2-2-事件循环的执行顺序" class="headerlink" title="2.2 事件循环的执行顺序"></a>2.2 事件循环的执行顺序</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"1. 同步代码"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"2. setTimeout（宏任务）"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"3. Promise（微任务）"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"4. 同步代码"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 输出顺序：</span><span class="token comment">// 1. 同步代码</span><span class="token comment">// 4. 同步代码</span><span class="token comment">// 3. Promise（微任务）</span><span class="token comment">// 2. setTimeout（宏任务）</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>执行流程</strong>：</p><ol><li>执行所有同步代码（1、4）</li><li>同步代码执行完，检查微任务队列（3）</li><li>微任务队列清空，执行宏任务（2）</li></ol><h3 id="2-3-微任务-vs-宏任务"><a href="#2-3-微任务-vs-宏任务" class="headerlink" title="2.3 微任务 vs 宏任务"></a>2.3 微任务 vs 宏任务</h3><table><thead><tr><th>类型</th><th>示例</th><th>优先级</th></tr></thead><tbody><tr><td><strong>微任务</strong></td><td>Promise.then/catch/finally、MutationObserver、queueMicrotask</td><td>高</td></tr><tr><td><strong>宏任务</strong></td><td>setTimeout、setInterval、setImmediate、I/O、UI 渲染</td><td>低</td></tr></tbody></table><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 微任务优先级更高</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"start"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"setTimeout"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Promise"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"end"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 输出：start → end → Promise → setTimeout</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="3-回调函数（Callback）：异步的起点"><a href="#3-回调函数（Callback）：异步的起点" class="headerlink" title="3. 回调函数（Callback）：异步的起点"></a>3. 回调函数（Callback）：异步的起点</h2><h3 id="3-1-基础回调"><a href="#3-1-基础回调" class="headerlink" title="3.1 基础回调"></a>3.1 基础回调</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">readFile</span><span class="token punctuation">(</span><span class="token parameter">path<span class="token punctuation">,</span> callback</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 模拟异步读文件</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token function">callback</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token string">"file content"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token function">readFile</span><span class="token punctuation">(</span><span class="token string">"/path/to/file"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-2-回调地狱（Callback-Hell）"><a href="#3-2-回调地狱（Callback-Hell）" class="headerlink" title="3.2 回调地狱（Callback Hell）"></a>3.2 回调地狱（Callback Hell）</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ❌ 嵌套过深，难以维护</span><span class="token function">readFile</span><span class="token punctuation">(</span><span class="token string">"/file1"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> data1</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>err<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">readFile</span><span class="token punctuation">(</span><span class="token string">"/file2"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> data2</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>err<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">readFile</span><span class="token punctuation">(</span><span class="token string">"/file3"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> data3</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>          <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>err<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data1<span class="token punctuation">,</span> data2<span class="token punctuation">,</span> data3<span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-3-回调的问题"><a href="#3-3-回调的问题" class="headerlink" title="3.3 回调的问题"></a>3.3 回调的问题</h3><ol><li><strong>可读性差</strong>：嵌套深，难以理解流程</li><li><strong>错误处理困难</strong>：每层都要单独处理错误</li><li><strong>无法复用</strong>：回调逻辑难以提取和组合</li><li><strong>控制反转</strong>：将控制权交给回调函数</li></ol><hr><h2 id="4-Promise：异步的转折点"><a href="#4-Promise：异步的转折点" class="headerlink" title="4. Promise：异步的转折点"></a>4. Promise：异步的转折点</h2><h3 id="4-1-Promise-的三种状态"><a href="#4-1-Promise-的三种状态" class="headerlink" title="4.1 Promise 的三种状态"></a>4.1 Promise 的三种状态</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> promise <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token comment">// pending（待定）</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">"success"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// → fulfilled（已兑现）</span>    <span class="token comment">// 或 reject('error'); → rejected（已拒绝）</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>promise  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>状态转移</strong>：</p><ul><li><code>pending</code> → <code>fulfilled</code>（通过 resolve）</li><li><code>pending</code> → <code>rejected</code>（通过 reject）</li><li>状态一旦改变，<strong>不可逆转</strong></li></ul><h3 id="4-2-Promise-链式调用"><a href="#4-2-Promise-链式调用" class="headerlink" title="4.2 Promise 链式调用"></a>4.2 Promise 链式调用</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ✅ 链式调用，易于理解</span><span class="token function">readFileAsPromise</span><span class="token punctuation">(</span><span class="token string">"/file1"</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data1</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data1<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token function">readFileAsPromise</span><span class="token punctuation">(</span><span class="token string">"/file2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data2</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data2<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token function">readFileAsPromise</span><span class="token punctuation">(</span><span class="token string">"/file3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data3</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data3<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Error:"</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-3-Promise-的常用方法"><a href="#4-3-Promise-的常用方法" class="headerlink" title="4.3 Promise 的常用方法"></a>4.3 Promise 的常用方法</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Promise.all — 所有 Promise 都成功才成功</span>Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span>p1<span class="token punctuation">,</span> p2<span class="token punctuation">,</span> p3<span class="token punctuation">]</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>v1<span class="token punctuation">,</span> v2<span class="token punctuation">,</span> v3<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>v1<span class="token punctuation">,</span> v2<span class="token punctuation">,</span> v3<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Promise.race — 最快的 Promise 决定结果</span>Promise<span class="token punctuation">.</span><span class="token function">race</span><span class="token punctuation">(</span><span class="token punctuation">[</span>p1<span class="token punctuation">,</span> p2<span class="token punctuation">,</span> p3<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"First:"</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Promise.allSettled — 等待所有 Promise 完成（无论成功失败）</span>Promise<span class="token punctuation">.</span><span class="token function">allSettled</span><span class="token punctuation">(</span><span class="token punctuation">[</span>p1<span class="token punctuation">,</span> p2<span class="token punctuation">,</span> p3<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">results</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  results<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>r<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">"fulfilled"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Success:"</span><span class="token punctuation">,</span> r<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Error:"</span><span class="token punctuation">,</span> r<span class="token punctuation">.</span>reason<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Promise.any — 至少一个成功就成功</span>Promise<span class="token punctuation">.</span><span class="token function">any</span><span class="token punctuation">(</span><span class="token punctuation">[</span>p1<span class="token punctuation">,</span> p2<span class="token punctuation">,</span> p3<span class="token punctuation">]</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"At least one:"</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"All failed:"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-4-Promise-的常见陷阱"><a href="#4-4-Promise-的常见陷阱" class="headerlink" title="4.4 Promise 的常见陷阱"></a>4.4 Promise 的常见陷阱</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ❌ 陷阱 1：忘记 return</span>promise  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 没有 return，下一个 then 收到 undefined</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// undefined</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// ✅ 正确做法</span>promise  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> data<span class="token punctuation">;</span> <span class="token comment">// 显式 return</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 正确的值</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// ❌ 陷阱 2：错误处理不当</span>promise  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"oops"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 这里不会执行</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 捕获错误</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// ❌ 陷阱 3：Promise 中的异常被吞掉</span><span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"error"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 自动转为 reject，但如果没有 catch，错误会被忽略</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="5-async-await：异步的终极形态"><a href="#5-async-await：异步的终极形态" class="headerlink" title="5. async/await：异步的终极形态"></a>5. async/await：异步的终极形态</h2><h3 id="5-1-基础语法"><a href="#5-1-基础语法" class="headerlink" title="5.1 基础语法"></a>5.1 基础语法</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// async 函数总是返回 Promise</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token string">"data"</span><span class="token punctuation">;</span> <span class="token comment">// 自动包装为 Promise.resolve('data')</span><span class="token punctuation">&#125;</span><span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 'data'</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-2-await-的作用"><a href="#5-2-await-的作用" class="headerlink" title="5.2 await 的作用"></a>5.2 await 的作用</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// await 暂停执行，等待 Promise 完成</span>  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetchDataAsPromise</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获得 Promise 的值</span><span class="token punctuation">&#125;</span><span class="token comment">// 等价于</span><span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">fetchDataAsPromise</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-3-顺序执行-vs-并行执行"><a href="#5-3-顺序执行-vs-并行执行" class="headerlink" title="5.3 顺序执行 vs 并行执行"></a>5.3 顺序执行 vs 并行执行</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ❌ 顺序执行（低效）：总耗时 = 1000 + 1000 + 1000 = 3000ms</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sequential</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> data1 <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1000ms</span>  <span class="token keyword">const</span> data2 <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1000ms</span>  <span class="token keyword">const</span> data3 <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1000ms</span>  <span class="token keyword">return</span> <span class="token punctuation">[</span>data1<span class="token punctuation">,</span> data2<span class="token punctuation">,</span> data3<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// ✅ 并行执行（高效）：总耗时 = max(1000, 1000, 1000) = 1000ms</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">parallel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> <span class="token punctuation">[</span>data1<span class="token punctuation">,</span> data2<span class="token punctuation">,</span> data3<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span>    <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/1"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/2"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/3"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">[</span>data1<span class="token punctuation">,</span> data2<span class="token punctuation">,</span> data3<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-4-错误处理"><a href="#5-4-错误处理" class="headerlink" title="5.4 错误处理"></a>5.4 错误处理</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 方式 1：try/catch</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Error:"</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// 方式 2：.catch()</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Error:"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token comment">// 提供默认值</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 方式 3：链式 catch</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-5-async-await-的陷阱"><a href="#5-5-async-await-的陷阱" class="headerlink" title="5.5 async/await 的陷阱"></a>5.5 async/await 的陷阱</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ❌ 陷阱 1：在循环中顺序 await（低效）</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">processItems</span><span class="token punctuation">(</span><span class="token parameter">items</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> item <span class="token keyword">of</span> items<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">await</span> <span class="token function">processItem</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 顺序执行</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// ✅ 正确做法：并行处理</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">processItems</span><span class="token punctuation">(</span><span class="token parameter">items</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>items<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">processItem</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// ❌ 陷阱 2：忘记 await</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> promise <span class="token operator">=</span> <span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 没有 await，返回 Promise</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>promise<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Promise &#123; &lt;pending> &#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// ✅ 正确做法</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 实际数据</span><span class="token punctuation">&#125;</span><span class="token comment">// ❌ 陷阱 3：async 函数中的错误不会自动捕获</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"oops"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 错误被包装在返回的 Promise 中</span><span class="token punctuation">&#125;</span><span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 如果没有 .catch()，错误会被忽略</span><span class="token comment">// ✅ 正确做法</span><span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="6-实战场景"><a href="#6-实战场景" class="headerlink" title="6. 实战场景"></a>6. 实战场景</h2><h3 id="6-1-并发控制（限制并发数）"><a href="#6-1-并发控制（限制并发数）" class="headerlink" title="6.1 并发控制（限制并发数）"></a>6.1 并发控制（限制并发数）</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">concurrentMap</span><span class="token punctuation">(</span><span class="token parameter">items<span class="token punctuation">,</span> fn<span class="token punctuation">,</span> limit <span class="token operator">=</span> <span class="token number">3</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> results <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> executing <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>index<span class="token punctuation">,</span> item<span class="token punctuation">]</span> <span class="token keyword">of</span> items<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> promise <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span>      <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fn</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span>      <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">result</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>        results<span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> result<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    executing<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>promise<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>executing<span class="token punctuation">.</span>length <span class="token operator">>=</span> limit<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">race</span><span class="token punctuation">(</span>executing<span class="token punctuation">)</span><span class="token punctuation">;</span>      executing<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>        executing<span class="token punctuation">.</span><span class="token function">findIndex</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">p</span><span class="token punctuation">)</span> <span class="token operator">=></span> p <span class="token operator">===</span> promise<span class="token punctuation">)</span><span class="token punctuation">,</span>        <span class="token number">1</span>      <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>executing<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> results<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 使用示例</span><span class="token keyword">const</span> urls <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"/api/1"</span><span class="token punctuation">,</span> <span class="token string">"/api/2"</span><span class="token punctuation">,</span> <span class="token string">"/api/3"</span><span class="token punctuation">,</span> <span class="token string">"/api/4"</span><span class="token punctuation">,</span> <span class="token string">"/api/5"</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> results <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">concurrentMap</span><span class="token punctuation">(</span>urls<span class="token punctuation">,</span> fetch<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 最多同时 3 个请求</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="6-2-重试机制"><a href="#6-2-重试机制" class="headerlink" title="6.2 重试机制"></a>6.2 重试机制</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">retryAsync</span><span class="token punctuation">(</span><span class="token parameter">fn<span class="token punctuation">,</span> maxRetries <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">,</span> delay <span class="token operator">=</span> <span class="token number">1000</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> maxRetries<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">===</span> maxRetries <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> error<span class="token punctuation">;</span>      <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> delay <span class="token operator">*</span> <span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// 使用示例</span><span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">retryAsync</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/unstable"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="6-3-超时控制"><a href="#6-3-超时控制" class="headerlink" title="6.3 超时控制"></a>6.3 超时控制</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">withTimeout</span><span class="token punctuation">(</span><span class="token parameter">promise<span class="token punctuation">,</span> ms</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">race</span><span class="token punctuation">(</span><span class="token punctuation">[</span>    promise<span class="token punctuation">,</span>    <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">_<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token operator">=></span>      <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">reject</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Timeout"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ms<span class="token punctuation">)</span>    <span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 使用示例</span><span class="token keyword">try</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">withTimeout</span><span class="token punctuation">(</span><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/slow"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Request timeout or failed:"</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="6-4-顺序执行多个异步操作"><a href="#6-4-顺序执行多个异步操作" class="headerlink" title="6.4 顺序执行多个异步操作"></a>6.4 顺序执行多个异步操作</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 方式 1：reduce</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sequence</span><span class="token punctuation">(</span><span class="token parameter">tasks</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> tasks<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span>    <span class="token punctuation">(</span><span class="token parameter">promise<span class="token punctuation">,</span> task</span><span class="token punctuation">)</span> <span class="token operator">=></span>      promise<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">results</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">task</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">result</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token operator">...</span>results<span class="token punctuation">,</span> result<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 方式 2：for...of（更清晰）</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sequence</span><span class="token punctuation">(</span><span class="token parameter">tasks</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> results <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> task <span class="token keyword">of</span> tasks<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    results<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">task</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> results<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 使用示例</span><span class="token keyword">const</span> results <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">sequence</span><span class="token punctuation">(</span><span class="token punctuation">[</span>  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fetchUser</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fetchUser</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fetchUser</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="7-异步编程的最佳实践"><a href="#7-异步编程的最佳实践" class="headerlink" title="7. 异步编程的最佳实践"></a>7. 异步编程的最佳实践</h2><table><thead><tr><th>原则</th><th>说明</th></tr></thead><tbody><tr><td><strong>优先使用 async/await</strong></td><td>比 Promise 链更易读，更接近同步代码</td></tr><tr><td><strong>并行优于顺序</strong></td><td>无依赖关系的异步操作应该并行执行</td></tr><tr><td><strong>正确处理错误</strong></td><td>使用 try/catch 或 .catch()，不要忽略错误</td></tr><tr><td><strong>避免回调地狱</strong></td><td>使用 Promise 或 async/await 替代深层嵌套</td></tr><tr><td><strong>理解事件循环</strong></td><td>了解微任务和宏任务的执行顺序</td></tr><tr><td><strong>控制并发数</strong></td><td>大量异步操作时，限制并发数避免资源耗尽</td></tr><tr><td><strong>设置超时</strong></td><td>网络请求应该有超时机制</td></tr><tr><td><strong>实现重试机制</strong></td><td>不稳定的操作应该支持重试</td></tr></tbody></table><hr><h2 id="8-总结"><a href="#8-总结" class="headerlink" title="8. 总结"></a>8. 总结</h2><pre class="line-numbers language-none"><code class="language-none">异步编程演进路线：回调函数 → Promise → async&#x2F;await  ↓         ↓         ↓难维护    易链式    最优雅嵌套深    错误处理  同步风格        更清晰    最易读<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>JavaScript 的异步编程从回调函数开始，经过 Promise 的改进，最终在 async/await 中达到了最优形态。理解事件循环、微任务和宏任务的执行机制，是掌握异步编程的关键。</p><p>在实际开发中，优先使用 async/await，合理利用 Promise.all 进行并行处理，正确处理错误，才能写出高效、可维护的异步代码。</p><hr><blockquote><p><strong>JS 系列持续更新中</strong>，下一篇将深入解析 <strong>JavaScript 原型链与继承机制</strong>，敬请关注。</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;异步编程是 JavaScript 的核心特性，也是最容易让初学者困惑的概念。从最初的回调函数，到 Promise，再到现代的 async/a</summary>
      
    
    
    
    <category term="JavaScript系列" scheme="https://liaojunhao.github.io/categories/JavaScript%E7%B3%BB%E5%88%97/"/>
    
    
    <category term="异步" scheme="https://liaojunhao.github.io/tags/%E5%BC%82%E6%AD%A5/"/>
    
    <category term="Promise" scheme="https://liaojunhao.github.io/tags/Promise/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 闭包深度解析：从词法作用域到内存管理</title>
    <link href="https://liaojunhao.github.io/2026/03/22/js-closure-deep-dive/"/>
    <id>https://liaojunhao.github.io/2026/03/22/js-closure-deep-dive/</id>
    <published>2026-03-22T05:18:00.000Z</published>
    <updated>2026-09-07T06:34:29.272Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>闭包（Closure）是 JavaScript 中最核心、最常被面试考查、却也最容易被误解的概念之一。本文将从<strong>词法作用域的底层机制</strong>出发，逐步剖析闭包的形成原理、典型应用场景、常见陷阱，以及生产环境中的内存管理策略。</p><hr><h2 id="1-闭包的定义与本质"><a href="#1-闭包的定义与本质" class="headerlink" title="1. 闭包的定义与本质"></a>1. 闭包的定义与本质</h2><p>MDN 对闭包的定义如下：</p><blockquote><p>A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).</p></blockquote><p>翻译过来：<strong>闭包 = 函数 + 该函数被创建时的词法环境的引用</strong>。</p><p>当 JavaScript 引擎执行函数时，会为它创建一个<strong>执行上下文（Execution Context）</strong>，其中包含：</p><ul><li><strong>词法环境（Lexical Environment）</strong>：记录当前作用域内的变量绑定</li><li><strong>变量环境（Variable Environment）</strong>：处理 <code>var</code> 声明</li><li><strong>this 绑定</strong></li></ul><p>闭包的本质在于：<strong>内部函数创建时，会捕获（capture）其外层函数的词法环境，并在外层函数执行完毕后依然持有该引用</strong>。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createScope</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> secret <span class="token operator">=</span> <span class="token number">42</span><span class="token punctuation">;</span> <span class="token comment">// 被内部函数捕获</span>  <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token function">reveal</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> secret<span class="token punctuation">;</span> <span class="token comment">// 即使 createScope 已返回，secret 仍可访问</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> fn <span class="token operator">=</span> <span class="token function">createScope</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 42</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>正常情况下，<code>createScope</code> 执行完毕后，其执行上下文应该被销毁，<code>secret</code> 也应被垃圾回收。但由于返回的 <code>reveal</code> 函数持有对 <code>secret</code> 的引用，引擎不会释放这块内存——这就是闭包。</p><hr><h2 id="2-词法作用域-vs-动态作用域"><a href="#2-词法作用域-vs-动态作用域" class="headerlink" title="2. 词法作用域 vs 动态作用域"></a>2. 词法作用域 vs 动态作用域</h2><p>理解闭包的前提是理解 JavaScript 采用的是<strong>词法作用域（Lexical Scoping）</strong>。</p><h3 id="2-1-词法作用域"><a href="#2-1-词法作用域" class="headerlink" title="2.1 词法作用域"></a>2.1 词法作用域</h3><p>函数的作用域在<strong>代码编写时</strong>就已经确定，而非运行时决定：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> x <span class="token operator">=</span> <span class="token string">"global"</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">outer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> x <span class="token operator">=</span> <span class="token string">"outer"</span><span class="token punctuation">;</span>  <span class="token keyword">function</span> <span class="token function">inner</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 'outer' — 在编写时就已经确定了作用域链</span>  <span class="token punctuation">&#125;</span>  <span class="token function">inner</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token function">outer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-2-与动态作用域的对比"><a href="#2-2-与动态作用域的对比" class="headerlink" title="2.2 与动态作用域的对比"></a>2.2 与动态作用域的对比</h3><p>如果 JavaScript 采用动态作用域（如 Bash），<code>inner</code> 中的 <code>x</code> 会从<strong>调用栈</strong>中查找，结果可能完全不同。词法作用域保证了代码行为的<strong>可预测性</strong>，这也是闭包能够可靠工作的基础。</p><hr><h2 id="3-闭包的形成机制"><a href="#3-闭包的形成机制" class="headerlink" title="3. 闭包的形成机制"></a>3. 闭包的形成机制</h2><h3 id="3-1-作用域链（Scope-Chain）"><a href="#3-1-作用域链（Scope-Chain）" class="headerlink" title="3.1 作用域链（Scope Chain）"></a>3.1 作用域链（Scope Chain）</h3><p>当访问一个变量时，引擎会沿作用域链逐级向上查找：</p><pre class="line-numbers language-none"><code class="language-none">inner 函数作用域 → outer 函数作用域 → 全局作用域<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>闭包使得这条链即使在外层函数执行完毕后也不会断裂。</p><h3 id="3-2-Environment-内部槽"><a href="#3-2-Environment-内部槽" class="headerlink" title="3.2 [[Environment]] 内部槽"></a>3.2 [[Environment]] 内部槽</h3><p>根据 ECMAScript 规范，每个函数对象都有一个内部槽 <code>[[Environment]]</code>，保存创建时的词法环境引用：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">outer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>  <span class="token keyword">let</span> b <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token function">inner</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// inner.[[Environment]] → outer 的词法环境 &#123; a: 1, b: 2 &#125;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a <span class="token operator">+</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-3-不同创建方式的闭包"><a href="#3-3-不同创建方式的闭包" class="headerlink" title="3.3 不同创建方式的闭包"></a>3.3 不同创建方式的闭包</h3><p>闭包不仅存在于 <code>return</code> 场景，任何将内部函数「逃逸」到外部作用域的方式都会形成闭包：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 回调函数</span><span class="token keyword">function</span> <span class="token function">delay</span><span class="token punctuation">(</span><span class="token parameter">msg<span class="token punctuation">,</span> ms</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 箭头函数捕获 msg</span><span class="token punctuation">&#125;</span><span class="token comment">// 事件监听</span><span class="token keyword">function</span> <span class="token function">setupButton</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Clicked </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token operator">++</span>count<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> times</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// Promise</span><span class="token keyword">function</span> <span class="token function">fetchUser</span><span class="token punctuation">(</span><span class="token parameter">id</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/api/users/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>id<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">res</span><span class="token punctuation">)</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 回调捕获 res</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 回调捕获 user</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="4-经典应用场景"><a href="#4-经典应用场景" class="headerlink" title="4. 经典应用场景"></a>4. 经典应用场景</h2><h3 id="4-1-模块模式（Module-Pattern）"><a href="#4-1-模块模式（Module-Pattern）" class="headerlink" title="4.1 模块模式（Module Pattern）"></a>4.1 模块模式（Module Pattern）</h3><p>在 ES Modules 出现之前，闭包是实现模块化的核心手段：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> UserModule <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 私有状态</span>  <span class="token keyword">const</span> cache <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">function</span> <span class="token function">validate</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> data <span class="token operator">&amp;&amp;</span> data<span class="token punctuation">.</span>name <span class="token operator">&amp;&amp;</span> data<span class="token punctuation">.</span>email<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 公开接口</span>  <span class="token keyword">return</span> <span class="token punctuation">&#123;</span>    <span class="token function">get</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>cache<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> cache<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment">// 模拟异步获取</span>      <span class="token keyword">return</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/api/users/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>id<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>        <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token operator">=></span> r<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>          cache<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> user<span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token keyword">return</span> user<span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      cache<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-2-柯里化（Currying）"><a href="#4-2-柯里化（Currying）" class="headerlink" title="4.2 柯里化（Currying）"></a>4.2 柯里化（Currying）</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">curry</span><span class="token punctuation">(</span><span class="token parameter">fn</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token function">curried</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>args<span class="token punctuation">.</span>length <span class="token operator">>=</span> fn<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>moreArgs</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token function">curried</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> args<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>moreArgs<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> add <span class="token operator">=</span> <span class="token function">curry</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b<span class="token punctuation">,</span> c</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">+</span> b <span class="token operator">+</span> c<span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 6</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 6</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 6</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-3-偏函数应用（Partial-Application）"><a href="#4-3-偏函数应用（Partial-Application）" class="headerlink" title="4.3 偏函数应用（Partial Application）"></a>4.3 偏函数应用（Partial Application）</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">bind</span><span class="token punctuation">(</span><span class="token parameter">fn<span class="token punctuation">,</span> context<span class="token punctuation">,</span> <span class="token operator">...</span>presetArgs</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>laterArgs</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token operator">...</span>presetArgs<span class="token punctuation">,</span> <span class="token operator">...</span>laterArgs<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 使用示例：创建日志函数</span><span class="token keyword">const</span> logError <span class="token operator">=</span> <span class="token function">bind</span><span class="token punctuation">(</span>console<span class="token punctuation">.</span>log<span class="token punctuation">,</span> console<span class="token punctuation">,</span> <span class="token string">"[ERROR]"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> logInfo <span class="token operator">=</span> <span class="token function">bind</span><span class="token punctuation">(</span>console<span class="token punctuation">.</span>log<span class="token punctuation">,</span> console<span class="token punctuation">,</span> <span class="token string">"[INFO]"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">logError</span><span class="token punctuation">(</span><span class="token string">"Something went wrong"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// [ERROR] Something went wrong</span><span class="token function">logInfo</span><span class="token punctuation">(</span><span class="token string">"Server started"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// [INFO] Server started</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-4-防抖与节流"><a href="#4-4-防抖与节流" class="headerlink" title="4.4 防抖与节流"></a>4.4 防抖与节流</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 防抖（Debounce）</span><span class="token keyword">function</span> <span class="token function">debounce</span><span class="token punctuation">(</span><span class="token parameter">fn<span class="token punctuation">,</span> delay</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> timer <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">;</span>    timer <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">,</span> delay<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 节流（Throttle）</span><span class="token keyword">function</span> <span class="token function">throttle</span><span class="token punctuation">(</span><span class="token parameter">fn<span class="token punctuation">,</span> interval</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> lastTime <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> now <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>now <span class="token operator">-</span> lastTime <span class="token operator">>=</span> interval<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      lastTime <span class="token operator">=</span> now<span class="token punctuation">;</span>      <span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-5-迭代器模式"><a href="#4-5-迭代器模式" class="headerlink" title="4.5 迭代器模式"></a>4.5 迭代器模式</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createIterator</span><span class="token punctuation">(</span><span class="token parameter">array</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> index <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">&#123;</span>    <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>index <span class="token operator">&lt;</span> array<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">value</span><span class="token operator">:</span> array<span class="token punctuation">[</span>index<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">return</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span> <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token punctuation">[</span>Symbol<span class="token punctuation">.</span>iterator<span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> iter <span class="token operator">=</span> <span class="token function">createIterator</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">20</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>iter<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// &#123; value: 10, done: false &#125;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>iter<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// &#123; value: 20, done: false &#125;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>iter<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// &#123; value: 30, done: false &#125;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>iter<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// &#123; value: undefined, done: true &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="5-经典陷阱与解决方案"><a href="#5-经典陷阱与解决方案" class="headerlink" title="5. 经典陷阱与解决方案"></a>5. 经典陷阱与解决方案</h2><h3 id="5-1-循环中的闭包（var-陷阱）"><a href="#5-1-循环中的闭包（var-陷阱）" class="headerlink" title="5.1 循环中的闭包（var 陷阱）"></a>5.1 循环中的闭包（var 陷阱）</h3><p>这是最经典的闭包面试题：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ❌ 错误：三次输出 3</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">3</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>根因分析</strong>：<code>var</code> 声明的 <code>i</code> 具有函数作用域，三个回调共享同一个 <code>i</code>。当回调执行时，循环已经结束，<code>i</code> 的值为 3。</p><p><strong>解法一：<code>let</code>（推荐）</strong></p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ✅ 正确：输出 0, 1, 2</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">3</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><code>let</code> 具有块级作用域，每次循环迭代都会创建一个独立的 <code>i</code> 绑定。</p><p><strong>解法二：IIFE 手动创建作用域</strong></p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// ✅ 正确：通过 IIFE 捕获每次迭代的 i</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">3</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">j</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-2-意外的共享引用"><a href="#5-2-意外的共享引用" class="headerlink" title="5.2 意外的共享引用"></a>5.2 意外的共享引用</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createFunctions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">3</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    result<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// let 下每次迭代是独立绑定，无问题</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> result<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> fns <span class="token operator">=</span> <span class="token function">createFunctions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>fns<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 0</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>fns<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>fns<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 2</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但如果捕获的是<strong>引用类型</strong>，仍需注意：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createHandlers</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> handlers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> state <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">count</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">3</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    handlers<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      state<span class="token punctuation">.</span>count<span class="token operator">++</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> state<span class="token punctuation">.</span>count<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> handlers<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> <span class="token punctuation">[</span>h1<span class="token punctuation">,</span> h2<span class="token punctuation">,</span> h3<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">createHandlers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">h1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">h2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 2</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">h3</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3 — 共享同一个 state 对象</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="6-内存管理与性能考量"><a href="#6-内存管理与性能考量" class="headerlink" title="6. 内存管理与性能考量"></a>6. 内存管理与性能考量</h2><h3 id="6-1-闭包与垃圾回收"><a href="#6-1-闭包与垃圾回收" class="headerlink" title="6.1 闭包与垃圾回收"></a>6.1 闭包与垃圾回收</h3><p>V8 引擎采用<strong>可达性（Reachability）</strong>算法进行垃圾回收。闭包使得外层变量保持可达，因此不会被回收。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">heavy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> hugeArray <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Array</span><span class="token punctuation">(</span><span class="token number">1000000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">"data"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> hugeArray<span class="token punctuation">.</span>length<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> fn <span class="token operator">=</span> <span class="token function">heavy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// hugeArray 常驻内存</span>fn <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token comment">// 解除引用，hugeArray 可被回收</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="6-2-常见内存泄漏场景"><a href="#6-2-常见内存泄漏场景" class="headerlink" title="6.2 常见内存泄漏场景"></a>6.2 常见内存泄漏场景</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 场景 1：未清理的事件监听</span><span class="token keyword">function</span> <span class="token function">bindEvent</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> bigData <span class="token operator">=</span> <span class="token function">loadBigData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token function">process</span><span class="token punctuation">(</span>bigData<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 忘记移除监听 → el、bigData 均无法回收</span><span class="token punctuation">&#125;</span><span class="token comment">// 修复：el.removeEventListener('click', handler)</span><span class="token comment">// 场景 2：闭包中的定时器</span><span class="token keyword">function</span> <span class="token function">startPolling</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> cache <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 如果不清除定时器，cache 永远不会被回收</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 场景 3：循环引用（现代引擎已能处理，但仍需注意）</span><span class="token keyword">function</span> <span class="token function">leak</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> obj <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  obj<span class="token punctuation">.</span>self <span class="token operator">=</span> obj<span class="token punctuation">;</span> <span class="token comment">// 循环引用</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> obj<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="6-3-性能优化建议"><a href="#6-3-性能优化建议" class="headerlink" title="6.3 性能优化建议"></a>6.3 性能优化建议</h3><table><thead><tr><th>策略</th><th>说明</th></tr></thead><tbody><tr><td>及时解引用</td><td>不再使用的闭包赋值为 <code>null</code></td></tr><tr><td>避免不必要闭包</td><td>能用块级作用域解决的，不要用闭包</td></tr><tr><td>控制捕获范围</td><td>只捕获需要的变量，不要捕获整个外部环境</td></tr><tr><td>使用 WeakMap</td><td>对于引用类型，考虑使用 <code>WeakMap</code> 避免阻止垃圾回收</td></tr></tbody></table><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 使用 WeakMap 避免内存泄漏</span><span class="token keyword">const</span> privateData <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WeakMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    privateData<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> name <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> privateData<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="7-闭包在-React-中的实战"><a href="#7-闭包在-React-中的实战" class="headerlink" title="7. 闭包在 React 中的实战"></a>7. 闭包在 React 中的实战</h2><p>React Hooks 的大量模式本质上都是闭包的应用：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// useEffect 闭包陷阱</span><span class="token keyword">function</span> <span class="token function">Timer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token comment">// ❌ 闭包捕获了 count = 0，每次都是 0 + 1 = 1</span>      <span class="token comment">// setCount(count + 1);</span>      <span class="token comment">// ✅ 使用函数式更新，避免闭包陷阱</span>      <span class="token function">setCount</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">prev</span><span class="token punctuation">)</span> <span class="token operator">=></span> prev <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearInterval</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>h1<span class="token operator">></span><span class="token punctuation">&#123;</span>count<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="8-总结"><a href="#8-总结" class="headerlink" title="8. 总结"></a>8. 总结</h2><pre class="line-numbers language-none"><code class="language-none">闭包的核心知识图谱：                    ┌─────────────┐                    │   闭包本质   │                    └──────┬──────┘                           │              ┌────────────┼────────────┐              ▼            ▼            ▼        词法作用域    [[Environment]]  作用域链              │            │            │              └────────────┼────────────┘                           │          ┌────────────────┼────────────────┐          ▼                ▼                ▼      模块模式         函数柯里化       防抖节流          │                │                │          └────────────────┼────────────────┘                           │          ┌────────────────┼────────────────┐          ▼                ▼                ▼      内存管理         常见陷阱         React 实战<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>闭包不是什么黑魔法，它是 JavaScript 词法作用域机制的<strong>必然产物</strong>。理解它的本质，才能在日常开发和架构设计中真正驾驭它。</p><hr><blockquote><p><strong>JS 系列持续更新中</strong>，下一篇将深入解析 <strong>JavaScript 原型链与继承机制</strong>，敬请关注。</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;闭包（Closure）是 JavaScript 中最核心、最常被面试考查、却也最容易被误解的概念之一。本文将从&lt;strong&gt;词法作用域的底</summary>
      
    
    
    
    <category term="JavaScript系列" scheme="https://liaojunhao.github.io/categories/JavaScript%E7%B3%BB%E5%88%97/"/>
    
    
    <category term="闭包" scheme="https://liaojunhao.github.io/tags/%E9%97%AD%E5%8C%85/"/>
    
    <category term="作用域" scheme="https://liaojunhao.github.io/tags/%E4%BD%9C%E7%94%A8%E5%9F%9F/"/>
    
  </entry>
  
  <entry>
    <title>6.落地智慧涌现模型</title>
    <link href="https://liaojunhao.github.io/2026/02/09/6.%E8%90%BD%E5%9C%B0%E6%99%BA%E6%85%A7%E6%B6%8C%E7%8E%B0%E6%A8%A1%E5%9E%8B/"/>
    <id>https://liaojunhao.github.io/2026/02/09/6.%E8%90%BD%E5%9C%B0%E6%99%BA%E6%85%A7%E6%B6%8C%E7%8E%B0%E6%A8%A1%E5%9E%8B/</id>
    <published>2026-02-09T03:14:33.000Z</published>
    <updated>2026-09-09T07:32:15.084Z</updated>
    
    <content type="html"><![CDATA[<h2 id="工欲善其事，必先利其器"><a href="#工欲善其事，必先利其器" class="headerlink" title="工欲善其事，必先利其器"></a>工欲善其事，必先利其器</h2><p>接下来我将介绍一种“以领域精进为目的”的记卡片方法</p><p>你可以选择任何知识管理工具（比如 Notion、Obsidian…）来落地这套方法</p><h2 id="卡片的分类"><a href="#卡片的分类" class="headerlink" title="卡片的分类"></a>卡片的分类</h2><p>以“精进领域”为目的，我们定义三种标签：</p><ul><li>领域：记录领域相关的<strong>知识</strong></li><li>项目：记录项目执行过程中“经由解决问题得到的<strong>知识</strong>”</li><li>兴趣：记录兴趣所在领域的<strong>知识</strong></li></ul><h2 id="提问机器如何推动领域精进？"><a href="#提问机器如何推动领域精进？" class="headerlink" title="提问机器如何推动领域精进？"></a>提问机器如何推动领域精进？</h2><p>到目前为止，我们已经为领域、项目、兴趣设置好标签，也知道卡片该如何写。接下来，只需要开动提问机器，不断提出问题，解决问题，在此过程中：</p><ul><li>为了解决问题，我们会查阅很多领域信息，在此过程中信息转化为知识，会变成卡片沉淀到 <strong>#领域/xx</strong> 下</li><li>在参与项目过程中，同样会产生很多知识，这些知识有的只局限在当前项目下，会变成卡片沉淀到 <strong>#项目/a项目</strong> 下，有些知识应用范围更广，可以同时沉淀在 <strong>#项目/a项目</strong> 与 <strong>#领域/xx</strong> 下</li></ul><p>很多人都知道“一万小时领域精进定律”，但“一万小时”是个不太准确的度量标准。就我的统计，普通人只需要沉淀上百张领域知识卡片，就能在一个细分领域产生独到见解。</p><p>如果你每天记录一张卡片，要达到最低标准实际上 3 个多月就够了。</p><h2 id="如何将洞察具象化？"><a href="#如何将洞察具象化？" class="headerlink" title="如何将洞察具象化？"></a>如何将洞察具象化？</h2><p>洞察产生于知识之间的联系。既然我们已经将知识具象化为卡片，洞察其实就是卡片之间的交互，具体来说包括两类交互：</p><ol><li>卡片内容之间的联系</li><li>卡片、标签的新增、合并、修改</li></ol><p>第一类：在 领域智慧从何而来？ &gt; 洞察 已经介绍过 —— 当你记录的知识卡片足够多，那些不同领域、不同项目、不同兴趣的卡片内容之间就会产生联系，这就是知识的新洞察。<br>第二类：是在“领域精进”的过程中自然而然出现的。</p><h2 id="如何将领域智慧具象化？"><a href="#如何将领域智慧具象化？" class="headerlink" title="如何将领域智慧具象化？"></a>如何将领域智慧具象化？</h2><p>“领域智慧的产生”是动态演进的结果：</p><ol><li>开动提问机器，不断产生问题</li><li>借由查阅信息、解决问题产生知识，沉淀为卡片</li><li>卡片、标签之间的交互产生洞察，洞察又以卡片的形式沉淀</li><li>上述过程不断重复，标签不断演进，最终形成的“成体系的标签”，就是领域智慧的外显</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>要落地智慧涌现模型，我们需要遵循 3 步：</p><ol><li>选择自己要精进的领域</li><li>在领域下参与 1 到多个项目，构建提问机器</li><li>借由知识管理工具，将知识、洞察外显出来</li><li>最终形成的“成体系的标签”，就是领域智慧的外显。领域智慧的内容，就是标签下卡片的内容</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;工欲善其事，必先利其器&quot;&gt;&lt;a href=&quot;#工欲善其事，必先利其器&quot; class=&quot;headerlink&quot; title=&quot;工欲善其事，必先利其器&quot;&gt;&lt;/a&gt;工欲善其事，必先利其器&lt;/h2&gt;&lt;p&gt;接下来我将介绍一种“以领域精进为目的”的记卡片方法&lt;/p&gt;
&lt;p&gt;你</summary>
      
    
    
    
    <category term="职业发展" scheme="https://liaojunhao.github.io/categories/%E8%81%8C%E4%B8%9A%E5%8F%91%E5%B1%95/"/>
    
    
    <category term="职场" scheme="https://liaojunhao.github.io/tags/%E8%81%8C%E5%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>5.智慧涌现模型</title>
    <link href="https://liaojunhao.github.io/2026/01/08/5.%E6%99%BA%E6%85%A7%E6%B6%8C%E7%8E%B0%E6%A8%A1%E5%9E%8B/"/>
    <id>https://liaojunhao.github.io/2026/01/08/5.%E6%99%BA%E6%85%A7%E6%B6%8C%E7%8E%B0%E6%A8%A1%E5%9E%8B/</id>
    <published>2026-01-08T03:15:16.000Z</published>
    <updated>2026-09-09T03:11:49.163Z</updated>
    
    <content type="html"><![CDATA[<h2 id="系统引擎：打造提问机器"><a href="#系统引擎：打造提问机器" class="headerlink" title="系统引擎：打造提问机器"></a>系统引擎：打造提问机器</h2><p>“形成自己的领域智慧”要求我们不断在领域中精进。如果将领域比作一台引擎，他的燃料是什么？答案是 —— 领域内的具体问题。</p><p>回忆下，你是如何从新人一步步成为行业熟手的？是不是参与一个个项目，在项目中遇到具体的问题，再借由“解决问题”不断熟练的？</p><p>所以，“领域精进”的关键是打造一台<strong>提问机器</strong>，能源源不断的产生领域问题。借由“解决问题”不断精进领域。</p><blockquote><p>领域是引擎，问题是燃料，借由“解决问题”精进领域</p></blockquote><p>如何打造提问机器？答案是 —— 参与领域中不同的项目。</p><p>所谓<strong>项目</strong>，是指“与领域相关的，有明确起始时间、结束时间的事情”，比如：</p><ul><li>公司给你分配的任务（任务有截止时间）</li><li>看一本领域内的专业书（书有明确页数）</li><li>组一个领域知识学习群，为群友答疑（知识有学懂的时候）</li><li>业余时间用领域知识做外包（外包项目有服务时长）</li><li>写一本领域相关的小册子（小册子有明确目录）</li></ul><p><strong>领域</strong>与<strong>项目</strong>的关系是：</p><ul><li>只有领域没有项目是纸上谈兵</li><li>只有项目没有领域是盲人摸象</li></ul><p>目前为止，我们已经构建了“驱动智慧涌现模型的引擎”：</p><blockquote><p>领域 &gt; 问题 &gt; 提问机器 &gt; 项目</p></blockquote><h2 id="系统中枢：解决问题"><a href="#系统中枢：解决问题" class="headerlink" title="系统中枢：解决问题"></a>系统中枢：解决问题</h2><p>接下来，我们开始搭建系统的中枢部分 —— 解决问题的部分。</p><p>在这里我要再引入一个概念 —— <strong>兴趣</strong>。兴趣是“你感兴趣的领域”。</p><p><strong>兴趣</strong>和<strong>领域</strong>的区别是“是否需要你负责” —— 兴趣不精进也没关系，领域不精进影响饭碗。</p><p>两者比较好的关系是：兴趣与领域有关联，这样才有转化为领域的可能性。</p><blockquote><p>💡 乔布斯“兴趣转化为领域”的例子<br>当乔布斯大一辍学后并没有离开学校，而是听了一些自己感兴趣的课，其中一门是美术字课。这在当时看来完全“无用”的课，为十年后他设计的第一台麦金塔（Macintosh）个人电脑的可变字体发挥了作用。<br>乔布斯这样回忆道：“如果我当年没有去上这门美术字课，苹果电脑就不会发明这么漂亮的字体。又由于 windows 是照抄苹果的，所以很可能所有个人电脑上就都没有这样的字体了。”</p></blockquote><p>当提问机器产出问题后，可以从领域数据、领域信息以及兴趣中寻求答案，在“寻求答案”的过程中就会产生知识。</p><p>目前为止，我们已经构建智慧涌现模型的引擎与中枢：</p><h2 id="系统核心：智慧涌现"><a href="#系统核心：智慧涌现" class="headerlink" title="系统核心：智慧涌现"></a>系统核心：智慧涌现</h2><p>有了引擎源源不断产出问题，中枢将问题解决并转化为知识，再结合上一节提到的“领域智慧的形成理论”，就能搭建完整的可落地系统：</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>为了形成自己的领域智慧，我们需要搭建智慧涌现模型。模型包括三部分：</p><ul><li><strong>引擎：打造提问机器，源源不断的产出领域问题</strong></li><li><strong>中枢：解决提问机器产出的问题，并在“寻求答案”的过程中产生知识</strong></li><li><strong>核心：知识之间的联系产生洞察，大量洞察汇聚为领域智慧</strong></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;系统引擎：打造提问机器&quot;&gt;&lt;a href=&quot;#系统引擎：打造提问机器&quot; class=&quot;headerlink&quot; title=&quot;系统引擎：打造提问机器&quot;&gt;&lt;/a&gt;系统引擎：打造提问机器&lt;/h2&gt;&lt;p&gt;“形成自己的领域智慧”要求我们不断在领域中精进。如果将领域比作一台引</summary>
      
    
    
    
    <category term="职业发展" scheme="https://liaojunhao.github.io/categories/%E8%81%8C%E4%B8%9A%E5%8F%91%E5%B1%95/"/>
    
    
    <category term="职场" scheme="https://liaojunhao.github.io/tags/%E8%81%8C%E5%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>4.领域智慧从何而来？</title>
    <link href="https://liaojunhao.github.io/2025/11/16/4.%E9%A2%86%E5%9F%9F%E6%99%BA%E6%85%A7%E4%BB%8E%E4%BD%95%E8%80%8C%E6%9D%A5%EF%BC%9F/"/>
    <id>https://liaojunhao.github.io/2025/11/16/4.%E9%A2%86%E5%9F%9F%E6%99%BA%E6%85%A7%E4%BB%8E%E4%BD%95%E8%80%8C%E6%9D%A5%EF%BC%9F/</id>
    <published>2025-11-16T10:30:00.000Z</published>
    <updated>2026-09-08T06:52:16.718Z</updated>
    
    <content type="html"><![CDATA[<p>领域智慧的产生，需要经历五个步骤：</p><ol><li>数据</li><li>信息</li><li>知识</li><li>洞察</li><li>智慧</li></ol><h2 id="1-数据"><a href="#1-数据" class="headerlink" title="1.数据"></a>1.数据</h2><p>数据是完全中立、不带任何信息的内容，比如程序员的代码、行业数据。<br>由于不带任何信息，所以数据在图中呈现为“没有颜色的圈”。</p><h2 id="2-信息"><a href="#2-信息" class="headerlink" title="2.信息"></a>2.信息</h2><p>当有人对数据进行解读，会赋予数据以信息的意义，比如：</p><ul><li>从行业数据的同比、环比中发现的趋势信息</li><li>阅读代码，解读出其在上下文中的用途信息</li></ul><p>由于信息是对数据的解读，所以信息在数据圈的基础上赋予了不同颜色。</p><h2 id="3-知识"><a href="#3-知识" class="headerlink" title="3.知识"></a>3.知识</h2><p>当你阅读信息，从中获得的个性化理解，就是知识。同样是看新闻联播，有人解读出国家发展趋势，有人解读出“美国人民生活在水深火热中”。</p><p>由于知识是“对信息的个性化理解”，不同人从同一信息中获得的知识可能不一样，所以知识在圈的基础上呈现不同形状。</p><p>值得注意的是，“别人输出的内容（文章、视频、回答…）”本质是信息，“你从别人内容中获得的个性化理解”才是知识，知识是主观的。</p><h2 id="4-洞察"><a href="#4-洞察" class="headerlink" title="4.洞察"></a>4.洞察</h2><p>当知识积累的足够多后，你会发现一些知识之间存在某些联系，这些联系不限于：</p><ul><li>差异：关注两件相似事物的不同</li><li>变化：事物从一种状态转换到另一种状态的过程</li><li>冲突：事物双方在目标或利益上的对立和抵触</li><li>呼应：事物之间的相互联系或相互影响</li><li>互补：事物在属性或功能上相辅相成，共同形成一个更完整的系统</li><li>联动：事物双方在行动上的协同，一方的行动直接影响另一方</li></ul><p>这些<strong>知识之间的联系</strong>就是你独有的洞察。</p><h2 id="5-智慧"><a href="#5-智慧" class="headerlink" title="5.智慧"></a>5.智慧</h2><p>当你对某一领域产生足够多洞察后，洞察会汇聚为你独特的领域智慧。</p><p>虽然智慧涌现模型中的图形是二维的，但我们大脑的神经元是网状结构，这意味着不管是知识、洞察还是智慧，在我们大脑中都不是以结构化的形式存储的，而是呈现为：</p><ul><li>知识：点</li><li>洞察：点与点之间连接成的线</li><li>智慧：线与线之间形成的网</li></ul><p>我们看到的各种“传授领域智慧的教材”（比如经典教科书，还有本手册）本质是将我们脑中的网状领域智慧，以“方便受众理解”的形式结构化呈现出来。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;领域智慧的产生，需要经历五个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据&lt;/li&gt;
&lt;li&gt;信息&lt;/li&gt;
&lt;li&gt;知识&lt;/li&gt;
&lt;li&gt;洞察&lt;/li&gt;
&lt;li&gt;智慧&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;1-数据&quot;&gt;&lt;a href=&quot;#1-数据&quot; class=&quot;headerl</summary>
      
    
    
    
    <category term="职业发展" scheme="https://liaojunhao.github.io/categories/%E8%81%8C%E4%B8%9A%E5%8F%91%E5%B1%95/"/>
    
    
    <category term="职场" scheme="https://liaojunhao.github.io/tags/%E8%81%8C%E5%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>3.什么是领域智慧？</title>
    <link href="https://liaojunhao.github.io/2025/08/08/3.%E4%BB%80%E4%B9%88%E6%98%AF%E9%A2%86%E5%9F%9F%E6%99%BA%E6%85%A7%EF%BC%9F/"/>
    <id>https://liaojunhao.github.io/2025/08/08/3.%E4%BB%80%E4%B9%88%E6%98%AF%E9%A2%86%E5%9F%9F%E6%99%BA%E6%85%A7%EF%BC%9F/</id>
    <published>2025-08-08T11:42:00.000Z</published>
    <updated>2026-09-08T06:52:13.976Z</updated>
    
    <content type="html"><![CDATA[<p>我们会将上一篇的 5 个步骤扩充为一套完整的系统，包括 3 部分：</p><ol><li>智慧涌现模型</li><li>四层圈层模型</li><li>连接上述两部分的影响力纽带</li></ol><p>完成学习后，你就能搭建自己的第二曲线系统。</p><h2 id="什么是领域？"><a href="#什么是领域？" class="headerlink" title="什么是领域？"></a>什么是领域？</h2><p>对于职场人，最常见的领域是你从事的工作方向，你需要对结果直接负责，这是你在社会立足的根本。通常存在于如下三部分的交集中：</p><ol><li>你从事且擅长的工作方向</li><li>优秀同行简历中展示的工作方向</li><li>优秀公司职位描述中要求的工作方向</li></ol><p>除了工作方向外，如果你在一些个人兴趣上能做到比较专业，那么这些兴趣也能发展为领域。“从兴趣出发的领域”通常是如下两部分的交集：</p><ul><li>你痴迷其中，即使没有回报也想做下去的方向</li><li>对别人有帮助，而不是你自嗨的方向</li></ul><h2 id="什么是领域智慧？"><a href="#什么是领域智慧？" class="headerlink" title="什么是领域智慧？"></a>什么是领域智慧？</h2><p>当我们说一个人“很有智慧”时，通常是指他对问题有鞭辟入里的回答。</p><p>回答的背后并不是“他刚好知道这些具体问题的答案”，而是“他有一套解决问题的框架，任何具体的问题都能在这套框架下寻得解决方向”。</p><p>再抽象点说，<strong>智慧</strong>指“长期实践中形成的一套抽象体系知识，可以解决受众的具象问题”，注意这里的两个关键词<strong>抽象</strong>与<strong>具象</strong>。</p><p>综上，<strong>领域智慧</strong>就是“在某一领域长期实践中形成的一套抽象的体系知识，可以解决该领域受众的具象问题”。</p><blockquote><p>举三个“领域智慧”的例子</p><ol><li>中医：经过长时间实践，形成了以阴阳、五行、脏腑学说为核心的理论体系，以望、闻、问、切为核心的诊断方法</li><li>《React 技术揭秘》：xx对 React 源码（程序员 &gt; 前端工程师 &gt; React 框架 &gt; React 源码）的领域智慧，将源码拆分为“理念、架构、实现”三个抽象层级，可以指导前端工程师解决 React 源码领域相关问题</li><li>《设计编辑器从0到1开发》：冰河末日对设计编辑器开发（程序员 &gt; 前端工程师 &gt; 设计编辑器框架 &gt; 源码）的领域智慧，将代码拆分为“理念、架构、实现”三个抽象层级，可以指导前端工程师解决 设计工具 领域相关的问题</li></ol></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我们会将上一篇的 5 个步骤扩充为一套完整的系统，包括 3 部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;智慧涌现模型&lt;/li&gt;
&lt;li&gt;四层圈层模型&lt;/li&gt;
&lt;li&gt;连接上述两部分的影响力纽带&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;完成学习后，你就能搭建自己的第二曲线系统。&lt;/p&gt;
&lt;h2 </summary>
      
    
    
    
    <category term="职业发展" scheme="https://liaojunhao.github.io/categories/%E8%81%8C%E4%B8%9A%E5%8F%91%E5%B1%95/"/>
    
    
    <category term="职场" scheme="https://liaojunhao.github.io/tags/%E8%81%8C%E5%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>2.什么是第二曲线？</title>
    <link href="https://liaojunhao.github.io/2025/05/20/2.%E4%BB%80%E4%B9%88%E6%98%AF%E7%AC%AC%E4%BA%8C%E6%9B%B2%E7%BA%BF%EF%BC%9F/"/>
    <id>https://liaojunhao.github.io/2025/05/20/2.%E4%BB%80%E4%B9%88%E6%98%AF%E7%AC%AC%E4%BA%8C%E6%9B%B2%E7%BA%BF%EF%BC%9F/</id>
    <published>2025-05-20T12:06:00.000Z</published>
    <updated>2026-09-08T06:52:10.019Z</updated>
    
    <content type="html"><![CDATA[<h2 id="要系统，不要目标"><a href="#要系统，不要目标" class="headerlink" title="要系统，不要目标"></a>要系统，不要目标</h2><p>呆伯特系列漫画的作者斯科特·亚当斯认为，有两种不同的做事方式：</p><ul><li>目标导向：目标是一个未来的具体目的地，最终可能实现，也可能不实现</li><li>系统导向：系统是你定期要做的事，没有明确的目的地，但在过程中就能不断积累优势</li></ul><p>举几个例子：</p><ul><li>在减肥领域，“我要瘦 10 斤”是个目标，“我要健康饮食”是个系统</li><li>在商业领域，“我要赚 100w”是个目标，“我要成为连续创业者”是个系统</li></ul><p>对于发展<strong>第二曲线</strong>，我们应该建立系统，而不是追求目标。因为目标导向只有两种状态：</p><ol><li>成功：实现目标</li><li>失败：未实现目标</li></ol><p>如果不成功，就处在持续的失败中。而系统导向的每一步都是践行系统，都是成功的。</p><p>琐事缠身、心力不稳、精神内耗是职场人的常态，在这种状态下探索第二曲线，如果还要经历失败，很难让人长期坚持下来。</p><p>所以，好的第二曲线应该是一套系统，就像种树，只要定期浇水施肥，树始终在成长。</p><p>最终，在复利和时间的积累下，第二曲线超越第一曲线，收获新的事业。</p><h2 id="什么是第二曲线？"><a href="#什么是第二曲线？" class="headerlink" title="什么是第二曲线？"></a>什么是第二曲线？</h2><p>除了“依附于公司”，我们还能依附什么？把视野打开，公司之上是不同的行业领域，我们的第二曲线完全可以绕开公司，依附于特定的领域。如此，第二曲线就不会遭遇“依附于公司造成的脆弱性”。</p><p>再考虑到晶体智力因素，一条优秀的第二曲线发展路径如下：</p><ol><li>积累领域知识</li><li>依靠领域知识构建你的领域智慧</li><li>利用领域智慧激发影响力</li><li>运用影响力吸引受众，形成围绕你的圈层</li><li>为圈层创造价值，并从中获得收益</li></ol><p>可以将上述过程形象的比喻为“在冰天雪地中升起篝火，不断吸引受冻的人围着篝火取暖”，其中：</p><ol><li>领域智慧是篝火</li><li>影响力是点燃篝火散发的温暖</li><li>圈层是围绕篝火取暖的人</li></ol><h2 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h2><p>优势很明显：</p><ol><li>是系统，不是目标<br>作为一套系统，5 个步骤是个头尾相连的循环，没有明确的目标。只要长期执行，就会积累复利。</li><li>依附于领域<br>整套系统依附于领域，不受“依附于公司”的脆弱性影响。</li><li>依赖于晶体智力<br>领域知识的积累、领域智慧的构建都属于晶体智力的范畴。随着年龄增长，流体智力下降，依赖晶体智力的事业会是更长远的选择。</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;要系统，不要目标&quot;&gt;&lt;a href=&quot;#要系统，不要目标&quot; class=&quot;headerlink&quot; title=&quot;要系统，不要目标&quot;&gt;&lt;/a&gt;要系统，不要目标&lt;/h2&gt;&lt;p&gt;呆伯特系列漫画的作者斯科特·亚当斯认为，有两种不同的做事方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目</summary>
      
    
    
    
    <category term="职业发展" scheme="https://liaojunhao.github.io/categories/%E8%81%8C%E4%B8%9A%E5%8F%91%E5%B1%95/"/>
    
    
    <category term="职场" scheme="https://liaojunhao.github.io/tags/%E8%81%8C%E5%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>1.职场为什么难？</title>
    <link href="https://liaojunhao.github.io/2025/03/12/1.%E8%81%8C%E5%9C%BA%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9A%BE%EF%BC%9F/"/>
    <id>https://liaojunhao.github.io/2025/03/12/1.%E8%81%8C%E5%9C%BA%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9A%BE%EF%BC%9F/</id>
    <published>2025-03-12T13:18:00.000Z</published>
    <updated>2026-09-08T06:52:05.360Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>职场的艰难有个人、环境两方面原因。</p></blockquote><h2 id="个人原因"><a href="#个人原因" class="headerlink" title="个人原因"></a>个人原因</h2><p>心理学家雷蒙德·卡特尔（Raymond Cattell）曾提出，人有两种不同类型的智力：</p><ul><li>流体智力：与抽象思维、逻辑推理、创意、环境适应相关的能力</li><li>晶体智力：利用以往经验和知识解决当前问题的能力</li></ul><p>负责流体智力表现的大脑前额叶皮质是儿童发育最晚，又是成年人最先衰退的部分。所以流体智力通常在年轻时（20 ～ 30 岁左右）达到高峰，随着年龄增长逐渐下降。</p><p>很多人发现：</p><ul><li>随着年龄增长，注意力不再能长时间集中</li><li>曾经能胜任的复杂逻辑、创意工作，现在无法胜任</li></ul><p>就是因为流体智力下降导致的。</p><p>如果你的工作依赖流体智力，那么随着年龄增长，你的竞争力将逐渐降低，并最终被年富力强的年轻人超越。</p><p>这是人体生理的客观规律，也是职场危机的个人原因。</p><h2 id="环境原因"><a href="#环境原因" class="headerlink" title="环境原因"></a>环境原因</h2><p>传统的职业发展路径充满了 <strong>脆弱性</strong>。</p><p>脆弱性一词来自《反脆弱：从不确定性中获益》一书，用于描述“系统、对象或个体在面对压力、冲击或不稳定环境时易于受损或失效的特性”。</p><p>脆弱性有 3 个特点：</p><ol><li>衰减的功能：在冲击下，脆弱体系的功能可能迅速下降<br>即“由于流体智力下降导致的竞争力下降”。</li><li>无法恢复：当面对外部挑战时，脆弱系统往往难以恢复到原始状态<br>公司要求员工有“连续且稳定的职业经历”（比如一家公司呆 2 年以上，工作之间不能有太长空窗期…），一旦履历连贯性被打破，对接下来的求职会很不利。</li><li>敏感性：对外部影响非常敏感，哪怕小的变动也可能导致显著的负面效果<br>有很多“不受我们控制的因素”会影响职业发展，大到行业步入下行期、公司经营不善、裁员，小到换新领导、同事恶意排挤…</li></ol><h2 id="问题根源"><a href="#问题根源" class="headerlink" title="问题根源"></a>问题根源</h2><p>从环境角度看，造成脆弱性的原因在于 —— 在传统的职业发展路径中，职场人的每一步决策都依附于“公司”这个载体。</p><p>倾巢之下岂有完卵，“公司经营不善”对“依附于公司的个体”来说就是降维打击。</p><p>从个人角度看，晶体智力相比流体智力的衰减曲线会更靠后（40 ～ 50 岁才开始衰减），所以“依赖晶体智力”的工作会有更长的职业生涯，比如教师、咨询师、律师、作家…</p><p>晶体智力的本质是以<strong>知识渡人</strong>，即使之前从事“依赖流体智力的工作”，但逐渐转移到“依赖晶体智力的工作”，也能延长职业生涯，比如：</p><ul><li>医生：年轻时在一线主刀，年长后以教学工作为主</li><li>工程师：年轻时在一线实施，年长后从事架构设计、团队建设</li></ul><p>综上所述，对 20 ～ 40 岁的职场人，更好的职业发展策略是 —— 在维持本职工作的同时，发展一条“依赖晶体智力且不依附于公司”的第二曲线。</p><p>此消彼长，渐进、平滑的完成职业转型。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;职场的艰难有个人、环境两方面原因。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;个人原因&quot;&gt;&lt;a href=&quot;#个人原因&quot; class=&quot;headerlink&quot; title=&quot;个人原因&quot;&gt;&lt;/a&gt;个人原因&lt;/h2&gt;&lt;p&gt;心理学家雷蒙德·卡特</summary>
      
    
    
    
    <category term="职业发展" scheme="https://liaojunhao.github.io/categories/%E8%81%8C%E4%B8%9A%E5%8F%91%E5%B1%95/"/>
    
    
    <category term="职场" scheme="https://liaojunhao.github.io/tags/%E8%81%8C%E5%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>计划阅读的书籍</title>
    <link href="https://liaojunhao.github.io/2023/06/30/%E8%AE%A1%E5%88%92%E9%98%85%E8%AF%BB%E7%9A%84%E4%B9%A6%E7%B1%8D/"/>
    <id>https://liaojunhao.github.io/2023/06/30/%E8%AE%A1%E5%88%92%E9%98%85%E8%AF%BB%E7%9A%84%E4%B9%A6%E7%B1%8D/</id>
    <published>2023-06-30T06:43:34.000Z</published>
    <updated>2026-09-07T06:34:37.552Z</updated>
    
    <content type="html"><![CDATA[<ul><li>《素书》</li><li>《刻意练习：如何从新手到大师》</li><li>《平凡的世界》</li><li>《格鲁夫给经理人的第一课》</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;《素书》&lt;/li&gt;
&lt;li&gt;《刻意练习：如何从新手到大师》&lt;/li&gt;
&lt;li&gt;《平凡的世界》&lt;/li&gt;
&lt;li&gt;《格鲁夫给经理人的第一课》&lt;/li&gt;
&lt;/ul&gt;
</summary>
      
    
    
    
    <category term="读书" scheme="https://liaojunhao.github.io/categories/%E8%AF%BB%E4%B9%A6/"/>
    
    
    <category term="书单" scheme="https://liaojunhao.github.io/tags/%E4%B9%A6%E5%8D%95/"/>
    
  </entry>
  
  <entry>
    <title>React最佳实践（一）</title>
    <link href="https://liaojunhao.github.io/2020/08/14/React%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%88%E4%B8%80%EF%BC%89/"/>
    <id>https://liaojunhao.github.io/2020/08/14/React%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%88%E4%B8%80%EF%BC%89/</id>
    <published>2020-08-14T09:02:16.000Z</published>
    <updated>2026-09-07T06:34:28.242Z</updated>
    
    <content type="html"><![CDATA[<h2 id="条件语句"><a href="#条件语句" class="headerlink" title="条件语句"></a>条件语句</h2><p>JSX 有许多不同方式来表达条 件逻辑，理解每种方式的益处及其存在的问题对于编写可读且可维护的代码非常重要。</p><ol><li>显示一个注销按钮时</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 下述做法可行，但可读性不好，组件合条件很多时会更差</span><span class="token keyword">let</span> button<span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>isLoggedIn<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  button <span class="token operator">=</span> <span class="token operator">&lt;</span>LogoutButton <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span>button<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>优化</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// JSX 可以利用行内条件来判断：</span><span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span>isLoggedIn <span class="token operator">&amp;&amp;</span> <span class="token operator">&lt;</span>LoginButton <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ol start="2"><li>if…else 语句</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">let</span> button<span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>isLoggedIn<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  button <span class="token operator">=</span> <span class="token operator">&lt;</span>LogoutButton <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>  button <span class="token operator">=</span> <span class="token operator">&lt;</span>LoginButton <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span>button<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>优化</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span>isLoggedIn <span class="token operator">?</span> <span class="token operator">&lt;</span>LogoutButton <span class="token operator">/</span><span class="token operator">></span> <span class="token operator">:</span> <span class="token operator">&lt;</span>LoginButton <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ol start="3"><li>来看更复杂的情况</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 需要多个变量才能判断是否要渲染组件：</span><span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span>dataIsReady <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>isAdmin <span class="token operator">||</span> userHasPermissions<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">&lt;</span>SecretData <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>优化</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 上述示例中的行内条件语句的写法很好，</span><span class="token comment">// 但可读性受到了很大影响。此时可以在组件内编写 一个辅助函数来检验 JSX 的条件语句:</span><span class="token function">canShowSecretData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> dataIsReady<span class="token punctuation">,</span> isAdmin<span class="token punctuation">,</span> userHasPermissions <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props    <span class="token keyword">return</span> dataIsReady <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>isAdmin <span class="token operator">||</span> userHasPermissions<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">canShowSecretData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">&lt;</span>SecretData <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在优化（使用 getter 方法来取代函数）</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">get</span> <span class="token function">canShowSecretData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> dataIsReady<span class="token punctuation">,</span> isAdmin<span class="token punctuation">,</span> userHasPermissions <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props    <span class="token keyword">return</span> dataIsReady <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>isAdmin <span class="token operator">||</span> userHasPermissions<span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>canShowSecretData <span class="token operator">&amp;&amp;</span> <span class="token operator">&lt;</span>SecretData <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="函数式编程基础"><a href="#函数式编程基础" class="headerlink" title="函数式编程基础"></a>函数式编程基础</h2><h3 id="纯粹性"><a href="#纯粹性" class="headerlink" title="纯粹性"></a>纯粹性</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 纯粹的函数</span><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">x<span class="token punctuation">,</span> y</span><span class="token punctuation">)</span> <span class="token operator">=></span> x <span class="token operator">+</span> y<span class="token punctuation">;</span><span class="token comment">// 非纯粹的函数</span><span class="token keyword">let</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">y</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>x <span class="token operator">=</span> x <span class="token operator">+</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="不可变性"><a href="#不可变性" class="headerlink" title="不可变性"></a>不可变性</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 以下代码不遵循不可变性</span><span class="token keyword">const</span> <span class="token function-variable function">add3</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">arr</span><span class="token punctuation">)</span> <span class="token operator">=></span> arr<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> myArr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token function">add3</span><span class="token punctuation">(</span>myArr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// [1, 2, 3]</span><span class="token function">add3</span><span class="token punctuation">(</span>myArr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// [1, 2, 3, 3]</span><span class="token comment">// 可以改用concat满足不可变性</span><span class="token keyword">const</span> <span class="token function-variable function">add3</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">arr</span><span class="token punctuation">)</span> <span class="token operator">=></span> arr<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> myArr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> result1 <span class="token operator">=</span> <span class="token function">add3</span><span class="token punctuation">(</span>myArr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// [1, 2, 3]</span><span class="token keyword">const</span> result2 <span class="token operator">=</span> <span class="token function">add3</span><span class="token punctuation">(</span>myArr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// [1, 2, 3]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="柯里化"><a href="#柯里化" class="headerlink" title="柯里化"></a>柯里化</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 原先的写法</span><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">x<span class="token punctuation">,</span> y</span><span class="token punctuation">)</span> <span class="token operator">=></span> x <span class="token operator">+</span> y<span class="token punctuation">;</span><span class="token comment">// 改成柯里化:</span><span class="token comment">// 这种函数写法相当方便，因为传入第一个参数后，</span><span class="token comment">// 第一个值被保留起来，返回的第二个函数 可以多次复用。</span><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token parameter">y</span><span class="token punctuation">)</span> <span class="token operator">=></span> x <span class="token operator">+</span> y<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="组合"><a href="#组合" class="headerlink" title="组合"></a>组合</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">x<span class="token punctuation">,</span> y</span><span class="token punctuation">)</span> <span class="token operator">=></span> x <span class="token operator">+</span> y<span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">square</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token operator">=></span> x <span class="token operator">*</span> x<span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">addAndSquare</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">x<span class="token punctuation">,</span> y</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">square</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h2 id="开发可复用的组件"><a href="#开发可复用的组件" class="headerlink" title="开发可复用的组件"></a>开发可复用的组件</h2><h3 id="使用-ES2015-语法类来创建组件"><a href="#使用-ES2015-语法类来创建组件" class="headerlink" title="使用 ES2015 语法类来创建组件"></a>使用 ES2015 语法类来创建组件</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 不推荐</span><span class="token keyword">const</span> Button <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createClass</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>button <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token comment">// 推荐使用继承React.Component</span><span class="token keyword">class</span> <span class="token class-name">Button</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>    <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token operator">&lt;</span>button <span class="token operator">/</span><span class="token operator">></span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>主要区别：<ul><li>prop</li><li>状态</li><li>自动绑定</li></ul></li></ul><h3 id="自动绑定"><a href="#自动绑定" class="headerlink" title="自动绑定"></a>自动绑定</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 以下的简单示例</span><span class="token keyword">const</span> Button <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createClass</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleClick<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 如果使用继承的方法（this结果会是null）</span><span class="token keyword">class</span> <span class="token class-name">Button</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleClick<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// 解决：</span><span class="token comment">// 1）箭头函数：自动将当前的 this 绑定到函数体</span><span class="token keyword">class</span> <span class="token class-name">Button</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><font color='red'>注意：</font> 这样做符合预期，也不会带来什么特殊问题。唯一的缺点在于，如果在意性能，那么就需要理解代码的本质。<br>实际上，在渲染方法中绑定函数会带来无法预料的副作用，因为每次渲染组件(应用在生命 周期内会多次渲染组件)时都会触发箭头函数。虽然在渲染方法内多次触发某个函数不太理想，但本身并没有什么问题。 <font color='red'>问题在于，如果这个函数传递给子组件，那么子组件在每次更新过程中都会接收新的 prop。</font>这可能会导致低效的渲染，进而引发问题，对于纯粹组件而言尤其如此。</p><p>解决函数绑定问题最佳方案是在构造器内进行绑定操作，这样即使多次渲染组件，它也不会发任何改变。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Button</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 就是这样，问题解决了!</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>handleClick <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">handleClick</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleClick<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="无状态函数式组件"><a href="#无状态函数式组件" class="headerlink" title="无状态函数式组件"></a>无状态函数式组件</h3><ul><li>React 0.14 引入了这个方法。它十分强大，可以使得代码更易维护和复用。</li><li>语法相当简洁优雅，查看以下示例:</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">&lt;</span>button <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ol><li>props 与上下文</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 无状态函数式组件可以接收 props 对 象作为参数:</span><span class="token parameter">props</span> <span class="token operator">=></span> <span class="token operator">&lt;</span>button<span class="token operator">></span><span class="token punctuation">&#123;</span>props<span class="token punctuation">.</span>text<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span><span class="token comment">// 更简洁的 ES2015 解构语法:</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">&#123;</span> text <span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">&lt;</span>button<span class="token operator">></span><span class="token punctuation">&#123;</span>text<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span><span class="token comment">// 无状态函数就可以通过 propTypes 属性来接收 props</span><span class="token keyword">const</span> <span class="token function-variable function">Button</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">&#123;</span> text <span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">&lt;</span>button<span class="token operator">></span><span class="token punctuation">&#123;</span>text<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>Button<span class="token punctuation">.</span>propTypes <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">text</span><span class="token operator">:</span> React<span class="token punctuation">.</span>PropTypes<span class="token punctuation">.</span>string<span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token comment">// 无状态函数式组件也接收表示上下文的第二个参数。</span><span class="token punctuation">(</span><span class="token parameter">props<span class="token punctuation">,</span> context</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>  <span class="token operator">&lt;</span>button<span class="token operator">></span>    <span class="token punctuation">&#123;</span>context<span class="token punctuation">.</span>currency<span class="token punctuation">&#125;</span>    <span class="token punctuation">&#123;</span>props<span class="token punctuation">.</span>value<span class="token punctuation">&#125;</span>  <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol><li><p>关键词 this &amp;&amp; 状态 &amp;&amp; 生命周期<br>无状态函数式组件与状态组件的一项区别在于，this 在无状态函数式组件的执行过程中不指向组件本身。<br>由于这个原因，与组件实例相关的 setState 等方法以及生命周期方法都无法使用。<br><font color='red'>新版的 React 出了 hooks 之后这个问题将不存在</font></p></li><li><p>ref 与事件处理器</p></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 因为无状态函数式组件不能访问组件实例，</span><span class="token comment">// 所以如果要使用 ref 或者事件处理器，需要按以下方式来定义。</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> input<span class="token punctuation">;</span>  <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> input<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div<span class="token operator">></span>      <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>input <span class="token operator">=</span> el<span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>      <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span>onClick<span class="token punctuation">&#125;</span><span class="token operator">></span>Focus<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="状态"><a href="#状态" class="headerlink" title="状态"></a>状态</h3><ul><li>某些情况下可能需要在状态更新完成时执行一些操作，React 为此提供了一个回调 函数:</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">clicked</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"the state is now"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>异步<ul><li>应该总是将 setState 方法当作异步的</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 在事件处理器中触发了 setState 后，尝试将当前状态值打印到控制台中，那 么获得的是旧状态值:</span><span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>        <span class="token literal-property property">clicked</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'the state is now'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">)</span> <span class="token comment">// the state is now null</span><span class="token punctuation">&#125;</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleClick<span class="token punctuation">&#125;</span><span class="token operator">></span>Click me<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span><span class="token punctuation">&#125;</span><span class="token comment">// 稍微修改一下代码:</span><span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>            <span class="token literal-property property">clicked</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'the state is now'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">)</span>        <span class="token comment">// 结果： the state is now Object &#123;clicked: true&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>示例使用 setTimeout 只是为了展示 React 的行为，<font color='red'>你永远不要这样编写事件监听器</font>。</p><h3 id="可派生的值"><a href="#可派生的值" class="headerlink" title="可派生的值"></a>可派生的值</h3><ul><li>只要能根据 props 计算最终值，就不应该将任何数据保存在状态中。</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Price</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">price</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>props<span class="token punctuation">.</span>currency<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>props<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token comment">// 不推荐</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>price<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// 如果在父组件中按照以下方式创建，那么这种做法是可行的:</span><span class="token operator">&lt;</span>Price currency<span class="token operator">=</span><span class="token string">"£"</span> value<span class="token operator">=</span><span class="token string">"100"</span> <span class="token operator">/</span><span class="token operator">></span><span class="token comment">// 问题在于，如果货币单位或价格在 Price 组件的生命周期内发生改变，</span><span class="token comment">// 则永远不会重新计算状态(因为只会调用构造器一次)，应用就会显示错误的价格。</span><span class="token comment">// 因此，只要可以，就应该用 props 来计算值：</span><span class="token function">getPrice</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>currency<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="prop-类型"><a href="#prop-类型" class="headerlink" title="prop 类型"></a>prop 类型</h3><ul><li>用 prop 类型定义清晰的接口，以便组件可以复用。</li></ul><h3 id="可复用组件"><a href="#可复用组件" class="headerlink" title="可复用组件"></a>可复用组件</h3><h2 id="组合一切"><a href="#组合一切" class="headerlink" title="组合一切"></a>组合一切</h2><h3 id="组件间的通信"><a href="#组件间的通信" class="headerlink" title="组件间的通信"></a>组件间的通信</h3><ul><li>children</li></ul><h3 id="容器组件与表现组件模式"><a href="#容器组件与表现组件模式" class="headerlink" title="容器组件与表现组件模式"></a>容器组件与表现组件模式</h3><ul><li>React 组件通常包含杂合在一起的逻辑与表现。</li><li>逻辑：一般指与 UI 无关的那些东西，如 API 的调用、数据操作以及事件处理器。</li><li>表现：则是指渲染方法中创建元素用来显示 UI 的部分。</li><li>容器组件：<ul><li>更关心行为部分</li><li>负责渲染对应的表现组件</li><li>发起 API 请求并操作数据</li><li>定义事件处理器</li><li>写作类的形式</li></ul></li><li>表现组件：<ul><li>更关心视觉表现</li><li>负责渲染 HTML 标记</li><li>以 props 的形式从父组件接收数据</li><li>通常写作无状态函数组件</li></ul></li></ul><h3 id="mixin"><a href="#mixin" class="headerlink" title="mixin"></a>mixin</h3><h3 id="高阶组件"><a href="#高阶组件" class="headerlink" title="高阶组件"></a>高阶组件</h3><h3 id="recompose"><a href="#recompose" class="headerlink" title="recompose"></a>recompose</h3><h3 id="函数子组件"><a href="#函数子组件" class="headerlink" title="函数子组件"></a>函数子组件</h3><h2 id="恰当地获取数据"><a href="#恰当地获取数据" class="headerlink" title="恰当地获取数据"></a>恰当地获取数据</h2><h3 id="数据流"><a href="#数据流" class="headerlink" title="数据流"></a>数据流</h3><ul><li>单向数据流，每个组件都以 prop 的形式从父组件接收数据，并且 prop 无法修改。</li></ul><h4 id="子组件与父组件的通行（回调函数）"><a href="#子组件与父组件的通行（回调函数）" class="headerlink" title="子组件与父组件的通行（回调函数）"></a>子组件与父组件的通行（回调函数）</h4><h4 id="公有父组件"><a href="#公有父组件" class="headerlink" title="公有父组件"></a>公有父组件</h4><h4 id="数据获取"><a href="#数据获取" class="headerlink" title="数据获取"></a>数据获取</h4><ul><li>用于获取数据的代码可以放在两个生命周期钩子中:componentWillMount 和 componentDidMount</li><li>前者会在组件首次渲染前触发，后者则在组件挂载完成后立即触发。</li><li>使用前者似乎是正确的做法，毕竟我们希望尽快加载数据，不过需要注意一点。实际上，服务端渲染和客户端渲染都会触发 componentWillMount 函数。</li><li><font color='red'>因此，我们只能用 componentDidMount 钩子</font></li></ul><ol><li>基于高阶函数封装数据请求</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 基于高阶函数封装请求</span><span class="token keyword">const</span> <span class="token function-variable function">withData</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token parameter">Component</span><span class="token punctuation">)</span> <span class="token operator">=></span>  <span class="token keyword">class</span> <span class="token class-name">extends</span> React<span class="token punctuation">.</span>Component <span class="token punctuation">&#123;</span>    <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token function">componentDidMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token comment">// 模拟虚拟请求</span>      <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>          <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">[</span>            <span class="token punctuation">&#123;</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">"001"</span><span class="token punctuation">,</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">"小明"</span><span class="token punctuation">,</span> <span class="token literal-property property">excerpt</span><span class="token operator">:</span> <span class="token string">"开发工程师"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>            <span class="token punctuation">&#123;</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">"002"</span><span class="token punctuation">,</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">"老王"</span><span class="token punctuation">,</span> <span class="token literal-property property">excerpt</span><span class="token operator">:</span> <span class="token string">"设计师"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>          <span class="token punctuation">]</span><span class="token punctuation">,</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token operator">&lt;</span>Component <span class="token punctuation">&#123;</span><span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">&#125;</span> <span class="token punctuation">&#123;</span><span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">const</span> withGists <span class="token operator">=</span> <span class="token function">withData</span><span class="token punctuation">(</span><span class="token string">"https://api.github.com/users/gaearon/gists"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">List</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"List -> gists"</span><span class="token punctuation">,</span> props<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>ul<span class="token operator">></span>      <span class="token punctuation">&#123;</span>props<span class="token punctuation">.</span>data<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">gist</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>        <span class="token operator">&lt;</span>li key<span class="token operator">=</span><span class="token punctuation">&#123;</span>gist<span class="token punctuation">.</span>id<span class="token punctuation">&#125;</span><span class="token operator">></span><span class="token punctuation">&#123;</span>gist<span class="token punctuation">.</span>title<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span>      <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span>    <span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>List<span class="token punctuation">.</span>propTypes <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">data</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>array<span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">const</span> ListWithGists <span class="token operator">=</span> <span class="token function">withGists</span><span class="token punctuation">(</span>List<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="2"><li>我们可以用 react-refetch 库来替换我们写的高阶函数</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">&#123;</span> connect <span class="token keyword">as</span> refetchConnect <span class="token punctuation">&#125;</span> <span class="token keyword">from</span> <span class="token string">"react-refetch"</span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">List</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">&#123;</span> gists <span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>gists<span class="token punctuation">.</span>pending<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>loading<span class="token operator">...</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>gists<span class="token punctuation">.</span>rejected<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span>gists<span class="token punctuation">.</span>reason<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>gists<span class="token punctuation">.</span>fulfilled<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      gists<span class="token punctuation">.</span>fulfilled <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>        <span class="token operator">&lt;</span>ul<span class="token operator">></span>          <span class="token punctuation">&#123;</span>gists<span class="token punctuation">.</span>value<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">gist</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>            <span class="token operator">&lt;</span>li key<span class="token operator">=</span><span class="token punctuation">&#123;</span>gist<span class="token punctuation">.</span>id<span class="token punctuation">&#125;</span><span class="token operator">></span><span class="token punctuation">&#123;</span>gist<span class="token punctuation">.</span>description<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span>          <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span>        <span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">></span>      <span class="token punctuation">)</span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">const</span> ListWithGists <span class="token operator">=</span> <span class="token function">refetchConnect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">gists</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://api.github.com/users/gaearon/gists</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">(</span>List<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="表单-amp-amp-事件"><a href="#表单-amp-amp-事件" class="headerlink" title="表单&amp;&amp;事件"></a>表单&amp;&amp;事件</h2><h3 id="自由表单组件"><a href="#自由表单组件" class="headerlink" title="自由表单组件"></a>自由表单组件</h3><h3 id="受控表单组件"><a href="#受控表单组件" class="headerlink" title="受控表单组件"></a>受控表单组件</h3><h3 id="自动生成表单"><a href="#自动生成表单" class="headerlink" title="自动生成表单"></a>自动生成表单</h3><h3 id="事件"><a href="#事件" class="headerlink" title="事件"></a>事件</h3><h2 id="样式"><a href="#样式" class="headerlink" title="样式"></a>样式</h2><h3 id="行内样式"><a href="#行内样式" class="headerlink" title="行内样式"></a>行内样式</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> style <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">color</span><span class="token operator">:</span> <span class="token string">"palevioletred"</span><span class="token punctuation">,</span>  <span class="token literal-property property">backgroundColor</span><span class="token operator">:</span> <span class="token string">"papayawhip"</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">Button</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">&lt;</span>button style<span class="token operator">=</span><span class="token punctuation">&#123;</span>style<span class="token punctuation">&#125;</span><span class="token operator">></span>Click me<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="Radium（解决行内样式问题）"><a href="#Radium（解决行内样式问题）" class="headerlink" title="Radium（解决行内样式问题）"></a>Radium（解决行内样式问题）</h3><h3 id="CSS-模块"><a href="#CSS-模块" class="headerlink" title="CSS 模块"></a>CSS 模块</h3><ul><li>行内样式方案不适合项目与团队，但仍然希望尽量紧密结合样式与组件， 那么还有一个名为 CSS 模块的方案供你选择。</li><li>Webpack &amp;&amp; 项目搭建</li><li>局部作用域的 CSS</li><li>原子级 CSS 模块</li><li>React CSS 模块</li><li>Styled Component</li></ul><h2 id="服务端渲染"><a href="#服务端渲染" class="headerlink" title="服务端渲染"></a>服务端渲染</h2><h3 id="使用服务端渲染的原因"><a href="#使用服务端渲染的原因" class="headerlink" title="使用服务端渲染的原因"></a>使用服务端渲染的原因</h3><ol><li>SEO<ul><li>在服务端渲染应用的一个主要原因就是 SEO。</li></ul></li><li>性能更强<ul><li>客户端渲染缺点：操作应用前，需要加载并运行文件包。</li></ul></li><li>不要低估复杂度<ul><li>维护通用应用</li><li>要搭建和维护带有路由和逻辑的服务器、管理服务端数据流等</li><li>缓存 服务器内容，以便更快地输出页面</li><li>…</li></ul></li><li>如果发现加载整个应用耗时很久，而你已经采取了一切优化手段(下面将详细讨论优化这一话题)，那么可以考虑用服务端渲染提升感知速度，以便为用户提供更好的体验。</li></ol><h3 id="Next-js"><a href="#Next-js" class="headerlink" title="Next.js"></a>Next.js</h3><h2 id="性能提升"><a href="#性能提升" class="headerlink" title="性能提升"></a>性能提升</h2><h3 id="key-属性"><a href="#key-属性" class="headerlink" title="key 属性"></a>key 属性</h3><ul><li>安装 npm install –save-dev react-addons-perf 插件帮助记录性能相关信息</li></ul><h3 id="优化手段"><a href="#优化手段" class="headerlink" title="优化手段"></a>优化手段</h3><ol><li>构建打包时要将 NODE_ENV 环境变量设置为 production</li><li>压缩最终代码来减小体 积，以便应用可以更快加载（new webpack.optimize.UglifyJsPlugin()）</li></ol><ul><li>是否要更新组件（shouldComponentUpdate）<ul><li>如果比较后发现什么都没有改变，那么就不修改 DOM，这是理想情况。但如果渲染方法执 行了很复杂的操作，React 就要耗费一些时间才能判断出不需要进行任何修改，这种情况就不够 理想了。</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 这个方法就是 shouldComponentUpdate，如果它返回 false，那么在父组件的更新过程中，组件及其全部子元素的渲染方法不会被调用。</span><span class="token function">shouldComponentUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>可以使用 React.PureComponent 做浅比较，这样不用维护 shouldComponentUpdate</li></ul><ol start="3"><li>why-did-you-update</li><li>在渲染方法中创建函数</li><li>props 常量</li><li>重构与良好设计</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;条件语句&quot;&gt;&lt;a href=&quot;#条件语句&quot; class=&quot;headerlink&quot; title=&quot;条件语句&quot;&gt;&lt;/a&gt;条件语句&lt;/h2&gt;&lt;p&gt;JSX 有许多不同方式来表达条 件逻辑，理解每种方式的益处及其存在的问题对于编写可读且可维护的代码非常重要。&lt;/p&gt;
&lt;ol</summary>
      
    
    
    
    <category term="React" scheme="https://liaojunhao.github.io/categories/React/"/>
    
    
    <category term="最佳实践" scheme="https://liaojunhao.github.io/tags/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
    
    <category term="JSX" scheme="https://liaojunhao.github.io/tags/JSX/"/>
    
  </entry>
  
  <entry>
    <title>游戏引擎Phaser使用总结</title>
    <link href="https://liaojunhao.github.io/2020/02/27/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8EPhaser%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/"/>
    <id>https://liaojunhao.github.io/2020/02/27/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8EPhaser%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/</id>
    <published>2020-02-27T08:31:46.000Z</published>
    <updated>2026-09-07T06:34:34.819Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Phaser 在渲染方面支持 webGL/canvas，架构方面 Phaser 直接内嵌了 3 个物理引擎（Arcade Physics，Ninja，p2.js）<br>提供了动画，预加载，设备适配方案，粒子系统等功能。兼容方面移动端优先。这边之介绍我们最常用的一些方法/对象跟自己踩过的一些坑的经验分享。</p></blockquote><h2 id="初始化游戏"><a href="#初始化游戏" class="headerlink" title="初始化游戏"></a>初始化游戏</h2><p><code>new Phaser.Game(width, height, renderer, parent, state, transparent, antialias)</code></p><ul><li>width：画布宽度</li><li>height：画布高度</li><li>renderer：渲染方式<ul><li>Phaser.AUTO</li><li>Phaser.CANVAS</li><li>Phaser.WEBGL</li></ul></li><li>parent：游戏 canvas 挂载的 DOM ID 或者 DOM 节点</li><li>state：游戏场景对象(按执行顺序：init、preload、create、update、render)</li><li>transparent：是否为透明</li><li>antialias：贴图是否抗锯齿</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">let</span> game <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Phaser<span class="token punctuation">.</span>Game</span><span class="token punctuation">(</span>  <span class="token number">800</span><span class="token punctuation">,</span>  <span class="token number">600</span><span class="token punctuation">,</span>  Phaser<span class="token punctuation">.</span><span class="token constant">AUTO</span><span class="token punctuation">,</span>  <span class="token string">"phaser-container"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#123;</span>    <span class="token literal-property property">init</span><span class="token operator">:</span> init<span class="token punctuation">,</span>    <span class="token literal-property property">preload</span><span class="token operator">:</span> preload<span class="token punctuation">,</span>    <span class="token literal-property property">create</span><span class="token operator">:</span> create<span class="token punctuation">,</span>    <span class="token literal-property property">update</span><span class="token operator">:</span> update<span class="token punctuation">,</span>    <span class="token literal-property property">render</span><span class="token operator">:</span> render<span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token boolean">false</span><span class="token punctuation">,</span>  <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="移动端适配"><a href="#移动端适配" class="headerlink" title="移动端适配"></a>移动端适配</h2><p>以 750*1336 设计稿为例。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">let</span> game <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Phaser<span class="token punctuation">.</span>Game</span><span class="token punctuation">(</span><span class="token number">750</span><span class="token punctuation">,</span> <span class="token number">1336</span><span class="token punctuation">,</span> Phaser<span class="token punctuation">.</span><span class="token constant">CANVAS</span><span class="token punctuation">,</span> <span class="token string">"game"</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 推荐 SHOW_ALL：</span><span class="token comment">// 比例不变，缩放不变形（通常使用该类型）</span>game<span class="token punctuation">.</span>scale<span class="token punctuation">.</span>scaleMode <span class="token operator">=</span> Phaser<span class="token punctuation">.</span>ScaleManager<span class="token punctuation">.</span><span class="token constant">SHOW_ALL</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h2 id="场景的生命周期"><a href="#场景的生命周期" class="headerlink" title="场景的生命周期"></a>场景的生命周期</h2><p>可以理解为类型 vue 哪有的生命周期，在一个场景从创建到渲染都会走的几个钩子函数</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 初始化</span><span class="token keyword">function</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token comment">// 预加载</span><span class="token keyword">function</span> <span class="token function">preload</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token comment">// 场景创建</span><span class="token keyword">function</span> <span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token comment">// 画布更新</span><span class="token keyword">function</span> <span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token comment">// 渲染设置</span><span class="token keyword">function</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="资源的预加载-Preload"><a href="#资源的预加载-Preload" class="headerlink" title="资源的预加载 Preload"></a>资源的预加载 Preload</h3><p>写在 preload 函数中</p><ol><li>game.load.image(key, url, overwrite)</li></ol><h3 id="场景创建-create"><a href="#场景创建-create" class="headerlink" title="场景创建 create"></a>场景创建 create</h3><h3 id="画布更新-update"><a href="#画布更新-update" class="headerlink" title="画布更新 update"></a>画布更新 update</h3><h3 id="渲染设置-render"><a href="#渲染设置-render" class="headerlink" title="渲染设置 render"></a>渲染设置 render</h3><h2 id="场景的管理"><a href="#场景的管理" class="headerlink" title="场景的管理"></a>场景的管理</h2><h3 id="场景的切换"><a href="#场景的切换" class="headerlink" title="场景的切换"></a>场景的切换</h3><h3 id="场景的销毁"><a href="#场景的销毁" class="headerlink" title="场景的销毁"></a>场景的销毁</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 销毁整个游戏</span>game<span class="token punctuation">.</span><span class="token function">destroy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h2 id="常见对象"><a href="#常见对象" class="headerlink" title="常见对象"></a>常见对象</h2><p>Game 对象</p><ul><li>World 对象<ul><li>设置游戏世界范围</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 不设默认为canvas画布大小</span>game<span class="token punctuation">.</span>world<span class="token punctuation">.</span><span class="token function">setBounds</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> width<span class="token punctuation">,</span> height<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>Stage 对象<br>Camare 对象<br>State<br>Sprite</p><h2 id="文本：Text"><a href="#文本：Text" class="headerlink" title="文本：Text"></a>文本：Text</h2><h3 id="自定义字体"><a href="#自定义字体" class="headerlink" title="自定义字体"></a>自定义字体</h3><pre class="line-numbers language-css" data-language="css"><code class="language-css"><span class="token comment">/* CSS中先设置好字体 */</span><span class="token atrule"><span class="token rule">@font-face</span></span> <span class="token punctuation">&#123;</span>  <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">"WEBFONT"</span><span class="token punctuation">;</span>  <span class="token property">font-display</span><span class="token punctuation">:</span> swap<span class="token punctuation">;</span>  <span class="token property">src</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">"../font/webfont.eot"</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token comment">/* IE9*/</span>  <span class="token property">src</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">"../font/webfont.eot?#iefix"</span><span class="token punctuation">)</span></span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"embedded-opentype"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">/* IE6-IE8 */</span>      <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">"../font/webfont.woff2"</span><span class="token punctuation">)</span></span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"woff2"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">"../font/webfont.woff"</span><span class="token punctuation">)</span></span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"woff"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">/* chrome、firefox */</span>      <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">"../font/webfont.ttf"</span><span class="token punctuation">)</span></span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"truetype"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment">/* chrome、firefox、opera、Safari, Android, iOS 4.2+*/</span>      <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">"../font/webfont.svg#思源黑体-细"</span><span class="token punctuation">)</span></span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"svg"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* iOS 4.1- */</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Phaser中添加字体的设置</span><span class="token keyword">let</span> style <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">font</span><span class="token operator">:</span> <span class="token string">"30px WEBFONT"</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>game<span class="token punctuation">.</span>add<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"你好世界"</span><span class="token punctuation">,</span> style<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="字体预加载"><a href="#字体预加载" class="headerlink" title="字体预加载"></a>字体预加载</h3><ul><li>自定义字体常见的坑：<ul><li>当字体<font color='red'> 没有加载完毕 </font>，Phaser 还是会渲染系统默认当字体</li></ul></li><li>解决方案：<ul><li>使用第三方库<font color='red'> fontfaceobserver </font>来做字体的预加载</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 在vue的钩子函数mounted中做预加载</span><span class="token keyword">async</span> <span class="token function">mounted</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 要加载的字体</span>  <span class="token keyword">let</span> fontOptions <span class="token operator">=</span> <span class="token punctuation">[</span>    <span class="token string">'HELVETI1-1'</span><span class="token punctuation">,</span>    <span class="token string">'WEBFONT'</span>  <span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token comment">// 加载完成后返回的promise队列</span>  <span class="token keyword">let</span> fontLoadList <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  fontOptions<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">let</span> font <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FontFaceObserver</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span>    fontLoadList<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>font<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token comment">// 等待所有promise完成</span>  <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>fontLoadList<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 游戏初始化</span>    <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">reason</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'字体加载失败：'</span><span class="token punctuation">,</span> reason<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="字体的行距"><a href="#字体的行距" class="headerlink" title="字体的行距"></a>字体的行距</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">text<span class="token punctuation">.</span>lineSpacing <span class="token operator">=</span> <span class="token number">40</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="字符的间距"><a href="#字符的间距" class="headerlink" title="字符的间距"></a>字符的间距</h3><p>很遗憾 Phaser 不支持，也不是 Phaser 的问题而是 canvas 对文字排版这块支持比较弱。可以使用一些 hack 手段来实现，但是会比较繁琐，可以看张鑫旭老师的这篇博文介绍：<br><a href="https://www.zhangxinxu.com/wordpress/2018/02/canvas-text-break-line-letter-spacing-vertical/">绘制字符间距</a>。</p><p>也可以用通过<code>BitmapText</code>这种图片字体来实现，不过种方式也不太适合中文（文字太多工作量太大）</p><h2 id="位图字体：BitmapText"><a href="#位图字体：BitmapText" class="headerlink" title="位图字体：BitmapText"></a>位图字体：BitmapText</h2><h2 id="时间：Time"><a href="#时间：Time" class="headerlink" title="时间：Time"></a>时间：Time</h2><p>基本时间事件</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Phaser.Timer.SECOND 每秒的毫秒数(1000)</span>game<span class="token punctuation">.</span>time<span class="token punctuation">.</span>events<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>Phaser<span class="token punctuation">.</span>Timer<span class="token punctuation">.</span><span class="token constant">SECOND</span> <span class="token operator">*</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h3 id="定时器：Timer"><a href="#定时器：Timer" class="headerlink" title="定时器：Timer"></a>定时器：Timer</h3><h3 id="定时器事件：TimerEvent"><a href="#定时器事件：TimerEvent" class="headerlink" title="定时器事件：TimerEvent"></a>定时器事件：TimerEvent</h3><p>使用<code>Phaser.Timer.add</code> / <code>Phaser.Timer.repeat</code> / <code>Phaser.Timer.loop</code> 方法来新建事件</p><h2 id="动画"><a href="#动画" class="headerlink" title="动画"></a>动画</h2><h2 id="事件绑定"><a href="#事件绑定" class="headerlink" title="事件绑定"></a>事件绑定</h2>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Phaser 在渲染方面支持 webGL/canvas，架构方面 Phaser 直接内嵌了 3 个物理引擎（Arcade Physics，Ninja，p2.js）&lt;br&gt;提供了动画，预加载，设备适配方案，粒子系统等功能。兼容方面移动端优先。这边之介</summary>
      
    
    
    
    <category term="2D图形处理" scheme="https://liaojunhao.github.io/categories/2D%E5%9B%BE%E5%BD%A2%E5%A4%84%E7%90%86/"/>
    
    
    <category term="Phaser" scheme="https://liaojunhao.github.io/tags/Phaser/"/>
    
    <category term="Canvas" scheme="https://liaojunhao.github.io/tags/Canvas/"/>
    
  </entry>
  
  <entry>
    <title>React基础知识（一）</title>
    <link href="https://liaojunhao.github.io/2019/05/22/React%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%EF%BC%88%E4%B8%80%EF%BC%89/"/>
    <id>https://liaojunhao.github.io/2019/05/22/React%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%EF%BC%88%E4%B8%80%EF%BC%89/</id>
    <published>2019-05-22T08:57:42.000Z</published>
    <updated>2026-09-07T06:34:27.211Z</updated>
    
    <content type="html"><![CDATA[<h2 id="JSX"><a href="#JSX" class="headerlink" title="JSX"></a>JSX</h2><h3 id="什么是-JSX"><a href="#什么是-JSX" class="headerlink" title="什么是 JSX"></a>什么是 JSX</h3><blockquote><p>是一种 JS 和 HTML 混合的语法,将组件的结构、数据甚至样式都聚合在一起定义组件</p></blockquote><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">ReactDOM<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>h1<span class="token operator">></span>Hello<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span><span class="token punctuation">,</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"root"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="什么是元素"><a href="#什么是元素" class="headerlink" title="什么是元素"></a>什么是元素</h3><blockquote><ol><li>JSX 其实只是一种语法糖,最终会通过 babeljs 转译成 createElement 语法</li><li>React 元素是构成 React 应用的最小单位</li><li>React 元素用来描述你在屏幕上看到的内容</li><li><code>React元素事实上是普通的JS对象</code>,ReactDOM 来确保浏览器中的 DOM 数据和 React 元素保持一致</li></ol></blockquote><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// jsx</span><span class="token operator">&lt;</span>h1 className<span class="token operator">=</span><span class="token string">"title"</span> style<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span><span class="token literal-property property">color</span><span class="token operator">:</span><span class="token string">'red'</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token operator">></span>hello<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span><span class="token comment">// 编译后</span>React<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"h1"</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">"title"</span><span class="token punctuation">,</span>  <span class="token literal-property property">style</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">color</span><span class="token operator">:</span> <span class="token string">'red'</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// createElement的结果</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">type</span><span class="token operator">:</span><span class="token string">'h1'</span><span class="token punctuation">,</span>  <span class="token literal-property property">props</span><span class="token operator">:</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">"title"</span><span class="token punctuation">,</span>    <span class="token literal-property property">style</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">color</span><span class="token operator">:</span> <span class="token string">'red'</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token literal-property property">children</span><span class="token operator">:</span><span class="token string">"hello"</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="组件-amp-Props"><a href="#组件-amp-Props" class="headerlink" title="组件&amp;Props"></a>组件&amp;Props</h2><h3 id="函数组件"><a href="#函数组件" class="headerlink" title="函数组件"></a>函数组件</h3><ul><li>函数组件接收一个单一的 props 对象并返回了一个 React 元素</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Welcome</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>h1<span class="token operator">></span>Hello<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>props<span class="token punctuation">.</span>name<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="类组件"><a href="#类组件" class="headerlink" title="类组件"></a>类组件</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Welcome</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>h1<span class="token operator">></span>Hello<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>name<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>总结：创建组件有两种方式“函数式”，“创建类式”<ul><li>函数组件<ol><li>实现功能简单，只是简单的调用和返回 JSX 而已（16 版本中出了 hooks 就不一样了）</li></ol></li><li>类组件<ol><li>可以实现复杂的业务功能</li><li>能够使用生命周期操作业务</li><li>函数式可以理解为静态组件（组件中的内容调取的时候就已经固定里，很难修改），而类这种方式，可以基于组件内部的状态来动态更新渲染的内容</li></ol></li></ul></li></ul><h3 id="组件渲染"><a href="#组件渲染" class="headerlink" title="组件渲染"></a>组件渲染</h3><ul><li>React 元素不但可以是 DOM 标签，还可以是用户自定义的组件</li><li>当 React 元素为用户自定义组件时，它会将 JSX 所接收的属性（attributes）转换为单个对象传递给组件，这个对象被称之为 <font>props</font></li><li>组件名称必须以大写字母开头</li><li>组件必须在使用的时候定义或引用它</li><li>组件的返回值只能有一个根元素</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Welcome</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>h1<span class="token operator">></span>Hello<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>props<span class="token punctuation">.</span>name<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> element <span class="token operator">=</span> <span class="token operator">&lt;</span>Welcome name<span class="token operator">=</span><span class="token string">"zhufengjiagou"</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>ReactDOM<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"root"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="props-组件之间的通信"><a href="#props-组件之间的通信" class="headerlink" title="props 组件之间的通信"></a>props 组件之间的通信</h3><ul><li>props 组件的属性是只读 <code>不可修改</code><ul><li>无论是使用函数或是类来声明一个组件，它决不能修改它自己的 props</li><li>纯函数没有改变它自己的输入值，当传入的值相同时，总是会返回相同的结果</li><li>所有的 React 组件必须像纯函数那样使用它们的 props</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">//纯函数</span><span class="token keyword">function</span> <span class="token function">sum</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">//非纯函数</span><span class="token keyword">function</span> <span class="token function">withdraw</span><span class="token punctuation">(</span><span class="token parameter">account<span class="token punctuation">,</span> amount</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  account<span class="token punctuation">.</span>total <span class="token operator">-=</span> amount<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>设置 props 默认值（static）<blockquote><p>props 是只读的，我们无法在方法中修改他的值，但是可以给默认值或者设置一些规则（例如：设置是否必须传递以及传递的类型等）</p></blockquote></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 给props设置一些默认值</span><span class="token keyword">static</span> defaultProps <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">lx</span><span class="token operator">:</span> <span class="token string">'系统提示'</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>类型检查<blockquote><p>要在组件的 props 上进行类型检查，你只需配置特定的 propTypes 属性</p></blockquote></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">import</span> PropTypes <span class="token keyword">from</span> <span class="token string">'prop-types'</span><span class="token punctuation">;</span> <span class="token comment">// 第三方插件</span><span class="token keyword">static</span> propTypes <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">con</span><span class="token operator">:</span> PropTypes<span class="token punctuation">.</span>string<span class="token punctuation">.</span>isRequired <span class="token comment">//=> 不仅传递的内容是字符串，并且还必须传递</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="State-amp-生命周期"><a href="#State-amp-生命周期" class="headerlink" title="State&amp;生命周期"></a>State&amp;生命周期</h2><h3 id="state"><a href="#state" class="headerlink" title="state"></a>state</h3><ul><li>state 私有的属性/状态，并且完全受控于当前组件</li><li>使用 this.setState() 来时刻更新组件 state</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Clock</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">date</span><span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">componentDidMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>timerID <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">tick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">componentWillUnmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">clearInterval</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>timerID<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">tick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>      <span class="token literal-property property">date</span><span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span>div<span class="token operator">></span>        <span class="token operator">&lt;</span>h1<span class="token operator">></span>Hello<span class="token punctuation">,</span> world<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>        <span class="token operator">&lt;</span>h2<span class="token operator">></span>It is <span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>date<span class="token punctuation">.</span><span class="token function">toLocaleTimeString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">.</span><span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="关于-setState-你应该了解三件事："><a href="#关于-setState-你应该了解三件事：" class="headerlink" title="关于 setState() 你应该了解三件事："></a>关于 setState() 你应该了解三件事：</h4><ol><li>不要直接修改 State</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 错误</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>comment <span class="token operator">=</span> <span class="token string">'Hello'</span><span class="token punctuation">;</span><span class="token comment">// 正确</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span><span class="token literal-property property">comment</span><span class="token operator">:</span> <span class="token string">'Hello'</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 构造函数是唯一可以给 this.state 赋值的地方</span><span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token literal-property property">date</span><span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="2"><li>State 的更新可能是异步的<ul><li>出于性能考虑，React 可能会把多个 setState() 调用合并成一个调用</li><li>因为 this.props 和 this.state 可能会异步更新，所以你不要依赖他们的值来更新下一个状态</li><li>解决办法：可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数</li></ul></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Counter</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">number</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function-variable function">handleClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token comment">/**     *     * 下面例子这样处理计算是会被合并，执行了2次+1（结果应该是2），但是结果是1     *     */</span>    <span class="token comment">// this.setState(&#123;number:this.state.number+1&#125;);</span>    <span class="token comment">// console.log(this.state.number); // 0</span>    <span class="token comment">// this.setState(&#123;number:this.state.number+1&#125;);</span>    <span class="token comment">// console.log(this.state.number); // 0</span>    <span class="token comment">// 解决办法把它改成函数</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">state</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">number</span><span class="token operator">:</span> state<span class="token punctuation">.</span>number <span class="token operator">+</span> <span class="token number">1</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">state</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">number</span><span class="token operator">:</span> state<span class="token punctuation">.</span>number <span class="token operator">+</span> <span class="token number">1</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span>div<span class="token operator">></span>        <span class="token operator">&lt;</span>p<span class="token operator">></span> <span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>number<span class="token punctuation">&#125;</span> <span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleClick<span class="token punctuation">&#125;</span><span class="token operator">></span><span class="token operator">+</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="3"><li>State 的更新会被合并<ul><li>当你调用 setState() 的时候，React 会把你提供的对象合并到当前的 state</li></ul></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 例如，你的 state 包含几个独立的变量：</span><span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">posts</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>    <span class="token literal-property property">comments</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 然后你可以分别调用 setState() 来单独地更新它们：</span><span class="token function">componentDidMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">response</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>      <span class="token literal-property property">posts</span><span class="token operator">:</span> response<span class="token punctuation">.</span>posts    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">fetchComments</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">response</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>      <span class="token literal-property property">comments</span><span class="token operator">:</span> response<span class="token punctuation">.</span>comments    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="数据是向下流动的"><a href="#数据是向下流动的" class="headerlink" title="数据是向下流动的"></a>数据是向下流动的</h4><ul><li>state 总是所属于特定的组件，而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件</li><li>把一个以组件构成的树想象成一个 props 的数据瀑布的话，那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源，但是它只能向下流动</li></ul><h3 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h3><blockquote><ol><li>只有类组件才有生命周期</li><li>hooks 出来之后有 useEffect 副作用操作的函数类似 class 的生命周期</li><li>你可以把 useEffect Hook 看做 componentDidMount，componentDidUpdate 和 componentWillUnmount 这三个函数的组合。</li></ol></blockquote><h4 id="旧版生命周期"><a href="#旧版生命周期" class="headerlink" title="旧版生命周期"></a>旧版生命周期</h4><p><img src="http://img.zhufengpeixun.cn/react15.jpg"></p><ul><li>[基础流程]<ul><li>constructor</li><li><del>componentWillMount 挂载前</del> （新版已删除）</li><li>render 第一次渲染</li><li>componentDidMount 第一次渲染之后</li></ul></li><li>[修改流程]<ul><li><code>(当组件的状态发生改变 setState或者传递给组件的属性发生改变 重新调用组件传递不同的属性 都会引发render重新执行渲染)</code></li><li><font color='red'>shouldComponentUpdate</font> 是否允许组件重新渲染 <code>默认true</code> (允许则执行后面函数，不允许直接结束即可)</li><li><font color='red'>componentWillUpdate</font> 重新渲染前</li><li><font color='red'>render</font> 第二次及以后重新渲染</li><li><font color='red'>componentDidUpdate</font> 重新渲染之后</li><li><font color='blue'>componentWillReceiveProps</font> 父组件把传递给子组件的属性发生改变后触发的钩子函数(会在 shouldComponentUpdate 之前执行)</li></ul></li><li>[卸载]<ul><li><code>原有渲染的内容是不消失的，只不过以后不能基于数据改变视图了</code></li><li>componentWillUnmount 卸载组件之前（一般不用）</li></ul></li><li>组件的加载顺序及状态变化加载顺序</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Counter</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span><span class="token operator">&lt;</span>Props<span class="token punctuation">,</span> State<span class="token operator">></span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 他会比较两个状态相等就不会刷新视图 PureComponent是浅比较</span>  <span class="token comment">/* 加载顺序 */</span>  <span class="token comment">// defaultProps</span>  <span class="token comment">// constructor</span>  <span class="token comment">// componentWillMount</span>  <span class="token comment">// render</span>  <span class="token comment">// componentDidMount</span>  <span class="token comment">// 状态(state)更新会触发的</span>  <span class="token comment">// shouldComponentUpdate nextProps, nextState=>boolean</span>  <span class="token comment">// componentWillUpdate</span>  <span class="token comment">// componentDidUpdate</span>  <span class="token comment">// 属性(props)更新</span>  <span class="token comment">// componentWillReceiveProps newProps</span>  <span class="token comment">// 卸载</span>  <span class="token comment">// componentWillUnmount</span>  <span class="token comment">/* 如果有子组件 */</span>  <span class="token comment">// 1.constructor构造函数</span>  <span class="token comment">// 2.组件将要加载 componentWillMount</span>  <span class="token comment">// 3.render</span>  <span class="token comment">//    子组件的初始化 => child constructor</span>  <span class="token comment">//    子组件挂载之前 => child componentWillMount</span>  <span class="token comment">//    子组件渲染DOM => child-render</span>  <span class="token comment">//    子组件挂载完成 => child componentDidMount</span>  <span class="token comment">// 4.组件挂载完成 componentDidMount</span>  <span class="token comment">/* 当父组件状态变化 */</span>  <span class="token comment">// 5.组件是否更新 shouldComponentUpdate</span>  <span class="token comment">// 6.组件将要更新 componentWillUpdate</span>  <span class="token comment">// 3.render 父组件渲染</span>  <span class="token comment">//    子组件监听到props变化 => child componentWillReceiveProps</span>  <span class="token comment">//    子组件渲染之前 => componentWillUpdate</span>  <span class="token comment">//    子组件渲染DOM => child-render</span>  <span class="token comment">//    子组件渲染之后 => componentDidUpdate</span>  <span class="token comment">// 7.组件完成更新 componentDidUpdate</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="新版生命周期"><a href="#新版生命周期" class="headerlink" title="新版生命周期"></a>新版生命周期</h4><p><img src="http://img.zhufengpeixun.cn/react16.jpg"></p><ul><li>新版的生命周期去掉了三个钩子<ul><li><font color='red'>componentWillReceiveProps</font></li><li><font color='red'>componentWillMount</font></li><li><font color='red'>componentWillUpdata</font></li></ul></li><li>新增了 2 个 1 个方法<ol><li>getDerivedStateFromProps<ul><li>每当状态、属性变更的时候会执行此方法</li><li>可以通过最新的属性得到最新状态</li><li>使用这个方法就不能在使用 componentWillReceiveProps 跟 componentWillUpdata</li><li><code>查看例子(1)</code></li></ul></li><li>getSnapshotBeforeUpdate<ul><li>getSnapshotBeforeUpdate 被调用于 render 之后，可以读取但是无法使用的 DOM，它可以使你的组件在可能更改之前从 DOM 捕获一些信息例如（滚动位置）</li><li>在没有 getSnapshotBeforeUpdate 之前 react 无法实现的的一个效果</li><li><code>查看例子(2)</code></li></ul></li><li>forceUpdate // 强制更新（当组件的属性和状态都没有改变的时候，我们也想刷新会使用这个）</li></ol></li></ul><p><code>例子(1)：</code></p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 前面要加static，nextProps新的属性对象，prevState代表老的状态对象</span><span class="token keyword">static</span> <span class="token function">getDerivedStateFromProps</span><span class="token punctuation">(</span><span class="token parameter">nextProps<span class="token punctuation">,</span> prevState</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> number <span class="token punctuation">&#125;</span> <span class="token operator">=</span> nextProps<span class="token punctuation">;</span>  <span class="token comment">// 当传入的type发生变化的时候，更新state</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>number <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">number</span><span class="token operator">:</span> number <span class="token operator">*</span> <span class="token number">2</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">number</span><span class="token operator">:</span> number <span class="token operator">*</span> <span class="token number">3</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>例子(2)：旧版的react做固定滚动列表的时候存在个问题，无法固定当前位置(DOM不断的添加scrollTop会不断被卷去)如下图：</code><br><img src="https://static.taishan.qq.com/materials/attachments/junhliao/bfdb2e0b-9ff3-462e-997c-97b0fca97d33.gif"><br><code>我们通过新版中的getSnapshotBeforeUpdate 在render之后获取DOM信息在componentDidUpdate更新后再去设置scrollTop值来解决这个问题</code></p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">//很关键的，我们获取当前rootNode的scrollHeight，传到componentDidUpdate 的参数perScrollHeight</span><span class="token function">getSnapshotBeforeUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>wrapper<span class="token punctuation">.</span>current<span class="token punctuation">.</span>scrollHeight<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token function">componentDidUpdate</span><span class="token punctuation">(</span><span class="token parameter">pervProps<span class="token punctuation">,</span> pervState<span class="token punctuation">,</span> prevScrollHeight</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">//当前向上卷去的高度</span>  <span class="token keyword">const</span> curScrollTop <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>wrapper<span class="token punctuation">.</span>current<span class="token punctuation">.</span>scrollTop<span class="token punctuation">;</span>  <span class="token comment">//当前向上卷去的高度加上增加的内容高度</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>wrapper<span class="token punctuation">.</span>current<span class="token punctuation">.</span>scrollTop <span class="token operator">=</span> curScrollTop <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>wrapper<span class="token punctuation">.</span>current<span class="token punctuation">.</span>scrollHeight <span class="token operator">-</span> prevScrollHeight<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://static.taishan.qq.com/materials/attachments/junhliao/83e601e6-d415-4843-8fe1-a817cbaf4266.gif"></p><ul><li>小知识：<ul><li>为什么请求数据要写在 componentDidMount 里面？<ul><li>因为历史原因（旧版的 react 写的不好）componentWillMount 可能会被执行多次而且方法被废除，只能写在 componentDidMount 里</li></ul></li></ul></li></ul><h4 id="Hooks-中的-useEffect"><a href="#Hooks-中的-useEffect" class="headerlink" title="Hooks 中的 useEffect"></a>Hooks 中的 useEffect</h4><ul><li>可以把 useEffect Hook 看做 componentDidMount，componentDidUpdate 和 componentWillUnmount 这三个函数的组合。这里不做深入介绍，他是执行<a href="http://localhost:8080/myBlog/views/function/2019/0525.html">副作用</a>操作的钩子函数</li></ul><h2 id="事件处理-amp-ref"><a href="#事件处理-amp-ref" class="headerlink" title="事件处理&amp;ref"></a>事件处理&amp;ref</h2><ul><li>React 事件的命名采用小驼峰式（camelCase），而不是纯小写。</li><li>使用 JSX 语法时你需要传入一个函数作为事件处理函数，而不是一个字符串</li><li>你不能通过返回 <code>false</code> 的方式阻止默认行为。你必须显式的使用 <code>preventDefault</code></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Link</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"The link was clicked."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"http://www.baidu.com"</span> onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleClick<span class="token punctuation">&#125;</span><span class="token operator">></span>        Click me      <span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="this"><a href="#this" class="headerlink" title="this"></a>this</h3><ul><li>你必须谨慎对待 JSX 回调函数中的 this,可以使用:<ul><li>公共属性(箭头函数)</li><li>匿名函数</li><li>bind 进行绑定</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">LoggingButton</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"this is:"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function-variable function">handleClick1</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"this is:"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">//onClick=&#123;this.handleClick.bind(this)</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">handleClick</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token operator">></span>Click me<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="向事件处理程序传递参数"><a href="#向事件处理程序传递参数" class="headerlink" title="向事件处理程序传递参数"></a>向事件处理程序传递参数</h3><ul><li>匿名函数</li><li>bind<ul><li>bind 传参数的时候，第二个参数是事件对象 event</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">LoggingButton</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function-variable function">handleClick1</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">id<span class="token punctuation">,</span> event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"id:"</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span><span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token operator">></span>          Click me        <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">handleClick</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token operator">></span>Click me<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="ref"><a href="#ref" class="headerlink" title="ref"></a>ref</h3><ul><li>Refs 提供了一种方式，允许我们访问 DOM 节点或在 render 方法中创建的 React 元素</li></ul><ol><li>ref 值是 <code>字符串</code></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Sum</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function-variable function">handleAdd</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">event</span><span class="token operator">:</span> React<span class="token punctuation">.</span>MouseEvent</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>refs<span class="token punctuation">.</span>a<span class="token punctuation">.</span>value<span class="token punctuation">;</span>    <span class="token keyword">let</span> b <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>refs<span class="token punctuation">.</span>b<span class="token punctuation">.</span>value<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>refs<span class="token punctuation">.</span>c<span class="token punctuation">.</span>value <span class="token operator">=</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span><span class="token operator">></span>        <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token string">"a"</span> <span class="token operator">/</span><span class="token operator">></span><span class="token operator">+</span><span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token string">"b"</span> <span class="token operator">/</span><span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleAdd<span class="token punctuation">&#125;</span><span class="token operator">>=</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>        <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token string">"c"</span> <span class="token operator">/</span><span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="2"><li>ref 值是 <code>函数</code></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Sum</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function-variable function">handleAdd</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">.</span>value<span class="token punctuation">;</span>    <span class="token keyword">let</span> b <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>b<span class="token punctuation">.</span>value<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>result<span class="token punctuation">.</span>value <span class="token operator">=</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span><span class="token operator">></span>        <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">(</span><span class="token parameter">ref</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>a <span class="token operator">=</span> ref<span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token operator">+</span>        <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">(</span><span class="token parameter">ref</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>b <span class="token operator">=</span> ref<span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleAdd<span class="token punctuation">&#125;</span><span class="token operator">>=</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>        <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">(</span><span class="token parameter">ref</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>result <span class="token operator">=</span> ref<span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="3"><li>DOM 元素添加 ref</li></ol><ul><li>可以使用 ref 去存储 DOM 节点的引用</li><li>当 ref 属性用于 HTML 元素时，构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性</li><li>真实 DOM 就是 <code>current</code></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Sum</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>a <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>b <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>result <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function-variable function">handleAdd</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">.</span>current<span class="token punctuation">.</span>value<span class="token punctuation">;</span>    <span class="token keyword">let</span> b <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>b<span class="token punctuation">.</span>current<span class="token punctuation">.</span>value<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>result<span class="token punctuation">.</span>current<span class="token punctuation">.</span>value <span class="token operator">=</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span><span class="token operator">></span>        <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>a<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token operator">+</span><span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>b<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleAdd<span class="token punctuation">&#125;</span><span class="token operator">>=</span><span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>        <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>result<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="4"><li>class 组件添加 Ref</li></ol><ul><li>当 ref 属性用于自定义 class 组件时，ref 对象接收组件的挂载实例作为其 current 属性</li><li>组件实例就是 <code>current</code></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Form</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>input <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function-variable function">getFocus</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">getFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span><span class="token operator">></span>        <span class="token operator">&lt;</span>TextInput ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>getFocus<span class="token punctuation">&#125;</span><span class="token operator">></span>获得焦点<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">class</span> <span class="token class-name">TextInput</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>input <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function-variable function">getFocus</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="5"><li>在函数组件中使用 ref，你可以使用</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">CustomTextInput</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 这里必须声明 textInput，这样 ref 才可以引用它</span>  <span class="token keyword">const</span> textInput <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">function</span> <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    textInput<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div<span class="token operator">></span>      <span class="token operator">&lt;</span>input type<span class="token operator">=</span><span class="token string">"text"</span> ref<span class="token operator">=</span><span class="token punctuation">&#123;</span>textInput<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>      <span class="token operator">&lt;</span>input type<span class="token operator">=</span><span class="token string">"button"</span> value<span class="token operator">=</span><span class="token string">"Focus the text input"</span> onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span>handleClick<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="6"><li>ref 转发</li></ol><ul><li><font color='red'> 你不能在函数组件上使用 ref 属性 </font>，因为他们没有实例</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Form</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>input <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function-variable function">getFocus</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">getFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span><span class="token operator">></span>        <span class="token operator">&lt;</span>TextInput ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>getFocus<span class="token punctuation">&#125;</span><span class="token operator">></span>获得焦点<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// Function components cannot be given refs. Attempts to</span><span class="token comment">// access this ref will fail. Did you mean to use React.forwardRef()?</span><span class="token keyword">function</span> <span class="token function">TextInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>input <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>使用 forwardRef<ul><li>Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧</li><li>Ref 转发允许某些组件接收 ref，并将其向下传递（换句话说，“转发”它）给子组件</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// forwardRef转发函数组件</span><span class="token keyword">const</span> TextInput <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">forwardRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">props<span class="token punctuation">,</span> ref</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">&lt;</span>input ref<span class="token operator">=</span><span class="token punctuation">&#123;</span>ref<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">class</span> <span class="token class-name">Form</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  input<span class="token punctuation">;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>input <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function-variable function">getFocus</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span><span class="token operator">></span>        <span class="token operator">&lt;</span>TextInput ref<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>input<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>getFocus<span class="token punctuation">&#125;</span><span class="token operator">></span>获得焦点<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="条件渲染"><a href="#条件渲染" class="headerlink" title="条件渲染"></a>条件渲染</h2><ul><li>使用 Javascript 运算符 <font color='red'>if</font> 或者 <font color='red'>条件运算符</font> 去创建元素来表现当前的状态。</li></ul><ol><li>if<ul><li>声明一个变量并使用 if 语句进行条件渲染是不错的方式</li></ul></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">UserGreeting</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>h1<span class="token operator">></span>Welcome back<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">GuestGreeting</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>h1<span class="token operator">></span>Please sign up<span class="token punctuation">.</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">Greeting</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> isLoggedIn <span class="token operator">=</span> props<span class="token punctuation">.</span>isLoggedIn<span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>isLoggedIn<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>UserGreeting <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>GuestGreeting <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>ReactDOM<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>  <span class="token comment">// 尝试更改为 isLoggedIn=&#123;true&#125;:</span>  <span class="token operator">&lt;</span>Greeting isLoggedIn<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token boolean">false</span><span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">,</span>  document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"root"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="2"><li>与运算符 &amp;&amp;<ul><li>更为方便地进行元素的条件渲染</li></ul></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Mailbox</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> unreadMessages <span class="token operator">=</span> props<span class="token punctuation">.</span>unreadMessages<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div<span class="token operator">></span>      <span class="token operator">&lt;</span>h1<span class="token operator">></span>Hello<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>      <span class="token punctuation">&#123;</span>unreadMessages<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>        <span class="token operator">&lt;</span>h2<span class="token operator">></span>You have <span class="token punctuation">&#123;</span>unreadMessages<span class="token punctuation">.</span>length<span class="token punctuation">&#125;</span> unread messages<span class="token punctuation">.</span><span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">></span>      <span class="token punctuation">)</span><span class="token punctuation">&#125;</span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> messages <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"React"</span><span class="token punctuation">,</span> <span class="token string">"Re: React"</span><span class="token punctuation">,</span> <span class="token string">"Re:Re: React"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>ReactDOM<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>  <span class="token operator">&lt;</span>Mailbox unreadMessages<span class="token operator">=</span><span class="token punctuation">&#123;</span>messages<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">,</span>  document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"root"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="3"><li>三目运算符<ul><li>另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false。</li></ul></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> isLoggedIn <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>isLoggedIn<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div<span class="token operator">></span>      <span class="token punctuation">&#123;</span>isLoggedIn        <span class="token operator">?</span> <span class="token operator">&lt;</span>LogoutButton onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleLogoutClick<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>        <span class="token operator">:</span> <span class="token operator">&lt;</span>LoginButton onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleLoginClick<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>      <span class="token punctuation">&#125;</span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="4"><li>阻止组件渲染<ul><li>你可能希望能隐藏组件，即使它已经被其他组件渲染。若要完成此操作，你可以让 render 方法直接返回 null，而不进行任何渲染。</li><li>重点：在组件的 render 方法中返回 null 并不会影响组件的生命周期。<font color='red'>（组件只是阻止渲染，而不是销毁）</font></li><li>优化：在做一些选项卡切换的时候，我们并不想直接销毁切换前的一些数据时就会用到这种方式，不建议直接销毁，看如下例子：</li></ul></li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 这样切换时会直接销毁组件，导致重复的走一遍组件生命周期（不推荐）</span><span class="token keyword">function</span> <span class="token function">WarningBanner</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"warning"</span><span class="token operator">></span>Warning<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">class</span> <span class="token class-name">Page</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">showWarning</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>showWaring <span class="token operator">?</span> <span class="token operator">&lt;</span>WarningBanner <span class="token operator">/</span><span class="token operator">></span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// 使用组件返回null并不会影响组件生命周期（推荐）</span><span class="token keyword">function</span> <span class="token function">WarningBanner</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>props<span class="token punctuation">.</span>warn<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"warning"</span><span class="token operator">></span>Warning<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">class</span> <span class="token class-name">Page</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">showWarning</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>handleToggleClick <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">handleToggleClick</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">handleToggleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">state</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span>      <span class="token literal-property property">showWarning</span><span class="token operator">:</span> <span class="token operator">!</span>state<span class="token punctuation">.</span>showWarning<span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span>div<span class="token operator">></span>        <span class="token operator">&lt;</span>WarningBanner warn<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>showWarning<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>        <span class="token operator">&lt;</span>button onClick<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleToggleClick<span class="token punctuation">&#125;</span><span class="token operator">></span>          <span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>showWarning <span class="token operator">?</span> <span class="token string">"Hide"</span> <span class="token operator">:</span> <span class="token string">"Show"</span><span class="token punctuation">&#125;</span>        <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="列表渲染-amp-Key"><a href="#列表渲染-amp-Key" class="headerlink" title="列表渲染&amp;Key"></a>列表渲染&amp;Key</h2><ol><li>常用 map 函数来遍历数组，将数组中的每个元素变成<code>&lt;li&gt;</code>标签，返回新数组进行赋值，然后渲染进 DOM</li></ol><ul><li>如果一个 map() 嵌套了太多层级，那可能就是你提取组件的一个好时机。</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> numbers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> listItems <span class="token operator">=</span> numbers<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">number</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">&lt;</span>li<span class="token operator">></span><span class="token punctuation">&#123;</span>number<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ol start="2"><li>key 帮助 React 识别哪些元素改变了，比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。</li></ol><ul><li>key 只是在兄弟节点之间必须唯一（不需要全局唯一）</li><li>注意：key 应该在遍历的时候进行定义</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> numbers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> listItems <span class="token operator">=</span> numbers<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">number</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>  <span class="token operator">&lt;</span>li key<span class="token operator">=</span><span class="token punctuation">&#123;</span>number<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token operator">></span><span class="token punctuation">&#123;</span>number<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h2 id="状态提升"><a href="#状态提升" class="headerlink" title="状态提升"></a>状态提升</h2><ul><li>多个组件需要反映相同的变化数据，这时我们建议将共享状态提升到最近的共同父组件中去。这就是状态提升</li></ul><h2 id="组合-vs-继承"><a href="#组合-vs-继承" class="headerlink" title="组合 vs 继承"></a>组合 vs 继承</h2><ul><li>包含关系 <font color='red'>children</font><ul><li>有些组件无法提前知晓它们子组件的具体内容，建议使用 children prop 来将他们的子组件传递到渲染结果中</li></ul></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">FancyBorder</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token string">"FancyBorder FancyBorder-"</span> <span class="token operator">+</span> props<span class="token punctuation">.</span>color<span class="token punctuation">&#125;</span><span class="token operator">></span>      <span class="token punctuation">&#123;</span>props<span class="token punctuation">.</span>children<span class="token punctuation">&#125;</span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">WelcomeDialog</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>FancyBorder color<span class="token operator">=</span><span class="token string">"blue"</span><span class="token operator">></span>      <span class="token operator">&lt;</span>h1 className<span class="token operator">=</span><span class="token string">"Dialog-title"</span><span class="token operator">></span>Welcome<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>      <span class="token operator">&lt;</span>p className<span class="token operator">=</span><span class="token string">"Dialog-message"</span><span class="token operator">></span>Thank you <span class="token keyword">for</span> visiting our spacecraft<span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>FancyBorder<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>那么继承呢？<ol><li>在成百上千个组件中使用 React。我们并没有发现需要使用继承来构建组件层次的情况。</li><li>Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意：组件可以接受任意 props，包括基本数据类型，React 元素以及函数。</li><li>如果你想要在组件间复用非 UI 的功能，我们建议将其提取为一个单独的 JavaScript 模块，如函数、对象或者类。组件可以直接引入（import）而无需通过 extend 继承它们。</li></ol></li></ul><h2 id="Context-上下文"><a href="#Context-上下文" class="headerlink" title="Context(上下文)"></a>Context(上下文)</h2><ul><li><p>在某些场景下，你想在整个组件树中传递数据，但却不想手动地在每一层传递属性。你可以直接在 React 中使用强大的 contextAPI 解决上述问题</p></li><li><p>数据是通过 props 属性自上而下（由父及子）进行传递的，但是这种做法对于某些类型的属性而言是极其繁琐的（例如：地区/偏好/UI 主题等）属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式，而不必显式地通过组件树的逐层传递 props。<br><img src="http://img.zhufengpeixun.cn/contextapi.gif"></p></li><li><p>Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据，例如当前认证的用户、主题或首选语言。举个例子：</p></li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>Toolbar theme<span class="token operator">=</span><span class="token string">"dark"</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">Toolbar</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// Toolbar 组件接受一个额外的“theme”属性，然后传递给 ThemedButton 组件。</span>  <span class="token comment">// 如果应用中每一个单独的按钮都需要知道 theme 的值，这会是件很麻烦的事，</span>  <span class="token comment">// 因为必须将这个值层层传递所有组件。</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div<span class="token operator">></span>      <span class="token operator">&lt;</span>ThemedButton theme<span class="token operator">=</span><span class="token punctuation">&#123;</span>props<span class="token punctuation">.</span>theme<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">class</span> <span class="token class-name">ThemedButton</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>Button theme<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>theme<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>使用 context, 我们可以避免通过中间元素传递 props：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Context 可以让我们无须明确地传遍每一个组件，就能将值深入传递进组件树。</span><span class="token comment">// 为当前的 theme 创建一个 context（“light”为默认值）。</span><span class="token keyword">const</span> ThemeContext <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createContext</span><span class="token punctuation">(</span><span class="token string">"light"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。</span>    <span class="token comment">// 无论多深，任何组件都能读取这个值。</span>    <span class="token comment">// 在这个例子中，我们将 “dark” 作为当前的值传递下去。</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>      <span class="token operator">&lt;</span>ThemeContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token string">"dark"</span><span class="token operator">></span>        <span class="token operator">&lt;</span>Toolbar <span class="token operator">/</span><span class="token operator">></span>      <span class="token operator">&lt;</span><span class="token operator">/</span>ThemeContext<span class="token punctuation">.</span>Provider<span class="token operator">></span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// 中间的组件再也不必指明往下传递 theme 了。</span><span class="token keyword">function</span> <span class="token function">Toolbar</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div<span class="token operator">></span>      <span class="token operator">&lt;</span>ThemedButton <span class="token operator">/</span><span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">class</span> <span class="token class-name">ThemedButton</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 指定 contextType 读取当前的 theme context。</span>  <span class="token comment">// React 会往上找到最近的 theme Provider，然后使用它的值。</span>  <span class="token comment">// 在这个例子中，当前的 theme 值为 “dark”。</span>  <span class="token keyword">static</span> contextType <span class="token operator">=</span> ThemeContext<span class="token punctuation">;</span>  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token operator">&lt;</span>Button theme<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>context<span class="token punctuation">&#125;</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>函数组件的使用</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> themes <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">light</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">foreground</span><span class="token operator">:</span> <span class="token string">"#000000"</span><span class="token punctuation">,</span>    <span class="token literal-property property">background</span><span class="token operator">:</span> <span class="token string">"#eeeeee"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token literal-property property">dark</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">foreground</span><span class="token operator">:</span> <span class="token string">"#ffffff"</span><span class="token punctuation">,</span>    <span class="token literal-property property">background</span><span class="token operator">:</span> <span class="token string">"#222222"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">const</span> ThemeContext <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createContext</span><span class="token punctuation">(</span>themes<span class="token punctuation">.</span>light<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">App</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>ThemeContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token punctuation">&#123;</span>themes<span class="token punctuation">.</span>dark<span class="token punctuation">&#125;</span><span class="token operator">></span>      <span class="token operator">&lt;</span>Toolbar <span class="token operator">/</span><span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>ThemeContext<span class="token punctuation">.</span>Provider<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">Toolbar</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div<span class="token operator">></span>      <span class="token operator">&lt;</span>ThemedButton <span class="token operator">/</span><span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">ThemedButton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> theme <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>ThemeContext<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>button style<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">background</span><span class="token operator">:</span> theme<span class="token punctuation">.</span>background<span class="token punctuation">,</span> <span class="token literal-property property">color</span><span class="token operator">:</span> theme<span class="token punctuation">.</span>foreground <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token operator">></span>      <span class="token constant">I</span> am styled by theme context<span class="token operator">!</span>    <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>注意：<ul><li>Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用，因为这会使得组件的复用性变差。</li></ul></li></ul><h2 id="样式写法"><a href="#样式写法" class="headerlink" title="样式写法"></a>样式写法</h2><ol><li>行内样式</li><li>外部样式</li><li>CSS-module</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;JSX&quot;&gt;&lt;a href=&quot;#JSX&quot; class=&quot;headerlink&quot; title=&quot;JSX&quot;&gt;&lt;/a&gt;JSX&lt;/h2&gt;&lt;h3 id=&quot;什么是-JSX&quot;&gt;&lt;a href=&quot;#什么是-JSX&quot; class=&quot;headerlink&quot; title=&quot;什么是 JSX</summary>
      
    
    
    
    <category term="React" scheme="https://liaojunhao.github.io/categories/React/"/>
    
    
    <category term="JSX" scheme="https://liaojunhao.github.io/tags/JSX/"/>
    
    <category term="组件" scheme="https://liaojunhao.github.io/tags/%E7%BB%84%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>函数的惰性载入</title>
    <link href="https://liaojunhao.github.io/2018/12/05/%E5%87%BD%E6%95%B0%E7%9A%84%E6%83%B0%E6%80%A7%E6%80%9D%E6%83%B3/"/>
    <id>https://liaojunhao.github.io/2018/12/05/%E5%87%BD%E6%95%B0%E7%9A%84%E6%83%B0%E6%80%A7%E6%80%9D%E6%83%B3/</id>
    <published>2018-12-05T09:22:10.000Z</published>
    <updated>2026-09-07T06:34:31.046Z</updated>
    
    <content type="html"><![CDATA[<p>这是一个 JS 编程时候常用到的高级技巧，在日常开发中我们会经常用 if 语句判断，将执行引导到正确的代码中，看看下面的 createXHR() 函数：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createXHR</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> XMLHttpRequest <span class="token operator">!=</span> <span class="token string">"undefined"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">XMLHttpRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> ActiveXObject <span class="token operator">!=</span> <span class="token string">"undefined"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString <span class="token operator">!=</span> <span class="token string">"string"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">var</span> versions <span class="token operator">=</span> <span class="token punctuation">[</span>          <span class="token string">"MSXML2.XMLHttp.6.0"</span><span class="token punctuation">,</span>          <span class="token string">"MSXML2.XMLHttp.3.0"</span><span class="token punctuation">,</span>          <span class="token string">"MSXML2.XMLHttp"</span><span class="token punctuation">,</span>        <span class="token punctuation">]</span><span class="token punctuation">,</span>        i<span class="token punctuation">,</span>        len<span class="token punctuation">;</span>      <span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> len <span class="token operator">=</span> versions<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i <span class="token operator">&lt;</span> len<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>          <span class="token keyword">new</span> <span class="token class-name">ActiveXObject</span><span class="token punctuation">(</span>versions<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString <span class="token operator">=</span> versions<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>          <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>ex<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          <span class="token comment">//跳过</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ActiveXObject</span><span class="token punctuation">(</span>arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"No XHR object available."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>每次调用 createxHR() 的时候，它都要对浏览器所支持的能力仔细检查。首先检查内置的 XER, 然后测试有没有基于 Activcx 的 XHR，最后如果都没有发现的话就地出一个错误。每次调用该函数都是 这样，即使每次调用时分支的结果都不变:如果浏览器支持内登 XHR，那么它就一直支持了，那么这种测试就变得没必要了。即使只有一个 if 语句的代码，也肯定比没有 if 语句的慢，所以如果 if 语句不必每次执行，那么代码可以运行地更快一些。解决方案就是称之为惰性载人的技巧。</p><p>可以用下面的方式使用惰性载入重写 <strong>createXHR</strong>：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createXHR</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> XMLHttpRequest <span class="token operator">!=</span> <span class="token string">'undefined'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token function-variable function">createXHR</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">XMLHttpRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> ActiveXObjext <span class="token operator">!=</span> <span class="token string">'undefined'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token function-variable function">createXHR</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString <span class="token operator">!=</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">var</span> versions <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"MSXML2.XMLHttp.6.0"</span><span class="token punctuation">,</span> <span class="token string">"MSXML2.XMLHttp.3.0"</span><span class="token punctuation">,</span> <span class="token string">"MSXML2.XMLHttp"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>i<span class="token punctuation">,</span>len<span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> len <span class="token operator">=</span> versions<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i <span class="token operator">&lt;</span> len<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">try</span> <span class="token punctuation">&#123;</span><span class="token keyword">new</span> <span class="token class-name">ActiveXObject</span><span class="token punctuation">(</span>versions<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString <span class="token operator">=</span> versions<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">// skip</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ActiveXObject</span><span class="token punctuation">(</span>arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span><span class="token function-variable function">createXHR</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'No XHR object available.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> <span class="token function">createXHR</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在这个惰性载入的 createXHR 中，if 语句的每一个分支都会为 createXHR 变量赋值，有覆盖原有的函数。最后一步便是调用新赋的函数。下一次调用 createXHR 的时候，就会直接调用被分配的函数，这样就不用再次执行 if 语句了。</p><p>第二种实现惰性载入的方式是在声明函数时就指定适当的函数。这样，第一次调用函数时就不会损失性能，而在代码首次加载时会损失一点点性能而已，以下就是按照这个思路重写的例子：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">var</span> createXHR <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> XMLHttpRequest <span class="token operator">!=</span> <span class="token string">'undefined'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">XMLHttpRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> ActiveXObjext <span class="token operator">!=</span> <span class="token string">'undefined'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString <span class="token operator">!=</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">var</span> versions <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"MSXML2.XMLHttp.6.0"</span><span class="token punctuation">,</span> <span class="token string">"MSXML2.XMLHttp.3.0"</span><span class="token punctuation">,</span> <span class="token string">"MSXML2.XMLHttp"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">var</span> i<span class="token punctuation">;</span>        <span class="token keyword">var</span> len<span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> len <span class="token operator">=</span> versions<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i <span class="token operator">&lt;</span> len<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">try</span> <span class="token punctuation">&#123;</span><span class="token keyword">new</span> <span class="token class-name">ActiveXObject</span><span class="token punctuation">(</span>versions<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString <span class="token operator">=</span> versions<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">// skip</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ActiveXObject</span><span class="token punctuation">(</span>arguments<span class="token punctuation">.</span>callee<span class="token punctuation">.</span>activeXString<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span><span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'No XHR object available.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这个例子使用的自执行函数 IIFE，用以确定应该使用哪一个函数实现。实际的逻辑都一样，不一样的地方就是第一行代码（使用 var 定义函数）新增了自执行的匿名函数，另外每个分支都返回正确的函数定义，以便立即将其赋值给 createXHR。</p><p><strong>总结</strong></p><blockquote><p>惰性载入函数是种编程优化的思想，只是在执行分支的时候牺牲一点点性能，就可以达到避免重复执行不必要代码的作用，在日常开发中是很常用到的一种高级函数技巧。</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这是一个 JS 编程时候常用到的高级技巧，在日常开发中我们会经常用 if 语句判断，将执行引导到正确的代码中，看看下面的 createXHR() 函数：&lt;/p&gt;
&lt;pre class=&quot;line-numbers language-javascript&quot; data-langu</summary>
      
    
    
    
    <category term="JavaScript系列" scheme="https://liaojunhao.github.io/categories/JavaScript%E7%B3%BB%E5%88%97/"/>
    
    
    <category term="函数" scheme="https://liaojunhao.github.io/tags/%E5%87%BD%E6%95%B0/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript代码优化技巧</title>
    <link href="https://liaojunhao.github.io/2018/07/03/JavaScript%E4%BB%A3%E7%A0%81%E4%BC%98%E5%8C%96%E6%8A%80%E5%B7%A7/"/>
    <id>https://liaojunhao.github.io/2018/07/03/JavaScript%E4%BB%A3%E7%A0%81%E4%BC%98%E5%8C%96%E6%8A%80%E5%B7%A7/</id>
    <published>2018-07-03T02:08:03.000Z</published>
    <updated>2026-09-07T06:34:32.029Z</updated>
    
    <content type="html"><![CDATA[<h2 id="if-多条件判断"><a href="#if-多条件判断" class="headerlink" title="if-多条件判断"></a>if-多条件判断</h2><p>在 if 多条件判断的情况下建议使用 include 方法。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> day <span class="token operator">=</span> <span class="token string">"星期二"</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>day <span class="token operator">===</span> <span class="token string">"星期二"</span> <span class="token operator">||</span> day <span class="token operator">===</span> <span class="token string">"星期三"</span> <span class="token operator">||</span> day <span class="token operator">===</span> <span class="token string">"星期四"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>day<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"星期二"</span><span class="token punctuation">,</span> <span class="token string">"星期三"</span><span class="token punctuation">,</span> <span class="token string">"星期四"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>day<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>day<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="简写-if…else"><a href="#简写-if…else" class="headerlink" title="简写 if…else"></a>简写 if…else</h2><p>当有不包含复杂逻辑的使用 if…else 条件时，可以三元运算符来简化。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">let</span> test<span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  test <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>  test <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span><span class="token keyword">let</span> test <span class="token operator">=</span> x <span class="token operator">></span> <span class="token number">10</span> <span class="token operator">?</span> <span class="token boolean">true</span> <span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token comment">// 或简写如下</span><span class="token keyword">let</span> test <span class="token operator">=</span> x <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="合并变量声明"><a href="#合并变量声明" class="headerlink" title="合并变量声明"></a>合并变量声明</h2><p>当要声明两个具有相同值或相同类型的变量时，可以使用简写方式。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">let</span> number1<span class="token punctuation">;</span><span class="token keyword">let</span> number2 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">// 优化后</span><span class="token keyword">let</span> number1<span class="token punctuation">,</span>  number2 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="合并变量赋值"><a href="#合并变量赋值" class="headerlink" title="合并变量赋值"></a>合并变量赋值</h2><p>当处理多个变量并进行赋值的时候，使用“解构”可以使得代码更加友好。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">let</span> test1<span class="token punctuation">,</span> test2<span class="token punctuation">,</span> test3<span class="token punctuation">;</span>test1 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>test2 <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>test3 <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span><span class="token comment">// 优化后</span><span class="token keyword">let</span> <span class="token punctuation">[</span>test1<span class="token punctuation">,</span> test2<span class="token punctuation">,</span> test3<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="运算符简写"><a href="#运算符简写" class="headerlink" title="运算符简写"></a>运算符简写</h2><p>在项目开发中，要处理大量的算术运算符，很多方式可以进行简写。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span>test1 <span class="token operator">=</span> test1 <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>test2 <span class="token operator">=</span> test2 <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>test3 <span class="token operator">=</span> test3 <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token comment">// 长</span><span class="token keyword">if</span> <span class="token punctuation">(</span>test1<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">callMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span>test1<span class="token operator">++</span><span class="token punctuation">;</span>test2<span class="token operator">--</span><span class="token punctuation">;</span>test3 <span class="token operator">*=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token comment">// 短</span>test1 <span class="token operator">&amp;&amp;</span> <span class="token function">callMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="将数字四舍五入到固定的小数点"><a href="#将数字四舍五入到固定的小数点" class="headerlink" title="将数字四舍五入到固定的小数点"></a>将数字四舍五入到固定的小数点</h2><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">toFixed</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">n<span class="token punctuation">,</span> fixed</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">~</span><span class="token operator">~</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> fixed<span class="token punctuation">)</span> <span class="token operator">*</span> n<span class="token punctuation">)</span> <span class="token operator">/</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> fixed<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="for-循环的简写"><a href="#for-循环的简写" class="headerlink" title="for 循环的简写"></a>for 循环的简写</h2><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> testData<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token comment">// 优化后</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token keyword">in</span> testData<span class="token punctuation">)</span> or  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token keyword">of</span> testData<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果是数组，可以使用 <strong>foreach</strong>。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">[</span><span class="token number">11</span><span class="token punctuation">,</span> <span class="token number">24</span><span class="token punctuation">,</span> <span class="token number">32</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">element<span class="token punctuation">,</span> index<span class="token punctuation">,</span> array</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"test["</span> <span class="token operator">+</span> index <span class="token operator">+</span> <span class="token string">"] = "</span> <span class="token operator">+</span> element<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h2 id="条件返回优化"><a href="#条件返回优化" class="headerlink" title="条件返回优化"></a>条件返回优化</h2><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">let</span> test<span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">callMe</span><span class="token punctuation">(</span><span class="token parameter">val</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">checkReturn</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>test <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> test<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token function">callMe</span><span class="token punctuation">(</span><span class="token string">"test"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">var</span> data <span class="token operator">=</span> <span class="token function">checkReturn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 优化后</span><span class="token keyword">function</span> <span class="token function">checkReturn</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> test <span class="token operator">||</span> <span class="token function">callMe</span><span class="token punctuation">(</span><span class="token string">"test"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="多用箭头函数"><a href="#多用箭头函数" class="headerlink" title="多用箭头函数"></a>多用箭头函数</h2><p>在项目中还是建议多用箭头函数，简洁而且可以避免 this 的问题。<br>还可以隐式回调，使用箭头函数，可以直接返回值，而不必使用 return 语句。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">function</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">+</span> b<span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">callMe</span><span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Hello"</span><span class="token punctuation">,</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 箭头函数的简写</span><span class="token function-variable function">callMe</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Hello"</span><span class="token punctuation">,</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="条件调用"><a href="#条件调用" class="headerlink" title="条件调用"></a>条件调用</h2><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">function</span> <span class="token function">test1</span><span class="token punctuation">(</span><span class="token parameter">title</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>title<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">test2</span><span class="token punctuation">(</span><span class="token parameter">title</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"我的名称是："</span> <span class="token operator">+</span> title<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">var</span> test3 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token keyword">var</span> title <span class="token operator">=</span> <span class="token string">"DevPoint"</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>test3 <span class="token operator">===</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">test1</span><span class="token punctuation">(</span>title<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>  <span class="token function">test2</span><span class="token punctuation">(</span>title<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span><span class="token punctuation">(</span>test3 <span class="token operator">===</span> <span class="token number">1</span> <span class="token operator">?</span> test1 <span class="token operator">:</span> test2<span class="token punctuation">)</span><span class="token punctuation">(</span>title<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="可以少用-switch"><a href="#可以少用-switch" class="headerlink" title="可以少用 switch"></a>可以少用 switch</h2><p>优化的方式可以将条件保存在键值对象中，根据条件调用。可以少点判断，节省那一丢丢的性能。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">switch</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">case</span> <span class="token number">1</span><span class="token operator">:</span>    <span class="token function">test1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token keyword">case</span> <span class="token number">2</span><span class="token operator">:</span>    <span class="token function">test2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token keyword">case</span> <span class="token number">3</span><span class="token operator">:</span>    <span class="token function">test</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span><span class="token keyword">var</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token number">1</span><span class="token operator">:</span> test1<span class="token punctuation">,</span>  <span class="token number">2</span><span class="token operator">:</span> test2<span class="token punctuation">,</span>  <span class="token number">3</span><span class="token operator">:</span> test<span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>data<span class="token punctuation">[</span>something<span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> data<span class="token punctuation">[</span>something<span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="十进制数值指数优化"><a href="#十进制数值指数优化" class="headerlink" title="十进制数值指数优化"></a>十进制数值指数优化</h2><p>这种感觉必要性不大</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">1e4</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="参数默认值"><a href="#参数默认值" class="headerlink" title="参数默认值"></a>参数默认值</h2><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">function</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token parameter">test1<span class="token punctuation">,</span> test2</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>test1 <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    test1 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>test2 <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    test2 <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> test1 <span class="token operator">+</span> test2<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span><span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">test1 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> test2 <span class="token operator">=</span> <span class="token number">2</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> test1 <span class="token operator">+</span> test2<span class="token punctuation">;</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="…运算符"><a href="#…运算符" class="headerlink" title="…运算符"></a>…运算符</h2><p>展开运算符**_…_**是一个 es6 / es2015 特性，它提供了一种非常方便的方式来执行浅拷贝，这与 **Object.assign()**的功能相同。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> test <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 优化后</span><span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> test <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token operator">...</span>data<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment">// 数组对象浅拷贝</span><span class="token keyword">const</span> map1 <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">"DevPoint"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  map2 <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">info</span><span class="token operator">:</span> <span class="token string">"Develop"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">const</span> newMap <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token operator">...</span>map1<span class="token punctuation">,</span> <span class="token operator">...</span>map2 <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>newMap<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// &#123; title: 'DevPoint', info: 'Develop' &#125;</span><span class="token keyword">const</span> test1 <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> test2 <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>test1<span class="token punctuation">]</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="多用模板字符串"><a href="#多用模板字符串" class="headerlink" title="多用模板字符串"></a>多用模板字符串</h2><p>对于拼接字符串很实用，可以更加友好。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">const</span> welcome <span class="token operator">=</span> <span class="token string">"Hi "</span> <span class="token operator">+</span> test1 <span class="token operator">+</span> <span class="token string">" "</span> <span class="token operator">+</span> test2 <span class="token operator">+</span> <span class="token string">"."</span><span class="token punctuation">;</span><span class="token comment">// 优化后</span><span class="token keyword">const</span> welcome <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hi </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>test1<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>test2<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>多行字符串的拼接。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">const</span> data <span class="token operator">=</span>  <span class="token string">"abc abc abc abc abc abc\n\t"</span> <span class="token operator">+</span> <span class="token string">"test test,test test test test\n\t"</span><span class="token punctuation">;</span><span class="token comment">// 优化后</span><span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">abc abc abc abc abc abc         test test,test test test test</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="对象属性赋值"><a href="#对象属性赋值" class="headerlink" title="对象属性赋值"></a>对象属性赋值</h2><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">let</span> test1 <span class="token operator">=</span> <span class="token string">"a"</span><span class="token punctuation">;</span><span class="token keyword">let</span> test2 <span class="token operator">=</span> <span class="token string">"b"</span><span class="token punctuation">;</span><span class="token comment">// 优化前</span><span class="token keyword">let</span> obj <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">test1</span><span class="token operator">:</span> test1<span class="token punctuation">,</span> <span class="token literal-property property">test2</span><span class="token operator">:</span> test2 <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token comment">// 优化后</span><span class="token keyword">let</span> obj <span class="token operator">=</span> <span class="token punctuation">&#123;</span> test1<span class="token punctuation">,</span> test2 <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="变量的解构赋值"><a href="#变量的解构赋值" class="headerlink" title="变量的解构赋值"></a>变量的解构赋值</h2><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">const</span> test1 <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>data<span class="token punctuation">.</span>test1<span class="token punctuation">;</span><span class="token keyword">const</span> test2 <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>data<span class="token punctuation">.</span>test2<span class="token punctuation">;</span><span class="token keyword">const</span> test2 <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>data<span class="token punctuation">.</span>test3<span class="token punctuation">;</span><span class="token comment">// 优化后</span><span class="token keyword">const</span> <span class="token punctuation">&#123;</span> test1<span class="token punctuation">,</span> test2<span class="token punctuation">,</span> test3 <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>data<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>变量的解构赋值是 es6/es2015 新增的能力，是非常有用的，特别是我曾经遇到过这样的面试题：</p><p>如何提取高度嵌套的对象里的指定属性？</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 有时会遇到一些嵌套程度非常深的对象：</span><span class="token keyword">const</span> school <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">classes</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">stu</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"Bob"</span><span class="token punctuation">,</span>      <span class="token literal-property property">age</span><span class="token operator">:</span> <span class="token number">24</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token comment">// 更标准的做法 ，可以用一行代码来解决这个问题：</span><span class="token keyword">const</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">classes</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">stu</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span> name <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span> <span class="token operator">=</span> school<span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 'Bob'</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></blockquote><h2 id="Array-find"><a href="#Array-find" class="headerlink" title="Array.find"></a>Array.find</h2><p>当确实有一个对象数组并且我们想要根据对象属性查找特定对象时，find 方法确实很有用。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">[</span>  <span class="token punctuation">&#123;</span>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"test1"</span><span class="token punctuation">,</span>    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"abc"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#123;</span>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"test2"</span><span class="token punctuation">,</span>    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"cde"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#123;</span>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"test1"</span><span class="token punctuation">,</span>    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"fgh"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">findtest1</span><span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> data<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>data<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">"test1"</span> <span class="token operator">&amp;&amp;</span> data<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>name <span class="token operator">===</span> name<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span> data<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span>filteredData <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>  <span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> data<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">"test1"</span> <span class="token operator">&amp;&amp;</span> data<span class="token punctuation">.</span>name <span class="token operator">===</span> <span class="token string">"fgh"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>filteredData<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// &#123; type: 'test1', name: 'fgh' &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="条件执行"><a href="#条件执行" class="headerlink" title="条件执行"></a>条件执行</h2><p>条件执行跟条件调用很像，如果有代码来检查类型，并且基于类型需要调用不同的方法，可以选择使用多个 else if 或进行切换，可以使用对象键值优化。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 优化前</span><span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">"test1"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">test1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">"test2"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">test2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">"test3"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">test3</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">"test4"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">test4</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Invalid value "</span> <span class="token operator">+</span> type<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 优化后</span><span class="token keyword">var</span> types <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">test1</span><span class="token operator">:</span> test1<span class="token punctuation">,</span>  <span class="token literal-property property">test2</span><span class="token operator">:</span> test2<span class="token punctuation">,</span>  <span class="token literal-property property">test3</span><span class="token operator">:</span> test3<span class="token punctuation">,</span>  <span class="token literal-property property">test4</span><span class="token operator">:</span> test4<span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">var</span> func <span class="token operator">=</span> types<span class="token punctuation">[</span>type<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token operator">!</span>func <span class="token operator">&amp;&amp;</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Invalid value "</span> <span class="token operator">+</span> type<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="避免全局变量"><a href="#避免全局变量" class="headerlink" title="避免全局变量"></a>避免全局变量</h2><p>创建全局变量被认为是糟糕的实践，尤其在团队开发的大背景下更是问题多多。随着代码量的增长，全局变量会导致一些非常重要的可维护性难题，全局变量越多，引入错误的概率会变得越高</p><p>一般而言，有如下三种解决办法</p><ul><li>零全局变量：实现方法是使用一个立即调用函数 IIFE 并将所有脚本放置其中</li><li>单全局变量和命名空间</li><li>用模块</li></ul><h2 id="事件处理"><a href="#事件处理" class="headerlink" title="事件处理"></a>事件处理</h2><p>将事件处理相关的代码和事件环境耦合在一起，导致可维护性很糟糕</p><ol><li>隔离应用逻辑<br>将应用逻辑从所有事件处理程序中抽离出来是一种最佳实践，将应用逻辑和事件处理的代码拆分开来</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">//不好的做法</span><span class="token keyword">function</span> <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> popup <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"popup"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  popup<span class="token punctuation">.</span>style<span class="token punctuation">.</span>left <span class="token operator">=</span> event<span class="token punctuation">.</span>clientX <span class="token operator">+</span> <span class="token string">"px"</span><span class="token punctuation">;</span>  popup<span class="token punctuation">.</span>style<span class="token punctuation">.</span>top <span class="token operator">=</span> event<span class="token punctuation">.</span>clientY <span class="token operator">+</span> <span class="token string">"px"</span><span class="token punctuation">;</span>  popup<span class="token punctuation">.</span>className <span class="token operator">=</span> <span class="token string">"reveal"</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token function">addListener</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> <span class="token string">"click"</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//好的做法</span><span class="token keyword">var</span> MyApplication <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token function-variable function">handleClick</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">showPopup</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token function-variable function">showPopup</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">var</span> popup <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"popup"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>style<span class="token punctuation">.</span>left <span class="token operator">=</span> event<span class="token punctuation">.</span>clientX <span class="token operator">+</span> <span class="token string">"px"</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>style<span class="token punctuation">.</span>top <span class="token operator">=</span> event<span class="token punctuation">.</span>clientY <span class="token operator">+</span> <span class="token string">"px"</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>className <span class="token operator">=</span> <span class="token string">"reveal"</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token function">addListener</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> <span class="token string">"click"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  MyApplication<span class="token punctuation">.</span><span class="token function">handleClick</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="2"><li>不要分发事件对象<br>应用逻辑不应当依赖于 event 对象来正确完成功能，方法接口应该表明哪些数据是必要的。代码不清晰就会导致 bug。最好的办法是让事件处理程序使用 event 对象来处理事件，然后拿到所有需要的数据传给应用逻辑</li></ol><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">//改进的做法</span><span class="token keyword">var</span> MyApplication <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token function-variable function">handleClick</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">showPopup</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> event<span class="token punctuation">.</span>clientY<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token function-variable function">showPopup</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">x<span class="token punctuation">,</span> y</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">var</span> popup <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"popup"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>style<span class="token punctuation">.</span>left <span class="token operator">=</span> x <span class="token operator">+</span> <span class="token string">"px"</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>style<span class="token punctuation">.</span>top <span class="token operator">=</span> y <span class="token operator">+</span> <span class="token string">"px"</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>className <span class="token operator">=</span> <span class="token string">"reveal"</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token function">addListener</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> <span class="token string">"click"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  MyApplication<span class="token punctuation">.</span><span class="token function">handleClick</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>当处理事件时，最好让事件程序成为接触到 event 对象的唯一的函数。事件处理程序应当在进入应用逻辑之前针对 event 对象执行任何必要的操作，包括阻止事件冒泡，都应当直接包含在事件处理程序中</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">//改进的做法</span><span class="token keyword">var</span> MyApplication <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token function-variable function">handleClick</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    event<span class="token punctuation">.</span><span class="token function">stopPropagation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">showPopup</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> event<span class="token punctuation">.</span>clientY<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token function-variable function">showPopup</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">x<span class="token punctuation">,</span> y</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">var</span> popup <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"popup"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>style<span class="token punctuation">.</span>left <span class="token operator">=</span> x <span class="token operator">+</span> <span class="token string">"px"</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>style<span class="token punctuation">.</span>top <span class="token operator">=</span> y <span class="token operator">+</span> <span class="token string">"px"</span><span class="token punctuation">;</span>    popup<span class="token punctuation">.</span>className <span class="token operator">=</span> <span class="token string">"reveal"</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token function">addListener</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> <span class="token string">"click"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  MyApplication<span class="token punctuation">.</span><span class="token function">handleClick</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="配置数据"><a href="#配置数据" class="headerlink" title="配置数据"></a>配置数据</h2><p>可能会变化的全局变量应该从代码中抽离出来</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">//好的做法</span><span class="token keyword">var</span> config <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token constant">MSG_INVALID_VALUE</span><span class="token operator">:</span> <span class="token string">"Invalid value"</span><span class="token punctuation">,</span>  <span class="token constant">URL_INVALID</span><span class="token operator">:</span> <span class="token string">"/errors/invalid.php"</span><span class="token punctuation">,</span>  <span class="token constant">CSS_SELECTED</span><span class="token operator">:</span> <span class="token string">"selected"</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">validate</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>value<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">alert</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span><span class="token constant">MSG_INVALID_VALUE</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    location<span class="token punctuation">.</span>href <span class="token operator">=</span> config<span class="token punctuation">.</span><span class="token constant">URL_INVALID</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">toggleSelected</span><span class="token punctuation">(</span><span class="token parameter">element</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">hasClass</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> config<span class="token punctuation">.</span><span class="token constant">CSS_SELECTED</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">removeClass</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> config<span class="token punctuation">.</span><span class="token constant">CSS_SELECTED</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    <span class="token function">addClass</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> config<span class="token punctuation">.</span><span class="token constant">CSS_SELECTED</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="函数优化"><a href="#函数优化" class="headerlink" title="函数优化"></a>函数优化</h2><p>在 javascript 开发中，大部分时间都在与函数打交道，所以希望这些函数有着良好的命名，函数体内包含的逻辑清晰明了。如果一个函数过长，不得不加上若干注释才能让这个函数显得易读一些，那这些函数就很有必要进行重构</p><p>如果在函数中有一段代码可以被独立出来，那最好把这些代码放进另外一个独立的函数中。这是一种很常见的优化工作，这样做的好处主要有以下几点：</p><ol><li>避免出现超大函数</li><li>独立出来的函数有助于代码复用</li><li>独立出来的函数更容易被覆写</li><li>独立出来的函数如果拥有一个良好的命名，它本身就起到了注释的作用</li></ol><p>比如在一个负责取得用户信息的函数里面，还需要打印跟用户信息有关的 log，那么打印 log 的语句就可以被封装在一个独立的函数里：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">var</span> <span class="token function-variable function">getUserInfo</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">ajax</span><span class="token punctuation">(</span><span class="token string">"http:// xxx.com/userInfo"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"userId: "</span> <span class="token operator">+</span> data<span class="token punctuation">.</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"userName: "</span> <span class="token operator">+</span> data<span class="token punctuation">.</span>userName<span class="token punctuation">)</span><span class="token punctuation">;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"nickName: "</span> <span class="token operator">+</span> data<span class="token punctuation">.</span>nickName<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token comment">//改成：</span><span class="token keyword">var</span> <span class="token function-variable function">getUserInfo</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">ajax</span><span class="token punctuation">(</span><span class="token string">"http:// xxx.com/userInfo"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">printDetails</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">var</span> <span class="token function-variable function">printDetails</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"userId: "</span> <span class="token operator">+</span> data<span class="token punctuation">.</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"userName: "</span> <span class="token operator">+</span> data<span class="token punctuation">.</span>userName<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"nickName: "</span> <span class="token operator">+</span> data<span class="token punctuation">.</span>nickName<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="尽量减少参数数量"><a href="#尽量减少参数数量" class="headerlink" title="尽量减少参数数量"></a>尽量减少参数数量</h3><p>如果调用一个函数时需要传入多个参数，那这个函数是让人望而生畏的，必须搞清楚这些参数代表的含义，必须小心翼翼地把它们按照顺序传入该函数。在实际开发中，向函数传递参数不可避免，但应该尽量减少函数接收的参数数量。下面举个非常简单的示例。有一个画图函数 draw，它现在只能绘制正方形，接收了 3 个参数，分别是图形的 width、heigth 以及 square：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">var</span> <span class="token function-variable function">draw</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">width<span class="token punctuation">,</span> height<span class="token punctuation">,</span> square</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>但实际上正方形的面积是可以通过 width 和 height 计算出来的，于是我们可以把参数 square 从 draw 函数中去掉：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">var</span> <span class="token function-variable function">draw</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">width<span class="token punctuation">,</span> height</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> square <span class="token operator">=</span> width <span class="token operator">*</span> height<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>假设以后这个 draw 函数开始支持绘制圆形，需要把参数 width 和 height 换成半径 radius，但图形的面积 square 始终不应该由客户传入，而是应该在 draw 函数内部，由传入的参数加上一定的规则计算得来。此时，可以使用策略模式，让 draw 函数成为一个支持绘制多种图形的函数</p><h3 id="传递对象参数代替过长的参数列表"><a href="#传递对象参数代替过长的参数列表" class="headerlink" title="传递对象参数代替过长的参数列表"></a>传递对象参数代替过长的参数列表</h3><p>有时候一个函数有可能接收多个参数，而参数的数量越多，函数就越难理解和使用。使用该函数的人首先得搞明白全部参数的含义，在使用的时候，还要小心翼翼，以免少传了某个参数或者把两个参数搞反了位置。如果想在第 3 个参数和第 4 个参数之中增加一个新的参数，就会涉及许多代码的修改，代码如下：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">var</span> <span class="token function-variable function">setUserInfo</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">id<span class="token punctuation">,</span> name<span class="token punctuation">,</span> address<span class="token punctuation">,</span> sex<span class="token punctuation">,</span> mobile<span class="token punctuation">,</span> qq</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"id= "</span> <span class="token operator">+</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"name= "</span> <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"address= "</span> <span class="token operator">+</span> address<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"sex= "</span> <span class="token operator">+</span> sex<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"mobile= "</span> <span class="token operator">+</span> mobile<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"qq= "</span> <span class="token operator">+</span> qq<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token function">setUserInfo</span><span class="token punctuation">(</span><span class="token number">1314</span><span class="token punctuation">,</span> <span class="token string">"xiaohuochai"</span><span class="token punctuation">,</span> <span class="token string">"beijing"</span><span class="token punctuation">,</span> <span class="token string">"male"</span><span class="token punctuation">,</span> <span class="token string">"150********"</span><span class="token punctuation">,</span> <span class="token number">121631835</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这时可以把参数都放入一个对象内，然后把该对象传入 setUserInfo 函数，setUserInfo 函数需要的数据可以自行从该对象里获取。现在不用再关心参数的数量和顺序，只要保证参数对应的 key 值不变就可以了：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">var</span> <span class="token function-variable function">setUserInfo</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">obj</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"id= "</span> <span class="token operator">+</span> obj<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"name= "</span> <span class="token operator">+</span> obj<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"address= "</span> <span class="token operator">+</span> obj<span class="token punctuation">.</span>address<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"sex= "</span> <span class="token operator">+</span> obj<span class="token punctuation">.</span>sex<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"mobile= "</span> <span class="token operator">+</span> obj<span class="token punctuation">.</span>mobile<span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"qq= "</span> <span class="token operator">+</span> obj<span class="token punctuation">.</span>qq<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token function">setUserInfo</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">1314</span><span class="token punctuation">,</span>  <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"xiaohuochai"</span><span class="token punctuation">,</span>  <span class="token literal-property property">address</span><span class="token operator">:</span> <span class="token string">"beijing"</span><span class="token punctuation">,</span>  <span class="token literal-property property">sex</span><span class="token operator">:</span> <span class="token string">"male"</span><span class="token punctuation">,</span>  <span class="token literal-property property">mobile</span><span class="token operator">:</span> <span class="token string">"150********"</span><span class="token punctuation">,</span>  <span class="token literal-property property">qq</span><span class="token operator">:</span> <span class="token number">121631835</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>但是在在一个函数里，改变传入的对象本身是不好的，因为对象是引用数据类型，如果外部意外修改了对象的属性，会引发一些不必要的 bug。</p></blockquote><p>函数的优化知识点还有很多，像惰性思想、递归尾调用等，后续在拿出来单独的篇章讲解。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;if-多条件判断&quot;&gt;&lt;a href=&quot;#if-多条件判断&quot; class=&quot;headerlink&quot; title=&quot;if-多条件判断&quot;&gt;&lt;/a&gt;if-多条件判断&lt;/h2&gt;&lt;p&gt;在 if 多条件判断的情况下建议使用 include 方法。&lt;/p&gt;
&lt;pre class=</summary>
      
    
    
    
    <category term="JavaScript系列" scheme="https://liaojunhao.github.io/categories/JavaScript%E7%B3%BB%E5%88%97/"/>
    
    
    <category term="性能优化" scheme="https://liaojunhao.github.io/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    <category term="最佳实践" scheme="https://liaojunhao.github.io/tags/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript函数重载</title>
    <link href="https://liaojunhao.github.io/2017/09/21/JavaScript%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD/"/>
    <id>https://liaojunhao.github.io/2017/09/21/JavaScript%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD/</id>
    <published>2017-09-21T06:56:19.000Z</published>
    <updated>2026-09-07T06:34:32.853Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>译者按: jQuery 之父 John Resig 巧妙地利用了闭包，实现了 JavaScript 函数重载。</p></blockquote><p>而所谓<strong>函数重载</strong>：就是函数名称一样，输入不一样，输出也就不一样。或者说，允许某个函数有各种不同输入，根据不同的输入，调用不同的函数，然后返回不同的结果。</p><p>但是 JS 中函数不能像传统编程那样重载。在其他语言比如 Java 中，一个函数可以有两个定义， 只要签名(接收参数的类型和数量)不同就行。如前所述，ECMAScript 函数没有签名，因为参数是由 包含零个或多个值的数组表示的。没有函数签名，自然也就没有重载。<strong>所以 JavaScript 中没有真正意义上的函数重载</strong></p><p>如果在 ECMAScript 中定义了两个同名函数，则后定义的会覆盖先定义的。来看下面的例子:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">addSomeNumber</span><span class="token punctuation">(</span><span class="token parameter">num</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> num <span class="token operator">+</span> <span class="token number">100</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">addSomeNumber</span><span class="token punctuation">(</span><span class="token parameter">num</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> num <span class="token operator">+</span> <span class="token number">200</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token function">addSomeNumber</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 300</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里，函数 addSomeNumber()被定义了两次。第一个版本给参数加 100，第二个版本加 200。最 后一行调用这个函数时，返回了 300，因为第二个定义覆盖了第一个定义。</p><h2 id="1-模拟函数重载"><a href="#1-模拟函数重载" class="headerlink" title="1.模拟函数重载"></a>1.模拟函数重载</h2><p>一般来说 JS 可以通过 if…else 或者 switch 来区分不同的逻辑实现模拟函数重载的效果。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">overload</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>arguments<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"一个参数"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>arguments<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"两个参数"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token function">overload</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//一个参数</span><span class="token function">overload</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//两个参数</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>凭直觉，函数重载可以通过 if…else 或者 switch 实现，这就不去管它了。但是有一次面试中，被说到不要使用这么多的 if…else，当时觉得很奇怪也不知道是性能问题还是阅读性问题，不让使用这么多，于是就好奇怎么可以不通过 if…else 来实现函数的重载。</p><h2 id="2-模拟函数重载-利用闭包"><a href="#2-模拟函数重载-利用闭包" class="headerlink" title="2.模拟函数重载 - 利用闭包"></a>2.模拟函数重载 - 利用闭包</h2><p>直到看到 jQuery 之父 John Resig 提出了一个非常巧(bian)妙(tai)的方法，利用了闭包。</p><p>这个例子是 John Resig 写他的书 《secrets of the JavaScript ninja》第一版中都有提到过，在书中的第 4 章中也有讲解 Function overloading，文中的 addMethod 函数 就是书中的例子 4.15，感兴趣的朋友可以去看看，但是它很代码量很少、很简单，巧妙地使用了 JavaScript 的特性。</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">addMethod</span><span class="token punctuation">(</span><span class="token parameter">object<span class="token punctuation">,</span> name<span class="token punctuation">,</span> fn</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 先把原来的object[name] 方法，保存在old中</span>  <span class="token keyword">var</span> old <span class="token operator">=</span> object<span class="token punctuation">[</span>name<span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token comment">// 重新定义 object[name] 方法</span>  object<span class="token punctuation">[</span>name<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 如果函数需要的参数 和 实际传入的参数 的个数相同，就直接调用fn</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>fn<span class="token punctuation">.</span>length <span class="token operator">==</span> arguments<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> arguments<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 如果不相同,判断old 是不是函数，</span>    <span class="token comment">// 如果是就调用old，也就是刚才保存的 object[name] 方法</span>    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> old <span class="token operator">==</span> <span class="token string">"function"</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">old</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> arguments<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 不传参数时，返回所有name</span><span class="token keyword">function</span> <span class="token function">find0</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>names<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 传一个参数时，返回firstName匹配的name</span><span class="token keyword">function</span> <span class="token function">find1</span><span class="token punctuation">(</span><span class="token parameter">firstName</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> result <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>names<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>names<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span>firstName<span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      result<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>names<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> result<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 传两个参数时，返回firstName和lastName都匹配的name</span><span class="token keyword">function</span> <span class="token function">find2</span><span class="token punctuation">(</span><span class="token parameter">firstName<span class="token punctuation">,</span> lastName</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> result <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>names<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>names<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">===</span> firstName <span class="token operator">+</span> <span class="token string">" "</span> <span class="token operator">+</span> lastName<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      result<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>names<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> result<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">Users</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">addMethod</span><span class="token punctuation">(</span><span class="token class-name">Users</span><span class="token punctuation">.</span>prototype<span class="token punctuation">,</span> <span class="token string">"find"</span><span class="token punctuation">,</span> find0<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">addMethod</span><span class="token punctuation">(</span><span class="token class-name">Users</span><span class="token punctuation">.</span>prototype<span class="token punctuation">,</span> <span class="token string">"find"</span><span class="token punctuation">,</span> find1<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">addMethod</span><span class="token punctuation">(</span><span class="token class-name">Users</span><span class="token punctuation">.</span>prototype<span class="token punctuation">,</span> <span class="token string">"find"</span><span class="token punctuation">,</span> find2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">var</span> users <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Users</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>users<span class="token punctuation">.</span>names <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"John Resig"</span><span class="token punctuation">,</span> <span class="token string">"John Russell"</span><span class="token punctuation">,</span> <span class="token string">"Dean Tom"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>users<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 输出[ 'John Resig', 'John Russell', 'Dean Tom' ]</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>users<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">"John"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 输出[ 'John Resig', 'John Russell' ]</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>users<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">"John"</span><span class="token punctuation">,</span> <span class="token string">"Resig"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 输出[ 'John Resig' ]</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>users<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">"John"</span><span class="token punctuation">,</span> <span class="token string">"E"</span><span class="token punctuation">,</span> <span class="token string">"Resig"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 输出undefined</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>从效果上来说，users 对象的 find 方法允许 3 种不同的输入: 0 个参数时，返回所有人名；1 个参数时，根据 firstName 查找人名并返回；2 个参数时，根据完整的名称查找人名并返回。</p><p>难点在于，users.find 事实上只能绑定一个函数，那它为何可以处理 3 种不同的输入呢？它不可能同时绑定 3 个函数 find0,find1 与 find2 啊！这里的关键在于 old 属性。</p><p>由 addMethod 函数的调用顺序可知，users.find 最终绑定的是 find2 函数。然而，在绑定 find2 时，old 为 find1；同理，绑定 find1 时，old 为 find0。3 个函数 find0,find1 与 find2 就这样通过闭包链接起来了。</p><p>根据 addMethod 的逻辑，当 fn.length 与 arguments.length 不匹配时，就会去调用 old，直到匹配为止。</p><h2 id="重载的好处"><a href="#重载的好处" class="headerlink" title="重载的好处"></a>重载的好处</h2><p>重载其实是把多个功能相近的函数合并为一个函数，重复利用了函数名。减少了代码的数量，还提高代码阅读性和代码质量，假如 jQuery 中的 css()方法不使用重载，那么就要有 5 个不同的函数，来完成功能，那我们就需要记住 5 个不同的函数名，和各个函数相对应的参数的个数和类型，显然就麻烦多了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><blockquote><p>虽然 JavaScript 并没有真正意义上的重载，但是重载的效果在 JavaScript 中却非常常见，比如 数组的 splice( )方法，一个参数可以删除，两个参数可以删除一部分，三个参数可以删除完了，再添加新元素。<br>再比如 parseInt( )方法 ，传入一个参数，就判断是用十六进制解析，还是用十进制解析，如果传入两个参数，就用第二个参数作为数字的基数，来进行解析。<br>文中提到的实现重载效果的方法，本质都是对参数进行判断，不管是判断参数个数，还是判断参数类型，都是根据参数的不同，来决定执行什么操作的。<br>虽然，重载能为我们带来许多的便利，但是也不能滥用，不要把一些根本不相关的函数合为一个函数，那样并没有什么意义。</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;译者按: jQuery 之父 John Resig 巧妙地利用了闭包，实现了 JavaScript 函数重载。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而所谓&lt;strong&gt;函数重载&lt;/strong&gt;：就是函数名称一样，输入不一样，输出也就不一样</summary>
      
    
    
    
    <category term="JavaScript系列" scheme="https://liaojunhao.github.io/categories/JavaScript%E7%B3%BB%E5%88%97/"/>
    
    
    <category term="函数" scheme="https://liaojunhao.github.io/tags/%E5%87%BD%E6%95%B0/"/>
    
    <category term="闭包" scheme="https://liaojunhao.github.io/tags/%E9%97%AD%E5%8C%85/"/>
    
  </entry>
  
</feed>
