<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>AquaHydro</title>
  
  <subtitle>不说话，装高手。</subtitle>
  <link href="https://blog.yiliang.app/atom.xml" rel="self"/>
  
  <link href="https://blog.yiliang.app/"/>
  <updated>2026-04-07T11:54:19.326Z</updated>
  <id>https://blog.yiliang.app/</id>
  
  <author>
    <name>AquaHydro</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Rust设计思想引申到JavaScript程序设计</title>
    <link href="https://blog.yiliang.app/2024/10/21/rust-she-ji-si-xiang-yin-shen-dao-javascript-cheng-xu-she-ji/"/>
    <id>https://blog.yiliang.app/2024/10/21/rust-she-ji-si-xiang-yin-shen-dao-javascript-cheng-xu-she-ji/</id>
    <published>2024-10-21T14:30:00.000Z</published>
    <updated>2026-04-07T11:54:19.326Z</updated>
    
    <content type="html"><![CDATA[<p>Rust是一门以安全性和性能著称的系统编程语言，其设计思想对JavaScript程序设计有着深远的启示。本文将探讨如何将Rust的设计理念应用到JavaScript开发中，以提升代码的可靠性和效率。</p><h2 id="Rust的设计思想"><a href="#Rust的设计思想" class="headerlink" title="Rust的设计思想"></a>Rust的设计思想</h2><h3 id="1-所有权与借用-Ownership-and-Borrowing"><a href="#1-所有权与借用-Ownership-and-Borrowing" class="headerlink" title="1. 所有权与借用 (Ownership and Borrowing)"></a>1. 所有权与借用 (Ownership and Borrowing)</h3><p>Rust通过所有权系统管理内存，避免了常见的内存泄漏和数据竞争问题。</p><h4 id="在JavaScript中的应用"><a href="#在JavaScript中的应用" class="headerlink" title="在JavaScript中的应用"></a>在JavaScript中的应用</h4><p>虽然JavaScript是垃圾回收语言，但我们仍可以通过明确的资源管理和避免全局状态来借鉴Rust的所有权思想。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createResource</span>(<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-keyword">let</span> resource = &#123; <span class="hljs-attr">data</span>: <span class="hljs-string">&#x27;important data&#x27;</span> &#125;;<br>  <span class="hljs-keyword">return</span> &#123;<br>    <span class="hljs-attr">useResource</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(resource.<span class="hljs-property">data</span>),<br>    <span class="hljs-attr">dispose</span>: <span class="hljs-function">() =&gt;</span> &#123; resource = <span class="hljs-literal">null</span>; &#125;<br>  &#125;;<br>&#125;<br><br><span class="hljs-keyword">const</span> res = <span class="hljs-title function_">createResource</span>();<br>res.<span class="hljs-title function_">useResource</span>();<br>res.<span class="hljs-title function_">dispose</span>();<br></code></pre></td></tr></table></figure><h3 id="2-不可变性-Immutability"><a href="#2-不可变性-Immutability" class="headerlink" title="2. 不可变性 (Immutability)"></a>2. 不可变性 (Immutability)</h3><p>Rust鼓励使用不可变数据，减少了状态变化带来的复杂性。</p><blockquote><p>Shared mutable state is the root of all evil（共享的可变状态是万恶之源）<br>– Pete Hunt</p></blockquote><h4 id="在JavaScript中的应用-1"><a href="#在JavaScript中的应用-1" class="headerlink" title="在JavaScript中的应用"></a>在JavaScript中的应用</h4><p>在JavaScript中，我们可以使用<code>const</code>声明和不可变数据结构来实现类似的效果。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> data = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">freeze</span>(&#123; <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;John&#x27;</span>, <span class="hljs-attr">age</span>: <span class="hljs-number">30</span> &#125;);<br>data.<span class="hljs-property">age</span> = <span class="hljs-number">31</span>; <span class="hljs-comment">// TypeError: Cannot assign to read only property &#x27;age&#x27;</span><br></code></pre></td></tr></table></figure><p>此外，我们还可以使用库如Immutable.js来帮助管理不可变数据。(在React前端应用中尤为重要)</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> &#123; <span class="hljs-title class_">Map</span> &#125; = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;immutable&#x27;</span>);<br><span class="hljs-keyword">const</span> map1 = <span class="hljs-title class_">Map</span>(&#123; <span class="hljs-attr">a</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">b</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">c</span>: <span class="hljs-number">3</span> &#125;);<br><span class="hljs-keyword">const</span> map2 = map1.<span class="hljs-title function_">set</span>(<span class="hljs-string">&#x27;b&#x27;</span>, <span class="hljs-number">50</span>);<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(map1.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;b&#x27;</span>)); <span class="hljs-comment">// 2</span><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(map2.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;b&#x27;</span>)); <span class="hljs-comment">// 50</span><br></code></pre></td></tr></table></figure><p>Immutable 实现的原理是 Persistent Data Structure（持久化数据结构）:</p><p>用一种数据结构来保存数据<br>当数据被修改时，会返回一个对象，但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费<br>也就是使用旧数据创建新数据时，要保证旧数据同时可用且不变，同时为了避免 deepCopy把所有节点都复制一遍带来的性能损耗，Immutable 使用了 Structural Sharing（结构共享）</p><p>如果对象树中一个节点发生变化，只修改这个节点和受它影响的父节点，其它节点则进行共享</p><p>如下图所示：</p><p><img src="https://blogr2.yiliang.app/2024/10/22/14-2b4c801a7b40eefcd4ee6767fb984fdf-df0457.gif"></p><h3 id="3-类型系统-Type-System"><a href="#3-类型系统-Type-System" class="headerlink" title="3. 类型系统 (Type System)"></a>3. 类型系统 (Type System)</h3><p>Rust的强类型系统在编译时捕获错误，提升了代码的安全性。</p><h4 id="在JavaScript中的应用-2"><a href="#在JavaScript中的应用-2" class="headerlink" title="在JavaScript中的应用"></a>在JavaScript中的应用</h4><p>虽然JavaScript是动态类型语言，但我们可以使用TypeScript来引入静态类型检查。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">add</span>(<span class="hljs-params"><span class="hljs-attr">a</span>: <span class="hljs-built_in">number</span>, <span class="hljs-attr">b</span>: <span class="hljs-built_in">number</span></span>): <span class="hljs-built_in">number</span> &#123;<br>  <span class="hljs-keyword">return</span> a + b;<br>&#125;<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">add</span>(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>)); <span class="hljs-comment">// 5</span><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">add</span>(<span class="hljs-string">&#x27;2&#x27;</span>, <span class="hljs-string">&#x27;3&#x27;</span>)); <span class="hljs-comment">// Error: Argument of type &#x27;string&#x27; is not assignable to parameter of type &#x27;number&#x27;.</span><br></code></pre></td></tr></table></figure><p>使用TypeScript可以在开发阶段捕获许多潜在的错误，提升代码的可靠性。</p><h2 id="Rust特点"><a href="#Rust特点" class="headerlink" title="Rust特点"></a>Rust特点</h2><h3 id="1-安全性"><a href="#1-安全性" class="headerlink" title="1. 安全性"></a>1. 安全性</h3><p>Rust通过所有权和借用检查在编译时捕获内存安全问题，JavaScript可以通过严格的编码规范和工具（如ESLint）来提升代码安全性。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// ESLint配置示例</span><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = &#123;<br>  <span class="hljs-string">&quot;env&quot;</span>: &#123;<br>    <span class="hljs-string">&quot;browser&quot;</span>: <span class="hljs-literal">true</span>,<br>    <span class="hljs-string">&quot;es2021&quot;</span>: <span class="hljs-literal">true</span><br>  &#125;,<br>  <span class="hljs-string">&quot;extends&quot;</span>: <span class="hljs-string">&quot;eslint:recommended&quot;</span>,<br>  <span class="hljs-string">&quot;parserOptions&quot;</span>: &#123;<br>    <span class="hljs-string">&quot;ecmaVersion&quot;</span>: <span class="hljs-number">12</span>,<br>    <span class="hljs-string">&quot;sourceType&quot;</span>: <span class="hljs-string">&quot;module&quot;</span><br>  &#125;,<br>  <span class="hljs-string">&quot;rules&quot;</span>: &#123;<br>    <span class="hljs-string">&quot;no-unused-vars&quot;</span>: <span class="hljs-string">&quot;warn&quot;</span>,<br>    <span class="hljs-string">&quot;no-console&quot;</span>: <span class="hljs-string">&quot;off&quot;</span><br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure><h3 id="2-性能"><a href="#2-性能" class="headerlink" title="2. 性能"></a>2. 性能</h3><p>Rust的零成本抽象和高效内存管理使其性能卓越。JavaScript可以通过避免不必要的对象创建和使用高效的数据结构来提升性能。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 使用高效的数据结构</span><br><span class="hljs-keyword">const</span> arr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Array</span>(<span class="hljs-number">1000000</span>).<span class="hljs-title function_">fill</span>(<span class="hljs-number">0</span>);<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">time</span>(<span class="hljs-string">&#x27;Array&#x27;</span>);<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; arr.<span class="hljs-property">length</span>; i++) &#123;<br>  arr[i] = i;<br>&#125;<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">timeEnd</span>(<span class="hljs-string">&#x27;Array&#x27;</span>); <span class="hljs-comment">// Array: 3.83203125 ms</span><br><br><span class="hljs-keyword">const</span> map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Map</span>();<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">time</span>(<span class="hljs-string">&#x27;Map&#x27;</span>);<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1000000</span>; i++) &#123;<br>  map.<span class="hljs-title function_">set</span>(i, i);<br>&#125;<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">timeEnd</span>(<span class="hljs-string">&#x27;Map&#x27;</span>); <span class="hljs-comment">// Map: 65.27001953125 ms</span><br></code></pre></td></tr></table></figure><h3 id="3-并发性"><a href="#3-并发性" class="headerlink" title="3. 并发性"></a>3. 并发性</h3><p>Rust的所有权系统天然支持安全的并发编程。JavaScript可以通过Web Workers和异步编程模型来实现高效并发。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 使用Web Workers</span><br><span class="hljs-keyword">const</span> worker = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Worker</span>(<span class="hljs-string">&#x27;worker.js&#x27;</span>);<br>worker.<span class="hljs-title function_">postMessage</span>(<span class="hljs-string">&#x27;start&#x27;</span>);<br><br>worker.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) &#123;<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;Worker said: &#x27;</span>, event.<span class="hljs-property">data</span>);<br>&#125;;<br><br><span class="hljs-comment">// worker.js</span><br>self.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) &#123;<br>  <span class="hljs-keyword">if</span> (event.<span class="hljs-property">data</span> === <span class="hljs-string">&#x27;start&#x27;</span>) &#123;<br>    self.<span class="hljs-title function_">postMessage</span>(<span class="hljs-string">&#x27;Hello from worker&#x27;</span>);<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure><p>通过这种方式，我们可以在不阻塞主线程的情况下执行耗时任务。</p><h3 id="模式匹配-Pattern-Matching"><a href="#模式匹配-Pattern-Matching" class="headerlink" title="模式匹配 (Pattern Matching)"></a>模式匹配 (Pattern Matching)</h3><p>Rust中的模式匹配通过<code>match</code>表达式提供了一种强大且灵活的控制流机制。并且Rust 编译器清晰地知道 match 中有哪些分支没有被覆盖，这种行为能强制我们处理所有的可能性，有效避免传说中<a href="https://linux.cn/article-6503-1.html">价值十亿美金的 null 陷阱</a>。</p><h4 id="在JavaScript中的应用-3"><a href="#在JavaScript中的应用-3" class="headerlink" title="在JavaScript中的应用"></a>在JavaScript中的应用</h4><p>虽然JavaScript没有原生的模式匹配语法，但我们可以使用<code>switch</code>语句或第三方库（如<code>match</code>库）来实现类似的功能。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 使用switch语句实现模式匹配</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">match</span>(<span class="hljs-params">value</span>) &#123;<br>  <span class="hljs-keyword">switch</span> (value) &#123;<br>    <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;a&#x27;</span>:<br>      <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Matched A&#x27;</span>;<br>    <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;b&#x27;</span>:<br>      <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;Matched B&#x27;</span>;<br>    <span class="hljs-attr">default</span>:<br>      <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;No Match&#x27;</span>;<br>  &#125;<br>&#125;<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">match</span>(<span class="hljs-string">&#x27;a&#x27;</span>)); <span class="hljs-comment">// Matched A</span><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">match</span>(<span class="hljs-string">&#x27;c&#x27;</span>)); <span class="hljs-comment">// No Match</span><br></code></pre></td></tr></table></figure><p>此外，我们还可以使用第三方库来实现更强大的模式匹配功能。例如，match库提供了类似Rust的模式匹配语法。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> &#123; match, when, otherwise &#125; = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;match&#x27;</span>);<br><br><span class="hljs-keyword">const</span> value = <span class="hljs-string">&#x27;a&#x27;</span>;<br><br><span class="hljs-keyword">const</span> result = <span class="hljs-title function_">match</span>(value)(<br>  <span class="hljs-title function_">when</span>(<span class="hljs-string">&#x27;a&#x27;</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">&#x27;Matched A&#x27;</span>),<br>  <span class="hljs-title function_">when</span>(<span class="hljs-string">&#x27;b&#x27;</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">&#x27;Matched B&#x27;</span>),<br>  <span class="hljs-title function_">otherwise</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-string">&#x27;No Match&#x27;</span>)<br>);<br><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(result); <span class="hljs-comment">// Matched A</span><br></code></pre></td></tr></table></figure><p>Rust编译器的检查也可以通过引入工具函数实现开发时通过Chrome DevTool进行开发时debugger</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">DCHECK_ALWAYS_ON</span> = <span class="hljs-literal">false</span>;<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">NOOP</span> = (<span class="hljs-params"></span>) =&gt; &#123;&#125;;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-variable constant_">DCHECK</span> =<br>  __DEV__ || <span class="hljs-variable constant_">DCHECK_ALWAYS_ON</span><br>    ? <span class="hljs-function">(<span class="hljs-params">condition, msg = <span class="hljs-string">&#x27;DCHECK failed&#x27;</span></span>) =&gt;</span> &#123;<br>        <span class="hljs-keyword">if</span> (!condition) &#123;<br>          <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">warn</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(msg));<br>          <span class="hljs-keyword">debugger</span>;<br>        &#125;<br>      &#125;<br>    : <span class="hljs-variable constant_">NOOP</span>;<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title function_">UNREACHABLE</span> = (<span class="hljs-params">msg = <span class="hljs-string">&#x27;UNREACHABLE&#x27;</span></span>) =&gt; <span class="hljs-title function_">DCHECK</span>(<span class="hljs-literal">false</span>, msg);<br><br></code></pre></td></tr></table></figure><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>Rust的设计思想为JavaScript开发提供了宝贵的借鉴。通过引入所有权管理、不可变数据和类型检查等理念，我们可以编写出更安全、高效和可维护的JavaScript代码。</p><h2 id="参考阅读"><a href="#参考阅读" class="headerlink" title="参考阅读"></a>参考阅读</h2><ul><li><a href="https://www.rust-lang.org/learn">Rust官方文档</a></li><li><a href="https://course.rs/about-book.html">Rust语言圣经</a></li><li><a href="https://zhuanlan.zhihu.com/p/20295971">Immutable 详解及 React 中实践</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API">Web Workers API</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Rust是一门以安全性和性能著称的系统编程语言，其设计思想对JavaScript程序设计有着深远的启示。本文将探讨如何将Rust的设计理念应用到JavaScript开发中，以提升代码的可靠性和效率。&lt;/p&gt;
&lt;h2 id=&quot;Rust的设计思想&quot;&gt;&lt;a href=&quot;#Rust</summary>
      
    
    
    
    <category term="技术" scheme="https://blog.yiliang.app/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="Rust" scheme="https://blog.yiliang.app/tags/Rust/"/>
    
    <category term="JavaScript" scheme="https://blog.yiliang.app/tags/JavaScript/"/>
    
    <category term="编程思想" scheme="https://blog.yiliang.app/tags/%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3/"/>
    
  </entry>
  
  <entry>
    <title>excel公式引擎方案设计</title>
    <link href="https://blog.yiliang.app/2024/07/25/excel-gong-shi-yin-qing-fang-an-she-ji/"/>
    <id>https://blog.yiliang.app/2024/07/25/excel-gong-shi-yin-qing-fang-an-she-ji/</id>
    <published>2024-07-25T07:39:32.000Z</published>
    <updated>2026-04-07T11:54:19.326Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h2><p>身处信息时代之中，我们最能明显感受到的一点就是密集数据大量爆发，人们积累的数据也越来越多。这些庞杂的数据出现在一起，传统使用的很多数据记录、查询、汇总工具并不能满足人们的需求。更有效的将这些大量数据处理，让计算机听懂人类需要的数据效果，从而形成更加自动化、智能的数据处理方式。</p><p>为了处理这些海量数据，出现了各种大数据引擎、搜索引擎、计算引擎、3D引擎等，用以更好解决数据庞杂带来人工无法处理的问题。而作为其中比较常用的就是Excel的计算公式引擎，本文主要是讲解计算引擎的前端实现方案。</p><h2 id="二、核心问题"><a href="#二、核心问题" class="headerlink" title="二、核心问题"></a>二、核心问题</h2><ul><li>核心需求点：满足用户对表格数据的逻辑比较、逻辑运算和统计需求。</li><li>核心问题：1.建立表格各类区域和公式之间的复杂依赖关系并得到合理的计算更新顺序。2.公式字符串的词法解析生成结构化数据</li></ul><h2 id="三、功能分析"><a href="#三、功能分析" class="headerlink" title="三、功能分析"></a>三、功能分析</h2><p>对于核心问题第一点来说，可以在以下两个资料中，学习到一些思想方法。</p><ul><li><a href="https://gcdn.grapecity.com.cn/lesson-161.html">前端电子表格的计算引擎解密 - 前端电子表格的计算引擎解密</a></li><li><a href="https://leetcode.cn/problems/design-excel-sum-formula/">[Leetcode] 631. 设计 Excel 求和公式</a></li></ul><p>核心问题第二点：<br>如图，A8单元格的字段中存在一个公式SUM(1,5.512,B1,A1)，formula记录了两个信息，其中的10是公式表达式的索引，22、23是公式表达式中包含的引用位置的索引：</p><p><img src="https://blogr2.yiliang.app/2024/07/25/15-64bdf443fa67d94393eca8cfcdc36235-0a93aa.webp"><br>在元数据部分可以看到对应的数据：</p><p><img src="https://blogr2.yiliang.app/2024/07/25/15-fef5e455970bbc260f9eb6145f9cdac4-dccbd6.webp"><br>exprs中的是表达式的模板，如果存在两个公式表达式只有引用位置不同，那它们共用一个模板：<br><img src="https://blogr2.yiliang.app/2024/07/25/15-d3b2f09d69d552502d71f51da9c93f34-6735f7.webp"></p><p>refs中的即是引用位置，通过相对位置进行表示：（<a href="###%E5%85%AC%E5%BC%8F%E4%B8%AD%E4%BD%BF%E7%94%A8%E5%BC%95%E7%94%A8">位置引用和公式字符串的转化关系参考这里</a>）<br><img src="https://blogr2.yiliang.app/2024/07/25/15-452ea323cd4806808f5b5681ca820f9b-11d8cf.webp"></p><p>由此可见钉钉表格完整的公式表达式由表达式模板(expres)+引用位置(refs)组合得出。<br>通过单元格上formula记录的索引，找到对应的表达式模板和引用位置，就能够完整表达整个公式。<br>公式之间的依赖关系并没有通过快照存储，需要在表格初始化时进行构建。</p><h2 id="四、概要设计"><a href="#四、概要设计" class="headerlink" title="四、概要设计"></a>四、概要设计</h2><h3 id="公式的基本原理"><a href="#公式的基本原理" class="headerlink" title="公式的基本原理"></a>公式的基本原理</h3><p>在工作表中可以使用常量和算术运算符创建简单的公式。</p><p>公式以输入“＝”开始。<br>复杂一些的公式可能包含函数和常量。</p><blockquote><p>函数：函数是预先编写的公式，可以对一个或多个值执行运算，并返回一个或多个值。函数可以简化和缩短工作表中的公式，尤其在用公式执行很长或复杂的计算时。</p></blockquote><blockquote><p>常量：不进行计算的值，因此也不会发生变化。</p></blockquote><p>基本功能：</p><ul><li>可以进行＋、－、、四则运算等计算</li><li>可以引用其他单元格中的数据。</li><li>可使用文本字符串，或与数据相结合。</li><li>可运用&gt;、&lt;之类的比较运算符比较单元格内的数据。</li><li>不仅可以用于公式的计算，还可以运用于其他情况中。</li></ul><p>位置引用：</p><ul><li>A1 相对引用</li><li>$A1 绝对引用列</li><li>A$1 绝对引用行</li><li>$A$1 绝对引用行和列</li></ul><p>下面将介绍公式功能的基本实现原理。</p><h3 id="公式的组成部分"><a href="#公式的组成部分" class="headerlink" title="公式的组成部分"></a>公式的组成部分</h3><p><img src="https://blogr2.yiliang.app/2024/07/25/15-877e0a6a1ff4552e09f5a87d2e8b9a9a-15b8c1.webp"></p><ol><li>函数：PI() 函数返回 pi 值：3.142…</li><li>引用：A2 返回单元格 A2 中的值。</li><li>常量：直接输入到公式中的数字或文本值，例如 2。</li><li>运算符：^（脱字号）运算符表示数字的乘方，而 *（星号）运算符表示数字的乘积。</li></ol><h3 id="公式中使用常量"><a href="#公式中使用常量" class="headerlink" title="公式中使用常量"></a>公式中使用常量</h3><p>常量是一个不是通过计算得出的值；它始终保持相同。<br>例如，日期 10&#x2F;9&#x2F;2008、数字 210 以及文本“季度收入”都是常量。<br>表达式或从表达式得到的值不是常量。<br>如果在公式中使用常量而不是对单元格的引用（例如 &#x3D;30+70+110），则仅在修改公式时结果才会变化。<br>通常，最好在各单元格中放置常量（必要时可轻松更改），然后在公式中引用这些单元格。</p><h3 id="公式中使用引用"><a href="#公式中使用引用" class="headerlink" title="公式中使用引用"></a>公式中使用引用</h3><p>引用的作用在于标识工作表上的单元格或单元格区域，并告知 程序 在何处查找要在公式中使用的值或数据。<br>你可以使用引用在一个公式中使用工作表不同部分中包含的数据，或者在多个公式中使用同一个单元格的值。<br>还可以引用同一个工作簿中其他工作表上的单元格和其他工作簿中的数据。<br>引用其他工作簿中的单元格被称为链接或外部引用。</p><ul><li><p>A1 引用样式<br>默认情况下，程序 使用 A1 引用样式，此样式引用字母标识列（从 A 到 XFD，共 16,384 列）以及数字标识行（从 1 到 1,048,576）。 这些字母和数字被称为行号和列标。</p><p>要引用某个单元格，请输入列标，后跟行号。<br>例如，B2 引用列 B 和行 2 交叉处的单元格。</p><table><thead><tr><th>若要引用</th><th>用途</th></tr></thead><tbody><tr><td>A 和行 10 交叉处的单元格</td><td>A10</td></tr><tr><td>在列 A 和行 10 到行 20 之间的单元格区域</td><td>A10:A20</td></tr><tr><td>在行 15 和列 B 到列 E 之间的单元格区域</td><td>B15:E15</td></tr><tr><td>行 5 中的全部单元格</td><td>5:5</td></tr><tr><td>行 5 到行 10 之间的全部单元格</td><td>5:10</td></tr><tr><td>列 H 中的全部单元格</td><td>H:H</td></tr><tr><td>列 H 到列 J 之间的全部单元格</td><td>H:J</td></tr><tr><td>列 A 到列 E 和行 10 到行 20 之间的单元格区域</td><td>A10:E20</td></tr></tbody></table></li><li><p>引用同一工作簿中另一个工作表上的单元格或单元格区域</p><p>下例中，AVERAGE 函数将计算同一个工作簿中名为 Marketing 的工作表的 B1:B10 区域内的平均值。</p><p><img src="https://blogr2.yiliang.app/2024/07/25/15-f1875f81cb589c1cde34dafb19e11c0c-4f12be.webp"></p><p>1、对名为 Marketing 的工作表的引用<br>2、引用 B1 到 B10 的单元格区域<br>3、感叹号 (！) 将工作表引用与单元格区域引用分开</p></li><li><p>绝对引用、相对引用和混合引用之间的区别</p><p><strong>相对引用</strong> 公式中的相对单元格引用（如 A1）是基于包含公式和单元格引用的单元格的相对位置。 如果公式所在单元格的位置改变，引用也随之改变。 如果多行或多列地复制或填充公式，引用会自动调整。 默认情况下，新公式使用相对引用。 例如，如果将单元格 B2 中的相对引用复制或填充到单元格 B3，将自动从 &#x3D;A1 调整到 &#x3D;A2。</p><p>复制的公式具有相对引用：</p><p><img src="https://blogr2.yiliang.app/2024/07/25/15-11decd03cf733e245e56a61be6fa6862-390f7e.webp"></p><p><strong>绝对引用</strong> 公式中的绝对单元格引用（如 $A$1）总是在特定位置引用单元格。 如果公式所在单元格的位置改变，绝对引用将保持不变。 如果多行或多列地复制或填充公式，绝对引用将不作调整。 默认情况下，新公式使用相对引用，因此您可能需要将它们转换为绝对引用。 例如，如果将单元格 B2 中的绝对引用复制或填充到单元格 B3，则该绝对引用在两个单元格中一样，都是 &#x3D;$A$1。</p><p>复制的公式具有绝对引用：</p><p><img src="https://blogr2.yiliang.app/2024/07/25/15-1daefc8d644a37bf90a9e05dbb86932b-a1ff34.webp"></p><p><strong>混合引用</strong> 混合引用具有绝对列和相对行或绝对行和相对列。 绝对引用列采用 $A1、$B1 等形式。 绝对引用行采用 A$1、B$1 等形式。 如果公式所在单元格的位置改变，则相对引用将改变，而绝对引用将不变。 如果多行或多列地复制或填充公式，相对引用将自动调整，而绝对引用将不作调整。 例如，如果将一个混合引用从单元格 A2 复制到 B3，它将从 &#x3D;A$1 调整到 &#x3D;B$1。</p><p>复制的公式具有混合引用：</p><p><img src="https://blogr2.yiliang.app/2024/07/25/15-7bf162d2ba059eda7eefea9f327bbe6d-c76b16.webp"></p></li></ul><h3 id="公式的词法分析"><a href="#公式的词法分析" class="headerlink" title="公式的词法分析"></a>公式的词法分析</h3><p><img src="https://blogr2.yiliang.app/2024/07/25/16-06e857276594aedc7fe2f723d8613d2a-e3c3f4.webp"><br><img src="https://blogr2.yiliang.app/2024/07/25/16-a3be05ff2b129bba28e2151f03502986-085ca7.webp"><br><img src="https://blogr2.yiliang.app/2024/07/25/16-edbfab0248564573d6a4b0168bd17f83-e02d78.webp"></p><h3 id="语法分析"><a href="#语法分析" class="headerlink" title="语法分析"></a>语法分析</h3><p>公式的语法分析使用开源库<a href="https://gerhobbelt.github.io/jison/docs/">jison</a>库，我们需要编写定义相关的语法规则，使用命令生成解析器。Jison 解析器会根据定义的语法规则对输入进行解析，并构建一个语法树。</p><p>首先，你需要使用 Jison 定义函数表达式的语法规则。这可以通过编写一个称为”Jison 文法”的规则文件来完成。Jison 文法使用类似于 BNF（巴科斯范式）的语法来描述语法规则。例如，下面是一个简单的 Jison 文法示例，用于解析简单的数学函数表达式：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs json">%lex<br>%%<br><span class="hljs-string">&quot;+&quot;</span>             return &#x27;+&#x27;;<br><span class="hljs-string">&quot;-&quot;</span>             return &#x27;-&#x27;;<br><span class="hljs-string">&quot;*&quot;</span>             return &#x27;*&#x27;;<br><span class="hljs-string">&quot;/&quot;</span>             return &#x27;/&#x27;;<br><span class="hljs-string">&quot;(&quot;</span>             return &#x27;(&#x27;;<br><span class="hljs-string">&quot;)&quot;</span>             return &#x27;)&#x27;;<br><span class="hljs-punctuation">[</span><span class="hljs-number">0</span><span class="hljs-number">-9</span><span class="hljs-punctuation">]</span>+          return &#x27;NUMBER&#x27;;<br><span class="hljs-punctuation">[</span> \t\n<span class="hljs-punctuation">]</span>+        <span class="hljs-comment">/* skip whitespace */</span><br>.               return &#x27;INVALID&#x27;;<br>/lex<br><br>%start expression<br><br>%%<br><br>expression<br>    <span class="hljs-punctuation">:</span> expression <span class="hljs-string">&quot;+&quot;</span> expression   <span class="hljs-punctuation">&#123;</span> $$ = $<span class="hljs-number">1</span> + $<span class="hljs-number">3</span>; <span class="hljs-punctuation">&#125;</span><br>    | expression <span class="hljs-string">&quot;-&quot;</span> expression   <span class="hljs-punctuation">&#123;</span> $$ = $<span class="hljs-number">1</span> - $<span class="hljs-number">3</span>; <span class="hljs-punctuation">&#125;</span><br>    | expression <span class="hljs-string">&quot;*&quot;</span> expression   <span class="hljs-punctuation">&#123;</span> $$ = $<span class="hljs-number">1</span> * $<span class="hljs-number">3</span>; <span class="hljs-punctuation">&#125;</span><br>    | expression <span class="hljs-string">&quot;/&quot;</span> expression   <span class="hljs-punctuation">&#123;</span> $$ = $<span class="hljs-number">1</span> / $<span class="hljs-number">3</span>; <span class="hljs-punctuation">&#125;</span><br>    | <span class="hljs-string">&quot;(&quot;</span> expression <span class="hljs-string">&quot;)&quot;</span>          <span class="hljs-punctuation">&#123;</span> $$ = $<span class="hljs-number">2</span>; <span class="hljs-punctuation">&#125;</span><br>    | NUMBER                      <span class="hljs-punctuation">&#123;</span> $$ = Number($<span class="hljs-number">1</span>); <span class="hljs-punctuation">&#125;</span><br>    ;<br><br></code></pre></td></tr></table></figure><h3 id="语法树"><a href="#语法树" class="headerlink" title="语法树"></a>语法树</h3><p>语法树是一个表示函数表达式结构的树状数据结构。每个节点代表一个语法规则的实例，例如函数调用、运算符、变量等。</p><p>例如：一个简单的数学表达式：2 + 3 * (4 - 1)可以解析成</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs js">&#123;<br>  <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;BinaryExpression&#x27;</span>,<br>  <span class="hljs-attr">operator</span>: <span class="hljs-string">&#x27;+&#x27;</span>,<br>  <span class="hljs-attr">left</span>: &#123;<br>    <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;NumericLiteral&#x27;</span>,<br>    <span class="hljs-attr">value</span>: <span class="hljs-number">2</span><br>  &#125;,<br>  <span class="hljs-attr">right</span>: &#123;<br>    <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;BinaryExpression&#x27;</span>,<br>    <span class="hljs-attr">operator</span>: <span class="hljs-string">&#x27;*&#x27;</span>,<br>    <span class="hljs-attr">left</span>: &#123;<br>      <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;NumericLiteral&#x27;</span>,<br>      <span class="hljs-attr">value</span>: <span class="hljs-number">3</span><br>    &#125;,<br>    <span class="hljs-attr">right</span>: &#123;<br>      <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;BinaryExpression&#x27;</span>,<br>      <span class="hljs-attr">operator</span>: <span class="hljs-string">&#x27;-&#x27;</span>,<br>      <span class="hljs-attr">left</span>: &#123;<br>        <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;NumericLiteral&#x27;</span>,<br>        <span class="hljs-attr">value</span>: <span class="hljs-number">4</span><br>      &#125;,<br>      <span class="hljs-attr">right</span>: &#123;<br>        <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;NumericLiteral&#x27;</span>,<br>        <span class="hljs-attr">value</span>: <span class="hljs-number">1</span><br>      &#125;<br>    &#125;<br>  &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><h3 id="计算表达式"><a href="#计算表达式" class="headerlink" title="计算表达式"></a>计算表达式</h3><p>计算表达式使用了开源库<a href="https://github.com/handsontable/formula.js">formula.js</a>。<br>该开源库能够支持三百多种函数的运算，但不支持 excel 中数组公式的计算，例如”&#x3D;A:C+1”，需要对其 IF 函数、加、减、乘、除等运算规则进行修改，使其支持参与运算的因子是数组的情况。</p><h3 id="公式的相互依赖"><a href="#公式的相互依赖" class="headerlink" title="公式的相互依赖"></a>公式的相互依赖</h3><p>公式可以存在单元格引用，而被引用的单元格值可能也是由公式计算得到的，例如：<br><img src="https://blogr2.yiliang.app/2024/07/25/16-a6bff834f810dbf5f8548003fa403ef4-3e7289.webp"><br>C3 上的公式是&#x3D;SUM(C1:D1)，F3 上的公式是&#x3D;SUM(F1:G1)，E6 上的公式是&#x3D;SUM(C3,F3).</p><p>如果我们将 C1 的值更新，依赖 C1 的 C3、依赖 C3 的 E6 需要重新计算并更新公式结果，并且根据依赖关系，必须先更新 C3 再更新 E6。</p><h2 id="五、详细设计"><a href="#五、详细设计" class="headerlink" title="五、详细设计"></a>五、详细设计</h2><hr><p>假设在 A1 单元格位置键入以下公式</p><figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs excel">=<span class="hljs-built_in">SUM</span>(<span class="hljs-symbol">B1</span>:<span class="hljs-symbol">C1</span>, <span class="hljs-symbol">B2</span>:<span class="hljs-symbol">C2</span>)<br></code></pre></td></tr></table></figure><p>首先我们在单元格数据里记录公式信息&#x2F;公式 ID（此处举例为“A”）</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-comment">// 单元格数据</span><br><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">9</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-comment">// ...</span><br>  <span class="hljs-attr">&quot;formula&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;A&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>另外，我们还需要在文档数据里更新公式信息，以及对应的引用区域。格式如下</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">&quot;formulas&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;A&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-comment">// 引用范围, 仅记录refs中引用对象的id</span><br>    <span class="hljs-attr">&quot;refs&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;R1&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;x&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;R2&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;y&quot;</span><br>    <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;expr&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;SUM([R1, R2])&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br></code></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">&quot;refs&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>  <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;x&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-comment">// sheetId，如果跨表，这里就是个链接</span><br>    <span class="hljs-attr">&quot;from&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;GsuvR1&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-comment">// range类型，下文讨论</span><br>    <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">16</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-comment">// range依旧保持为[4]int类型</span><br>    <span class="hljs-attr">&quot;range&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>      <span class="hljs-number">1</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-number">0</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-number">2</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-number">1</span><br>    <span class="hljs-punctuation">]</span><br>  <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;y&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;from&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;GsuvR1&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">0</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;range&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>      <span class="hljs-number">1</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-number">1</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-number">2</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-number">1</span><br>    <span class="hljs-punctuation">]</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">]</span><br></code></pre></td></tr></table></figure><h3 id="公式的还原方法"><a href="#公式的还原方法" class="headerlink" title="公式的还原方法"></a>公式的还原方法</h3><p>在实际的应用程序中，我们还需要还原公式用以展示和编辑。</p><p>由于存在 相对引用 和 绝对引用，以及 混合引用 的场景，这使得引用位置的还原变得复杂。<br>我们通过 type 字段记录相关的位置信息，在还原时配合 ref 结构中的 type 字段，可以准确还原位置引用。</p><table aria-label="" class="banded flipColors">  <thead>    <tr>      <td>        <p>          <b class="ocpLegacyBold">对于正在复制的公式：</b>        </p>      </td>      <td>        <p>          <b class="ocpLegacyBold">如果引用是：A1=</b>        </p>      </td>      <td>        <p>          <b class="ocpLegacyBold">它会更改为：C3=</b>        </p>      </td>    </tr>  </thead>  <tbody>    <tr>      <td>        <p>            <img src="https://support.content.office.net/zh-cn/media/73d5a617-c594-4d28-a613-34df7d53dffc.gif" loading="lazy" alt="正从 A1 被复制到向下和向右移两个单元格的公式。">        </p>      </td>      <td>        <p>$A$1（绝对列和绝对行）</p>      </td>      <td>        <p>$A$1（引用是绝对的）</p>      </td>    </tr>    <tr>      <td></td>      <td>        <p>A$1（相对列和绝对行）</p>      </td>      <td>        <p>C$1（引用是混合型）</p>      </td>    </tr>    <tr>      <td></td>      <td>        <p>$A1（绝对列和相对行）</p>      </td>      <td>        <p>$A3（引用是混合型）</p>      </td>    </tr>    <tr>      <td></td>      <td>        <p>A1（相对列和相对行）</p>      </td>      <td>        <p>C3（引用是相对的）</p>      </td>    </tr>  </tbody></table><blockquote><p>钉钉：使用 type 的二进制数字表示所有不同位置的$符号</p></blockquote><table dir="auto" data-sourcepos="63:1-93:23">  <thead>    <tr data-sourcepos="63:1-63:26">      <th data-sourcepos="63:2-63:10">TYPE</th>      <th data-sourcepos="63:12-63:25">含义</th>    </tr>  </thead>  <tbody>    <tr data-sourcepos="65:1-65:32">      <td data-sourcepos="65:2-65:8">-1</td>      <td data-sourcepos="65:10-65:31">被删除的引用</td>    </tr>    <tr data-sourcepos="66:1-66:20">      <td data-sourcepos="66:2-66:8">0(00)</td>      <td data-sourcepos="66:10-66:19"><code>A1</code></td>    </tr>    <tr data-sourcepos="67:1-67:20">      <td data-sourcepos="67:2-67:8">1(01)</td>      <td data-sourcepos="67:10-67:19"><code>A$1</code></td>    </tr>    <tr data-sourcepos="68:1-68:22">      <td data-sourcepos="68:2-68:8">2(10)</td>      <td data-sourcepos="68:10-68:21"><code>$A1</code></td>    </tr>    <tr data-sourcepos="69:1-69:22">      <td data-sourcepos="69:2-69:8">3(11)</td>      <td data-sourcepos="69:10-69:21"><code>$A$1</code></td>    </tr>    <tr data-sourcepos="70:1-70:22">      <td data-sourcepos="70:2-70:8">4(100)</td>      <td data-sourcepos="70:10-70:21"><code>1:1</code></td>    </tr>    <tr data-sourcepos="71:1-71:22">      <td data-sourcepos="71:2-71:8">5(101)</td>      <td data-sourcepos="71:10-71:21"><code>1:$1</code></td>    </tr>    <tr data-sourcepos="72:1-72:22">      <td data-sourcepos="72:2-72:8">6(110)</td>      <td data-sourcepos="72:10-72:21"><code>$1:1</code></td>    </tr>    <tr data-sourcepos="73:1-73:22">      <td data-sourcepos="73:2-73:8">7(111)</td>      <td data-sourcepos="73:10-73:21"><code>$1:$1</code></td>    </tr>    <tr data-sourcepos="74:1-74:20">      <td data-sourcepos="74:2-74:8">8(1000)</td>      <td data-sourcepos="74:10-74:19"><code>C:D</code></td>    </tr>    <tr data-sourcepos="75:1-75:20">      <td data-sourcepos="75:2-75:8">9(1001)</td>      <td data-sourcepos="75:10-75:19"><code>C:$D</code></td>    </tr>    <tr data-sourcepos="76:1-76:20">      <td data-sourcepos="76:2-76:8">10(1010)</td>      <td data-sourcepos="76:10-76:19"><code>$C:D</code></td>    </tr>    <tr data-sourcepos="77:1-77:20">      <td data-sourcepos="77:2-77:8">11(1011)</td>      <td data-sourcepos="77:10-77:19"><code>$C:$D</code></td>    </tr>    <tr data-sourcepos="78:1-78:20">      <td data-sourcepos="78:2-78:8">16(10000)</td>      <td data-sourcepos="78:10-78:19"><code>A1:B1</code></td>    </tr>    <tr data-sourcepos="79:1-79:20">      <td data-sourcepos="79:2-79:8">17(10001)</td>      <td data-sourcepos="79:10-79:19"><code>A1:B$1</code></td>    </tr>    <tr data-sourcepos="80:1-80:20">      <td data-sourcepos="80:2-80:8">18(10010)</td>      <td data-sourcepos="80:10-80:19"><code>A1:$B1</code></td>    </tr>    <tr data-sourcepos="81:1-81:21">      <td data-sourcepos="81:2-81:8">19(10111)</td>      <td data-sourcepos="81:10-81:20"><code>A1:$B$1</code></td>    </tr>    <tr data-sourcepos="82:1-82:20">      <td data-sourcepos="82:2-82:8">20(10100)</td>      <td data-sourcepos="82:10-82:19"><code>A$1:B1</code></td>    </tr>    <tr data-sourcepos="83:1-83:21">      <td data-sourcepos="83:2-83:8">21(10101)</td>      <td data-sourcepos="83:10-83:20"><code>A$1:B$1</code></td>    </tr>    <tr data-sourcepos="84:1-84:21">      <td data-sourcepos="84:2-84:8">22(10110)</td>      <td data-sourcepos="84:10-84:20"><code>A$1:$B1</code></td>    </tr>    <tr data-sourcepos="85:1-85:22">      <td data-sourcepos="85:2-85:8">23(10111)</td>      <td data-sourcepos="85:10-85:21"><code>A$1:$B$1</code></td>    </tr>    <tr data-sourcepos="86:1-86:20">      <td data-sourcepos="86:2-86:8">24(11000)</td>      <td data-sourcepos="86:10-86:19"><code>$A1:B1</code></td>    </tr>    <tr data-sourcepos="87:1-87:21">      <td data-sourcepos="87:2-87:8">25(11001)</td>      <td data-sourcepos="87:10-87:20"><code>$A1:B$1</code></td>    </tr>    <tr data-sourcepos="88:1-88:21">      <td data-sourcepos="88:2-88:8">26(11010)</td>      <td data-sourcepos="88:10-88:20"><code>$A1:$B1</code></td>    </tr>    <tr data-sourcepos="89:1-89:22">      <td data-sourcepos="89:2-89:8">27(11011)</td>      <td data-sourcepos="89:10-89:21"><code>$A1:$B$1</code></td>    </tr>    <tr data-sourcepos="90:1-90:21">      <td data-sourcepos="90:2-90:8">28(11100)</td>      <td data-sourcepos="90:10-90:20"><code>$A$1:B1</code></td>    </tr>    <tr data-sourcepos="91:1-91:22">      <td data-sourcepos="91:2-91:8">29(11101)</td>      <td data-sourcepos="91:10-91:21"><code>$A$1:B$1</code></td>    </tr>    <tr data-sourcepos="92:1-92:22">      <td data-sourcepos="92:2-92:8">30(11110)</td>      <td data-sourcepos="92:10-92:21"><code>$A$1:$B1</code></td>    </tr>    <tr data-sourcepos="93:1-93:23">      <td data-sourcepos="93:2-93:8">31(11111)</td>      <td data-sourcepos="93:10-93:22"><code>$A$1:$B$1</code></td>    </tr>  </tbody></table><p>我们也采用相同的做法。</p><p>可以看到 四种类型的区域 可以定四个不同的基数 0，4，8，16。</p><ul><li><p>区域格式为$A$1，词法分析出单元格类型，则基数为 0，第一二个数字前面有$，type&#x3D;0+1+2&#x3D;3，二进制后四位 0011</p></li><li><p>区域格式为$A1:$B$1，词法分析出矩形区域类型，则基数为 16，第 1 第 3 第 4 字符前有$，type &#x3D; 16+8+2+1&#x3D;27，二进制后四位 1011</p></li></ul><p>通过先判断 type 值的范围，确定出区域类型，得到基数。<br>其中二进制值去掉基数之后 1 的位数就是$符号出现的位置。<br>最后就能通过 [$符号位置、range、区域类型] 三个信息反推出原公式。</p><h3 id="公式的链路计算"><a href="#公式的链路计算" class="headerlink" title="公式的链路计算"></a>公式的链路计算</h3><p>假设存在以下公式</p><p>A &#x3D; B<br>C &#x3D; A<br>D &#x3D; A+B<br>E &#x3D; A + B + C + D<br><font color=Red>B &#x3D; A</font></p><p>注：红色公式会导致依赖链路成环。</p><p>基于上述公式信息，我们可以构建如下依赖关系。</p><p>[<br>    { id: ‘b’, dep: [ ‘a’, ‘d’, ‘e’ ] },<br>    { id: ‘a’, dep: [ ‘c’, ‘d’, ‘e’, <font color=Red>‘b’</font> ] },<br>    { id: ‘c’, dep: [ ‘e’ ] },<br>    { id: ‘d’, dep: [ ‘e’ ] }<br>]</p><img src="https://blogr2.yiliang.app/2024/07/25/16-d1b63e4cb8c047a5414193c9af8d2149-63ef5c.webp" width="220" height="270" align="middle" /><p>当 B 节点数据变更之后，我们需要递归更新上游链路的所有节点。</p><p>更新逻辑如下：</p><p>1、清空更新链路</p><p>2、查询变更节点的上游</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs json">path<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>b<span class="hljs-punctuation">]</span><br><br>next<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>a<span class="hljs-punctuation">,</span>d<span class="hljs-punctuation">,</span>e<span class="hljs-punctuation">]</span>  <span class="hljs-comment">// path里的节点所有上游</span><br></code></pre></td></tr></table></figure><p>发现 path 中的数据 b 不在 next 里（没有入度），则添加 b 到更新链路中（此时更新链路为 [b]）。</p><blockquote><p>此处的检查是为了保证更新顺序（最小入度）。</p></blockquote><p>对 next 去重后重复查询上游节点。</p><p>3、递归查询变更节点的上游</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs json">path<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>a<span class="hljs-punctuation">,</span>d<span class="hljs-punctuation">,</span>e<span class="hljs-punctuation">]</span><br><br>next<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>c<span class="hljs-punctuation">,</span>d<span class="hljs-punctuation">,</span>e<span class="hljs-punctuation">,</span>b<span class="hljs-punctuation">,</span> e<span class="hljs-punctuation">,</span><span class="hljs-punctuation">]</span><br></code></pre></td></tr></table></figure><p>发现 path 中的数据 a 不在 next 里（没有入度），则添加 a 到更新链路中（此时更新链路为 [b, a]）。</p><blockquote><p>这里需要注意的是，如果 next 中的节点存在于更新链路中，则出现循环依赖，递归终止。</p></blockquote><blockquote><p>如果开启 N 次迭代计算，就使用当前的更新链路[b,a]进行 N 次重复计算。否则 b 和 a 所在的单元格就需要报引用错误。</p></blockquote><p>如果 B &#x3D; A 不存在（依赖成环），则重复上述步骤。</p><p>对 next 去重后重复查询上游节点。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs json">path<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>c<span class="hljs-punctuation">,</span>d<span class="hljs-punctuation">,</span>e<span class="hljs-punctuation">]</span><br><br>next<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>e<span class="hljs-punctuation">,</span>e<span class="hljs-punctuation">]</span><br></code></pre></td></tr></table></figure><p>发现 path 中的数据 c,d 不在 next 里（没有入度），则添加 c,d 到更新链路中（此时更新链路为 [b, a, c, d]）。</p><p>对 next 去重后重复查询上游节点。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs json">path<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>e<span class="hljs-punctuation">]</span><br><br>next<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span><br></code></pre></td></tr></table></figure><p>发现 path 中的数据 e 不在 next 里（没有入度），则添加 e 到更新链路中（此时更新链路为 [b, a, c, d, e]）。</p><p>至此，公式递归完毕，按照更新链路依次更新单元格数据即可。</p><h3 id="公式的冲突处理"><a href="#公式的冲突处理" class="headerlink" title="公式的冲突处理"></a>公式的冲突处理</h3><p>由于存在冲突的情况：假设两个客户端基于同一个版本进行了不同的编辑操作，两种操作发生了冲突，没有经过冲突处理之前，两个客户端得到的计算结果都不是正确的。</p><p>因此，在发生公式编辑操作时，前端不会将计算结果进行上发到服务端，而是等到后端处理完op冲突收到op后，再进行计算结果。</p><h3 id="公式的初始化"><a href="#公式的初始化" class="headerlink" title="公式的初始化"></a>公式的初始化</h3><p>如果服务端快照不存储计算结果，服务端也没有计算能力的话，就需要前端在表格初始化时，进行一次<font color=Red>全局公式计算</font>。</p><p>如何进行初始化全局计算？</p><p>由于上方描述的算法是基于某个节点出现变更，再生成该节点变更之后需要更新的节点链路。项目初始化时就需要先得到<font color=Red>入度最小的节点</font>，并由该节点作为函数调动的发起者，得到完整的更新链路。然后再对更新链路中含有公式的单元格进行依次计算更新。</p><p>如何得到入度最小的节点？<br>表格初始化先使用快照model的单元格数据，转化成前端表格数据表。然后对前端表格数据表进行遍历，依据快照中的ref formula构建依赖图，在创建依赖图的方法中添加回调使用Map记录id出现次数。等数据初始化完毕，出现次数最少的节点即为入度最小的节点此案例为B）</p><h2 id="进阶"><a href="#进阶" class="headerlink" title="进阶:"></a>进阶:</h2><ul><li>使用Web Worker，实现多线程计算？</li><li>采用C、C++、Rust实现编译代码， 使用WASM实现更好的计算速度?</li><li>服务端运算的支持?</li></ul><h2 id="参考阅读"><a href="#参考阅读" class="headerlink" title="参考阅读"></a>参考阅读</h2><ul><li><a href="https://gcdn.grapecity.com.cn/lesson-161.html">前端电子表格的计算引擎解密 - 前端电子表格的计算引擎解密</a></li><li><a href="https://leetcode.cn/problems/design-excel-sum-formula/">[Leetcode] 631. 设计 Excel 求和公式</a></li><li><a href="https://univer.ai/zh-CN/guides/sheet/architecture/formula">Univer公式引擎架构设计</a></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://blog.yiliang.app/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="业务" scheme="https://blog.yiliang.app/tags/%E4%B8%9A%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>前端编程范式与设计模式</title>
    <link href="https://blog.yiliang.app/2024/07/24/qian-duan-bian-cheng-fan-shi-yu-she-ji-mo-shi/"/>
    <id>https://blog.yiliang.app/2024/07/24/qian-duan-bian-cheng-fan-shi-yu-she-ji-mo-shi/</id>
    <published>2024-07-24T11:55:59.000Z</published>
    <updated>2026-04-07T11:54:19.326Z</updated>
    
    <content type="html"><![CDATA[<p>在前端开发中，理解并运用各种编程范式和设计模式有助于提升代码的可维护性、扩展性和可读性。本文将介绍一些常见且有用的编程范式和设计模式，并提供详细的代码示例。</p><h2 id="编程范式"><a href="#编程范式" class="headerlink" title="编程范式"></a>编程范式</h2><h3 id="1-异步编程-Asynchronous-Programming"><a href="#1-异步编程-Asynchronous-Programming" class="headerlink" title="1. 异步编程 (Asynchronous Programming)"></a>1. 异步编程 (Asynchronous Programming)</h3><p>异步编程允许程序在等待某些任务完成时继续执行其他任务，从而避免阻塞。前端常见的异步操作包括网络请求、文件读取等。</p><h4 id="使用回调函数"><a href="#使用回调函数" class="headerlink" title="使用回调函数"></a>使用回调函数</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">fetchData</span>(<span class="hljs-params">callback</span>) &#123;<br>  <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>    <span class="hljs-keyword">const</span> data = &#123; <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;John&#x27;</span> &#125;;<br>    <span class="hljs-title function_">callback</span>(data);<br>  &#125;, <span class="hljs-number">1000</span>);<br>&#125;<br><br><span class="hljs-title function_">fetchData</span>(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> &#123;<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;Data received:&#x27;</span>, data);<br>&#125;);<br></code></pre></td></tr></table></figure><h4 id="使用Promise"><a href="#使用Promise" class="headerlink" title="使用Promise"></a>使用Promise</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">fetchData</span>(<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> &#123;<br>    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>      <span class="hljs-keyword">const</span> data = &#123; <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;John&#x27;</span> &#125;;<br>      <span class="hljs-title function_">resolve</span>(data);<br>    &#125;, <span class="hljs-number">1000</span>);<br>  &#125;);<br>&#125;<br><br><span class="hljs-title function_">fetchData</span>()<br>  .<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;Data received:&#x27;</span>, data);<br>  &#125;)<br>  .<span class="hljs-title function_">catch</span>(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;Error:&#x27;</span>, error);<br>  &#125;);<br></code></pre></td></tr></table></figure><h4 id="使用async-await"><a href="#使用async-await" class="headerlink" title="使用async&#x2F;await"></a>使用async&#x2F;await</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">fetchData</span>(<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">&#x27;https://api.example.com/data&#x27;</span>);<br>    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.<span class="hljs-title function_">json</span>();<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;Data received:&#x27;</span>, data);<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;Error:&#x27;</span>, error);<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-面向对象编程-Object-Oriented-Programming-OOP"><a href="#2-面向对象编程-Object-Oriented-Programming-OOP" class="headerlink" title="2. 面向对象编程 (Object-Oriented Programming, OOP)"></a>2. 面向对象编程 (Object-Oriented Programming, OOP)</h3><p>面向对象编程通过对象来组织代码，核心概念包括封装、继承和多态。</p><h4 id="基本类与继承"><a href="#基本类与继承" class="headerlink" title="基本类与继承"></a>基本类与继承</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Animal</span> &#123;<br>  <span class="hljs-title function_">constructor</span>(<span class="hljs-params">name</span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">name</span> = name;<br>  &#125;<br><br>  <span class="hljs-title function_">speak</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;<span class="hljs-variable language_">this</span>.name&#125;</span> makes a sound.`</span>);<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Dog</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">Animal</span> &#123;<br>  <span class="hljs-title function_">speak</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;<span class="hljs-variable language_">this</span>.name&#125;</span> barks.`</span>);<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">const</span> dog = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Dog</span>(<span class="hljs-string">&#x27;Rex&#x27;</span>);<br>dog.<span class="hljs-title function_">speak</span>(); <span class="hljs-comment">// Rex barks.</span><br></code></pre></td></tr></table></figure><h4 id="多态"><a href="#多态" class="headerlink" title="多态"></a>多态</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Cat</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">Animal</span> &#123;<br>  <span class="hljs-title function_">speak</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;<span class="hljs-variable language_">this</span>.name&#125;</span> meows.`</span>);<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">const</span> animals = [<span class="hljs-keyword">new</span> <span class="hljs-title class_">Dog</span>(<span class="hljs-string">&#x27;Rex&#x27;</span>), <span class="hljs-keyword">new</span> <span class="hljs-title class_">Cat</span>(<span class="hljs-string">&#x27;Whiskers&#x27;</span>)];<br>animals.<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">animal</span> =&gt;</span> animal.<span class="hljs-title function_">speak</span>());<br><span class="hljs-comment">// Rex barks.</span><br><span class="hljs-comment">// Whiskers meows.</span><br></code></pre></td></tr></table></figure><h3 id="3-函数式编程-Functional-Programming-FP"><a href="#3-函数式编程-Functional-Programming-FP" class="headerlink" title="3. 函数式编程 (Functional Programming, FP)"></a>3. 函数式编程 (Functional Programming, FP)</h3><p>函数式编程强调使用纯函数、不可变数据和函数组合，避免可变状态和副作用。</p><h4 id="纯函数与不可变性"><a href="#纯函数与不可变性" class="headerlink" title="纯函数与不可变性"></a>纯函数与不可变性</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> <span class="hljs-title function_">add</span> = (<span class="hljs-params">a, b</span>) =&gt; a + b;<br><br><span class="hljs-keyword">const</span> numbers = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>];<br><span class="hljs-keyword">const</span> newNumbers = numbers.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">num</span> =&gt;</span> num * <span class="hljs-number">2</span>);<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(newNumbers); <span class="hljs-comment">// [2, 4, 6]</span><br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(numbers); <span class="hljs-comment">// [1, 2, 3] - 原数组不变</span><br></code></pre></td></tr></table></figure><h4 id="高阶函数"><a href="#高阶函数" class="headerlink" title="高阶函数"></a>高阶函数</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> <span class="hljs-title function_">withLogging</span> = fn =&gt; <span class="hljs-function">(<span class="hljs-params">...args</span>) =&gt;</span> &#123;<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Calling <span class="hljs-subst">$&#123;fn.name&#125;</span> with args:`</span>, args);<br>  <span class="hljs-keyword">return</span> <span class="hljs-title function_">fn</span>(...args);<br>&#125;;<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">multiply</span> = (<span class="hljs-params">a, b</span>) =&gt; a * b;<br><span class="hljs-keyword">const</span> loggedMultiply = <span class="hljs-title function_">withLogging</span>(multiply);<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">loggedMultiply</span>(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>)); <span class="hljs-comment">// Logs: Calling multiply with args: [3, 4], 12</span><br></code></pre></td></tr></table></figure><h3 id="4-反应式编程-Reactive-Programming"><a href="#4-反应式编程-Reactive-Programming" class="headerlink" title="4. 反应式编程 (Reactive Programming)"></a>4. 反应式编程 (Reactive Programming)</h3><p>反应式编程处理动态数据流和异步事件，常用于构建响应式UI和处理事件流。</p><h4 id="使用RxJS处理事件流"><a href="#使用RxJS处理事件流" class="headerlink" title="使用RxJS处理事件流"></a>使用RxJS处理事件流</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">import</span> &#123; fromEvent &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;rxjs&#x27;</span>;<br><span class="hljs-keyword">import</span> &#123; throttleTime, map &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;rxjs/operators&#x27;</span>;<br><br><span class="hljs-keyword">const</span> clicks = <span class="hljs-title function_">fromEvent</span>(<span class="hljs-variable language_">document</span>, <span class="hljs-string">&#x27;click&#x27;</span>);<br><span class="hljs-keyword">const</span> positions = clicks.<span class="hljs-title function_">pipe</span>(<br>  <span class="hljs-title function_">throttleTime</span>(<span class="hljs-number">1000</span>),<br>  <span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> (&#123; <span class="hljs-attr">x</span>: event.<span class="hljs-property">clientX</span>, <span class="hljs-attr">y</span>: event.<span class="hljs-property">clientY</span> &#125;))<br>);<br><br>positions.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function"><span class="hljs-params">position</span> =&gt;</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;Clicked at:&#x27;</span>, position));<br></code></pre></td></tr></table></figure><h2 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h2><h3 id="1-单例模式-Singleton-Pattern"><a href="#1-单例模式-Singleton-Pattern" class="headerlink" title="1. 单例模式 (Singleton Pattern)"></a>1. 单例模式 (Singleton Pattern)</h3><p>单例模式确保一个类只有一个实例，并提供全局访问点。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Logger</span> &#123;<br>  <span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Logger</span>.<span class="hljs-property">instance</span>) &#123;<br>      <span class="hljs-keyword">return</span> <span class="hljs-title class_">Logger</span>.<span class="hljs-property">instance</span>;<br>    &#125;<br>    <span class="hljs-title class_">Logger</span>.<span class="hljs-property">instance</span> = <span class="hljs-variable language_">this</span>;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">logs</span> = [];<br>  &#125;<br><br>  <span class="hljs-title function_">log</span>(<span class="hljs-params">message</span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">logs</span>.<span class="hljs-title function_">push</span>(message);<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`LOG: <span class="hljs-subst">$&#123;message&#125;</span>`</span>);<br>  &#125;<br><br>  <span class="hljs-title function_">printLogCount</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;<span class="hljs-variable language_">this</span>.logs.length&#125;</span> logs`</span>);<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">const</span> logger1 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Logger</span>();<br><span class="hljs-keyword">const</span> logger2 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Logger</span>();<br><br>logger1.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;This is the first log&#x27;</span>);<br>logger2.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;This is the second log&#x27;</span>);<br>logger1.<span class="hljs-title function_">printLogCount</span>(); <span class="hljs-comment">// 2 logs</span><br></code></pre></td></tr></table></figure><h3 id="2-工厂模式-Factory-Pattern"><a href="#2-工厂模式-Factory-Pattern" class="headerlink" title="2. 工厂模式 (Factory Pattern)"></a>2. 工厂模式 (Factory Pattern)</h3><p>工厂模式通过定义一个接口或抽象类来创建对象，而不指定具体类。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Shape</span> &#123;<br>  <span class="hljs-title function_">constructor</span>(<span class="hljs-params">type</span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">type</span> = type;<br>  &#125;<br><br>  <span class="hljs-title function_">draw</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Drawing a <span class="hljs-subst">$&#123;<span class="hljs-variable language_">this</span>.type&#125;</span>`</span>);<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ShapeFactory</span> &#123;<br>  <span class="hljs-title function_">createShape</span>(<span class="hljs-params">type</span>) &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Shape</span>(type);<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">const</span> factory = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ShapeFactory</span>();<br><span class="hljs-keyword">const</span> circle = factory.<span class="hljs-title function_">createShape</span>(<span class="hljs-string">&#x27;circle&#x27;</span>);<br><span class="hljs-keyword">const</span> square = factory.<span class="hljs-title function_">createShape</span>(<span class="hljs-string">&#x27;square&#x27;</span>);<br><br>circle.<span class="hljs-title function_">draw</span>(); <span class="hljs-comment">// Drawing a circle</span><br>square.<span class="hljs-title function_">draw</span>(); <span class="hljs-comment">// Drawing a square</span><br></code></pre></td></tr></table></figure><h3 id="3-观察者模式-Observer-Pattern"><a href="#3-观察者模式-Observer-Pattern" class="headerlink" title="3. 观察者模式 (Observer Pattern)"></a>3. 观察者模式 (Observer Pattern)</h3><p>观察者模式定义对象之间的一对多依赖关系，当一个对象状态改变时，所有依赖对象会收到通知并更新。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Subject</span> &#123;<br>  <span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">observers</span> = [];<br>  &#125;<br><br>  <span class="hljs-title function_">addObserver</span>(<span class="hljs-params">observer</span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">observers</span>.<span class="hljs-title function_">push</span>(observer);<br>  &#125;<br><br>  <span class="hljs-title function_">removeObserver</span>(<span class="hljs-params">observer</span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">observers</span> = <span class="hljs-variable language_">this</span>.<span class="hljs-property">observers</span>.<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">obs</span> =&gt;</span> obs !== observer);<br>  &#125;<br><br>  <span class="hljs-title function_">notify</span>(<span class="hljs-params">data</span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">observers</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">observer</span> =&gt;</span> observer.<span class="hljs-title function_">update</span>(data));<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Observer</span> &#123;<br>  <span class="hljs-title function_">update</span>(<span class="hljs-params">data</span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Observer received data: <span class="hljs-subst">$&#123;data&#125;</span>`</span>);<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">const</span> subject = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Subject</span>();<br><span class="hljs-keyword">const</span> observer1 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Observer</span>();<br><span class="hljs-keyword">const</span> observer2 = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Observer</span>();<br><br>subject.<span class="hljs-title function_">addObserver</span>(observer1);<br>subject.<span class="hljs-title function_">addObserver</span>(observer2);<br>subject.<span class="hljs-title function_">notify</span>(<span class="hljs-string">&#x27;Some data&#x27;</span>);<br></code></pre></td></tr></table></figure><h3 id="4-装饰者模式-Decorator-Pattern"><a href="#4-装饰者模式-Decorator-Pattern" class="headerlink" title="4. 装饰者模式 (Decorator Pattern)"></a>4. 装饰者模式 (Decorator Pattern)</h3><p>装饰者模式允许向现有对象添加新功能，而不改变其结构。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Coffee</span> &#123;<br>  <span class="hljs-title function_">cost</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-number">5</span>;<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MilkDecorator</span> &#123;<br>  <span class="hljs-title function_">constructor</span>(<span class="hljs-params">coffee</span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">coffee</span> = coffee;<br>  &#125;<br><br>  <span class="hljs-title function_">cost</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">coffee</span>.<span class="hljs-title function_">cost</span>() + <span class="hljs-number">1</span>;<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">SugarDecorator</span> &#123;<br>  <span class="hljs-title function_">constructor</span>(<span class="hljs-params">coffee</span>) &#123;<br>    <span class="hljs-variable language_">this</span>.<span class="hljs-property">coffee</span> = coffee;<br>  &#125;<br><br>  <span class="hljs-title function_">cost</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">coffee</span>.<span class="hljs-title function_">cost</span>() + <span class="hljs-number">0.5</span>;<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">let</span> coffee = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Coffee</span>();<br>coffee = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MilkDecorator</span>(coffee);<br>coffee = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SugarDecorator</span>(coffee);<br><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(coffee.<span class="hljs-title function_">cost</span>()); <span class="hljs-comment">// 6.5</span><br></code></pre></td></tr></table></figure><h2 id="前端中的依赖注入"><a href="#前端中的依赖注入" class="headerlink" title="前端中的依赖注入"></a>前端中的依赖注入</h2><p>参考阅读<a href="https://redi.wendell.fun/zh-CN/blogs/di">这篇文章</a></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这些编程范式和设计模式各有其独特的应用场景，理解并掌握它们能够帮助开发者编写出更高效、可维护和可扩展的代码。在实际开发中，根据具体需求选择合适的范式和模式是提升项目质量的重要一环。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在前端开发中，理解并运用各种编程范式和设计模式有助于提升代码的可维护性、扩展性和可读性。本文将介绍一些常见且有用的编程范式和设计模式，并提供详细的代码示例。&lt;/p&gt;
&lt;h2 id=&quot;编程范式&quot;&gt;&lt;a href=&quot;#编程范式&quot; class=&quot;headerlink&quot; title</summary>
      
    
    
    
    <category term="技术" scheme="https://blog.yiliang.app/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="编程范式" scheme="https://blog.yiliang.app/tags/%E7%BC%96%E7%A8%8B%E8%8C%83%E5%BC%8F/"/>
    
    <category term="设计模式" scheme="https://blog.yiliang.app/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>C语言编译为WASM vs JavaScript：计算斐波那契数列的性能对比</title>
    <link href="https://blog.yiliang.app/2024/07/24/c-yu-yan-bian-yi-wei-wasm-vs-javascript-ji-suan-fei-bo-na-qi-shu-lie-de-xing-neng-dui-bi/"/>
    <id>https://blog.yiliang.app/2024/07/24/c-yu-yan-bian-yi-wei-wasm-vs-javascript-ji-suan-fei-bo-na-qi-shu-lie-de-xing-neng-dui-bi/</id>
    <published>2024-07-24T00:30:59.000Z</published>
    <updated>2026-04-07T11:54:19.326Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在现代Web开发中，性能优化是一个重要的议题。WebAssembly（WASM）作为一种新兴的技术，为Web应用带来了显著的性能提升。<strong>在需要高性能计算的Web应用程序，例如音视频、协作冲突中起着非常重要的作用。</strong>本文将探讨C语言编译为WASM在计算斐波那契数列时相较于JavaScript的性能优势。</p></blockquote><p><img src="https://blogr2.yiliang.app/2024/07/26/-02c46aee9974e0cb789073d42c0abf9a--eca445.png"></p><h2 id="什么是WebAssembly？"><a href="#什么是WebAssembly？" class="headerlink" title="什么是WebAssembly？"></a>什么是WebAssembly？</h2><p>WebAssembly（WASM）是一种新的二进制格式，可以在现代Web浏览器中高效地运行。这种格式可以将C、C++、Rust等编译型语言编译为高效的字节码，然后在浏览器中执行，从而提升了计算性能。</p><h2 id="斐波那契数列简介"><a href="#斐波那契数列简介" class="headerlink" title="斐波那契数列简介"></a>斐波那契数列简介</h2><p>斐波那契数列是一种经典的数列，其中每一个数都是前两个数的和。即：</p><ul><li>F(0) &#x3D; 0</li><li>F(1) &#x3D; 1</li><li>F(n) &#x3D; F(n-1) + F(n-2)  (n ≥ 2)</li></ul><p>计算斐波那契数列常常用来测试编程语言和计算平台的性能，因为它具有简单而计算量大的特性。</p><h2 id="使用JavaScript计算斐波那契数列"><a href="#使用JavaScript计算斐波那契数列" class="headerlink" title="使用JavaScript计算斐波那契数列"></a>使用JavaScript计算斐波那契数列</h2><p>在JavaScript中，我们可以通过递归或者迭代的方法来计算斐波那契数列。以下是一个使用递归方法的JavaScript代码示例：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">_fibonacciJS</span>(<span class="hljs-params">n</span>) &#123;<br>    <span class="hljs-keyword">if</span> (n == <span class="hljs-number">1</span> || n == <span class="hljs-number">2</span>) &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br>    &#125;<br>    <span class="hljs-keyword">return</span> <span class="hljs-title function_">_fibonacciJS</span>(n - <span class="hljs-number">1</span>) + <span class="hljs-title function_">_fibonacciJS</span>(n - <span class="hljs-number">2</span>);<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="使用C语言编译为WASM计算斐波那契数列"><a href="#使用C语言编译为WASM计算斐波那契数列" class="headerlink" title="使用C语言编译为WASM计算斐波那契数列"></a>使用C语言编译为WASM计算斐波那契数列</h2><p>C语言是一种高效的编译型语言，编译为WASM后，其性能相较于JavaScript会有显著提升。以下是一个使用C语言计算斐波那契数列的示例代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">fibonacci</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span><br>&#123;<br>    <span class="hljs-keyword">if</span> (n == <span class="hljs-number">1</span> || n == <span class="hljs-number">2</span>)<br>    &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;<br>    &#125;<br>    <span class="hljs-keyword">return</span> fibonacci(n - <span class="hljs-number">1</span>) + fibonacci(n - <span class="hljs-number">2</span>);<br>&#125;<br></code></pre></td></tr></table></figure><p>然后，使用 <a href="https://emscripten.org/docs/tools_reference/emcc.html">emcc</a> 编译器将其编译为Wasm：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">emcc -O3 -o fibonacci.js -s EXPORTED_FUNCTIONS=&#x27;[&quot;_fibonacci&quot;]&#x27; fibonacci.c<br></code></pre></td></tr></table></figure><h2 id="在HTML网页中进行测试"><a href="#在HTML网页中进行测试" class="headerlink" title="在HTML网页中进行测试"></a>在HTML网页中进行测试</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-keyword">html</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">&quot;en&quot;</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">&quot;UTF-8&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">&quot;X-UA-Compatible&quot;</span> <span class="hljs-attr">content</span>=<span class="hljs-string">&quot;IE=edge&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;viewport&quot;</span> <span class="hljs-attr">content</span>=<span class="hljs-string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>fjb Wasm<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>num: <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;number&quot;</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;num&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>JS: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;JSresultDom&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Wasm: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;WasmresultDom&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">&quot;fibonacci.js&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="language-javascript"></span><br><span class="language-javascript">        <span class="hljs-keyword">function</span> <span class="hljs-title function_">_fibonacciJS</span>(<span class="hljs-params">n</span>) &#123;</span><br><span class="language-javascript">            <span class="hljs-keyword">if</span> (n == <span class="hljs-number">1</span> || n == <span class="hljs-number">2</span>) &#123;</span><br><span class="language-javascript">                <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;</span><br><span class="language-javascript">            &#125;</span><br><span class="language-javascript">            <span class="hljs-keyword">return</span> <span class="hljs-title function_">_fibonacciJS</span>(n - <span class="hljs-number">1</span>) + <span class="hljs-title function_">_fibonacciJS</span>(n - <span class="hljs-number">2</span>);</span><br><span class="language-javascript">        &#125;</span><br><span class="language-javascript">        num.<span class="hljs-property">onchange</span> = <span class="hljs-function">() =&gt;</span> &#123;</span><br><span class="language-javascript"></span><br><span class="language-javascript">            <span class="hljs-keyword">const</span> jsStart = performance.<span class="hljs-title function_">now</span>();</span><br><span class="language-javascript">            <span class="hljs-keyword">const</span> jsResult = <span class="hljs-title function_">_fibonacciJS</span>(num.<span class="hljs-property">value</span>);</span><br><span class="language-javascript"></span><br><span class="language-javascript">            <span class="hljs-keyword">const</span> jsEnd = performance.<span class="hljs-title function_">now</span>();</span><br><span class="language-javascript">            <span class="hljs-title class_">JSresultDom</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">` (JS: <span class="hljs-subst">$&#123;(jsEnd - jsStart).toFixed(<span class="hljs-number">2</span>)&#125;</span>ms)   jsResult: <span class="hljs-subst">$&#123;jsResult&#125;</span>`</span>;</span><br><span class="language-javascript"></span><br><span class="language-javascript"></span><br><span class="language-javascript">            <span class="hljs-keyword">const</span> wasmStart = performance.<span class="hljs-title function_">now</span>();</span><br><span class="language-javascript">            <span class="hljs-keyword">const</span> wasmResult = <span class="hljs-title class_">Module</span>.<span class="hljs-title function_">_fibonacci</span>(num.<span class="hljs-property">value</span>);</span><br><span class="language-javascript">            <span class="hljs-keyword">const</span> wasmEnd = performance.<span class="hljs-title function_">now</span>();</span><br><span class="language-javascript">            <span class="hljs-title class_">WasmresultDom</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">` (Wasm: <span class="hljs-subst">$&#123;(wasmEnd - wasmStart).toFixed(<span class="hljs-number">2</span>)&#125;</span>ms)   wasmResult: <span class="hljs-subst">$&#123;wasmResult&#125;</span>`</span>;</span><br><span class="language-javascript">        &#125;</span><br><span class="language-javascript">    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span><br><br><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span><br></code></pre></td></tr></table></figure><p>性能对比：</p><p><img src="https://blogr2.yiliang.app/2024/07/24/20-0734f478e0ecdabf2d470a841208277d-853ba9.webp"></p><h2 id="源码参考"><a href="#源码参考" class="headerlink" title="源码参考"></a>源码参考</h2><ul><li><a href="https://github.com/AquaHydro/code-examples/tree/main/fib-wasm">fib-wasm</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;在现代Web开发中，性能优化是一个重要的议题。WebAssembly（WASM）作为一种新兴的技术，为Web应用带来了显著的性能提升。&lt;strong&gt;在需要高性能计算的Web应用程序，例如音视频、协作冲突中起着非常重要的作用。&lt;/strong&gt;本文</summary>
      
    
    
    
    <category term="技术" scheme="https://blog.yiliang.app/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="性能" scheme="https://blog.yiliang.app/tags/%E6%80%A7%E8%83%BD/"/>
    
  </entry>
  
  <entry>
    <title>api接口的一些设计原则和最佳实践</title>
    <link href="https://blog.yiliang.app/2024/07/21/api-jie-kou-de-yi-xie-she-ji-yuan-ze-he-zui-jia-shi-jian/"/>
    <id>https://blog.yiliang.app/2024/07/21/api-jie-kou-de-yi-xie-she-ji-yuan-ze-he-zui-jia-shi-jian/</id>
    <published>2024-07-21T00:30:59.000Z</published>
    <updated>2026-04-07T11:54:19.326Z</updated>
    
    <content type="html"><![CDATA[<h3 id="1-单一职责原则（Single-Responsibility-Principle-SRP）"><a href="#1-单一职责原则（Single-Responsibility-Principle-SRP）" class="headerlink" title="1. 单一职责原则（Single Responsibility Principle, SRP）"></a>1. <strong>单一职责原则（Single Responsibility Principle, SRP）</strong></h3><ul><li>每个模块或函数应该只负责一件事情。新增接口的职责就是接收新数据并插入数据库，而不是处理自动生成的字段。</li></ul><h3 id="2-最小惊讶原则（Principle-of-Least-Astonishment-POLA）"><a href="#2-最小惊讶原则（Principle-of-Least-Astonishment-POLA）" class="headerlink" title="2. 最小惊讶原则（Principle of Least Astonishment, POLA）"></a>2. <strong>最小惊讶原则（Principle of Least Astonishment, POLA）</strong></h3><ul><li>系统应该按照用户或开发者预期的方式工作。自动生成的字段不应该由客户端提供，因为这会违反常规做法，容易导致混淆和错误。</li></ul><h3 id="3-安全性原则"><a href="#3-安全性原则" class="headerlink" title="3. 安全性原则"></a>3. <strong>安全性原则</strong></h3><ul><li>不让客户端传递自动生成的字段可以避免潜在的安全问题，例如数据伪造和篡改。确保数据的真实性和完整性。</li></ul><h3 id="4-简化设计（KISS-Keep-It-Simple-Stupid）"><a href="#4-简化设计（KISS-Keep-It-Simple-Stupid）" class="headerlink" title="4. 简化设计（KISS - Keep It Simple, Stupid）"></a>4. <strong>简化设计（KISS - Keep It Simple, Stupid）</strong></h3><ul><li>保持设计简单，避免不必要的复杂性。只处理需要的字段，减少代码中的冗余和潜在错误。</li></ul><h3 id="5-封装（Encapsulation）"><a href="#5-封装（Encapsulation）" class="headerlink" title="5. 封装（Encapsulation）"></a>5. <strong>封装（Encapsulation）</strong></h3><ul><li>封装数据库操作细节，客户端不需要关心数据库是如何生成id和创建时间的，只需要得到结果。这也是一种信息隐藏的方式。</li></ul><h3 id="实践中的体现"><a href="#实践中的体现" class="headerlink" title="实践中的体现"></a>实践中的体现</h3><p>在实际开发中，遵循这些原则可以提高代码的可维护性、安全性和可读性。例如：</p><ul><li><strong>数据库自动生成字段</strong>：让数据库来生成id、创建时间等字段，可以保证数据的一致性和唯一性，避免手动处理带来的错误。</li><li><strong>API设计</strong>：在API设计中，明确哪些字段是客户端需要提供的，哪些是由服务器生成的，保证接口的清晰性和可靠性。</li></ul><blockquote><p>Talk is cheap. Show me the code.                </p><p>​                                                                                                -Linux 创始人 Linus Torvalds</p></blockquote><h4 id="从请求体上下文ctx-user中只取该接口控制器AuthController需要的字段数据，其中创建上下文的中间件verifyAuth可以应用在不同的路由-业务接口中。"><a href="#从请求体上下文ctx-user中只取该接口控制器AuthController需要的字段数据，其中创建上下文的中间件verifyAuth可以应用在不同的路由-业务接口中。" class="headerlink" title="从请求体上下文ctx.user中只取该接口控制器AuthController需要的字段数据，其中创建上下文的中间件verifyAuth可以应用在不同的路由&#x2F;业务接口中。"></a>从请求体上下文<code>ctx.user</code>中只取该接口控制器<code>AuthController</code>需要的字段数据，其中创建上下文的中间件<code>verifyAuth</code>可以应用在不同的路由&#x2F;业务接口中。</h4><p><img src="https://blogr2.yiliang.app/image-20240721081744300.png" alt="image-20240721081744300"></p><p><img src="https://blogr2.yiliang.app/image-20240721081613510.png" alt="image-20240721081613510"></p><p><img src="https://blogr2.yiliang.app/image-20240721075318479.png" alt="image-20240721075318479"></p><h4 id="cos的上传需要返回https链接，符合高版本chrome浏览器的安全策略以及最佳做法"><a href="#cos的上传需要返回https链接，符合高版本chrome浏览器的安全策略以及最佳做法" class="headerlink" title="cos的上传需要返回https链接，符合高版本chrome浏览器的安全策略以及最佳做法"></a>cos的上传需要返回https链接，符合高版本chrome浏览器的安全策略以及最佳做法</h4><p><img src="https://blogr2.yiliang.app/image-20240721082223721.png" alt="image-20240721082223721"></p><p><img src="https://blogr2.yiliang.app/image-20240721082517590.png" alt="image-20240721082517590"></p><h3 id="扩展阅读"><a href="#扩展阅读" class="headerlink" title="扩展阅读"></a>扩展阅读</h3><p><a href="https://github.com/ShawnLeee/the-book/blob/master/clean%20code-%E4%BB%A3%E7%A0%81%E6%95%B4%E6%B4%81%E4%B9%8B%E9%81%93%20%E4%B8%AD%E6%96%87%E5%AE%8C%E6%95%B4%E7%89%88-%E5%B8%A6%E4%B9%A6%E7%AD%BE.pdf">代码整洁之道 中文完整版-带书签.pdf</a> </p><p><a href="https://cloud.tencent.com/developer/news/366899">九年总结：优秀程序设计的18大原则</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;1-单一职责原则（Single-Responsibility-Principle-SRP）&quot;&gt;&lt;a href=&quot;#1-单一职责原则（Single-Responsibility-Principle-SRP）&quot; class=&quot;headerlink&quot; title=&quot;1.</summary>
      
    
    
    
    <category term="技术" scheme="https://blog.yiliang.app/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="接口" scheme="https://blog.yiliang.app/tags/%E6%8E%A5%E5%8F%A3/"/>
    
  </entry>
  
  <entry>
    <title>如何将 univer-sheet 的粘贴解析性能提升 85%</title>
    <link href="https://blog.yiliang.app/2024/07/11/ru-he-jiang-univer-sheet-de-nian-tie-jie-xi-xing-neng-ti-sheng-85/"/>
    <id>https://blog.yiliang.app/2024/07/11/ru-he-jiang-univer-sheet-de-nian-tie-jie-xi-xing-neng-ti-sheng-85/</id>
    <published>2024-07-11T13:05:59.000Z</published>
    <updated>2026-04-07T11:54:19.326Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><strong>注:</strong> 本文基于 <a href="https://github.com/dream-num/univer">univer-sheet</a> 源码，对其复制粘贴解析逻辑进行核心优化解读。</p></blockquote><h2 id="效果图："><a href="#效果图：" class="headerlink" title="效果图："></a>效果图：</h2><ul><li><p><strong>变更前:</strong></p><p><img src="https://blogr2.yiliang.app/2024/07/11/-bcfe20cc2b851df896bc62c5c98a63b4--236cb6.webp"></p></li><li><p><strong>变更后:</strong></p><p><img src="https://blogr2.yiliang.app/2024/07/11/-d1eb6c86878a076da8d0256fa2106115--4e8ed1.webp"></p></li></ul><p>由上图可以看到在提交变更之前，粘贴解析长任务耗时 27.5 秒并且内存没有得到回收，出现了内存泄漏问题。在变更之后，耗时仅需要 2.68 秒，对应的内存也得到释放。</p><blockquote><p><strong>注:</strong> 内存泄漏问题也可以通过堆快照定位。</p></blockquote><p>PR 请求可以 <a href="https://github.com/dream-num/univer/pull/2631">点此查看</a>。</p><h2 id="耗时原因分析"><a href="#耗时原因分析" class="headerlink" title="耗时原因分析:"></a>耗时原因分析:</h2><p><img src="https://blogr2.yiliang.app/2024/07/11/-f3fb909ef8b68491b23d88017135f919--bc1c2a.webp"></p><p>通过开发者工具结合源码分析，我们能发现 <code>windows.getComputedStyle().getPropertyValue</code> 出现了大量耗时的情况。</p><h3 id="为什么-getComputedStyle-和-getPropertyValue-方法会大量耗时造成页面卡死？"><a href="#为什么-getComputedStyle-和-getPropertyValue-方法会大量耗时造成页面卡死？" class="headerlink" title="为什么 getComputedStyle() 和 getPropertyValue() 方法会大量耗时造成页面卡死？"></a>为什么 <code>getComputedStyle()</code> 和 <code>getPropertyValue()</code> 方法会大量耗时造成页面卡死？</h3><ul><li><p><strong>强制重排（reflow）</strong>: <code>getComputedStyle()</code> 方法会导致浏览器计算元素的所有样式，这可能需要重新计算整个文档的布局。这是因为浏览器需要确保样式是最新的，并且在一些情况下可能会重新布局页面。这种重排操作是非常耗时的，特别是当页面上有大量的元素时。</p></li><li><p><strong>同步操作</strong>: <code>getComputedStyle()</code> 方法是同步的，这意味着浏览器必须在返回结果之前完成所有的计算。这会阻塞主线程，导致页面的其他操作变慢或卡顿。</p></li><li><p><strong>布局树的生成</strong>: 浏览器需要生成和更新布局树（layout tree），以便计算每个元素的最终样式。这些操作通常非常复杂，涉及大量的计算和内存操作。</p></li><li><p><strong>复杂的 CSS 规则</strong>: 如果页面中有大量复杂的 CSS 规则，或者样式表层级嵌套较深，浏览器计算每个元素的最终样式时会更加复杂和耗时。</p></li></ul><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>DOM 树是树结构，我们可以采用深度优先遍历的方式，将上层样式传递到下层节点模拟计算样式，来避免使用 <code>getComputedStyle</code>。节点样式是通过样式选择器的优先级，来确定最终的样式。了解这两个基础逻辑后，我们就可以开始编码了。</p><h3 id="解析-style-标签，将标签的样式存储在-Map-中"><a href="#解析-style-标签，将标签的样式存储在-Map-中" class="headerlink" title="解析 style 标签，将标签的样式存储在 Map 中"></a>解析 style 标签，将标签的样式存储在 Map 中</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">const</span> style = <span class="hljs-variable language_">this</span>.<span class="hljs-property">_dom</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;style&#x27;</span>);<br><span class="hljs-keyword">if</span> (style) &#123;<br>    <span class="hljs-keyword">const</span> shadowHost = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">&#x27;div&#x27;</span>);<br>    <span class="hljs-keyword">const</span> shadowRoot = shadowHost.<span class="hljs-title function_">attachShadow</span>(&#123; <span class="hljs-attr">mode</span>: <span class="hljs-string">&#x27;open&#x27;</span> &#125;);<br>    <span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>.<span class="hljs-title function_">appendChild</span>(shadowHost);<br>    shadowRoot.<span class="hljs-title function_">appendChild</span>(style);<br>    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> rule <span class="hljs-keyword">of</span> style.<span class="hljs-property">sheet</span>!.<span class="hljs-property">cssRules</span>) &#123;<br>        <span class="hljs-keyword">const</span> cssRule = rule <span class="hljs-keyword">as</span> <span class="hljs-title class_">CSSStyleRule</span>;<br>        <span class="hljs-keyword">const</span> selectorText = cssRule.<span class="hljs-property">selectorText</span>;<br>        <span class="hljs-keyword">const</span> style = cssRule.<span class="hljs-property">style</span>;<br>        <span class="hljs-variable language_">this</span>.<span class="hljs-property">_styleMap</span>.<span class="hljs-title function_">set</span>(selectorText, style);<br>    &#125;<br>    style.<span class="hljs-title function_">remove</span>();<br>    shadowHost.<span class="hljs-title function_">remove</span>();<br>&#125;<br></code></pre></td></tr></table></figure><blockquote><p><strong>注:</strong> style 标签只有挂载到 DOM 上，才会实现 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CSSStyleSheet"><code>CSSStyleSheet</code></a> 接口。而使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components/Using_shadow_DOM">shadow DOM</a> 的目的是为了样式隔离，避免造成全局样式污染。</p></blockquote><h3 id="获取样式函数"><a href="#获取样式函数" class="headerlink" title="获取样式函数"></a>获取样式函数</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">private</span> <span class="hljs-title function_">_getStyle</span>(<span class="hljs-params"><span class="hljs-attr">node</span>: <span class="hljs-title class_">HTMLElement</span>, <span class="hljs-attr">styleStr</span>: <span class="hljs-built_in">string</span></span>) &#123;<br>    <span class="hljs-keyword">const</span> <span class="hljs-attr">recordStyle</span>: <span class="hljs-title class_">Record</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt; = <span class="hljs-title function_">turnToStyleObject</span>(styleStr);<br>    <span class="hljs-keyword">const</span> style = node.<span class="hljs-property">style</span>;<br>    <span class="hljs-comment">// retrieve multiple sources for a node and compile them into a cohesive new style string. eg.`background`、`background-color`</span><br>    ···<br>    ···<br>    <span class="hljs-comment">// style represents inline styles with the highest priority, followed by selectorText which corresponds to stylesheet rules, and recordStyle pertains to inherited styles with the lowest priority.</span><br>        value =<br>            style.<span class="hljs-title function_">getPropertyValue</span>(key) ||<br>            <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">_getStyleBySelectorText</span>(<span class="hljs-string">`#<span class="hljs-subst">$&#123;node.id&#125;</span>`</span>, key) ||<br>            value ||<br>            <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">_getStyleBySelectorText</span>(node.<span class="hljs-property">nodeName</span>.<span class="hljs-title function_">toLowerCase</span>(), key) ||<br>            recordStyle[key] ||<br>            <span class="hljs-string">&#x27;&#x27;</span>;<br>        value &amp;&amp; (newStyleStr += <span class="hljs-string">`<span class="hljs-subst">$&#123;key&#125;</span>:<span class="hljs-subst">$&#123;value&#125;</span>;`</span>);<br>    &#125;<br>    <span class="hljs-keyword">return</span> newStyleStr;<br>&#125;<br></code></pre></td></tr></table></figure><blockquote><p>详细函数实现请翻阅 <code>packages/sheets-ui/src/services/clipboard/html-to-usm/converter.ts</code>。函数实现时需要注意在各类 html 中，例如 <code>background</code>、<code>background-color</code> 在表格中均可代表背景颜色，应该补充边界处理。</p></blockquote><h2 id="内存泄漏问题"><a href="#内存泄漏问题" class="headerlink" title="内存泄漏问题"></a>内存泄漏问题</h2><p>参考 StackOverflow 上的 <a href="https://stackoverflow.com/questions/56451731/dom-parser-chrome-extension-memory-leak">这篇帖子</a>。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs diff">export default function parseToDom(rawHtml: string) &#123;<br><span class="hljs-deletion">-const parser = new DOMParser();</span><br><span class="hljs-deletion">- const html = `&lt;x-univer id=&quot;univer-root&quot;&gt;$&#123;rawHtml&#125;&lt;/x-univer&gt;`;</span><br><span class="hljs-deletion">- const doc = parser.parseFromString(html, &#x27;text/html&#x27;);</span><br><br><span class="hljs-deletion">- return doc.querySelector(&#x27;#univer-root&#x27;);</span><br><span class="hljs-addition">+ const template = document.createElement(&#x27;body&#x27;);</span><br><span class="hljs-addition">+ template.innerHTML = rawHtml;</span><br><span class="hljs-addition">+return template;</span><br>&#125;<br></code></pre></td></tr></table></figure><p>在解决内存泄漏时，剔除了 DOMParser API 的使用，并去除了将 html 字符串挂载到 DOM 上的行为。在粘贴行为结束后的调用dispose函数回收解析过程中使用的 Map 和临时变量。经过这样处理，不仅解决了内存泄漏的问题，还节约了挂载构建 DOM 树的时间。</p><h2 id="参考阅读"><a href="#参考阅读" class="headerlink" title="参考阅读"></a>参考阅读</h2><ul><li><a href="https://univer.ai/zh-CN/guides/sheet/architecture/univer">Univer 架构</a></li><li><a href="https://zhuanlan.zhihu.com/p/574069391">Chromium 渲染流水线——字节码到像素的一生</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注:&lt;/strong&gt; 本文基于 &lt;a href=&quot;https://github.com/dream-num/univer&quot;&gt;univer-sheet&lt;/a&gt; 源码，对其复制粘贴解析逻辑进行核心优化解读。&lt;/p&gt;
&lt;/blockqu</summary>
      
    
    
    
    <category term="技术" scheme="https://blog.yiliang.app/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="性能优化" scheme="https://blog.yiliang.app/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
  </entry>
  
</feed>
