<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Jeremy</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>http://shchome.top/</id>
  <link href="http://shchome.top/" rel="alternate"/>
  <link href="http://shchome.top/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Jeremy</rights>
  <subtitle>所有的程序员都是编剧，所有的计算机都是烂演员</subtitle>
  <title>Jeremy个人博客</title>
  <updated>2026-05-24T03:45:48.283Z</updated>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="前端" scheme="http://shchome.top/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="网络" scheme="http://shchome.top/tags/%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<p>近年来各大公司对信息安全传输越来越重视，也逐步把网站升级到 HTTPS 了，那么大家知道 HTTPS 的原理是怎样的吗，到底是它是如何确保信息安全传输的？</p><ol><li>HTTP 为什么不安全</li><li>安全通信的四大原则</li><li>HTTPS 通信原理简述<ul><li>对称加密</li><li>数字证书</li><li>非对称加密</li><li>数字签名</li></ul></li><li>其它 HTTPS 相关问题</li></ol><h2 id="HTTP-为什么不安全"><a href="#HTTP-为什么不安全" class="headerlink" title="HTTP 为什么不安全"></a>HTTP 为什么不安全</h2><p>HTTP 由于是明文传输，主要存在三大风险</p><p>1、 窃听风险</p><p>中间人可以获取到通信内容，由于内容是明文，所以获取明文后有安全风险</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d07f1195acff4a38befb83990e6143f4~tplv-k3u1fbpfcp-zoom-1.image"></p><p>2、 篡改风险</p><p>中间人可以篡改报文内容后再发送给对方，风险极大</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a4b9225db32748cdbd6279adc01315cd~tplv-k3u1fbpfcp-zoom-1.image"></p><p>3、 冒充风险</p><p>比如你以为是在和某宝通信，但实际上是在和一个钓鱼网站通信。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5bf6e7ebf3034a2bb570031f79f6a9fc~tplv-k3u1fbpfcp-zoom-1.image"></p><p>HTTPS 显然是为了解决这三大风险而存在的，接下来我们看看 HTTPS 到底解决了什么问题。</p><h2 id="安全通信的四大原则"><a href="#安全通信的四大原则" class="headerlink" title="安全通信的四大原则"></a>安全通信的四大原则</h2><p>看了上一节，不难猜到 HTTPS 就是为了解决上述三个风险而生的，一般我们认为安全的通信需要包括以下四个原则: <strong>机密性</strong>、<strong>完整性</strong>，<strong>身份认证</strong>和<strong>不可否认</strong></p><ol><li><strong>机密性</strong>：即对数据加密，解决了窃听风险，因为即使被中间人窃听，由于数据是加密的，他也拿不到明文</li><li><strong>完整性</strong>：指数据在传输过程中没有被篡改，不多不少，保持原样，中途如果哪怕改了一个标点符号，接收方也能识别出来，从来判定接收报文不合法</li><li><strong>身份认证</strong>：确认对方的真实身份，即证明「你妈是你妈」的问题，这样就解决了冒充风险，用户不用担心访问的是某宝结果却在和钓鱼网站通信的问题</li><li><strong>不可否认</strong>: 即不可否认已发生的行为，比如小明向小红借了 1000 元，但没打借条，或者打了借条但没有<strong>签名</strong>，就会造成小红的资金损失</li></ol><p>接下来我们一步步来看看 HTTPS 是如何实现以满足以上四大安全通信原则的。</p><h2 id="HTTPS-通信原理简述"><a href="#HTTPS-通信原理简述" class="headerlink" title="HTTPS 通信原理简述"></a>HTTPS 通信原理简述</h2><h3 id="对称加密：-HTTPS-的最终加密形式"><a href="#对称加密：-HTTPS-的最终加密形式" class="headerlink" title="对称加密： HTTPS 的最终加密形式"></a>对称加密： HTTPS 的最终加密形式</h3><p>既然 HTTP 是明文传输的，那我们给报文加密不就行了，既然要加密，我们肯定需要通信双方协商好密钥吧，一种是通信双方使用<strong>同一把密钥</strong>，即<strong>对称加密</strong>的方式来给报文进行加解密。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/014fae78877647c9a6f7b1f002f6fc91~tplv-k3u1fbpfcp-zoom-1.image"></p><p>如图示：使用对称加密的通信双方使用<strong>同一把</strong>密钥进行加解密。</p><p>对称加密具有加解密速度快，性能高的特点，也是 HTTPS 最终采用的加密形式，但是这里有一个关键问题，对称加密的通信双方要使用同一把密钥，这个密钥是如何协商出来的？如果通过报文的方式直接传输密钥，之后的通信其实还是在裸奔，因为这个密钥会被中间人截获甚至替换掉，这样中间人就可以用截获的密钥解密报文，甚至替换掉密钥以达到篡改报文的目的。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f5d3f66b39ee445cb0a0ec51d4ae6899~tplv-k3u1fbpfcp-zoom-1.image"></p><p>有人说对这个密钥加密不就完了，但对方如果要解密这个密钥还是要传加密密钥给对方，依然还是会被中间人截获的，这么看来直接传输密钥无论怎样都无法摆脱俄罗斯套娃的难题，是不可行的。</p><h3 id="非对称加密：解决单向对称密钥的传输问题"><a href="#非对称加密：解决单向对称密钥的传输问题" class="headerlink" title="非对称加密：解决单向对称密钥的传输问题"></a>非对称加密：解决单向对称密钥的传输问题</h3><p>直接传输密钥无论从哪一端传从上节分析来看是不行了，这里我们再看另一种加密方式：<strong>非对称加密</strong>。</p><p>非对称加密即加解密双方使用不同的密钥，一把作为公钥，可以公开的，一把作为私钥，不能公开，公钥加密的密文只有私钥可以解密，私钥加密的内容，也只有公钥可以解密。</p><p><strong>注：私钥加密其实这个说法其实并不严谨，准确的说私钥加密应该叫私钥签名，因为私密加密的信息公钥是可以解密的，而公钥是公开的，任何人都可以拿到，用公钥解密叫做验签</strong></p><p>这样的话对于 server 来说，保管好私钥，发布公钥给其他 client, 其他 client 只要把对称加密的密钥加密传给 server 即可，如此一来由于公钥加密只有私钥能解密，而私钥只有 server 有，所以能保证 client 向 server 传输是安全的，server 解密后即可拿到对称加密密钥，这样交换了密钥之后就可以用对称加密密钥通信了。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b93457b2fdf149c79d28f797422926ff~tplv-k3u1fbpfcp-zoom-1.image"></p><p>但是问题又来了， server 怎么把公钥<strong>安全地</strong>传输给 client 呢。如果直接传公钥，也会存在被中间人调包的风险。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f2a7676a20804813bb1687e8b4bb4755~tplv-k3u1fbpfcp-zoom-1.image"></p><h3 id="数字证书，解决公钥传输信任问题"><a href="#数字证书，解决公钥传输信任问题" class="headerlink" title="数字证书，解决公钥传输信任问题"></a>数字证书，解决公钥传输信任问题</h3><p>如何解决公钥传输问题呢，从现实生活中的场景找答案，员工入职时，企业一般会要求提供学历证明，显然不是什么阿猫阿狗的本本都可称为学历，这个学历必须由**第三方权威机构（Certificate Authority，简称 CA）**即教育部颁发，同理，server 也可以向 CA 申请证书，<strong>在证书中附上公钥</strong>，然后将证书传给 client，证书由站点管理者向 CA 申请，申请的时候会提交 DNS 主机名等信息，CA 会根据这些信息生成证书</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/864ac12b6bf048a1a2149e36cd7533dd~tplv-k3u1fbpfcp-zoom-1.image"></p><p>这样当 client 拿到证书后，就可以获得证书上的公钥，再用此公钥加密<strong>对称加密密钥</strong>传给 server 即可，看起来确实很完美，不过在这里大家要考虑两个问题</p><p><strong>问题一、 如何验证证书的真实性，如何防止证书被篡改</strong></p><p>想象一下上文中我们提到的学历，企业如何认定你提供的学历证书是真是假呢，答案是用学历编号，企业拿到证书后用学历编号在学信网上一查就知道证书真伪了，学历编号其实就是我们常说的<strong>数字签名</strong>，可以防止证书造假。</p><p>回到 HTTPS 上，证书的数字签名该如何产生的呢，一图胜千言</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/02663b84d1b54ad9bce43c47af21d975~tplv-k3u1fbpfcp-zoom-1.image"></p><p>步骤如下<br>1、 首先使用一些摘要算法（如 MD5）将证书明文（如证书序列号，DNS主机名等）生成摘要，然后再用第三方权威机构的私钥对生成的摘要进行加密（签名）</p><blockquote><p> 消息摘要是把任意长度的输入揉和而产生长度固定的伪随机输入的算法，无论输入的消息有多长，计算出来的消息摘要的长度总是固定的，一般来说，只要内容不同，产生的摘要必然不同（相同的概率可以认为接近于 0），所以可以验证内容是否被篡改了。</p></blockquote><p>为啥要先生成摘要再加密呢，不能直接加密？</p><p>因为使用非对称加密是非常耗时的，如果把整个证书内容都加密生成签名的话，客户端验验签也需要把签名解密，证书明文较长，客户端验签就需要很长的时间，而用摘要的话，会把内容很长的明文压缩成小得多的定长字符串，客户端验签的话就会快得多。</p><p>2、客户端拿到证书后也用同样的摘要算法对证书明文计算摘要，两者一笔对就可以发现报文是否被篡改了，那为啥要用第三方权威机构（Certificate Authority，简称 CA）私钥对摘要加密呢，因为摘要算法是公开的，中间人可以替换掉证书明文，再根据证书上的摘要算法计算出摘要后把证书上的摘要也给替换掉！这样 client 拿到证书后计算摘要发现一样，误以为此证书是合法就中招了。所以必须要用 CA 的私钥给摘要进行加密生成签名，这样的话 client 得用 CA 的公钥来给签名解密，拿到的才是未经篡改合法的摘要（私钥签名，公钥才能解密）</p><p>server 将证书传给 client 后，client 的验签过程如下</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/435de079c5ea4cee8080188b1604899e~tplv-k3u1fbpfcp-zoom-1.image"></p><p>这样的话，由于只有 CA 的公钥才能解密签名，如果客户端收到一个假的证书，使用 CA 的公钥是无法解密的，如果客户端收到了真的证书，但证书上的内容被篡改了，摘要比对不成功的话，客户端也会认定此证书非法。</p><p>细心的你一定发现了问题，CA 公钥如何安全地传输到 client ？如果还是从 server 传输到 client，依然无法解决公钥被调包的风险，<strong>实际上此公钥是存在于 CA 证书上，而此证书（也称 Root CA 证书）被操作系统信任，内置在操作系统上的，无需传输</strong>，如果用的是  Mac 的同学，可以打开 keychain 查看一下，可以看到很多内置的被信任的证书。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/000d3f8ba74240c79d20cfd19aa4d509~tplv-k3u1fbpfcp-zoom-1.image"></p><p>server 传输 CA 颁发的证书，客户中收到证书后使用<strong>内置 CA 证书中的公钥</strong>来解密签名，验签即可，这样的话就解决了公钥传输过程中被调包的风险。</p><p><strong>问题二、 如何防止证书被调包</strong></p><p>实际上任何站点都可以向第三方权威机构申请证书，中间人也不例外。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9aaa4305ebe34efdb5e229ee9e1ae3d1~tplv-k3u1fbpfcp-zoom-1.image"></p><p>正常站点和中间人都可以向 CA 申请证书，获得认证的证书由于都是 CA 颁发的，所以都是合法的，那么此时中间人是否可以在传输过程中将正常站点发给 client 的证书替换成自己的证书呢，如下所示</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1c5e25a281924ba0bbdcb3efa41bbce2~tplv-k3u1fbpfcp-zoom-1.image"></p><p>答案是不行，因为客户端除了通过验签的方式验证证书是否合法之外，<strong>还需要验证证书上的域名与自己的请求域名是否一致</strong>，中间人中途虽然可以替换自己向 CA 申请的合法证书，但此证书中的域名与 client 请求的域名不一致，client 会认定为不通过！</p><p>但是上面的证书调包给了我们一种思路，什么思路？大家想想，  HTTPS 既然是加密的， charles 这些「中间人」为啥能抓到明文的包呢，其实就是用了证书调包这一手法，想想看，在用 charles 抓 HTTPS 的包之前我们先要做什么，当然是安装 charles 的证书</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4c07aa643d94414884c9c15687a9fc16~tplv-k3u1fbpfcp-zoom-1.image"></p><p>这个证书里有 charles 的公钥，这样的话 charles 就可以将 server 传给 client 的证书调包成自己的证书，client 拿到后就可以用你安装的 charles  证书来验签等，验证通过之后就会用 charles 证书中的公钥来加密对称密钥了，整个流程如下</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6623888038634dbb9c88e05abd0f28ef~tplv-k3u1fbpfcp-zoom-1.image"></p><p>由此可知，charles 这些中间人能抓取 HTTPS 包的前提是信任它们的 CA 证书，然后就可以通过替换证书的方式进行瞒天过海，所以我们千万不要随便信任第三方的证书，避免安全风险。</p><h2 id="其它-HTTPS-相关问题"><a href="#其它-HTTPS-相关问题" class="headerlink" title="其它 HTTPS 相关问题"></a>其它 HTTPS 相关问题</h2><blockquote><p> 什么是双向认证<br>以上的讲述过程中，我们只是在 client 端验证了 server 传输证书的合法性，但 server 如何验证 client 的合法性，还是用证书，我们在网上进行转账等操作时，想想看是不是要先将银行发给我们的 U 盾插到电脑上？其实也是因为 U 盾内置了证书，通信时将证书发给 server，server 验证通过之后即可开始通信。<br><strong>画外音：身份认证只是 U 盾功能的一种，还有其他功能，比如加解密都是在 U 盾中执行，保证了密钥不会出现在内存中</strong></p></blockquote><blockquote><p> 什么是证书信任链</p></blockquote><p>前文说了，我们可以向 CA 申请证书，但全世界的顶级 CA（Root CA） 就那么几个，每天都有很多人要向它申请证书，它也忙不过来啊，怎么办呢，想想看在一个公司里如果大家都找 CEO 办事，他是不是要疯了，那他能怎么办？授权，他会把权力交给 CTO，CFO 等，这样你们只要把 CTO 之类的就行了，CTO 如果也忙不过来呢，继续往下授权啊。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/210ba3784a7f4390a0b78a6212804f9d~tplv-k3u1fbpfcp-zoom-1.image"></p><p>同样的，既然顶级 CA 忙不过来，那它就向下一级，下下级 CA 授权即可，这样我们就只要找一级&#x2F;二级&#x2F;三级 CA 申请证书即可。怎么证明这些证书被 Root CA 授权过了呢，小一点的 CA 可以让大一点的 CA 来签名认证，比如一级 CA 让 Root CA 来签名认证，二级 CA 让一级 CA 来签名认证,Root CA 没有人给他签名认证，只能自己证明自己了，这个证书就叫「自签名证书」或者「根证书」，我们必须信任它，不然证书信任链是走不下去的（这个根证书前文我们提过，其实是是内置在操作系统中的）</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/559934ee448c4b0d819704b5a20be36e~tplv-k3u1fbpfcp-zoom-1.image" alt="证书信任链"></p><p>现在我们看看如果站点申请的是 二级 CA 颁发的证书，client 收到之后会如何验证这个证书呢，实际上 service 传了传给二级 CA 的证书外，<strong>还会把证书信任链也一起传给客户端</strong>，这样客户端会按如下步骤进行验证：</p><ol><li>浏览器就使用信任的根证书（根公钥）解析证书链的根证书得到一级证书的公钥+摘要验签</li><li>拿一级证书的公钥解密一级证书，拿到二级证书的公钥和摘要验签</li><li>再然后拿二级证书的公钥解密 server 传过来的二级证书，得到服务器的公钥和摘要验签，验证过程就结束了</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>相信大家看完本文应该对 HTTPS 的原理有了很清楚的认识了， HTTPS 无非就是 HTTP + SSL&#x2F;TLS</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be6ddacff31f494dbc76948f852c501a~tplv-k3u1fbpfcp-zoom-1.image"></p><p>而 SSL&#x2F;TLS 的功能其实本质上是<strong>如何协商出安全的对称加密密钥以利用此密钥进行后续通讯的过程</strong>，带着这个疑问相信你不难理解数字证书和数字签名这两个让人费解的含义，搞懂了这些也就明白了为啥  HTTPS 是加密的，charles 这些工具却能抓包出明文来。</p><p>巨人的肩膀</p><ul><li><a href="https://juejin.cn/post/6844903958863937550">https://juejin.cn/post/6844903958863937550</a></li><li><a href="https://showme.codes/2017-02-20/understand-https/">https://showme.codes/2017-02-20/understand-https/</a></li><li>极客时间，透视 HTTP 协议</li><li><a href="https://zhuanlan.zhihu.com/p/67199487">https://zhuanlan.zhihu.com/p/67199487</a></li></ul>]]>
    </content>
    <id>http://shchome.top/2026/05/22/HTTPS%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/</id>
    <link href="http://shchome.top/2026/05/22/HTTPS%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/"/>
    <published>2026-05-22T13:05:00.000Z</published>
    <summary>
      <![CDATA[<p>近年来各大公司对信息安全传输越来越重视，也逐步把网站升级到 HTTPS 了，那么大家知道 HTTPS 的原理是怎样的吗，到底是它是如何确保信息安全传输的？</p>
<ol>
<li>HTTP 为什么不安全</li>
<li>安全通信的四大原则</li>
<li>HTTPS]]>
    </summary>
    <title>HTTPS原理解析</title>
    <updated>2026-05-24T03:45:48.283Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="前端" scheme="http://shchome.top/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="前端" scheme="http://shchome.top/tags/%E5%89%8D%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>Koa 有两个核心知识点：一个是中间件ctx，一个就是洋葱模型。<br>中间件ctx利用js原型，很巧妙的把request和response对象封装在里面。<br>另外一个核心就是本文要分析的洋葱模型。</p><h2 id="1-认识洋葱模型"><a href="#1-认识洋葱模型" class="headerlink" title="1 认识洋葱模型"></a>1 认识洋葱模型</h2><figure class="highlight qml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">&#x27;koa&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"><span class="keyword">const</span> PORT = <span class="number">3000</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// #1</span></span><br><span class="line">app.use(<span class="keyword">async</span> (ctx, next)=&gt;&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">1</span>)</span><br><span class="line">    <span class="keyword">await</span> next();</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">1</span>)</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// #2</span></span><br><span class="line">app.use(<span class="keyword">async</span> (ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>)</span><br><span class="line">    <span class="keyword">await</span> next();</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">app.use(<span class="keyword">async</span> (ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">3</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">app.listen(PORT);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">`http://localhost:<span class="subst">$&#123;PORT&#125;</span>`</span>);</span><br></pre></td></tr></table></figure><p>打印顺序</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">2</span><br><span class="line">1</span><br></pre></td></tr></table></figure><p>当程序运行到await next()的时候就会暂停当前程序，进入下一个中间件，处理完之后才会回过头来继续处理。</p><h2 id="2-原理"><a href="#2-原理" class="headerlink" title="2 原理"></a>2 原理</h2><p>核心：中间件管理和next实现，其中next是巧妙的使用了Promise特性。洋葱模型，本质上是Promise.resolve()的递归。</p><h4 id="2-1-中间件管理"><a href="#2-1-中间件管理" class="headerlink" title="2.1 中间件管理"></a>2.1 中间件管理</h4><p>app.listen使用了this.callback()来生成node的httpServer的回调函数。</p><figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">listen(...args) &#123;</span><br><span class="line">    debug(<span class="string">&#x27;listen&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">server</span> = http.createServer(<span class="keyword">this</span>.callback());</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">server</span>.listen(...args);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Koa.js中间件引擎是有koa-compose模块，即如下的compose方法</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">callback() &#123;</span><br><span class="line">    <span class="keyword">const</span> fn = compose(<span class="keyword">this</span>.middleware); <span class="comment">// 核心：中间件的管理和next的实现</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">this</span>.listeners(<span class="string">&#x27;error&#x27;</span>).length) <span class="keyword">this</span>.on(<span class="string">&#x27;error&#x27;</span>, <span class="keyword">this</span>.onerror);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> handleRequest = (req, res) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> ctx = <span class="keyword">this</span>.createContext(req, res); <span class="comment">// 创建ctx</span></span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.handleRequest(ctx, fn);</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> handleRequest;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们app.use的时候，只是把方法存在了一个数组里</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">use(fn) &#123;</span><br><span class="line">    <span class="keyword">this</span>.middleware.push(fn);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-2-next实现"><a href="#2-2-next实现" class="headerlink" title="2.2 next实现"></a>2.2 next实现</h4><p>dispatch函数，它将遍历整个middleware，然后将context和dispatch(i + 1)传给middleware中的方法。<br>dispatch return Promise这段代码就很巧妙的实现了两点:</p><ul><li>将context一路传下去给中间件</li><li>将middleware中的下一个中间件fn作为未来next的返回值</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">compose</span> (<span class="params">middleware</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params">context, next</span>) &#123;</span><br><span class="line">    <span class="comment">// last called middleware #</span></span><br><span class="line">    <span class="keyword">let</span> index = -<span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">dispatch</span>(<span class="number">0</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">dispatch</span> (<span class="params">i</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (i &lt;= index) <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;next() called multiple times&#x27;</span>))</span><br><span class="line">      index = i</span><br><span class="line">      <span class="keyword">let</span> fn = middleware[i]</span><br><span class="line">      <span class="keyword">if</span> (i === middleware.<span class="property">length</span>) fn = next</span><br><span class="line">      <span class="keyword">if</span> (!fn) <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>()</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">          <span class="comment">// 核心代码：返回Promise</span></span><br><span class="line">          <span class="comment">// next时，交给下一个dispatch（下一个中间件方法）</span></span><br><span class="line">          <span class="comment">// 同时，当前同步代码挂起，直到中间件全部完成后继续</span></span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="title function_">fn</span>(context, <span class="keyword">function</span> <span class="title function_">next</span> (<span class="params"></span>) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="title function_">dispatch</span>(i + <span class="number">1</span>)</span><br><span class="line">        &#125;))</span><br><span class="line">      &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(err)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-思考"><a href="#3-思考" class="headerlink" title="3 思考"></a>3 思考</h2><p>洋葱模型实现原理，等同于如下代码：<br><strong>next()返回的是promise，需要使用await去等待promise的resolve值。</strong><br>promise的嵌套就像是洋葱模型的形状就是一层包裹着一层，直到await到最里面一层的promise的resolve值返回。</p><figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.resolve(middleware1(context, <span class="keyword">async</span>() =&gt; &#123; <span class="regexp">//</span> 注意<span class="keyword">async</span>关键字不能省略</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(middleware2(context, <span class="keyword">async</span>() =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(middleware3(context, <span class="keyword">async</span>() =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve();</span><br><span class="line">    &#125;));</span><br><span class="line">  &#125;));</span><br><span class="line">&#125;))</span><br><span class="line">.<span class="keyword">then</span>(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    console.log(<span class="string">&#x27;end&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>http://shchome.top/2026/05/20/Koa%20%E6%B4%8B%E8%91%B1%E6%A8%A1%E5%9E%8B/</id>
    <link href="http://shchome.top/2026/05/20/Koa%20%E6%B4%8B%E8%91%B1%E6%A8%A1%E5%9E%8B/"/>
    <published>2026-05-20T00:36:00.000Z</published>
    <summary>
      <![CDATA[<p>Koa 有两个核心知识点：一个是中间件ctx，一个就是洋葱模型。<br>中间件ctx利用js原型，很巧妙的把request和response对象封装在里面。<br>另外一个核心就是本文要分析的洋葱模型。</p>
<h2 id="1-认识洋葱模型"><a href="#1-认]]>
    </summary>
    <title>koa洋葱模型</title>
    <updated>2026-05-24T03:45:48.283Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="前端" scheme="http://shchome.top/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="前端" scheme="http://shchome.top/tags/%E5%89%8D%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>这是一道有着成熟的业界规范的 coding 题，完成这道题的前置知识就是要了解什么是 Promises&#x2F;A+。<br>这道题的难点就在于它是有规范的，任何一个不满足所有规范条件的解答都是错误的。同时，成熟的规范也配套了成熟的测试用例，官方提供了 872 个测试用例针对规范中的所有条件一一进行检测，哪怕只有一条失败，那也是错误的解答。<br>而这道题的答题关键也恰恰是因为它是有规范的，只要我们对于规范了然于胸，那么编写代码自然也是水到渠成。因为官方规范提供了一个符合 Promises&#x2F;A+ 规范的 Promise 应该具有的全部条件，并且在 Requirements 一节中结构清晰、逻辑充分的表述了出来，我们只需将规范中的文字转变为代码，就能够实现一个 Promises&#x2F;A+ 规范的 Promise。</p><h2 id="Promise"><a href="#Promise" class="headerlink" title="Promise"></a>Promise</h2><p>Promise 翻译过来就是承诺的意思，这个承诺会在未来有一个确切的答复，并且该承诺有三种状态，分别是：</p><ol><li>等待中（pending）</li><li>完成了（resolved）</li><li>拒绝了（rejected）</li></ol><h4 id="Promise的一些特点"><a href="#Promise的一些特点" class="headerlink" title="Promise的一些特点"></a>Promise的一些特点</h4><ol><li>Promise状态一旦从等待状态变成为其他状态就永远不能更改状态了。</li></ol><figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">(resolve, reject)</span> =&gt;</span> &#123;</span><br><span class="line">  resolve(<span class="string">&#x27;success&#x27;</span>)</span><br><span class="line">  <span class="regexp">//</span> 无效</span><br><span class="line">  reject(<span class="string">&#x27;reject&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><ol start="2"><li>当我们在构造 Promise 的时候，构造函数内部的代码是立即执行的：</li></ol><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> Promise(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(<span class="string">&#x27;new Promise&#x27;</span>)</span><br><span class="line">  resolve(<span class="string">&#x27;success&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="built_in">console</span>.<span class="built_in">log</span>(<span class="string">&#x27;finifsh&#x27;</span>)</span><br><span class="line"><span class="comment">// new Promise -&gt; finifsh</span></span><br></pre></td></tr></table></figure><ol start="3"><li>Promise 实现了链式调用，也就是说每次调用 then 之后返回的都是一个 Promise，并且是一个全新的 Promise，原因也是因为状态不可变。如果你在 then 中 使用了 return，那么 return 的值会被 Promise.resolve() 包装</li></ol><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Promise.resolve(<span class="number">1</span>)</span><br><span class="line">  .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.<span class="built_in">log</span>(res) <span class="comment">// =&gt; 1</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">2</span> <span class="comment">// 包装成 Promise.resolve(2)</span></span><br><span class="line">  &#125;)</span><br><span class="line">  .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.<span class="built_in">log</span>(res) <span class="comment">// =&gt; 2</span></span><br><span class="line">  &#125;)</span><br></pre></td></tr></table></figure><ol start="4"><li>Promise 也很好地解决了回调地狱的问题，可以把之前的回调地狱例子改写为如下代码：</li></ol><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ajax(url)</span><br><span class="line">  .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.<span class="built_in">log</span>(res)</span><br><span class="line">      <span class="keyword">return</span> ajax(url1)</span><br><span class="line">  &#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.<span class="built_in">log</span>(res)</span><br><span class="line">      <span class="keyword">return</span> ajax(url2)</span><br><span class="line">  &#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> <span class="built_in">console</span>.<span class="built_in">log</span>(res))</span><br></pre></td></tr></table></figure><h2 id="手写Promise"><a href="#手写Promise" class="headerlink" title="手写Promise"></a>手写Promise</h2><h4 id="实现一个简易版的Promise"><a href="#实现一个简易版的Promise" class="headerlink" title="实现一个简易版的Promise"></a>实现一个简易版的Promise</h4><ol><li>大体框架</li></ol><figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> PENDING = <span class="string">&#x27;pending&#x27;</span></span><br><span class="line"><span class="keyword">const</span> RESOLVED = <span class="string">&#x27;resolved&#x27;</span></span><br><span class="line"><span class="keyword">const</span> REJECTED = <span class="string">&#x27;rejected&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> MyPromise(fn) &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="literal">that</span> = this</span><br><span class="line">  <span class="literal">that</span>.state = PENDING</span><br><span class="line">  <span class="literal">that</span>.value = <span class="literal">null</span></span><br><span class="line">  <span class="literal">that</span>.resolvedCallbacks = []</span><br><span class="line">  <span class="literal">that</span>.rejectedCallbacks = []</span><br><span class="line">  <span class="regexp">// 待完善 resolve 和 reject 函数</span></span><br><span class="line"><span class="regexp">  //</span> 待完善执行 fn 函数</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>首先我们创建了三个常量用于表示状态，对于经常使用的一些值都应该通过常量来管理，便于开发及后期维护</li><li>在函数体内部首先创建了常量 that，因为代码可能会异步执行，用于获取正确的 this 对象</li><li>一开始 Promise 的状态应该是 pending</li><li>value 变量用于保存 resolve 或者 reject 中传入的值</li><li>resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回调，因为当执行完 Promise 时状态可能还是等待中，这时候应该把 then 中的回调保存起来用于状态改变时使用</li></ul><ol start="2"><li>接下来完善resolve 和 reject 函数</li></ol><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">function resolve(value) &#123;</span><br><span class="line">  if (that.<span class="keyword">state</span> === PENDING) &#123;</span><br><span class="line">    that.<span class="keyword">state</span> = RESOLVED</span><br><span class="line">    that.value = value</span><br><span class="line">    that.resolvedCallbacks.map(cb =&gt; cb(that.value))</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">function reject(value) &#123;</span><br><span class="line">  if (that.<span class="keyword">state</span> === PENDING) &#123;</span><br><span class="line">    that.<span class="keyword">state</span> = REJECTED</span><br><span class="line">    that.value = value</span><br><span class="line">    that.rejectedCallbacks.map(cb =&gt; cb(that.value))</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这两个函数代码类似，就一起解析了</p><ul><li>首先两个函数都得判断当前状态是否为等待中，因为规范规定只有等待态才可以改变状态</li><li>将当前状态更改为对应状态，并且将传入的值赋值给 value</li><li>遍历回调数组并执行</li></ul><ol start="3"><li>完成以上两个函数以后，我们就该实现如何执行 Promise 中传入的函数了</li></ol><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">try &#123;</span><br><span class="line">  <span class="built_in">fn</span>(resolve, reject)</span><br><span class="line">&#125; catch (e) &#123;</span><br><span class="line">  <span class="built_in">reject</span>(e)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实现很简单，执行传入的参数并且将之前两个函数当做参数传进去</p><p>接下来实现较为复杂的then函数</p><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">MyPromise.prototype.then = function(<span class="keyword">on</span>Fulfilled, <span class="keyword">on</span>Rejected) &#123;</span><br><span class="line">  <span class="keyword">const</span> that = this</span><br><span class="line">  <span class="keyword">on</span>Fulfilled = typeof <span class="keyword">on</span>Fulfilled === &#x27;function&#x27; ? <span class="keyword">on</span>Fulfilled : v =&gt; v</span><br><span class="line">  <span class="keyword">on</span>Rejected =</span><br><span class="line">    typeof <span class="keyword">on</span>Rejected === &#x27;function&#x27;</span><br><span class="line">      ? <span class="keyword">on</span>Rejected</span><br><span class="line">      : r =&gt; &#123;</span><br><span class="line">          throw r</span><br><span class="line">        &#125;</span><br><span class="line">  if (that.<span class="keyword">state</span> === PENDING) &#123;</span><br><span class="line">    that.resolvedCallbacks.push(<span class="keyword">on</span>Fulfilled)</span><br><span class="line">    that.rejectedCallbacks.push(<span class="keyword">on</span>Rejected)</span><br><span class="line">  &#125;</span><br><span class="line">  if (that.<span class="keyword">state</span> === RESOLVED) &#123;</span><br><span class="line">    <span class="keyword">on</span>Fulfilled(that.value)</span><br><span class="line">  &#125;</span><br><span class="line">  if (that.<span class="keyword">state</span> === REJECTED) &#123;</span><br><span class="line">    <span class="keyword">on</span>Rejected(that.value)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>首先判断两个参数是否为函数类型，因为这两个参数是可选参数</li><li>当参数不是函数类型时，需要创建一个函数赋值给对应的参数，同时也实现了透传。</li></ul><p>以上就是简单版 Promise 实现，接下来一小节是实现完整版 Promise 的解析，相信看完完整版的你，一定会对于 Promise 的理解更上一层楼。</p><h2 id="符合-Promise-A-规范的-Promise"><a href="#符合-Promise-A-规范的-Promise" class="headerlink" title="符合 Promise&#x2F;A+ 规范的 Promise"></a>符合 Promise&#x2F;A+ 规范的 Promise</h2><figure class="highlight zephir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Promise</span><span class="params">(executor)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 2.1. Promise 的状态</span></span><br><span class="line">  <span class="comment">// Promise 必须处于以下三种状态之一：pending，fulfilled 或者 rejected。</span></span><br><span class="line">  this.state = <span class="string">&quot;pending&quot;</span>;</span><br><span class="line">  <span class="comment">// 2.2.6.1. 如果 promise 处于 fulfilled 状态，所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行。</span></span><br><span class="line">  this.onFulfilledCallback = [];</span><br><span class="line">  <span class="comment">// 2.2.6.2. 如果 promise 处于 rejected 状态，所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。</span></span><br><span class="line">  this.onRejectedCallback = [];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="keyword">self</span> = this;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">resolve</span><span class="params">(value)</span> </span>&#123;</span><br><span class="line">    setTimeout(<span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="comment">// 2.1.1. 当 Promise 处于 pending 状态时：</span></span><br><span class="line">      <span class="comment">// 2.1.1.1. 可以转换到 fulfilled 或 rejected 状态。</span></span><br><span class="line">      <span class="comment">// 2.1.2. 当 Promise 处于 fulfilled 状态时：</span></span><br><span class="line">      <span class="comment">// 2.1.2.1. 不得过渡到任何其他状态。</span></span><br><span class="line">      <span class="comment">// 2.1.2.2. 必须有一个不能改变的值。</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">self</span>.state === <span class="string">&quot;pending&quot;</span>) &#123;</span><br><span class="line">        <span class="keyword">self</span>.state = <span class="string">&quot;fulfilled&quot;</span>;</span><br><span class="line">        <span class="keyword">self</span>.data = value;</span><br><span class="line">        <span class="comment">// 2.2.6.1. 如果 promise 处于 fulfilled 状态，所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行。</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="keyword">self</span>.onFulfilledCallback.length; i++) &#123;</span><br><span class="line">          <span class="keyword">self</span>.onFulfilledCallback[i](value);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">reject</span><span class="params">(reason)</span> </span>&#123;</span><br><span class="line">    setTimeout(<span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="comment">// 2.1.1. 当 Promise 处于 pending 状态时：</span></span><br><span class="line">      <span class="comment">// 2.1.1.1. 可以转换到 fulfilled 或 rejected 状态。</span></span><br><span class="line">      <span class="comment">// 2.1.3. 当 Promise 处于 rejected 状态时：</span></span><br><span class="line">      <span class="comment">// 2.1.2.1. 不得过渡到任何其他状态。</span></span><br><span class="line">      <span class="comment">// 2.1.2.2. 必须有一个不能改变的值。</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">self</span>.state === <span class="string">&quot;pending&quot;</span>) &#123;</span><br><span class="line">        <span class="keyword">self</span>.state = <span class="string">&quot;rejected&quot;</span>;</span><br><span class="line">        <span class="keyword">self</span>.data = reason;</span><br><span class="line">        <span class="comment">// 2.2.6.2. 如果 promise 处于 rejected 状态，所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="keyword">self</span>.onRejectedCallback.length; i++) &#123;</span><br><span class="line">          <span class="keyword">self</span>.onRejectedCallback[i](reason);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 补充说明：用户传入的函数可能也会执行异常，所以这里用 try...catch 包裹</span></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    executor(resolve, reject);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (reason) &#123;</span><br><span class="line">    reject(reason);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>then方法</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 2.2. then 方法</span></span><br><span class="line"><span class="comment">// 一个 promise 必须提供一个 then 方法来访问其当前值或最终值或 rejected 的原因。</span></span><br><span class="line"><span class="comment">// 一个 promise 的 then 方法接受两个参数：</span></span><br><span class="line"><span class="comment">// promise.then(onFulfilled, onRejected)</span></span><br><span class="line"><span class="title class_">Promise</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">then</span> = <span class="keyword">function</span> (<span class="params">onFulfilled, onRejected</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> self = <span class="variable language_">this</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> promise2;</span><br><span class="line">  <span class="comment">// 2.2.7. then 必须返回一个 promise</span></span><br><span class="line">  <span class="keyword">return</span> (promise2 = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span> (<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">    <span class="comment">// 2.2.2. 如果 onFulfilled 是一个函数:</span></span><br><span class="line">    <span class="comment">// 2.2.2.1. 它必须在 promise 的状态变为 fulfilled 后被调用，并将 promise 的值作为它的第一个参数。</span></span><br><span class="line">    <span class="comment">// 2.2.2.2. 它一定不能在 promise 的状态变为 fulfilled 前被调用。</span></span><br><span class="line">    <span class="comment">// 2.2.2.3. 它最多只能被调用一次。</span></span><br><span class="line">    <span class="keyword">if</span> (self.<span class="property">state</span> === <span class="string">&quot;fulfilled&quot;</span>) &#123;</span><br><span class="line">      <span class="comment">// 2.2.4. onFulfilled 或 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用。</span></span><br><span class="line">      <span class="comment">// 3.1. 这可以通过“宏任务”机制（例如 setTimeout 或 setImmediate）或“微任务”机制（例如 MutationObserver 或 process.nextTick）来实现。</span></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="comment">// 2.2.1. onFulfilled 和 onRejected 都是可选参数：</span></span><br><span class="line">        <span class="comment">// 2.2.1.1. 如果 onFulfilled 不是一个函数，它必须被忽略。</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> onFulfilled === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">          <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 2.2.2.1. 它必须在 promise 的状态变为 fulfilled 后被调用，并将 promise 的值作为它的第一个参数。</span></span><br><span class="line">            <span class="comment">// 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。</span></span><br><span class="line">            <span class="keyword">const</span> x = <span class="title function_">onFulfilled</span>(self.<span class="property">data</span>);</span><br><span class="line">            <span class="comment">// 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x，则运行 Promise 处理程序 [[Resolve]](promise2, x)。</span></span><br><span class="line">            <span class="title function_">promiseResolutionProcedure</span>(promise2, x, resolve, reject);</span><br><span class="line">          &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">            <span class="comment">// 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常，promise2 必须用 e 作为 reason 来变为 rejected 状态。</span></span><br><span class="line">            <span class="title function_">reject</span>(e);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="comment">// 2.2.7.3. 如果 onFulfilled 不是一个函数且 promise1 为 fulfilled 状态，promise2 必须用和 promise1 一样的值来变为 fulfilled 状态。</span></span><br><span class="line">          <span class="title function_">resolve</span>(self.<span class="property">data</span>);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 2.2.3. 如果 onRejected 是一个函数，</span></span><br><span class="line">    <span class="comment">// 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用，并将 promise 的 reason 作为它的第一个参数。</span></span><br><span class="line">    <span class="comment">// 2.2.3.2. 它一定不能在 promise 的状态变为 rejected 前被调用。</span></span><br><span class="line">    <span class="comment">// 2.2.3.3. 它最多只能被调用一次。</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (self.<span class="property">state</span> === <span class="string">&quot;rejected&quot;</span>) &#123;</span><br><span class="line">      <span class="comment">// 2.2.4. onFulfilled 或 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用。</span></span><br><span class="line">      <span class="comment">// 3.1. 这可以通过“宏任务”机制（例如 setTimeout 或 setImmediate）或“微任务”机制（例如 MutationObserver 或 process.nextTick）来实现。</span></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="comment">// 2.2.1. onFulfilled 和 onRejected 都是可选参数：</span></span><br><span class="line">        <span class="comment">// 2.2.1.2. 如果 onRejected 不是一个函数，它必须被忽略。</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> onRejected === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">          <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用，并将 promise 的 reason 作为它的第一个参数。</span></span><br><span class="line">            <span class="comment">// 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。</span></span><br><span class="line">            <span class="keyword">const</span> x = <span class="title function_">onRejected</span>(self.<span class="property">data</span>);</span><br><span class="line">            <span class="comment">// 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x，则运行 Promise 处理程序 [[Resolve]](promise2, x)。</span></span><br><span class="line">            <span class="title function_">promiseResolutionProcedure</span>(promise2, x, resolve, reject);</span><br><span class="line">          &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">            <span class="comment">// 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常，promise2 必须用 e 作为 reason 来变为 rejected 状态。</span></span><br><span class="line">            <span class="title function_">reject</span>(e);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 2.2.7.4. 如果 onRejected 不是一个函数且 promise1 为 rejected 状态，promise2 必须用和 promise1 一样的 reason 来变为 rejected 状态。</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="title function_">reject</span>(self.<span class="property">data</span>);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (self.<span class="property">state</span> === <span class="string">&quot;pending&quot;</span>) &#123;</span><br><span class="line">      <span class="comment">// 2.2.6. then 可能会被同一个 promise 多次调用。</span></span><br><span class="line"></span><br><span class="line">      <span class="comment">// 2.2.6.1. 如果 promise 处于 fulfilled 状态，所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行。</span></span><br><span class="line">      self.<span class="property">onFulfilledCallback</span>.<span class="title function_">push</span>(<span class="keyword">function</span> (<span class="params">promise1Value</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> onFulfilled === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">          <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 2.2.2.1. 它必须在 promise 的状态变为 fulfilled 后被调用，并将 promise 的值作为它的第一个参数。</span></span><br><span class="line">            <span class="comment">// 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。</span></span><br><span class="line">            <span class="keyword">const</span> x = <span class="title function_">onFulfilled</span>(self.<span class="property">data</span>);</span><br><span class="line">            <span class="comment">// 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x，则运行 Promise 处理程序 [[Resolve]](promise2, x)。</span></span><br><span class="line">            <span class="title function_">promiseResolutionProcedure</span>(promise2, x, resolve, reject);</span><br><span class="line">          &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">            <span class="comment">// 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常，promise2 必须用 e 作为 reason 来变为 rejected 状态。</span></span><br><span class="line">            <span class="title function_">reject</span>(e);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 2.2.7.3. 如果 onFulfilled 不是一个函数且 promise1 为 fulfilled 状态，promise2 必须用和 promise1 一样的值来变为 fulfilled 状态。</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="title function_">resolve</span>(promise1Value);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="comment">// 2.2.6.2. 如果 promise 处于 rejected 状态，所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。</span></span><br><span class="line">      self.<span class="property">onRejectedCallback</span>.<span class="title function_">push</span>(<span class="keyword">function</span> (<span class="params">promise1Reason</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> onRejected === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">          <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用，并将 promise 的 reason 作为它的第一个参数。</span></span><br><span class="line">            <span class="comment">// 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。</span></span><br><span class="line">            <span class="keyword">const</span> x = <span class="title function_">onRejected</span>(self.<span class="property">data</span>);</span><br><span class="line">            <span class="comment">// 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x，则运行 Promise 处理程序 [[Resolve]](promise2, x)。</span></span><br><span class="line">            <span class="title function_">promiseResolutionProcedure</span>(promise2, x, resolve, reject);</span><br><span class="line">          &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">            <span class="comment">// 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常，promise2 必须用 e 作为 reason 来变为 rejected 状态。</span></span><br><span class="line">            <span class="title function_">reject</span>(e);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 2.2.7.4. 如果 onRejected 不是一个函数且 promise1 为 rejected 状态，promise2 必须用和 promise1 一样的 reason 来变为 rejected 状态。</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="title function_">reject</span>(promise1Reason);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;));</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>http://shchome.top/2026/05/18/%E6%89%8B%E5%86%99Promise/</id>
    <link href="http://shchome.top/2026/05/18/%E6%89%8B%E5%86%99Promise/"/>
    <published>2026-05-18T11:47:00.000Z</published>
    <summary>
      <![CDATA[<p>这是一道有着成熟的业界规范的 coding 题，完成这道题的前置知识就是要了解什么是 Promises&#x2F;A+。<br>这道题的难点就在于它是有规范的，任何一个不满足所有规范条件的解答都是错误的。同时，成熟的规范也配套了成熟的测试用例，官方提供了 872 个测试用例]]>
    </summary>
    <title>手写Promise</title>
    <updated>2026-05-24T03:45:48.283Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="前端" scheme="http://shchome.top/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="http://shchome.top/tags/JavaScript/"/>
    <content>
      <![CDATA[<p>相信大家都知道,这是前端面试中的一道高频考题,借着马上要去面试的时机, 在此做一个总结。</p><p>正文开始<br>相信大家看到这个题目，大家心中马上就有了答案，无非就那几步。但是面试时为了更好地突出自己，我们需要对面试题进行举一反三的回答。所以我们今天就来好好学习怎么把这个问题回答的更彻底。</p><p>废话不多说，下面我们来说说我们一般的回答：</p><ol><li>输入网址</li><li>根据输入的网址获取网址对应的IP地址</li><li>根据解析出来的IP地址找到对应的服务端，并和服务端建立连接</li><li>连接建立完成后，开始向服务端发起HTTP请求</li><li>服务端响应HTTP请求并返回处理后的数据</li><li>浏览器解析服务端返回的数据</li><li>浏览器对页面进行布局渲染</li></ol><p>下面我们就开始一步步的细讲，由于篇幅原因，部分内容可能需要另开博客仔细讲解。</p><h3 id="一：输入网址"><a href="#一：输入网址" class="headerlink" title="一：输入网址"></a>一：输入网址</h3><p>这一步没什么讲的，直接跳过</p><h3 id="二：根据输入的网址获取网址对应的IP地址"><a href="#二：根据输入的网址获取网址对应的IP地址" class="headerlink" title="二：根据输入的网址获取网址对应的IP地址"></a>二：根据输入的网址获取网址对应的IP地址</h3><p>我们在向浏览器输入网址时，其实就是要向服务器请求我们想要的页面内容，但是浏览器不能识别我们所输入的网址，要将域名转换成IP地址才能够被正常的请求。将域名转换成IP地址这项工作需要DNS服务器的参与。<br>DNS的作用就是通过域名查找对应的IP地址。因为IP地址存在数字和英文的组合（IPv6），很不利于人类记忆，所以就出现了域名。你可以把域名看成是某个IP的别名，DNS就是去查询这个别名的真正名称。<br>首先我们来看一张草图：<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%9A%E7%BD%91%E9%A1%B5%E4%BB%8E%E8%BE%93%E5%85%A5%E7%BD%91%E5%9D%80%E5%88%B0%E6%B8%B2%E6%9F%93%E5%AE%8C%E6%88%90%E7%BB%8F%E5%8E%86%E4%BA%86%E5%93%AA%E4%BA%9B%E8%BF%87%E7%A8%8B%EF%BC%9F/DNS%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%80%92%E5%BD%92%E6%9F%A5%E8%AF%A2%E5%92%8C%E8%BF%AD%E4%BB%A3%E6%9F%A5%E8%AF%A2.png" alt="DNS服务器递归查询和迭代查询"><br>上图详细的介绍了DNS的解析过程。客户端收到你输入的域名地址后，比如说，当你想访问<a href="http://www.Google.com时，会进行如下一系列操作：">www.Google.com时，会进行如下一系列操作：</a></p><ol><li>首先会去缓存中查询是否有对应IP；</li><li>如果在缓存中找不到对应的IP，那么就去系统配置的DNS配置文件中查询；</li><li>如果配置文件中也没有找到，接下来就会直接去DNS根服务器查询，此时会找出负责com这个顶级域名的服务器；</li><li>然后去该服务器查找Google这个二级域名；</li><li>接下来三级域名的查询其实是我们配置的，你可以给www这个域名配置一个IP，然后还可以给别的三级域名配置一个IP；</li></ol><p>以上介绍的是 DNS 迭代查询和递归查询，区别就是前者是由客户端去做请求，后者是由系统配置的DNS服务器做请求，得到结果后将数据返回给客户端。<br>说明：正常情况下，本地DNS服务器的缓存中已经有了comDNS服务器的地址，因此请求根域名这一步不是必需的。<br>PS: 为什么DNS更适合用UDP?(曾经一个哥们面试时被问到)<br>这个问题的回答需要从TCP和UDP的特性来回答，很明显，使用UDP相比于TCP耗费的网络性能肯定要少一些，基于UDP的DNS协议只需要一次请求、一次应答即可；而基于TCP的DNS协议需要经过三次握手、四次挥手的连接过程。当然这是基于数据包的数量以及占有网络资源方面的分析，那么数据包的一致性方面呢？首先我们应该要知道的是DNS数据包都不是那种特别大的，所以使用UDP不需要分包，如果丢包就是全部丢包，如果收到了数据，就是收到了全部数据，所以只需要考虑丢包的情况，出现了丢包就重新请求一次就好了。而且DNS的报文允许填入序列号字段，对于请求报文和其对应的应答报文，这个字段是相同的，通过它可以区分DNS应答是对应的哪个请求</p><ul><li>DNS通常是基于UDP的，但当数据长度大于512字节的时候，为了保证传输质量，就会使用基于TCP的实现方式</li></ul><h3 id="三：根据解析出来的IP地址找到对应的服务端，并和服务端建立连接"><a href="#三：根据解析出来的IP地址找到对应的服务端，并和服务端建立连接" class="headerlink" title="三：根据解析出来的IP地址找到对应的服务端，并和服务端建立连接"></a>三：根据解析出来的IP地址找到对应的服务端，并和服务端建立连接</h3><p>当第二步完成之后，我们会得到服务端的IP地址，这个时候就需要去和服务端建立连接了。和DNS的请求不同，客户端和服务端通过TCP建立连接，在这里我们可以简单的来说说TCP三次握手，先来看一下下面的一张图：<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%9A%E7%BD%91%E9%A1%B5%E4%BB%8E%E8%BE%93%E5%85%A5%E7%BD%91%E5%9D%80%E5%88%B0%E6%B8%B2%E6%9F%93%E5%AE%8C%E6%88%90%E7%BB%8F%E5%8E%86%E4%BA%86%E5%93%AA%E4%BA%9B%E8%BF%87%E7%A8%8B%EF%BC%9F/TCP%20%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B.png" alt="TCP三次握手"><br>PS：重新画了一次TCP三次握手的图片，想不到还能画出来<br>废话不多说，下面我们直接来看图：</p><ol><li>第一次握手(SYN&#x3D;1; seq&#x3D;x)：建立连接。客户端发送连接请求报文段，将SYN置为1，sequence Number为x；然后客户端进入SYN_SENT状态，等待服务端确认。</li><li>第二次握手(ACK&#x3D;1; SYN&#x3D;1; Ack&#x3D;x+1; seq&#x3D;y)：服务端收到SYN报文段。服务端收到SYN报文段，需要对SYN报文段进行确认，设置Acknowledgment Number为x+1(Sequence Number +1);同时自己还要发送SYN请求信息，将SYN置为1，Sequence Number为y;然后服务端将上述所有信息封装到报文段中，一并发送给客户端，服务端进入SYN_RECV状态。</li><li>第三次握手(ACK&#x3D;1; Ack&#x3D;y+1)：客户端收到SYN+ACK报文段。然后将Acknowledgment Number置为y+1，向服务端发送ACK报文段，这段报文发送完毕后，客户端和服务端同时进入ESTABLISHED状态，完成TCP三次握手。<br>完成了TCP三次握手后，客户端和服务端就可以开始传输数据了。以上是TCP三次握手的简单描述。</li></ol><h3 id="四：连接完成后，开始向服务端发起HTTP请求"><a href="#四：连接完成后，开始向服务端发起HTTP请求" class="headerlink" title="四：连接完成后，开始向服务端发起HTTP请求"></a>四：连接完成后，开始向服务端发起HTTP请求</h3><p>当TCP握手结束后，如果客户端发起的是HTTP请求，那么可以直接与服务端交互；但是如果发起的是HTTPS请求，那么就还需要TLS握手才能发起请求。<br>HTTPS还是通过HTTP来传输信息，但是信息都是通过了TLS协议加密。<br>TLS协议位于传输层之上，应用层之下。首次进行TLS协议传输需要两个RIT,接下来可以通过 Session Resumption 减少到一个 RTT。<br>在TLS中最主要的还是两种加密方式–对称加密和非对称加密。由于篇幅有限，这里就简单介绍下。</p><h4 id="对称加密"><a href="#对称加密" class="headerlink" title="对称加密"></a>对称加密</h4><p>对称加密就是两边都有相同的密钥，两边都知道如何将消息加密解密。<br>这种加密方式虽然很好，但是由于密钥也是通过网络传输，一旦密钥被截获，那就没有加密解密的必要了。</p><h4 id="非对称加密"><a href="#非对称加密" class="headerlink" title="非对称加密"></a>非对称加密</h4><p>非对称加密有公钥和私钥之分，公钥所有人都可以知道，通常情况下，服务端同时生成公钥和私钥，然后将公钥发送到客户端，私钥自己保存。客户端收到公钥后，利用公钥将数据加密，然后将密文发送到服务端，密文必须通过私钥才能被解密。这种加密方式就可以完美解决对称加密的弊端。<br>关于TLS握手的情况比较复杂，由于篇幅有限，暂时不在此细说，感兴趣的童靴可以自行Google。</p><p>请求数据在进入服务端之前，可能还会先经过负责负载均衡的服务器，它的作用主要是将请求合理的分发到多台服务器。</p><h3 id="五：服务端响应HTTP请求并返回处理后的数据"><a href="#五：服务端响应HTTP请求并返回处理后的数据" class="headerlink" title="五：服务端响应HTTP请求并返回处理后的数据"></a>五：服务端响应HTTP请求并返回处理后的数据</h3><p>这里假设服务端响应的是一个HTML文件，当浏览器接收到服务端返回的响应后，首先浏览器会判断请求的状态码，如果是200，就继续解析，如果是400或500的话就提示报错，如果是300的话就会重定向，这里也有个重定向的次数问题，浏览器不可能去无限重定向，当重定向次数达到一个上限时依旧会报错。<br>如果服务端和浏览器之间不需要在进行数据交互，那么此时就需要关闭连接，也就是所谓的TCP四次挥手，关于TCP的四次挥手过程，我在这里做一个简单的描述，先看下面的一张图：<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%9A%E7%BD%91%E9%A1%B5%E4%BB%8E%E8%BE%93%E5%85%A5%E7%BD%91%E5%9D%80%E5%88%B0%E6%B8%B2%E6%9F%93%E5%AE%8C%E6%88%90%E7%BB%8F%E5%8E%86%E4%BA%86%E5%93%AA%E4%BA%9B%E8%BF%87%E7%A8%8B%EF%BC%9F/TCP%20%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B.png" alt="TCP四次挥手"><br>TCP连接的关闭需要发送四次数据包，因此叫做四次挥手，也叫做改进的三次握手，客户端和服务器均可主动发起挥手动作，在socket编程中，任何一方执行close()操作即可产生挥手操作。</p><ol><li>第一次挥手(FIN&#x3D;1; seq&#x3D;x)：假设客户端想要关闭连接，客户端发送一个FIN标志位置为1的包，表示自己已经没有数据可以发送了，但是仍然可以接受数据。数据包发送完毕后，客户端进入FIN_WAIT_1状态。</li><li>第二次挥手(ACK&#x3D;1; Ack&#x3D;x+1)：服务器端确认客户端的FIN包，发送一个确认包，表明自己接收到了客户端关闭连接的请求，但还没有做好关闭连接的准备。发送完毕后，服务端进入CLOSE_WAIT状态，客户端接收到这个确认包之后，进入FIN_WAIT_2状态，等到服务器端关闭连接。</li><li>第三次挥手(FIN&#x3D;1, seq&#x3D;y)：服务器端准备好关闭连接时，向客户端发送一个FIN标志位置为1的结束请求包，发送完毕后，服务器端进入LAST_ACK状态，等待来自客户端最后一个ACK。</li><li>第四次挥手(ACK&#x3D;1, Ack&#x3D;y+1): 客户端接收到来自服务器端的关闭请求，发送一个确认包，并进入TIME_WAIT状态，等待可能出现要求重传ACK包。服务器端接收到这个确认包之后，关闭连接，进入CLOSE状态。客户端等待了某个固定的时间(两个最大段生命周期)之后，没有收到服务器端的ACK，认为服务器端已经正常关闭连接，于是自己也关闭连接，进入CLOSED状态。</li></ol><h3 id="六：浏览器解析服务端返回的数据"><a href="#六：浏览器解析服务端返回的数据" class="headerlink" title="六：浏览器解析服务端返回的数据"></a>六：浏览器解析服务端返回的数据</h3><p>当状态码为200的时候，浏览器会继续解析文件，如果文件时gzip格式的，首先会进行解码，然后通过文件的编码格式决定如何去解码文件。</p><h3 id="七：浏览器对页面进行布局渲染"><a href="#七：浏览器对页面进行布局渲染" class="headerlink" title="七：浏览器对页面进行布局渲染"></a>七：浏览器对页面进行布局渲染</h3><p>这一部分较为复杂，打算另开一篇博客详细讲解…</p><p>扩展阅读</p><ol><li>为什么连接的时候是三次握手，而断开时却需要四次挥手？<br>答：因为当Server端收到Client端的SYN连接请求报文后，可以直接发送SYN+ACK报文。其中ACK报文是用来应答的，SYN报文是用来同步的。但是关闭连接时，当Server端收到FIN报文时，很可能并不会立即关闭SOCKET，所以只能先回复一个ACK报文，告诉Client端收到了发过来的报文。只有等到Server端所有的报文都发送完了，才能发送FIN报文，因此不能一起发送。所以需要四步握手。</li><li>为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态？<br>答：虽然按照四次挥手的原理分析，当四个报文都发送完毕了，就可以直接进入到CLOSE状态了，但是我们必须要考虑到网络的不可靠性，有可能最后一个ACK确认报文丢失，所以TIME_WAIT状态就是用来重发可能丢失的ACK确认报文。</li><li>TCP 建立连接为什么需要三次握手，而不是两次或者四次？<br>答： 假设TCP建立连接只有两次握手，那么只要完成了前两次握手服务端就默认完成了连接，就需要为与客户端的连接保留一部分资源。但是我们不能保证网络一定可靠，假如客户端并没有收到服务端的回应，那么客户端默认没有建立连接。如果出现大量这样的情况会导致服务端崩溃。其实三次握手完全就可以确认之前的通信情况，后面继续进行就是徒劳的。</li></ol><p>本文结束，谢谢您的阅读。</p>]]>
    </content>
    <id>http://shchome.top/2026/05/16/%E7%BD%91%E9%A1%B5%E4%BB%8E%E8%BE%93%E5%85%A5%E7%BD%91%E5%9D%80%E5%88%B0%E6%B8%B2%E6%9F%93%E5%AE%8C%E6%88%90%E8%AF%A6%E7%BB%86%E8%A7%A3%E8%AF%BB/</id>
    <link href="http://shchome.top/2026/05/16/%E7%BD%91%E9%A1%B5%E4%BB%8E%E8%BE%93%E5%85%A5%E7%BD%91%E5%9D%80%E5%88%B0%E6%B8%B2%E6%9F%93%E5%AE%8C%E6%88%90%E8%AF%A6%E7%BB%86%E8%A7%A3%E8%AF%BB/"/>
    <published>2026-05-16T01:22:00.000Z</published>
    <summary>
      <![CDATA[<p>相信大家都知道,这是前端面试中的一道高频考题,借着马上要去面试的时机, 在此做一个总结。</p>
<p>正文开始<br>相信大家看到这个题目，大家心中马上就有了答案，无非就那几步。但是面试时为了更好地突出自己，我们需要对面试题进行举一反三的回答。所以我们今天就来好好学习怎么]]>
    </summary>
    <title>面试题：网页从输入网址到渲染完成经历了哪些过程？</title>
    <updated>2026-05-24T03:45:48.283Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="前端" scheme="http://shchome.top/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="前端" scheme="http://shchome.top/tags/%E5%89%8D%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>准备出去面试找找自己的不足，为了在面试时掌握主动权，所以写这篇博客回顾前端知识点。</p><h2 id="JavaScript-有几种数据类型，分别是怎么存储的，如何判断一个数据是什么类型的？"><a href="#JavaScript-有几种数据类型，分别是怎么存储的，如何判断一个数据是什么类型的？" class="headerlink" title="JavaScript 有几种数据类型，分别是怎么存储的，如何判断一个数据是什么类型的？"></a>JavaScript 有几种数据类型，分别是怎么存储的，如何判断一个数据是什么类型的？</h2><p>在 JavaScript 中有两种数据类型-基本数据类型和引用数据类型，基本数据类型现阶段有 boolean，number，string，null，undefined，symbol 6 种，基本数据类型存储的是值。除了基本数据类型，其它的全部都是引用数据类型，引用数据类型存储的是地址(指针)，当我们创建一个引用数据类型时，JS 引擎会帮我们在堆中开辟一块空闲区域，引用数据类型此时存储的就是这块区域对应的地址值。<br>判断数据的类型可以使用 typeOf</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">typeof</span> <span class="number">1</span>           <span class="comment">// &#x27;number&#x27;</span></span><br><span class="line"><span class="built_in">typeof</span> <span class="string">&#x27;1&#x27;</span>         <span class="comment">// &#x27;string&#x27;</span></span><br><span class="line"><span class="built_in">typeof</span> <span class="literal">true</span>        <span class="comment">// &#x27;boolean&#x27;</span></span><br><span class="line"><span class="built_in">typeof</span> <span class="literal">undefined</span>   <span class="comment">// &#x27;undefined&#x27;</span></span><br><span class="line"><span class="built_in">typeof</span> <span class="literal">null</span>        <span class="comment">// &#x27;object&#x27;</span></span><br><span class="line"><span class="built_in">typeof</span> Symbol()    <span class="comment">// &#x27;symbol&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">typeof</span> []          <span class="comment">// &#x27;object&#x27;</span></span><br><span class="line"><span class="built_in">typeof</span> &#123;&#125;          <span class="comment">// &#x27;object&#x27;</span></span><br><span class="line"><span class="built_in">typeof</span> <span class="built_in">console</span>.<span class="built_in">log</span>  <span class="comment">// &#x27;function&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="typeof-和-instanceof-有什么区别？-instanceof-是怎么用的，其原理是什么？"><a href="#typeof-和-instanceof-有什么区别？-instanceof-是怎么用的，其原理是什么？" class="headerlink" title="typeof 和 instanceof 有什么区别？ instanceof 是怎么用的，其原理是什么？"></a>typeof 和 instanceof 有什么区别？ instanceof 是怎么用的，其原理是什么？</h2><p>根据上面的代码我们可以看出，typeof 能够判断绝大部分数据类型，但是 typeof null 时返回的却是 object。这算是 JavaScript 一个悠久的 bug 了，在最初的 JavaScript 中采用的是 32 位系统，为了性能的考虑，采用了低位存储数据的类型，000 表示的数据类型是 object, 但是 null 表示的却是全 0，所以 typeof 错误的将 null 判断为 object。<br>然而用 instanceof 来判断的话</p><figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">null</span> <span class="keyword">instanceof</span> <span class="literal">null</span></span><br><span class="line"><span class="regexp">//</span> Right-hand side <span class="keyword">of</span> <span class="string">&#x27;instanceof&#x27;</span> <span class="keyword">is</span> <span class="keyword">not</span> an object</span><br></pre></td></tr></table></figure><p>可以看出因为 null 不是一个 object，所以判断的时候报错，其它基本数据类型判断时同样会报错。<br>但是如果这么写</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> s = <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&#x27;123&#x27;</span>)</span><br><span class="line">s <span class="keyword">instanceof</span> <span class="title class_">String</span>        <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>所以 instanceof 主要是判断数据是哪一种具体的 object， 其实 instanceof 主要的作用就是判断一个实例是否属于某种类型，当然 instanceof 也可以判断一个实例是否是其父类型或者祖先类型的实例。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">Parent</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  a = <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="title class_">Child</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  b = <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Child</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">Parent</span>()</span><br><span class="line"><span class="keyword">let</span> child = <span class="keyword">new</span> <span class="title class_">Child</span>()</span><br><span class="line">child <span class="keyword">instanceof</span> <span class="title class_">Child</span>     <span class="comment">// true</span></span><br><span class="line">child <span class="keyword">instanceof</span> <span class="title class_">Parent</span>    <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>这是 instanceof 的用法，根据 instanceof 的用法，我们可以大致推断出 instanceof 的代码实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">new_instanceof</span> (<span class="params">leftValue, rightValue</span>) &#123;</span><br><span class="line">  leftValue = leftValue.<span class="property">__proto__</span></span><br><span class="line">  rightValue = rightValue.<span class="property"><span class="keyword">prototype</span></span></span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (leftValue === <span class="literal">null</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (leftValue === rightValue) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">    leftValue = leftValue.<span class="property">__proto__</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">new_instanceof</span>(<span class="string">&#x27;1&#x27;</span>, <span class="title class_">String</span>)     <span class="comment">// true</span></span><br><span class="line"><span class="title function_">new_instanceof</span>(<span class="string">&#x27;1&#x27;</span>, <span class="title class_">Number</span>)     <span class="comment">// false</span></span><br><span class="line"><span class="title function_">new_instanceof</span>(<span class="number">0</span>, <span class="title class_">Number</span>)       <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>其实 instanceof 的实现主要是通过原型链来判断的，掌握了原型链，instanceof 就很简单了。<br>那么现在问题来了，instanceof 是否能够准确判断呢？<br>现在来看这么一段代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Parent1</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Parent2</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Child</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Child</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">Parent1</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> child1 = <span class="keyword">new</span> <span class="title class_">Child</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(child1 <span class="keyword">instanceof</span> <span class="title class_">Parent1</span>)    <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(child1 <span class="keyword">instanceof</span> <span class="title class_">Parent2</span>)    <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Child</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">Parent2</span>()</span><br><span class="line"><span class="keyword">const</span> child2 = <span class="keyword">new</span> <span class="title class_">Child</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(child2 <span class="keyword">instanceof</span> <span class="title class_">Parent1</span>)    <span class="comment">// false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(child2 <span class="keyword">instanceof</span> <span class="title class_">Parent2</span>)    <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>what? 发生了什么，两次输出的结果不一样，因为原型链是可以改变的，所以 instanceof 的判断也是不准确的。<br>其实判断数据类型还有一种方法，那就是 Object.prototype.toString 方法，我们可以利用这个方法对数据类型做一个比较精确的判断</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Object<span class="selector-class">.prototype</span><span class="selector-class">.toString</span><span class="selector-class">.call</span>(<span class="string">&#x27;1&#x27;</span>)</span><br><span class="line"><span class="comment">// [object String]</span></span><br><span class="line">Object<span class="selector-class">.prototype</span><span class="selector-class">.toString</span><span class="selector-class">.call</span>(<span class="number">1</span>)</span><br><span class="line"><span class="comment">// [object Number]</span></span><br><span class="line">Object<span class="selector-class">.prototype</span><span class="selector-class">.toString</span><span class="selector-class">.call</span>(false)</span><br><span class="line"><span class="comment">// [object Boolean]</span></span><br><span class="line">Object<span class="selector-class">.prototype</span><span class="selector-class">.toString</span><span class="selector-class">.call</span>()</span><br><span class="line"><span class="comment">// [object Function]</span></span><br><span class="line">Object<span class="selector-class">.prototype</span><span class="selector-class">.toString</span><span class="selector-class">.call</span>(undefined)</span><br><span class="line"><span class="comment">// [object Undefined]</span></span><br><span class="line">Object<span class="selector-class">.prototype</span><span class="selector-class">.toString</span><span class="selector-class">.call</span>(null)</span><br><span class="line"><span class="comment">// [object Null]</span></span><br><span class="line">Object<span class="selector-class">.prototype</span><span class="selector-class">.toString</span><span class="selector-class">.call</span>(<span class="built_in">Symbol</span>())</span><br><span class="line"><span class="comment">// [object Symbol]</span></span><br><span class="line">Object<span class="selector-class">.prototype</span><span class="selector-class">.toString</span><span class="selector-class">.call</span>(&#123;&#125;)</span><br><span class="line"><span class="comment">// [object Object]</span></span><br></pre></td></tr></table></figure><h2 id="JS-类型转换"><a href="#JS-类型转换" class="headerlink" title="JS 类型转换"></a>JS 类型转换</h2><p>类型转换一直都是 JavaScript 中比较复杂的一部分，但是熟悉了规则就不需要惧怕了。</p><ul><li>转 boolean 类型</li><li>转 number 类型</li><li>转 string 类型</li></ul><p>转换规则参考下表:</p><table><thead><tr><th align="center">原始值</th><th align="center">转换目标</th><th align="center">结果</th></tr></thead><tbody><tr><td align="center">number</td><td align="center">boolean</td><td align="center">除了 0，-0，NaN 全为 true</td></tr><tr><td align="center">string</td><td align="center">boolean</td><td align="center">除了空字符串都为 true</td></tr><tr><td align="center">null, undefined</td><td align="center">boolean</td><td align="center">false</td></tr><tr><td align="center">Symbol, 引用类型</td><td align="center">boolean</td><td align="center">true</td></tr><tr><td align="center">number</td><td align="center">string</td><td align="center">1 -&gt; ‘1’</td></tr><tr><td align="center">null,undefined</td><td align="center">string</td><td align="center">‘null’，’undefined’</td></tr><tr><td align="center">boolean</td><td align="center">string</td><td align="center">true -&gt; ‘true’</td></tr><tr><td align="center">symbol</td><td align="center">string</td><td align="center">Cannot convert a Symbol value to a string</td></tr><tr><td align="center">引用类型</td><td align="center">string</td><td align="center">如果对象有 toString() 方法，就调用 toString() 方法。如果该方法返回原始值，就讲这个值转化为字符串。如果没有，就会调用该对象的 valueOf() 方法。存在就调用这个方法，如果返回值是原始值，就转化为字符串。否则就报错。</td></tr><tr><td align="center">string</td><td align="center">number</td><td align="center">‘1’ -&gt; 1，’100’ -&gt; 100， ‘a’ -&gt; NaN</td></tr><tr><td align="center">boolean</td><td align="center">number</td><td align="center">false -&gt; 0，true -&gt; 1</td></tr><tr><td align="center">Symbol</td><td align="center">number</td><td align="center">TypeError: Cannot convert a Symbol value to a number</td></tr><tr><td align="center">null</td><td align="center">number</td><td align="center">0</td></tr><tr><td align="center">undefined</td><td align="center">number</td><td align="center">NaN</td></tr><tr><td align="center">数组</td><td align="center">number</td><td align="center">空数组转为 0，存在一个元素并且是数字转数字，其它情况 NaN</td></tr><tr><td align="center">除了数组以外的引用数据类型</td><td align="center">number</td><td align="center">NaN</td></tr></tbody></table><h4 id="对象转原始类型"><a href="#对象转原始类型" class="headerlink" title="对象转原始类型"></a>对象转原始类型</h4><p>对象转原始类型的时候，会调用内置的[[ToPrimitive]]函数，该函数判断逻辑简化来说就是：</p><ul><li>如果已经是原始类型了，就不在进行转换</li><li>如果需要转成字符串类型就调用 toString 方法，如果能转换为基本类型的话就返回转换后的值。不是字符串类型就先调用 valueOf()方法，如果结果是基础类型的话就直接返回，不是基础类型再去调用 toString()方法。</li><li>如果都没有返回原始类型的值就会报错。</li></ul><p>当然也可以重写 ToPrimitive 方法，该方法在调用时优先级最高。</p><h4 id="四则运算符"><a href="#四则运算符" class="headerlink" title="四则运算符"></a>四则运算符</h4><p>四则运算在面试题中经常出现，我们分别来解析。<br>首先加法运算符和其它的都不一样。它主要有下面几个特点：</p><ul><li>运算符中如果有一方为字符串，那么就把另外一方也转换为字符串。</li><li>运算符的一方不是字符串或者数字，就把它转换成字符串或者数字。</li></ul><figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span> + [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]    <span class="comment">// 11,2,3</span></span><br><span class="line"><span class="literal">true</span> + <span class="literal">false</span>   <span class="comment">// 1</span></span><br><span class="line"><span class="number">1</span> + <span class="string">&#x27;1&#x27;</span>        <span class="comment">// 11</span></span><br></pre></td></tr></table></figure><p>另外加法中还有这样的一个运算</p><figure class="highlight 1c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x27;a&#x27; <span class="punctuation">+</span> <span class="punctuation">+</span> &#x27;b&#x27;    <span class="comment">// aNaN</span></span><br></pre></td></tr></table></figure><p>这是因为+ ‘b’会被转换成数字， 结果为 NaN, 所以最终输出为’aNaN’<br>除了加法运算符，其它的运算符都是只要一方为数字，就把另一方转换为数字。</p><h2 id="V8-下的垃圾回收机制"><a href="#V8-下的垃圾回收机制" class="headerlink" title="V8 下的垃圾回收机制"></a>V8 下的垃圾回收机制</h2><p>V8 实现了准确式 GC, GC 算法采用了分代式垃圾回收算法，将堆内存分成了新生代和老生代。</p><h4 id="新生代算法"><a href="#新生代算法" class="headerlink" title="新生代算法"></a>新生代算法</h4><p>新生代中存活的时间比较短，采用 Scavenge GC 算法。<br>在新生代中，内存空间分成两部分，分为 From 空间和 To 空间，一般大小是 8:2， 在这两个空间中，From 空间是被使用的，而 To 空间是空闲的，一个对象创建后就会被放入 From 空间，当 From 空间被占满后，此时就启用了新生代 GC 算法，算法会检测 From 空间中存活的对象并复制到 To 空间，如果对象失活了就释放空间，复制完成之后将 From 空间和 To 空间互换，这样 GC 就结束了。</p><h4 id="老生代算法"><a href="#老生代算法" class="headerlink" title="老生代算法"></a>老生代算法</h4><p>老生代中的对象一般存活时间较长且数量也多，使用了两个算法，分别是标记清除算法和标记压缩算法。<br>在讲算法前，先来说下什么情况下对象会出现在老生代空间中：</p><ul><li>新生代中的对象是否已经经历过一次 Scavenge 算法，如果经历过的话，会将对象从新生代空间移到老生代空间中。</li><li>To 空间的对象占比大小超过 25 %。在这种情况下，为了不影响到内存分配，会将对象从新生代空间移到老生代空间中。<br>老生代中的空间很复杂，有如下几个空间</li></ul><figure class="highlight thrift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">AllocationSpace</span> </span>&#123;</span><br><span class="line">  <span class="comment">// TODO(v8:7464): Actually map this space&#x27;s memory as read-only.</span></span><br><span class="line">  RO_SPACE,    <span class="comment">// 不变的对象空间</span></span><br><span class="line">  NEW_SPACE,   <span class="comment">// 新生代用于 GC 复制算法的空间</span></span><br><span class="line">  OLD_SPACE,   <span class="comment">// 老生代常驻对象空间</span></span><br><span class="line">  CODE_SPACE,  <span class="comment">// 老生代代码对象空间</span></span><br><span class="line">  MAP_SPACE,   <span class="comment">// 老生代 map 对象</span></span><br><span class="line">  LO_SPACE,    <span class="comment">// 老生代大空间对象</span></span><br><span class="line">  NEW_LO_SPACE,  <span class="comment">// 新生代大空间对象</span></span><br><span class="line"></span><br><span class="line">  FIRST_SPACE = RO_SPACE,</span><br><span class="line">  LAST_SPACE = NEW_LO_SPACE,</span><br><span class="line">  FIRST_GROWABLE_PAGED_SPACE = OLD_SPACE,</span><br><span class="line">  LAST_GROWABLE_PAGED_SPACE = MAP_SPACE</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在老生代中，以下情况会先启动标记清除算法：</p><ul><li>某一个空间没有分块的时候</li><li>空间中被对象超过一定限制</li><li>空间不能保证新生代中的对象移动到老生代中<br>在这个阶段中，会遍历堆中所有的对象，然后标记活的对象，在标记完成后，销毁所有没有被标记的对象。在标记大型对内存时，可能需要几百毫秒才能完成一次标记。这就会导致一些性能上的问题。为了解决这个问题，2011 年，V8 从 stop-the-world 标记切换到增量标志。在增量标记期间，GC 将标记工作分解为更小的模块，可以让 JS 应用逻辑在模块间隙执行一会，从而不至于让应用出现停顿情况。但在 2018 年，GC 技术又有了一个重大突破，这项技术名为并发标记。该技术可以让 GC 扫描和标记对象时，同时允许 JS 运行。</li></ul>]]>
    </content>
    <id>http://shchome.top/2026/05/14/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%A2%98/</id>
    <link href="http://shchome.top/2026/05/14/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <published>2026-05-14T14:15:00.000Z</published>
    <summary>
      <![CDATA[<p>准备出去面试找找自己的不足，为了在面试时掌握主动权，所以写这篇博客回顾前端知识点。</p>
<h2 id="JavaScript-有几种数据类型，分别是怎么存储的，如何判断一个数据是什么类型的？"><a href="#JavaScript-有几种数据类型，分别是怎么存储的，]]>
    </summary>
    <title>前端面试</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="前端" scheme="http://shchome.top/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="前端" scheme="http://shchome.top/tags/%E5%89%8D%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>React基于浏览器的事件机制自身实现了一套事件机制，包括事件注册、事件的合成、事件冒泡、事件派发等<br>在React中这套事件机制被称之为合成事件</p><h4 id="初识React事件机制"><a href="#初识React事件机制" class="headerlink" title="初识React事件机制"></a>初识React事件机制</h4><p>React的事件机制与传统的DOM事件机制有着本质的区别。传统的DOM事件机制是基于浏览器的原生事件系统，而React的事件机制则是一套独立的事件系统，称为合成事件（Synthetic Event）。合成事件的出现是为了解决浏览器兼容性问题，以及优化事件处理的性能。</p><h4 id="合成事件的实现原理"><a href="#合成事件的实现原理" class="headerlink" title="合成事件的实现原理"></a>合成事件的实现原理</h4><p>合成事件是在浏览器原生事件的基础上进行封装的，基于W3C标准，和浏览器事件一样，能够进行事件的触发、捕获、冒泡、合成、派发。它将不同浏览器的原生事件统一为一种标准的事件格式。当用户触发一个DOM事件时，React会捕获该事件，然后将其转换为一个合成事件。合成事件包含了与原生事件相同的信息，但它还具有额外的属性，例如target、currentTarget和stopPropagation()方法。</p><h4 id="事件代理和委托"><a href="#事件代理和委托" class="headerlink" title="事件代理和委托"></a>事件代理和委托</h4><p>事件代理和委托是React事件机制中的两个重要概念。事件代理是指将事件监听器注册到父元素上，然后由父元素将事件传递给子元素。事件委托是指将事件监听器注册到子元素上，然后由子元素将事件冒泡到父元素。</p><p>事件代理和委托可以有效地减少事件监听器的数量，提高事件处理的性能。例如，如果一个页面中有100个按钮，每个按钮都需要注册一个事件监听器，那么总共需要注册100个事件监听器。如果使用事件代理，只需要注册一个事件监听器到父元素上，然后由父元素将事件传递给子元素。这样，只需要注册一个事件监听器就可以处理100个按钮的点击事件。</p><h4 id="事件冒泡和捕获"><a href="#事件冒泡和捕获" class="headerlink" title="事件冒泡和捕获"></a>事件冒泡和捕获</h4><p>事件冒泡和捕获是React事件机制中的另外两个重要概念。事件冒泡是指事件从子元素向父元素逐级传递的过程。事件捕获是指事件从父元素向子元素逐级传递的过程。</p><p>事件冒泡和捕获可以通过stopPropagation()方法和preventDefault()方法来控制。stopPropagation()方法可以阻止事件冒泡，preventDefault()方法可以阻止事件的默认行为。</p><h4 id="React事件机制的优势"><a href="#React事件机制的优势" class="headerlink" title="React事件机制的优势"></a>React事件机制的优势</h4><p>React的事件机制具有以下优势：</p><ul><li>跨平台兼容性 ：React的事件机制可以兼容不同的浏览器，即使是不同版本的浏览器。</li><li>高性能 ：React的事件机制采用了合成事件和事件代理等技术，可以有效地减少事件监听器的数量，提高事件处理的性能。</li><li>易于使用 ：React的事件机制提供了简单的API，使得开发者可以轻松地处理各种用户交互事件。</li></ul>]]>
    </content>
    <id>http://shchome.top/2026/05/11/React%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6/</id>
    <link href="http://shchome.top/2026/05/11/React%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6/"/>
    <published>2026-05-11T02:28:00.000Z</published>
    <summary>
      <![CDATA[<p>React基于浏览器的事件机制自身实现了一套事件机制，包括事件注册、事件的合成、事件冒泡、事件派发等<br>在React中这套事件机制被称之为合成事件</p>
<h4 id="初识React事件机制"><a href="#初识React事件机制" class="header]]>
    </summary>
    <title>React事件机制</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="设计模式" scheme="http://shchome.top/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    <category term="前端" scheme="http://shchome.top/tags/%E5%89%8D%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>说到设计模式，大家想到的就是六大原则，23种模式。这么多模式，并非都要记住，但作为前端开发，对于前端出现率高的设计模式还是有必要了解并掌握的，浅浅掌握9种模式后，整理了这份文章。<br>那么，我们先了解六大原则</p><h4 id="六大原则："><a href="#六大原则：" class="headerlink" title="六大原则："></a>六大原则：</h4><ol><li>依赖倒置原则(Dependence Inversion Principle)：高层(业务层)不应该直接调用底层(基础层)模块</li><li>开闭原则(Open Close Principle)：单模块对拓展开放、对修改关闭</li><li>单一原则(Single Responsibility Principle)：单模块负责的职责必须是单一的</li><li>迪米特法则(Law of Demeter)：对外暴露接口应该简单</li><li>接口隔离原则(Interface Segregation Principle)：单个接口(类)都应该按业务隔离开</li><li>里氏替换原则(Liskov Substitution Principle)：子类可以替换父类</li></ol><p>六大原则也可以用六个字替换：高内聚低耦合。</p><ul><li>高层不直接依赖底层：依赖倒置原则</li><li>内部修改关闭，外部开放扩展：开闭原则</li><li>聚合单一功能：单一原则</li><li>低知识接口，对外接口简单：迪米特法则</li><li>耦合多个接口，不如隔离拆分：接口隔离原则</li><li>合并复用，子类可以替换父类：里氏替换原则<br>我们采用模式编写时，要尽可能遵守这六大原则</li></ul><p>下面我们一起来看看前端常用的一些设计模式</p><h4 id="1-单例模式"><a href="#1-单例模式" class="headerlink" title="1. 单例模式"></a>1. 单例模式</h4><p>单例模式是一种只允许创建一个实例的模式。在前端开发中，常用于创建全局唯一的对象，例如全局的状态管理器、日志记录器等。单例模式可以保证全局只有一个实例，避免了重复创建和资源浪费的问题。<br>单例模式的代码实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 需求：判断一款应用的开闭状态，根据不同状态给出不同提示</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">applicationStation</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">state</span> = <span class="string">&#x27;off&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">play</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span> === <span class="string">&#x27;on&#x27;</span>) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;已打开&#x27;</span>)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">state</span> = <span class="string">&#x27;on&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">shutdown</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span> === <span class="string">&#x27;off&#x27;</span>) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;已关闭&#x27;</span>)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">state</span> = <span class="string">&#x27;off&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">window</span>.<span class="property">applicationStation</span> = <span class="keyword">new</span> <span class="title function_">applicationStation</span>()</span><br><span class="line"><span class="comment">// applicationStation.instance = undefined</span></span><br><span class="line"><span class="comment">// applicationStation.getInstance = function() &#123;</span></span><br><span class="line"><span class="comment">//    return function() &#123;</span></span><br><span class="line"><span class="comment">//        if (!applicationStation.instance) &#123;  // 如果全局没有实例再创建</span></span><br><span class="line"><span class="comment">//            applicationStation.instance = new applicationStation()</span></span><br><span class="line"><span class="comment">//        &#125;</span></span><br><span class="line"><span class="comment">//        return applicationStation.instance</span></span><br><span class="line"><span class="comment">//    &#125;()</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"><span class="comment">// application1和application2拥有同一个applicationStation对象</span></span><br><span class="line"><span class="keyword">const</span> application1 = <span class="variable language_">window</span>.<span class="property">applicationStation</span></span><br><span class="line"><span class="keyword">const</span> application2 = <span class="variable language_">window</span>.<span class="property">applicationStation</span></span><br></pre></td></tr></table></figure><p>以上代码是单例模式的一个示例，通过该模式可以保证全局只有一个实例，避免了重复创建和资源浪费的问题。在这个示例中，Singleton 类只能创建一个实例，如果多次创建，返回的都是同一个实例，因此 instance1 和 instance2 的值是相等的。单例模式常用于创建全局唯一的对象，例如全局的状态管理器、日志记录器等。</p><h4 id="2-工厂模式"><a href="#2-工厂模式" class="headerlink" title="2. 工厂模式"></a>2. 工厂模式</h4><p>工厂模式是一种根据参数的不同创建不同对象的模式。在前端开发中，常用于创建不同类型的组件、插件等。工厂模式可以将对象的创建和使用分离，提高代码的灵活性和可维护性。<br>工厂模式的代码实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 需求：公司员工创建完信息后需要为每一个员工创建一个信息名片</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">setPerson</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">obj</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">pesonObj</span> = obj</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">creatCard</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="comment">//创建信息名片</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">otherFynction</span>(<span class="params"></span>)&#123;</span><br><span class="line">    </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">obj</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title function_">setPerson</span>(obj)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> person = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line"><span class="keyword">const</span> card = person.<span class="title function_">creatCard</span>(&#123;</span><br><span class="line">    <span class="attr">name</span>:<span class="string">&#x27;张三&#x27;</span>,</span><br><span class="line">    <span class="attr">age</span>:<span class="string">&#x27;20&#x27;</span>,</span><br><span class="line">    <span class="attr">department</span>:<span class="string">&#x27;人力资源部门&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>以上代码中，Product 类表示要创建的产品，ProductFactory 类实现了工厂模式，通过 createProduct 方法创建产品实例。在使用时，可以通过工厂类创建产品实例，而不需要直接调用产品类的构造函数。通过工厂模式可以将对象的创建和使用分离，提高代码的灵活性和可维护性。</p><h4 id="3-观察者模式"><a href="#3-观察者模式" class="headerlink" title="3. 观察者模式"></a>3. 观察者模式</h4><p>观察者模式是一种对象间的一对多依赖关系，当一个对象状态改变时，所有依赖它的对象都会自动更新。在前端开发中，常用于实现事件监听和消息订阅等。观察者模式可以降低对象间的耦合度，提高代码的可读性和可复用性。<br>观察者模式的代码实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Subject</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">observers</span> = [];</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">addObserver</span>(<span class="params">observer</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">observers</span>.<span class="title function_">push</span>(observer);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">removeObserver</span>(<span class="params">observer</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> index = <span class="variable language_">this</span>.<span class="property">observers</span>.<span class="title function_">indexOf</span>(observer);</span><br><span class="line">    <span class="keyword">if</span> (index !== -<span class="number">1</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">observers</span>.<span class="title function_">splice</span>(index, <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">notify</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">observers</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">observer</span> =&gt;</span> observer.<span class="title function_">update</span>(data));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Observer</span> &#123;</span><br><span class="line">  <span class="title function_">update</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Received data: <span class="subst">$&#123;data&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用示例</span></span><br><span class="line"><span class="keyword">const</span> subject = <span class="keyword">new</span> <span class="title class_">Subject</span>();</span><br><span class="line"><span class="keyword">const</span> observer1 = <span class="keyword">new</span> <span class="title class_">Observer</span>();</span><br><span class="line"><span class="keyword">const</span> observer2 = <span class="keyword">new</span> <span class="title class_">Observer</span>();</span><br><span class="line"></span><br><span class="line">subject.<span class="title function_">addObserver</span>(observer1);</span><br><span class="line">subject.<span class="title function_">addObserver</span>(observer2);</span><br><span class="line"></span><br><span class="line">subject.<span class="title function_">notify</span>(<span class="string">&quot;Hello World!&quot;</span>);</span><br></pre></td></tr></table></figure><p>以上代码中，Subject 类实现了观察者模式，通过 addObserver 方法添加观察者，通过 notify 方法通知观察者，触发其 update 方法。Observer 类实现了具体的观察者，通过 update 方法接收数据并进行处理。</p><h4 id="4-装饰器模式"><a href="#4-装饰器模式" class="headerlink" title="4. 装饰器模式"></a>4. 装饰器模式</h4><p>装饰器模式是一种在不改变对象自身的基础上，动态地给对象增加新的功能的模式。在前端开发中，常用于实现组件的复用和功能的增强等。装饰器模式可以避免类的继承带来的复杂性和耦合度，提高代码的灵活性和可维护性。<br>装饰器模式的代码实现：</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">interface <span class="type">Component</span> &#123;</span><br><span class="line">  operation(): void;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ConcreteComponent</span> <span class="title">implements</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  public operation(): void &#123;</span><br><span class="line">    console.log(<span class="string">&quot;ConcreteComponent: operation.&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Decorator</span> <span class="title">implements</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  <span class="keyword">protected</span> component: <span class="type">Component</span>;</span><br><span class="line"></span><br><span class="line">  constructor(component: <span class="type">Component</span>) &#123;</span><br><span class="line">    <span class="keyword">this</span>.component = component;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  public operation(): void &#123;</span><br><span class="line">    console.log(<span class="string">&quot;Decorator: operation.&quot;</span>);</span><br><span class="line">    <span class="keyword">this</span>.component.operation();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ConcreteDecoratorA</span> <span class="keyword">extends</span> <span class="title">Decorator</span> </span>&#123;</span><br><span class="line">  public operation(): void &#123;</span><br><span class="line">    <span class="keyword">super</span>.operation();</span><br><span class="line">    console.log(<span class="string">&quot;ConcreteDecoratorA: operation.&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ConcreteDecoratorB</span> <span class="keyword">extends</span> <span class="title">Decorator</span> </span>&#123;</span><br><span class="line">  public operation(): void &#123;</span><br><span class="line">    <span class="keyword">super</span>.operation();</span><br><span class="line">    console.log(<span class="string">&quot;ConcreteDecoratorB: operation.&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用示例</span></span><br><span class="line">const concreteComponent = <span class="keyword">new</span> <span class="type">ConcreteComponent</span>();</span><br><span class="line">const concreteDecoratorA = <span class="keyword">new</span> <span class="type">ConcreteDecoratorA</span>(concreteComponent);</span><br><span class="line">const concreteDecoratorB = <span class="keyword">new</span> <span class="type">ConcreteDecoratorB</span>(concreteDecoratorA);</span><br><span class="line"></span><br><span class="line">concreteDecoratorB.operation();</span><br></pre></td></tr></table></figure><p>以上代码中，Component 接口定义了装饰器模式中的组件的基本操作，ConcreteComponent 类实现了具体的组件，Decorator 类实现了装饰器的基本操作，并通过 protected 属性持有被装饰的组件。ConcreteDecoratorA 和 ConcreteDecoratorB 类分别实现了具体的装饰器操作。在使用时，可以通过多次装饰对象来增加功能，而不需要直接修改原始对象的代码。通过装饰器模式可以动态地增加对象的功能，提高代码的灵活性和可复用性。</p><h4 id="5-代理模式"><a href="#5-代理模式" class="headerlink" title="5. 代理模式"></a>5. 代理模式</h4><p>代理模式是一种通过一个代理对象控制对目标对象的访问的模式。在前端开发中，常用于实现图片懒加载、数据缓存等。代理模式可以保护目标对象，控制其访问和使用，提高代码的安全性和可读性。<br>代理模式的代码实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> target = &#123;</span><br><span class="line">  <span class="title function_">method</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Target method.&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> proxy = <span class="keyword">new</span> <span class="title class_">Proxy</span>(target, &#123;</span><br><span class="line">  <span class="title function_">get</span>(<span class="params">target, prop</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Called <span class="subst">$&#123;prop&#125;</span> method.`</span>);</span><br><span class="line">    <span class="keyword">return</span> target[prop];</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用示例</span></span><br><span class="line">proxy.<span class="title function_">method</span>(); <span class="comment">// &quot;Called method method. Target method.&quot;</span></span><br></pre></td></tr></table></figure><p>以上代码中，target 对象实现了原始功能，proxy 对象实现了代理功能，通过 new Proxy() 创建代理对象。代理对象通过 get 方法拦截对目标对象方法的访问，并在控制台输出信息。通过代理模式可以控制对目标对象的访问和使用，实现数据缓存、权限控制等功能，提高代码的可读性和可维护性。</p><h4 id="6-适配器模式"><a href="#6-适配器模式" class="headerlink" title="6. 适配器模式"></a>6. 适配器模式</h4><p>适配器模式是一种将不同接口转换成统一接口的模式。在前端开发中，常用于实现不同浏览器的兼容、不同数据格式的转换等。适配器模式可以降低系统间的耦合度，提高代码的复用性和可维护性。<br>适配器模式的代码实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Adaptee</span> &#123;</span><br><span class="line">  <span class="title function_">specificRequest</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;适配者中的业务代码被调用&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Target</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">adaptee</span> = <span class="keyword">new</span> <span class="title class_">Adaptee</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">request</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> info = <span class="variable language_">this</span>.<span class="property">adaptee</span>.<span class="title function_">specificRequest</span>();</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;info&#125;</span> - 转换器 - 适配器代码被调用`</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用示例</span></span><br><span class="line"><span class="keyword">let</span> target = <span class="keyword">new</span> <span class="title class_">Target</span>();</span><br><span class="line">target.<span class="title function_">request</span>(); <span class="comment">// &quot;适配者中的业务代码被调用 - 转换器 - 适配器代码被调用&quot;</span></span><br></pre></td></tr></table></figure><p>以上代码中，Adaptee 类实现了原始的业务代码，Target 类实现了适配器代码，并通过 new Adaptee() 创建了一个 Adaptee 对象用于调用原始业务代码。在 request 方法中，首先调用 Adaptee 对象的 specificRequest 方法获取原始业务代码的信息，然后进行转换并返回。通过适配器模式可以将原始业务代码和适配器代码分开实现，提高代码的可维护性和可复用性。</p><h4 id="7-MVC模式"><a href="#7-MVC模式" class="headerlink" title="7. MVC模式"></a>7. MVC模式</h4><p>MVC模式是一种将应用程序分为三个部分：模型、视图和控制器。在前端开发中，常用于实现数据的管理、页面的渲染和交互的处理等。MVC模式可以降低代码的复杂度，提高代码的可维护性和可测试性。<br>MVC模式的代码实现：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Model</span> &#123;</span><br><span class="line">  <span class="keyword">constructor</span>() &#123;</span><br><span class="line">    <span class="keyword">this</span>.<span class="keyword">data</span> = &#123;</span><br><span class="line">      name: <span class="string">&quot;example&quot;</span>,</span><br><span class="line">      age: <span class="number">18</span>,</span><br><span class="line">      gender: <span class="string">&quot;male&quot;</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  setData(key, value) &#123;</span><br><span class="line">    <span class="keyword">this</span>.<span class="keyword">data</span>[key] = value;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  getData() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.<span class="keyword">data</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">View</span> &#123;</span><br><span class="line">  <span class="keyword">constructor</span>() &#123;</span><br><span class="line">    <span class="keyword">this</span>.container = document.createElement(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  render(<span class="keyword">data</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; name, age, gender &#125; = <span class="keyword">data</span>;</span><br><span class="line">    <span class="keyword">this</span>.container.innerHTML = `</span><br><span class="line">      &lt;p&gt;Name: $&#123;name&#125;&lt;/p&gt;</span><br><span class="line">      &lt;p&gt;Age: $&#123;age&#125;&lt;/p&gt;</span><br><span class="line">      &lt;p&gt;Gender: $&#123;gender&#125;&lt;/p&gt;</span><br><span class="line">    `;</span><br><span class="line">    document.body.appendChild(<span class="keyword">this</span>.container);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Controller</span> &#123;</span><br><span class="line">  <span class="keyword">constructor</span>(model, view) &#123;</span><br><span class="line">    <span class="keyword">this</span>.model = model;</span><br><span class="line">    <span class="keyword">this</span>.view = view;</span><br><span class="line">    <span class="keyword">this</span>.view.render(<span class="keyword">this</span>.model.getData());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  setData(key, value) &#123;</span><br><span class="line">    <span class="keyword">this</span>.model.setData(key, value);</span><br><span class="line">    <span class="keyword">this</span>.view.render(<span class="keyword">this</span>.model.getData());</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用示例</span></span><br><span class="line"><span class="keyword">const</span> model = new Model();</span><br><span class="line"><span class="keyword">const</span> view = new View();</span><br><span class="line"><span class="keyword">const</span> controller = new Controller(model, view);</span><br><span class="line"></span><br><span class="line">controller.setData(<span class="string">&quot;age&quot;</span>, <span class="number">20</span>);</span><br></pre></td></tr></table></figure><p>以上代码中，Model 类实现了应用程序的数据管理，View 类实现了应用程序的视图展示，Controller 类实现了视图和数据的绑定和交互处理。在 Controller 类中，通过调用 model.setData 方法和 view.render 方法实现了数据的修改和页面的重新渲染。通过MVC模式可以将应用程序分解为不同的部分，提高代码的可维护性和可测试性。</p>]]>
    </content>
    <id>http://shchome.top/2026/05/08/%E5%89%8D%E7%AB%AF%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/</id>
    <link href="http://shchome.top/2026/05/08/%E5%89%8D%E7%AB%AF%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    <published>2026-05-08T12:06:00.000Z</published>
    <summary>
      <![CDATA[<p>说到设计模式，大家想到的就是六大原则，23种模式。这么多模式，并非都要记住，但作为前端开发，对于前端出现率高的设计模式还是有必要了解并掌握的，浅浅掌握9种模式后，整理了这份文章。<br>那么，我们先了解六大原则</p>
<h4 id="六大原则："><a href="#六大]]>
    </summary>
    <title>前端常用的设计模式</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="性能优化" scheme="http://shchome.top/categories/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    <category term="前端性能优化" scheme="http://shchome.top/tags/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    <content>
      <![CDATA[<p>互联网从逻辑上看是一张大网，但实际上是由许多小网络组成的，这其中就有小网络“互连互通”的问题，典型的就是各个电信运营商的网络，比如国内的电信、联通、移动三大家。<br>这些小网络内部的沟通很顺畅，但网络之间却只有很少的联通点。如果你在 A 网络，而网 站在 C 网络，那么就必须“跨网”传输，和成千上万的其他用户一起去“挤”连接点 的“独木桥”。而带宽终究是有限的，能抢到多少只能看你的运气。<br>另外，网络中还存在许多的路由器、网关，数据每经过一个节点，都要停顿一下，在二层、 三层解析转发，这也会消耗一定的时间，带来延迟。<br>最终结果就是，如果仅用现有的 HTTP 传输方式，大多数网站都会访问速度缓慢、用户体 验糟糕。<br>放到全球来看，物理距离非常大，你在北京，访问旧金山的网站，要跨越半个地球，地理位置距离远、运营商网络、路由转发的影响就会成倍增加。</p><h2 id="CDN概念"><a href="#CDN概念" class="headerlink" title="CDN概念"></a>CDN概念</h2><p>CDN（Content Delivery Network，内容分发网络）是指一种通过互联网互相连接的电脑网络系统，利用最靠近每位用户的服务器，更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户，来提供高性能、可扩展性及低成本的网络内容传递给用户。</p><p>典型的CDN系统由下面三个部分组成：</p><ol><li>分发服务系统： 最基本的工作单元就是Cache设备，cache（边缘cache）负责直接响应最终用户的访问请求，把缓存在本地的内容快速地提供给用户。同时cache还负责与源站点进行内容同步，把更新的内容以及本地没有的内容从源站点获取并保存在本地。Cache设备的数量、规模、总服务能力是衡量一个CDN系统服务能力的最基本的指标。</li><li>负载均衡系统： 主要功能是负责对所有发起服务请求的用户进行访问调度，确定提供给用户的最终实际访问地址。两级调度体系分为全局负载均衡（GSLB）和本地负载均衡（SLB）。全局负载均衡主要根据用户就近性原则，通过对每个服务节点进行“最优”判断，确定向用户提供服务的cache的物理位置。本地负载均衡主要负责节点内部的设备负载均衡</li><li>运营管理系统： 运营管理系统分为运营管理和网络管理子系统，负责处理业务层面的与外界系统交互所必须的收集、整理、交付工作，包含客户管理、产品管理、计费管理、统计分析等功能。</li></ol><h2 id="CDN的作用"><a href="#CDN的作用" class="headerlink" title="CDN的作用"></a>CDN的作用</h2><p>CDN一般会用来托管Web资源（包括文本、图片和脚本等），可供下载的资源（媒体文件、软件、文档等），应用程序（门户网站等）。使用CDN来加速这些资源的访问。</p><ol><li>在性能方面，引入CDN的作用在于：<ul><li>用户收到的内容来自最近的数据中心，延迟更低，内容加载更快</li><li>部分资源请求分配给了CDN，减少了服务器的负载</li></ul></li><li>在安全方面，CDN有助于防御DDoS、MITM等网络攻击：<ul><li>针对DDoS：通过监控分析异常流量，限制其请求频率</li><li>针对MITM：从源服务器到 CDN 节点到 ISP（Internet Service Provider），全链路 HTTPS 通信<br>除此之外，CDN作为一种基础的云服务，同样具有资源托管、按需扩展（能够应对流量高峰）等方面的优势。</li></ul></li></ol><h2 id="CDN的原理"><a href="#CDN的原理" class="headerlink" title="CDN的原理"></a>CDN的原理</h2><ul><li><p>用户未使用CDN缓存资源的过程：</p><ol><li>浏览器通过DNS对域名进行解析（就是上面的DNS解析过程），依次得到此域名对应的IP地址</li><li>浏览器根据得到的IP地址，向域名的服务主机发送数据请求</li><li>服务器向浏览器返回响应数据</li></ol></li><li><p>用户使用CDN缓存资源的过程：</p><ol><li>对于点击的数据的URL，经过本地DNS系统的解析，发现该URL对应的是一个CDN专用的DNS服务器，DNS系统就会将域名解析权交给CNAME指向的CDN专用的DNS服务器。</li><li>CDN专用DNS服务器将CND的全局负载均衡设备IP地址返回给用户</li><li>用户向CDN的全局负载均衡设备发起数据请求</li><li>CDN的全局负载均衡设备根据用户的IP地址，以及用户请求的内容URL，选择一台用户所属区域的区域负载均衡设备，告诉用户向这台设备发起请求</li><li>区域负载均衡设备选择一台合适的缓存服务器来提供服务，将该缓存服务器的IP地址返回给全局负载均衡设备</li><li>全局负载均衡设备把服务器的IP地址返回给用户</li><li>用户向该缓存服务器发起请求，缓存服务器响应用户的请求，将用户所需内容发送至用户终端。</li></ol></li></ul><p>如果缓存服务器没有用户想要的内容，那么缓存服务器就会向它的上一级缓存服务器请求内容，以此类推，直到获取到需要的资源。最后如果还是没有，就会回到自己的服务器去获取资源。<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/fcd8f20339064af1a4e3e4847e0a3d6a~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.awebp"></p><p>CNAME（意为：别名）：在域名解析中，实际上解析出来的指定域名对应的IP地址，或者该域名的一个CNAME，然后再根据这个CNAME来查找对应的IP地址。</p><h2 id="CDN的使用场景"><a href="#CDN的使用场景" class="headerlink" title="CDN的使用场景"></a>CDN的使用场景</h2><ul><li>使用第三方的CDN服务</li><li>使用CDN进行静态资源的缓存: 将自己网站的静态资源放在CDN上，比如js、css、图片等。可以将整个项目放在CDN上，完成一键部署。</li><li>直播传送: 直播本质上是使用流媒体进行传送，CDN也是支持流媒体传送的，所以直播完全可以使用CDN来提高访问速度。CDN在处理流媒体的时候与处理普通静态文件有所不同，普通文件如果在边缘节点没有找到的话，就会去上一层接着寻找，但是流媒体本身数据量就非常大，如果使用回源的方式，必然会带来性能问题，所以流媒体一般采用的都是主动推送的方式来进行。</li></ul>]]>
    </content>
    <id>http://shchome.top/2026/05/05/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-CDN/</id>
    <link href="http://shchome.top/2026/05/05/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-CDN/"/>
    <published>2026-05-05T00:51:00.000Z</published>
    <summary>
      <![CDATA[<p>互联网从逻辑上看是一张大网，但实际上是由许多小网络组成的，这其中就有小网络“互连互通”的问题，典型的就是各个电信运营商的网络，比如国内的电信、联通、移动三大家。<br>这些小网络内部的沟通很顺畅，但网络之间却只有很少的联通点。如果你在 A 网络，而网 站在 C 网络，那么就]]>
    </summary>
    <title>前端性能优化-CDN</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="JavaScript" scheme="http://shchome.top/categories/JavaScript/"/>
    <category term="ES6" scheme="http://shchome.top/tags/ES6/"/>
    <content>
      <![CDATA[<p>在ES6之前，实现继承不是一个容易的操作，我们需要先建立一个子类的实例对象this，然后再将父类的方法添加到这个this上面（Parent.apply(this)）来实现继承。</p><p>我们可以先来看看ES5继承的实现方式，请看下面示例：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Rectangle</span> (<span class="params">length, width</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">length</span> = length;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">width</span> = width;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Rectangle</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">getArea</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">length</span> * <span class="variable language_">this</span>.<span class="property">width</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Square</span> (<span class="params">width, length</span>) &#123;</span><br><span class="line">  <span class="title class_">Rectangle</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>, width, length)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Square</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="title class_">Rectangle</span>.<span class="property"><span class="keyword">prototype</span></span>, &#123;</span><br><span class="line">  <span class="attr">constructor</span>: &#123;</span><br><span class="line">    <span class="attr">writable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> square = <span class="keyword">new</span> <span class="title class_">Square</span>(<span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(square.<span class="title function_">getArea</span>())             <span class="comment">// 6</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(square <span class="keyword">instanceof</span> <span class="title class_">Square</span>)     <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(square <span class="keyword">instanceof</span> <span class="title class_">Rectangle</span>)  <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>以上代码简单的实现了一个继承过程。为了实现这个继承过程，我们先创建了子类对象Square的实例对象this，然后调用call方法将父类Reatangle的方法添加到子类对象的this上。</p><h2 id="ES6中的继承"><a href="#ES6中的继承" class="headerlink" title="ES6中的继承"></a>ES6中的继承</h2><p>学习过其他面向对象编程语言的都或多或少知道类和类继承的概念，而JavaScript虽然也是面向对象的却并不支持这些特性，要实现类的继承只能通过其他方法定义并关联上多个相似的对象。这个状态一直从ECMAScript1延续到了ECMAScript5， 直到ECMAScript6终于引入了类的概念。<br>ES6类的出现让我们可以轻松的实现继承功能，和其他能够实现类继承的编程语言一样，ES6的继承也是通过关键字extends来实现的。ES6实现继承的实质是先创造父类的实例对象this，然后通过子类的构造函数修改this。<br>下面我们将上面的函数进行一个修改</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Rectangle</span> </span>&#123;</span><br><span class="line">  constructor (length, width) &#123;</span><br><span class="line">    <span class="keyword">this</span>.length = length;</span><br><span class="line">    <span class="keyword">this</span>.width = width;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  getArea () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.width * <span class="keyword">this</span>.length;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Square</span> <span class="keyword">extends</span> <span class="title">Rectangle</span> </span>&#123;</span><br><span class="line">  constructor (length, width) &#123;</span><br><span class="line">    <span class="keyword">super</span>(length, width)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let square = <span class="keyword">new</span> <span class="type">Square</span>(<span class="number">2</span>, <span class="number">4</span>);</span><br><span class="line">console.log(square.getArea());            <span class="comment">// 8</span></span><br><span class="line">console.log(square instanceof <span class="type">Square</span>)     <span class="comment">// true</span></span><br><span class="line">console.log(square instanceof <span class="type">Rectangle</span>)  <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>类的出现让我们轻松的实现了继承功能，使用extends可以指定类的继承函数，原型会自己调整，然后通过super()方法即可快速访问父类的构造函数。</p><h2 id="super关键字"><a href="#super关键字" class="headerlink" title="super关键字"></a>super关键字</h2><p>下面我们来详细看一下super关键字</p><blockquote><p>super这个关键字既可以当做函数使用，也可以当做对象使用</p></blockquote><p>第一种情况，super作为函数调用时代表父类的构造函数，ES6中有明确的规定，子类的构造函数必须执行一次super函数，但是如果我们不使用构造函数，当创建新的类实例时就会自动调用super()方法，并传入所有的参数。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Square</span> <span class="keyword">extends</span> <span class="title">Rectangle</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 没有构造函数</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等价于</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Square</span> <span class="keyword">extends</span> <span class="title">Rectangle</span> </span>&#123;</span><br><span class="line">  constructor (length, width) &#123;</span><br><span class="line">    <span class="keyword">super</span>(length, width)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>super虽然代表了父类A的构造函数，但是返回的却是子类B的实例，也就是说super内部this指向的是B，因此super()在这里相当于A.prototype.constractor.call(this)</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>&#123;</span><br><span class="line">  constructor () &#123;</span><br><span class="line">    console.log(<span class="keyword">new</span>.target.name);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">B</span> <span class="keyword">extends</span> <span class="title">A</span> </span>&#123;</span><br><span class="line">  constructor () &#123;</span><br><span class="line">    <span class="keyword">super</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">new</span> <span class="type">A</span>();     <span class="comment">// A</span></span><br><span class="line"><span class="keyword">new</span> <span class="type">B</span>();     <span class="comment">// B</span></span><br></pre></td></tr></table></figure><p>上面的代码中，new.target指向的正是当前执行的函数。从结果中我们可以看到，在super()执行的时候它指向的是子类B的构造函数，而不是父类A的构造函数，也就是说，super()内部this的指向是B。<br>super在使用的时候还有另外一种情况–super作为对象时在普通方法中指向父类的原型对象，在静态方法中指向父类</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>&#123;</span><br><span class="line">  p () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">B</span> <span class="keyword">extends</span> <span class="title">A</span></span>&#123;</span><br><span class="line">  constructor () &#123;</span><br><span class="line">    <span class="keyword">super</span>();</span><br><span class="line">    console.log(<span class="keyword">super</span>.p());      <span class="comment">// 1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">new</span> <span class="type">B</span>();</span><br></pre></td></tr></table></figure><p>上面的代码中，子类B中的super就是当成一个对象来使用的，此时的super就相当于普通函数中的A.prototype，所以super.p()就相当于A。prototype.p()。<br>还有一点需要注意的是，在子类的构造函数中，如果需要访问父类的属性可以使用this来实现，但是只有调用了super()方法之后才可以使用this关键字，否则会报错，这是因为子类实例的构建是基于对父类实例的加工，只有super方法才能够返回父类的实例。</p><p>下面我们来总结一下super()的使用需要注意的一些细节</p><ul><li>只可以在派生类的构造函数中使用super()方法，如果尝试在非派生类或函数中使用则会导致程序抛出异常。</li><li>如果不想使用super()，则唯一的办法是让父类的构造函数返回一个对象。</li><li>在派生类的构造函数中使用this之前一定要先调用super()方法，如果次序颠倒程序会抛出异常。</li><li>super指向的是父类的原型对象，所以定义在父类实例上的属性和方法是无法通过super方法调用的</li></ul><p>下面我们再来看一个容易出错的例子</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>&#123;</span><br><span class="line">  constructor () &#123;</span><br><span class="line">    <span class="keyword">this</span>.x = <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  print () &#123;</span><br><span class="line">    console.log(<span class="keyword">this</span>.x)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">B</span> <span class="keyword">extends</span> <span class="title">A</span> </span>&#123;</span><br><span class="line">  constructor () &#123;</span><br><span class="line">    <span class="keyword">super</span>();</span><br><span class="line">    <span class="keyword">this</span>.x = <span class="number">2</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  m () &#123;</span><br><span class="line">    <span class="keyword">super</span>.print();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">let b = <span class="keyword">new</span> <span class="type">B</span>();</span><br><span class="line">b.m();    <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p>上面的代码中，super.print()虽然调用的是父类A.prototype.print()，但是A.prototype.print却被添加到子类B的this中，所以代码输出结果是2，所以上述代码相当于执行A.prototype.print.call(this)。同理如果使用super赋值，赋值的属性会变成子类实例的属性，此时的super就是this。<br>下面我们再来看看super作用在静态方法中的情况，借用《ES6标准入门》中的一个例子：</p><figure class="highlight wren"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">myMethod</span>(<span class="params">msg</span>) &#123;</span><br><span class="line">    <span class="variable">console</span>.<span class="property">log</span>(&#x27;<span class="keyword">static</span>&#x27;, <span class="variable">msg</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">myMethod</span>(<span class="params">msg</span>) &#123;</span><br><span class="line">    <span class="variable">console</span>.<span class="property">log</span>(&#x27;instance&#x27;, <span class="variable">msg</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span> <span class="variable">extends</span> <span class="title class_">Parent</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">myMethod</span>(<span class="params">msg</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>.<span class="property">myMethod</span>(<span class="variable">msg</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">myMethod</span> (<span class="variable">msg</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>.<span class="property">myMethod</span>(<span class="variable">msg</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Child</span>.<span class="property">myMethod</span>(<span class="number">1</span>);    <span class="comment">// static 1</span></span><br><span class="line"><span class="variable">let</span> <span class="variable">child</span> <span class="operator">=</span> <span class="variable">new</span> <span class="title class_">Child</span>();</span><br><span class="line"><span class="variable">child</span>.<span class="property">myMethod</span>(<span class="number">2</span>);    <span class="comment">// instance 2</span></span><br></pre></td></tr></table></figure><p>上面的代码中，super在静态代码中指向父类，在普通方法中指向父类的原型对象。<br>另外在使用super的时候我们必须显式指定是作为对象还是作为函数使用，因为super无法看出是作为函数使用还是作为对象使用，所以在解析代码时就会报错。</p><h2 id="extends关键字"><a href="#extends关键字" class="headerlink" title="extends关键字"></a>extends关键字</h2><p>说完了super关键字，我们再来说说extends关键字，extends可以继承任何类型的表达式，只要该表达式最终返回的是一个可继承的函数，也就是说extends可以继承具有prototype属性的函数，由于函数都有prototype属性(除了Function函数)，因此A可以是任意函数。<br>那么 ES6 extends继承到底做了什么操作？<br>我们先来看看这段包含静态方法的ES6继承代码</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ES6</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span>&#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">name</span>)&#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">static</span> <span class="title function_">sayHello</span>(<span class="params"></span>)&#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">sayName</span>(<span class="params"></span>)&#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;my name is &#x27;</span> + <span class="variable language_">this</span>.<span class="property">name</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">name</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Parent</span>&#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">name, age</span>)&#123;</span><br><span class="line">        <span class="variable language_">super</span>(name);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">sayAge</span>(<span class="params"></span>)&#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;my age is &#x27;</span> + <span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">age</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> parent = <span class="keyword">new</span> <span class="title class_">Parent</span>(<span class="string">&#x27;Parent&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> child = <span class="keyword">new</span> <span class="title class_">Child</span>(<span class="string">&#x27;Child&#x27;</span>, <span class="number">18</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;parent: &#x27;</span>, parent); <span class="comment">// parent:  Parent &#123;name: &quot;Parent&quot;&#125;</span></span><br><span class="line"><span class="title class_">Parent</span>.<span class="title function_">sayHello</span>(); <span class="comment">// hello</span></span><br><span class="line">parent.<span class="title function_">sayName</span>(); <span class="comment">// my name is Parent</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;child: &#x27;</span>, child); <span class="comment">// child:  Child &#123;name: &quot;Child&quot;, age: 18&#125;</span></span><br><span class="line"><span class="title class_">Child</span>.<span class="title function_">sayHello</span>(); <span class="comment">// hello</span></span><br><span class="line">child.<span class="title function_">sayName</span>(); <span class="comment">// my name is Child</span></span><br><span class="line">child.<span class="title function_">sayAge</span>(); <span class="comment">// my age is 18</span></span><br></pre></td></tr></table></figure><p>这其中包含了两条原型链，我们来看看具体代码</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// <span class="number">1</span>、构造器原型链</span><br><span class="line">Child.__proto__ <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> Parent<span class="comment">; // true</span></span><br><span class="line">Parent.__proto__ <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> Function.prototype<span class="comment">; // true</span></span><br><span class="line">Function.prototype.__proto__ <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> Object.prototype<span class="comment">; // true</span></span><br><span class="line">Object.prototype.__proto__ <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> null<span class="comment">; // true</span></span><br><span class="line">// <span class="number">2</span>、实例原型链</span><br><span class="line">child.__proto__ <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> Child.prototype<span class="comment">; // true</span></span><br><span class="line">Child.prototype.__proto__ <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> Parent.prototype<span class="comment">; // true</span></span><br><span class="line">Parent.prototype.__proto__ <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> Object.prototype<span class="comment">; // true</span></span><br><span class="line">Object.prototype.__proto__ <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> null<span class="comment">; // true</span></span><br></pre></td></tr></table></figure><p>结合以上代码我们可以看出：ES6的extends主要就是</p><ol><li>把子类构造函数(Child)的原型(<strong>proto</strong>)指向了父类构造函数(Parent)</li><li>把子类实例child的原型对象(Child.prototype) 的原型(<strong>proto</strong>)指向了父类parent的原型对象(Parent.prototype)。</li><li>子类构造函数Child继承了父类构造函数Preant的里的属性。使用super调用的(ES5则用call或者apply调用传参)。</li></ol><p>本文完</p>]]>
    </content>
    <id>http://shchome.top/2026/05/02/%E7%90%86%E8%A7%A3ES6%E4%B8%AD%E7%9A%84%E7%BB%A7%E6%89%BF%E5%92%8C%E6%B4%BE%E7%94%9F/</id>
    <link href="http://shchome.top/2026/05/02/%E7%90%86%E8%A7%A3ES6%E4%B8%AD%E7%9A%84%E7%BB%A7%E6%89%BF%E5%92%8C%E6%B4%BE%E7%94%9F/"/>
    <published>2026-05-02T10:39:00.000Z</published>
    <summary>
      <![CDATA[<p>在ES6之前，实现继承不是一个容易的操作，我们需要先建立一个子类的实例对象this，然后再将父类的方法添加到这个this上面（Parent.apply(this)）来实现继承。</p>
<p>我们可以先来看看ES5继承的实现方式，请看下面示例：</p>
<figure cl]]>
    </summary>
    <title>理解ES6中的继承和派生</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="框架" scheme="http://shchome.top/categories/%E6%A1%86%E6%9E%B6/"/>
    <category term="Vue" scheme="http://shchome.top/tags/Vue/"/>
    <content>
      <![CDATA[<p>熟悉我的人想必都知道，我的主要技术栈是Vue。虽然平时没怎么接触过React，但是原理和Vue是差不多的–都是通过构建虚拟DOM，然后利用diff算法更新真实DOM实现页面的更新。今天我们就一起来学习虚拟DOM。</p><hr><p>相信从事过前端开发的童靴都知道，操作Virtual DOM会比操作原生DOM要快。但是为什么说操作原生DOM慢呢？</p><h2 id="为什么操作原生DOM慢？"><a href="#为什么操作原生DOM慢？" class="headerlink" title="为什么操作原生DOM慢？"></a>为什么操作原生DOM慢？</h2><p>我们知道执行JS代码有一个JS引擎，执行渲染也有一个渲染引擎。<br>一方面当我们使用JS去操作DOM时，涉及到两个线程之间的通信，会产生一部分性能的损耗。如果操作DOM的次数过多，那么就相当于这两个线程一直在进行通信。<br>另一方面当我们操作DOM的时候，很容易就引起重绘和回流操作，对性能会产生很大的影响。</p><h2 id="虚拟DOM"><a href="#虚拟DOM" class="headerlink" title="虚拟DOM"></a>虚拟DOM</h2><p>相较于直接操作DOM，操作JS对象会快很多。Virtual DOM就是用一个原生JS对象去描述一个DOM节点，当你使用jQuery或者原生api去操作DOM时，浏览器很可能会出现重绘和回流操作。<br>比如当你在一次操作时，需要更新100个DOM节点，理想情况是DOM节点更新完毕后，浏览器进行一次回流操作，将修改的节点信息渲染出来。<br>但是浏览器没有那么智能，当它收到第一个更新DOM的请求时，并不知道后来还有99个，因此会马上执行更新操作，这样就会出现100次更新和100次回流。<br>Virtual DOM就是为了解决类似问题而被设计出来的，虚拟DOM不会立即操作DOM，而是将这100次操作作用在虚拟DOM上，最终通过diff函数生成真实DOM的补丁用来更新真实DOM，通知浏览器去执行绘制工作。<br>在Vue中，Virtual DOM是用VNode这样的一个类去表示。</p><h2 id="VNode对象"><a href="#VNode对象" class="headerlink" title="VNode对象"></a>VNode对象</h2><p>一个VNode的实例对象包括以下属性</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">VNode</span> &#123;</span><br><span class="line">    <span class="comment">// 当前节点的标签名  </span></span><br><span class="line">    <span class="attr">tag</span>?: <span class="built_in">string</span>;</span><br><span class="line">    <span class="comment">// 当前节点的数据对象</span></span><br><span class="line">    <span class="attr">data</span>?: <span class="title class_">VNodeData</span>;</span><br><span class="line">    <span class="comment">// 当前节点的子节点</span></span><br><span class="line">    <span class="attr">children</span>?: <span class="title class_">VNode</span>[];</span><br><span class="line">    <span class="comment">// 当前节点的文本，一般注释节点会有该属性</span></span><br><span class="line">    <span class="attr">text</span>?: <span class="built_in">string</span>;</span><br><span class="line">    <span class="comment">// 当前节点对应的真实节点</span></span><br><span class="line">    <span class="attr">elm</span>?: <span class="title class_">Node</span>;</span><br><span class="line">    <span class="comment">// 当前节点的nameSpace</span></span><br><span class="line">    <span class="attr">ns</span>?: <span class="built_in">string</span>;</span><br><span class="line">    <span class="comment">// 当前节点的作用域</span></span><br><span class="line">    <span class="attr">context</span>?: <span class="title class_">Vue</span>;</span><br><span class="line">    <span class="comment">// 节点的key，有利于diff执行效率</span></span><br><span class="line">    <span class="attr">key</span>?: <span class="built_in">string</span> | <span class="built_in">number</span>;</span><br><span class="line">    <span class="comment">// 创建组件实例时会用到的选项信息</span></span><br><span class="line">    <span class="attr">componentOptions</span>?: <span class="title class_">VNodeComponentOptions</span>;</span><br><span class="line">    <span class="comment">// 组件实例</span></span><br><span class="line">    <span class="attr">componentInstance</span>?: <span class="title class_">Vue</span>;</span><br><span class="line">    <span class="comment">// 组件的父节点</span></span><br><span class="line">    <span class="attr">parent</span>?: <span class="title class_">VNode</span>;</span><br><span class="line">    <span class="attr">raw</span>?: <span class="built_in">boolean</span>;</span><br><span class="line">    <span class="comment">// 静态节点的标识</span></span><br><span class="line">    <span class="attr">isStatic</span>?: <span class="built_in">boolean</span>;</span><br><span class="line">    <span class="comment">// 是否作为根节点插入</span></span><br><span class="line">    <span class="attr">isRootInsert</span>: <span class="built_in">boolean</span>;</span><br><span class="line">    <span class="comment">// 当前节点是否是注释节点</span></span><br><span class="line">    <span class="attr">isComment</span>: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面我们来简单模拟一段代码</p><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;ul <span class="keyword">class</span>=&#x27;<span class="symbol">list</span>&#x27;&gt;</span><br><span class="line">  &lt;<span class="symbol">li</span>&gt;<span class="symbol">1</span>&lt;/<span class="symbol">li</span>&gt;</span><br><span class="line">&lt;/<span class="symbol">ul</span>&gt;</span><br></pre></td></tr></table></figure><p>以上代码转换成VNode就是</p><figure class="highlight perl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">const ul = &#123;</span><br><span class="line">  tag: <span class="string">&#x27;ul&#x27;</span>,</span><br><span class="line">  props: &#123;</span><br><span class="line">    <span class="class"><span class="keyword">class</span>: &#x27;<span class="title">list</span>&#x27;</span></span><br><span class="line"><span class="class">  &#125;,</span></span><br><span class="line"><span class="class">  <span class="title">children</span>: </span>&#123;</span><br><span class="line">    tag: <span class="string">&#x27;li&#x27;</span>,</span><br><span class="line">    children: <span class="string">&#x27;1&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用js对象模拟DOM节点的好处是，页面的更新可以先全部反映在js对象上，操作内存中的js对象的速度显然要快多了。等更新完后，再将最终的js对象映射成真实的DOM，交由浏览器去绘制。<br>那么具体是怎么实现的呢？我们来看看Vue中createElement方法</p><h2 id="createElement解析"><a href="#createElement解析" class="headerlink" title="createElement解析"></a>createElement解析</h2><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">SIMPLE_NORMALIZE</span> = <span class="number">1</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">ALWAYS_NORMALIZE</span> = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">createElement</span> (<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">context</span>: <span class="title class_">Component</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">tag</span>: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">data</span>: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">children</span>: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">normalizationType</span>: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">alwaysNormalize</span>: <span class="built_in">boolean</span></span></span><br><span class="line"><span class="params"></span>): <span class="title class_">VNode</span> | <span class="title class_">Array</span>&lt;<span class="title class_">VNode</span>&gt; &#123;</span><br><span class="line">    <span class="comment">// 兼容没有data的情况</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(data) || <span class="title function_">isPrimitive</span>(data)) &#123;</span><br><span class="line">        normalizationType = children</span><br><span class="line">        children = data</span><br><span class="line">        data = <span class="literal">undefined</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果alwaysNormalize是true</span></span><br><span class="line">    <span class="comment">// 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">isTrue</span>(alwaysNormalize)) &#123;</span><br><span class="line">        normalizationType = <span class="variable constant_">ALWAYS_NORMALIZE</span></span><br><span class="line">    &#125;</span><br><span class="line">    调用_createElement创建虚拟节点</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">_createElement</span>(context, tag, data, children, normalizationType)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">_createElement</span> (<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">context</span>: <span class="title class_">Component</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">tag</span>?: <span class="built_in">string</span> | <span class="title class_">Class</span>&lt;<span class="title class_">Component</span>&gt; | <span class="title class_">Function</span> | <span class="title class_">Object</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">data</span>?: <span class="title class_">VNodeData</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">children</span>?: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">normalizationType</span>?: <span class="built_in">number</span></span></span><br><span class="line"><span class="params"></span>): <span class="title class_">VNode</span> | <span class="title class_">Array</span>&lt;<span class="title class_">VNode</span>&gt; &#123;</span><br><span class="line">    <span class="comment">// 判断是否存在data.__ob__， 如果存在说明data是被监控的数据</span></span><br><span class="line">    <span class="comment">// 不能作为虚拟节点，抛出警告然后返回一个空节点</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// data 被监控不能作为虚拟节点的原因：</span></span><br><span class="line">    <span class="comment">// data在vnode渲染过程中可能会被改变，这样会触发监控，导致不符合预期的操作</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">isDef</span>(data) &amp;&amp; <span class="title function_">isDef</span>((<span class="attr">data</span>: <span class="built_in">any</span>).<span class="property">__ob__</span>)) &#123;</span><br><span class="line">        process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp; <span class="title function_">warn</span>(</span><br><span class="line">            <span class="string">`Avoid using observed data object as vnode data: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(data)&#125;</span>\n`</span> +</span><br><span class="line">            <span class="string">&#x27;Always create fresh vnode data objects in each render!&#x27;</span>,</span><br><span class="line">            context</span><br><span class="line">        )</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">createEmptyVNode</span>()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 当组件的is属性被设置为一个falsy的值</span></span><br><span class="line">    <span class="comment">// Vue将不会知道要把这个组件渲染成什么</span></span><br><span class="line">    <span class="comment">// 所以渲染一个空节点</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">isDef</span>(data) &amp;&amp; <span class="title function_">isDef</span>(data.<span class="property">is</span>)) &#123;</span><br><span class="line">        tag = data.<span class="property">is</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!tag) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">createEmptyVNode</span>()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 确保节点的key必须为String/Number</span></span><br><span class="line">    <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span> &amp;&amp;</span><br><span class="line">      <span class="title function_">isDef</span>(data) &amp;&amp; <span class="title function_">isDef</span>(data.<span class="property">key</span>) &amp;&amp; !<span class="title function_">isPrimitive</span>(data.<span class="property">key</span>)</span><br><span class="line">    ) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!__WEEX__ || !(<span class="string">&#x27;@binding&#x27;</span> <span class="keyword">in</span> data.<span class="property">key</span>)) &#123;</span><br><span class="line">            <span class="title function_">warn</span>(</span><br><span class="line">            <span class="string">&#x27;Avoid using non-primitive value as key, &#x27;</span> +</span><br><span class="line">            <span class="string">&#x27;use string/number value instead.&#x27;</span>,</span><br><span class="line">            context</span><br><span class="line">            )</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 支持使用作用域插槽</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(children) &amp;&amp;</span><br><span class="line">        <span class="keyword">typeof</span> children[<span class="number">0</span>] === <span class="string">&#x27;function&#x27;</span></span><br><span class="line">    ) &#123;</span><br><span class="line">        data = data || &#123;&#125;</span><br><span class="line">        data.<span class="property">scopedSlots</span> = &#123; <span class="attr">default</span>: children[<span class="number">0</span>] &#125;</span><br><span class="line">        children.<span class="property">length</span> = <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 根据normalizationType的值，选择不同的处理方法</span></span><br><span class="line">    <span class="keyword">if</span> (normalizationType === <span class="variable constant_">ALWAYS_NORMALIZE</span>) &#123;</span><br><span class="line">        children = <span class="title function_">normalizeChildren</span>(children)</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (normalizationType === <span class="variable constant_">SIMPLE_NORMALIZE</span>) &#123;</span><br><span class="line">        children = <span class="title function_">simpleNormalizeChildren</span>(children)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> vnode, ns</span><br><span class="line">    <span class="comment">// 标签名是string时执行下面的方法</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> tag === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="title class_">Ctor</span></span><br><span class="line">        <span class="comment">// 获取标签的命名空间</span></span><br><span class="line">        ns = (context.<span class="property">$vnode</span> &amp;&amp; context.<span class="property">$vnode</span>.<span class="property">ns</span>) || config.<span class="title function_">getTagNamespace</span>(tag)</span><br><span class="line">        <span class="comment">// 判断是否是保留的标签</span></span><br><span class="line">        <span class="keyword">if</span> (config.<span class="title function_">isReservedTag</span>(tag)) &#123;</span><br><span class="line">            <span class="comment">// 如果是保留标签,就创建一个这样的vnode</span></span><br><span class="line">            vnode = <span class="keyword">new</span> <span class="title class_">VNode</span>(</span><br><span class="line">            config.<span class="title function_">parsePlatformTagName</span>(tag), data, children,</span><br><span class="line">            <span class="literal">undefined</span>, <span class="literal">undefined</span>, context</span><br><span class="line">            )</span><br><span class="line">        <span class="comment">// 如果不是保留标签，那么我们将尝试从vm的components上查找是否有这个标签的定义</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((!data || !data.<span class="property">pre</span>) &amp;&amp; <span class="title function_">isDef</span>(<span class="title class_">Ctor</span> = <span class="title function_">resolveAsset</span>(context.<span class="property">$options</span>, <span class="string">&#x27;components&#x27;</span>, tag))) &#123;</span><br><span class="line">            <span class="comment">// 如果找到了这个标签的定义，就以此创建虚拟组件节点</span></span><br><span class="line">            vnode = <span class="title function_">createComponent</span>(<span class="title class_">Ctor</span>, data, context, children, tag)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 正常创建一个VNode</span></span><br><span class="line">            vnode = <span class="keyword">new</span> <span class="title class_">VNode</span>(</span><br><span class="line">            tag, data, children,</span><br><span class="line">            <span class="literal">undefined</span>, <span class="literal">undefined</span>, context</span><br><span class="line">            )</span><br><span class="line">        &#125;</span><br><span class="line">    <span class="comment">// 当标签不是string时就直接创建</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// direct component options / constructor</span></span><br><span class="line">        vnode = <span class="title function_">createComponent</span>(tag, data, context, children)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(vnode)) &#123;</span><br><span class="line">        <span class="comment">// 如果vnode是一个数组就直接返回</span></span><br><span class="line">        <span class="keyword">return</span> vnode</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="title function_">isDef</span>(vnode)) &#123;</span><br><span class="line">        <span class="comment">// 如果有namespace，就应用下namespace，然后返回vnode</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="title function_">isDef</span>(ns)) <span class="title function_">applyNS</span>(vnode, ns)</span><br><span class="line">        <span class="keyword">if</span> (<span class="title function_">isDef</span>(data)) <span class="title function_">registerDeepBindings</span>(data)</span><br><span class="line">        <span class="keyword">return</span> vnode</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 否则，返回一个空节点</span></span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">createEmptyVNode</span>()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或许看上面的代码有点晕，所以我在网上找了张图可以对照着理解上述代码<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/Virtual%20DOM/1.png"></p><h2 id="patch-实现"><a href="#patch-实现" class="headerlink" title="patch 实现"></a>patch 实现</h2><p>关于patch的实现，代码量太大了，所以就不在这儿贴出来了，在这里我简述一下它的比较过程，感兴趣的童靴可以去看看Vue源码，源码位于&#x2F;src&#x2F;core&#x2F;vdom&#x2F;patch.js<br>首先patch接收四个参数</p><ul><li>oldVnode：旧的虚拟节点或者当前真实节点</li><li>vnode：新的虚拟节点</li><li>hydrating：是否要和真的dom混合</li><li>removeOnly：特殊flag，用于<transition-group>组件</li></ul><p>patch函数的执行过程：</p><ul><li>如果vnode不存在，但是oldVnode存在，说明需要删除此节点，调用invokeDestroyHook(oldVnode)</li><li>如果vnode存在但是oldVnode不存在，说明此节点是新加的节点，调用createElm(vnode, insertedVnodeQueue)创建新节点</li><li>如果vnode和oldVnode都存在的情况，就需要比较节点值了。当两个节点值的比较的时候，就调用patchVnode函数。节点的比较主要分为5种情况：</li></ul><ol><li>当它们的引用一致时，可以认为是没有变化，不需要进行操作。</li></ol><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">if (oldVnode <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> vnode) &#123;</span><br><span class="line">      return</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>文本节点的比较，需要修改则调用setTextContent。</li></ol><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (oldVnode.<span class="built_in">text</span> !== vnode.<span class="built_in">text</span>) &#123;</span><br><span class="line">    nodeOps.setTextContent(elm, vnode.<span class="built_in">text</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>两个节点都有子节点，而且它们不一样，这是就调用updateChildren进行子节点的比较。</li></ol><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">if (isDef(oldCh) &amp;&amp; <span class="built_in">isDef</span>(ch)) &#123;</span><br><span class="line">    if (oldCh !== ch) <span class="built_in">updateChildren</span>(elm, oldCh, ch, insertedVnodeQueue, removeOnly)</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><ol start="4"><li>只有新的节点有有子节点，调用addVnodes方法。</li></ol><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="built_in">isDef</span>(ch)) &#123;</span><br><span class="line">    <span class="keyword">if</span> (process<span class="selector-class">.env</span><span class="selector-class">.NODE_ENV</span> !== <span class="string">&#x27;production&#x27;</span>) &#123;</span><br><span class="line">        <span class="built_in">checkDuplicateKeys</span>(ch)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">isDef</span>(oldVnode.text)) nodeOps<span class="selector-class">.setTextContent</span>(elm, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    <span class="built_in">addVnodes</span>(elm, null, ch, <span class="number">0</span>, ch<span class="selector-class">.length</span> - <span class="number">1</span>, insertedVnodeQueue)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="5"><li>新节点没有子节点，老节点有子节点，直接删掉老节点。</li></ol><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">if</span> (isDef(oldCh)) &#123;</span><br><span class="line">    <span class="attribute">removeVnodes</span>(elm, oldCh, <span class="number">0</span>, oldCh.length - <span class="number">1</span>)<span class="punctuation">\</span></span><br><span class="line"><span class="punctuation"></span>&#125;</span><br></pre></td></tr></table></figure><p>1245步执行的操作都比较简单，最重要的还是updateChildred，为了形象的展示updateChildren所进行的操作，我们来看看这样的一张图。</p><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/Virtual%20DOM/2.png"></p><p>过程可以概括为：oldCh和newCh各有两个头尾的变量StartIdx和EndIdx，它们的2个变量相互比较，一共有4种比较方式。如果4种比较都没匹配，如果设置了key，就会用key进行比较，在比较的过程中，变量会往中间靠，一旦StartIdx&gt;EndIdx表明oldCh和newCh至少有一个已经遍历完了，就会结束比较。<br>在updateChildren中，算法如下：</p><ul><li>分别获取oldVnode和vnode的firstChild、lastChild，赋值给oldStartVnode、oldEndVnode、newStartVnode、newEndVnode</li><li>如果oldStartVnode和newStartVnode是同一节点，调用patchVnode进行patch，然后将oldStartVnode和newStartVnode都设置为下一0个子节点，重复上述流程。</li></ul><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/Virtual%20DOM/3.png"></p><ul><li>如果oldEndVnode和newEndVnode是同一节点，调用patchVnode进行patch，然后将oldEndVnode和newEndVnode都设置为上一个子节点，重复上述流程。</li></ul><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/Virtual%20DOM/4.png"></p><ul><li>如果oldStartVnode和newEndVnode是同一节点，调用patchVnode进行patch，如果removeOnly是false，那么可以把oldStartVnode.elm移动到oldEndVnode.elm之后，然后把oldStartVnode设置为下一个节点，newEndVnode设置为上一个节点，重复上述流程。</li></ul><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/Virtual%20DOM/5.png"></p><ul><li>如果newStartVnode和oldEndVnode是同一节点，调用patchVnode进行patch，如果removeOnly是false，那么可以把oldEndVnode.elm移动到oldStartVnode.elm之前，然后把newStartVnode设置为下一个节点，oldEndVnode设置为上一个节点，重复上述流程</li></ul><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/Virtual%20DOM/6.png"></p><ul><li><p>如果以上都不匹配，就尝试在oldChildren中寻找跟newStartVnode具有相同key的节点，如果找不到相同key的节点，说明newStartVnode是一个新节点，就创建一个，然后把newStartVnode设置为下一个节点</p></li><li><p>如果上一步找到了跟newStartVnode相同key的节点，那么通过其他属性的比较来判断这2个节点是否是同一个节点，如果是，就调用patchVnode进行patch，如果removeOnly是false，就把newStartVnode.elm插入到oldStartVnode.elm之前，把newStartVnode设置为下一个节点，重复上述流程</p></li></ul><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/Virtual%20DOM/7.png"></p><ul><li>如果在oldChildren中没有寻找到newStartVnode的同一节点，那就创建一个新节点，把newStartVnode设置为下一个节点，重复上述流程</li><li>如果oldStartVnode跟oldEndVnode重合了，并且newStartVnode跟newEndVnode也重合了，这个循环就结束了。</li></ul><hr><p>Vue源码中这一块还是比较复杂的，本文只是抛砖引玉，希望小伙伴们还是能够亲自去看一看。</p><p>参考文档：<br><a href="https://juejin.im/post/5b836c606fb9a019cf2cbf0e">vue虚拟dom实现</a><br><a href="https://ustbhuangyi.github.io/vue-analysis/data-driven/virtual-dom.html">Vue.js 技术揭秘</a><br><a href="https://juejin.im/post/5ad6182df265da23906c8627">面试官：你了解vue的diff算法吗?</a><br><a href="https://github.com/aooy/blog/issues/2">解析vue2.0的diff算法</a></p>]]>
    </content>
    <id>http://shchome.top/2026/04/28/Virtual%20DOM/</id>
    <link href="http://shchome.top/2026/04/28/Virtual%20DOM/"/>
    <published>2026-04-28T01:08:00.000Z</published>
    <summary>
      <![CDATA[<p>熟悉我的人想必都知道，我的主要技术栈是Vue。虽然平时没怎么接触过React，但是原理和Vue是差不多的–都是通过构建虚拟DOM，然后利用diff算法更新真实DOM实现页面的更新。今天我们就一起来学习虚拟DOM。</p>
<hr>
<p>相信从事过前端开发的童靴都知道，操作]]>
    </summary>
    <title>Virtual DOM</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="CSS" scheme="http://shchome.top/categories/CSS/"/>
    <category term="页面布局" scheme="http://shchome.top/tags/%E9%A1%B5%E9%9D%A2%E5%B8%83%E5%B1%80/"/>
    <content>
      <![CDATA[<p>在CSS中有个很重要的概念–块级格式化上下文（BFC），搞懂BFC对我们理解一些“诡异性”事件很有帮助。</p><hr><h2 id="什么是BFC"><a href="#什么是BFC" class="headerlink" title="什么是BFC"></a>什么是BFC</h2><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">Block </span>fomatting <span class="built_in">context</span>= <span class="keyword">block-level </span><span class="keyword">box+Formatting </span><span class="built_in">Context</span></span><br></pre></td></tr></table></figure><ul><li><p>block-level box即块级元素</p><blockquote><p>display属性为block，list-item，table的元素，会生成block-level box;并且参与block fomatting context;</p></blockquote></li><li><p>inline-level box即行内元素</p><blockquote><p>display属性为inline，inline-block，inline-table的元素，会生成inline-level box。并且参与inline formatting context;</p></blockquote></li><li><p>Formatting Context<br>格式化上下文是W3C CSS2.1规范中的一个概念。它是页面中的一块渲染区域，并且有一套渲染规则，它决定了其子元素将如何定位，以及和其他元素的关系，相互作用。最常见的格式化上下文有块格式化上下文（简称BFC）和内联格式化上下文（简称IFC）。<br>CSS2.1中只有BFC和IFC，CSS3中还增加了G（网格）FC和F（flex）FC。</p></li></ul><h4 id="BFC定义"><a href="#BFC定义" class="headerlink" title="BFC定义"></a>BFC定义</h4><p>BFC(Block Formatting Context)块级格式化上下文，是用于布局块级盒子的一块渲染区域。MDN上的解释：BFC是Web页面 CSS 视觉渲染的一部分，用于决定块盒子的布局及浮动相互影响范围的一个区域。<br>注意：一个BFC的范围包含创建该上下文元素的所有子元素，但不包括创建了新BFC的子元素的内部元素。这从另一方角度说明，一个元素不能同时存在于两个BFC中。因为如果一个元素能够同时处于两个BFC中，那么就意味着这个元素能与两个BFC中的元素发生作用，就违反了BFC的隔离作用。</p><h2 id="三种文档流的定位方案"><a href="#三种文档流的定位方案" class="headerlink" title="三种文档流的定位方案"></a>三种文档流的定位方案</h2><h4 id="常规流-Normal-flow"><a href="#常规流-Normal-flow" class="headerlink" title="常规流(Normal flow)"></a>常规流(Normal flow)</h4><ul><li>在常规流中，盒一个接着一个排列;</li><li>在块级格式化上下文里面， 它们竖着排列；</li><li>在行内格式化上下文里面， 它们横着排列;</li><li>当position为static或relative，并且float为none时会触发常规流；</li><li>对于静态定位(static positioning)，position: static，盒的位置是常规流布局里的位置；</li><li>对于相对定位(relative positioning)，position: relative，盒偏移位置由top、bottom、left、right属性定义。即使有偏移，仍然保留原有的位置，其它常规流不能占用这个位置。</li></ul><h4 id="浮动-Floats"><a href="#浮动-Floats" class="headerlink" title="浮动(Floats)"></a>浮动(Floats)</h4><ul><li>左浮动元素尽量靠左、靠上，右浮动同理</li><li>这导致常规流环绕在它的周边，除非设置 clear 属性</li><li>浮动元素不会影响块级元素的布局</li><li>但浮动元素会影响行内元素的布局，让其围绕在自己周围，撑大父级元素，从而间接影响块级元素布局</li><li>最高点不会超过当前行的最高点、它前面的浮动元素的最高点</li><li>不超过它的包含块，除非元素本身已经比包含块更宽</li><li>行内元素出现在左浮动元素的右边和右浮动元素的左边，左浮动元素的左边和右浮动元素的右边是不会摆放浮动元素的</li></ul><h4 id="绝对定位-Absolute-positioning"><a href="#绝对定位-Absolute-positioning" class="headerlink" title="绝对定位(Absolute positioning)"></a>绝对定位(Absolute positioning)</h4><ul><li>绝对定位方案，盒从常规流中被移除，不影响常规流的布局；</li><li>它的定位相对于它的包含块，相关CSS属性：top、bottom、left、right；</li><li>如果元素的属性position为absolute或fixed，它是绝对定位元素；</li><li>对于position: absolute，元素定位将相对于上级元素中最近的一个relative、fixed、absolute，如果没有则相对于body；</li></ul><h2 id="BFC触发方式"><a href="#BFC触发方式" class="headerlink" title="BFC触发方式"></a>BFC触发方式</h2><ol><li>根元素，即HTML标签</li><li>浮动元素：float值为left、right</li><li>overflow值不为 visible，为 auto、scroll、hidden</li><li>display值为 inline-block、table-cell、table-caption、table、inline-table、flex、inline-flex、grid、inline-grid</li><li>定位元素：position值为 absolute、fixed<br>注意: display:table也可以生成BFC的原因在于Table会默认生成一个匿名的table-cell，是这个匿名的table-cell生成了BFC。</li></ol><h2 id="约束规则"><a href="#约束规则" class="headerlink" title="约束规则"></a>约束规则</h2><p>浏览器对BFC区域的约束规则：</p><ol><li>生成BFC元素的子元素会一个接一个的放置</li><li>垂直方向上他们的起点是一个包含块的顶部，两个相邻子元素之间的垂直距离取决于元素的margin特性。在BFC中相邻的块级元素的外边距会折叠(Mastering margin collapsing)。</li><li>生成BFC元素的子元素中，每一个子元素左外边距与包含块的左边界相接触（对于从右到左的格式化，右外边距接触右边界），即使浮动元素也是如此（尽管子元素的内容区域会由于浮动而压缩），除非这个子元素也创建了一个新的BFC（如它自身也是一个浮动元素）。<br>规则解读：</li><li>内部的Box会在垂直方向上一个接一个的放置</li><li>内部的Box垂直方向上的距离由margin决定。（完整的说法是：属于同一个BFC的两个相邻Box的margin会发生折叠，不同BFC不会发生折叠。）</li><li>每个元素的左外边距与包含块的左边界相接触（从左向右），即使浮动元素也是如此。（这说明BFC中子元素不会超出他的包含块，而position为absolute的元素可以超出他的包含块边界）</li><li>BFC的区域不会与float的元素区域重叠</li><li>计算BFC的高度时，浮动子元素也参与计算</li></ol><h2 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h2><p>BFC是页面上的一个隔离的独立容器，容器里面的子元素不会影响到外面元素，反之亦然。我们可以利用BFC的这个特性来做很多事。</p><h4 id="5-1-阻止元素被浮动元素覆盖"><a href="#5-1-阻止元素被浮动元素覆盖" class="headerlink" title="5.1 阻止元素被浮动元素覆盖"></a>5.1 阻止元素被浮动元素覆盖</h4><p>一个正常文档流的block元素可能被一个float元素覆盖，挤占正常文档流，因此可以设置一个元素的float、display、position值等方式触发BFC，以阻止被浮动盒子覆盖。</p><h4 id="5-2-可以包含浮动元素"><a href="#5-2-可以包含浮动元素" class="headerlink" title="5.2 可以包含浮动元素"></a>5.2 可以包含浮动元素</h4><p>通过改变包含浮动子元素的父盒子的属性值，触发BFC，以此来包含子元素的浮动盒子。<br>注意，这里触发BFC并不能阻止其它形式的脱离文档流的元素覆盖正常流元素。</p><h4 id="5-3-阻止因为浏览器因为四舍五入造成的多列布局换行的情况"><a href="#5-3-阻止因为浏览器因为四舍五入造成的多列布局换行的情况" class="headerlink" title="5.3 阻止因为浏览器因为四舍五入造成的多列布局换行的情况"></a>5.3 阻止因为浏览器因为四舍五入造成的多列布局换行的情况</h4><p>有时候因为多列布局采用小数点位的width导致因为浏览器因为四舍五入造成的换行的情况，可以在最后一列触发BFC的形式来阻止换行的发生。比如下面栗子的特殊情况</p><h4 id="5-4-阻止相邻元素的margin合并"><a href="#5-4-阻止相邻元素的margin合并" class="headerlink" title="5.4  阻止相邻元素的margin合并"></a>5.4  阻止相邻元素的margin合并</h4><p>属于同一个BFC的两个相邻块级子元素的上下margin会发生重叠，(设置writing-mode:tb-rl时，水平margin会发生重叠)。所以当两个相邻块级子元素分属于不同的BFC时可以阻止margin重叠。<br>这里给任一个相邻块级盒子的外面包一个div，通过改变此div的属性使两个原盒子分属于两个不同的BFC，以此来阻止margin重叠。<br>但是这里有个疑问：<br>如果外面包一层div，设置能触发BFC的任何属性都能阻止相邻元素的margin合并。因为分属不同BFC不会发生margin合并。<br>而如果在外面不包一个div的话，当设置display为inline-block、inline-flex、table-captain，和position为absolute、fixed，float为left、right是可以阻止margin合并的。</p><hr><p>本文完</p>]]>
    </content>
    <id>http://shchome.top/2026/04/22/%E6%B5%85%E6%9E%90CSS%E4%B8%AD%E7%9A%84BFC/</id>
    <link href="http://shchome.top/2026/04/22/%E6%B5%85%E6%9E%90CSS%E4%B8%AD%E7%9A%84BFC/"/>
    <published>2026-04-22T13:26:00.000Z</published>
    <summary>
      <![CDATA[<p>在CSS中有个很重要的概念–块级格式化上下文（BFC），搞懂BFC对我们理解一些“诡异性”事件很有帮助。</p>
<hr>
<h2 id="什么是BFC"><a href="#什么是BFC" class="headerlink" title="什么是BFC"></a>什么是]]>
    </summary>
    <title>浅析CSS中的BFC</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="前端" scheme="http://shchome.top/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="设计模式" scheme="http://shchome.top/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    <content>
      <![CDATA[<p>经常在一些文章中看到–发布-订阅模式又叫做观察者模式，久而久之，我也认为这两个设计模式是一样的，也就是叫法有所不同而已，直到最近进行了一个深入学习，才发现二者还是有区别的。</p><h2 id="观察者模式"><a href="#观察者模式" class="headerlink" title="观察者模式"></a>观察者模式</h2><p>我认为大多数人都会同意观察者模式是学起来最好入门的，因为我们从字面意思就能知道它主要是做什么的。<br>这里我列举2个通俗易懂的例子：</p><ol><li>那些年，90后的小时候，我们没有手机。悠闲地上午时光，我们出门各自玩耍，临出门前会跟老妈说一下是否回家吃午饭。总在临近中午之时开始关注一个事情，“午饭好了么，是否该回家吃饭了”。没有手机可咋整？跑回去瞅瞅。老妈说，饭还没开始做呢。<br>于是再出去浪一会。老妈说饭马上就好了，洗手准备吃饭。于是咕咕叫的肚子变得欢喜。后来，手机成了必备品，便没了这样的烦恼。中午时分总会接到老妈的电话，语气不甚和善的喊一句：“还吃不吃饭了，麻利回来～”。<br>观察者模式也便是如此了。当有一到多用户（粗去玩耍的我们）都在观察一个事情（饭是否已做好）的变化。使用观察者模式（手机），不需要我们时不时的跑回家确认，事情变化发生（饭已做好）时，老妈可便捷的通知所有关注午饭的我们。<br>上面问题概述起来，适用观察者模式有几个要素：被观察者（老妈）、观察者（外出的我们）、事件（老妈是否做好饭）和由被观察者维护的观察者列表（老妈知道要回家吃饭的是谁）。<br>不知从上述实例，小伙伴是否看出隐藏的几点。<br><strong>1.观察者并非一成不变。并不是每天都是所有人回家吃午饭，只有提前告知老妈要回家吃午饭的人才会接到电话。因为观察者的可变性，需要被观察者维护一个列表。</strong><br><strong>2.观察者模式可以便捷的完成目标。不需要观察者不停的轮询查看事件变化，也不需要被观察者多次询问观察者意愿。只需要观察者提前加入或离开列表，便可以由被观察者准确的进行事件通知。 说起来，微信订阅号便是观察者模式的一种实现。感兴趣的人订阅公众号，在公众号有新的文章时推送给所有订阅人。</strong></li><li>我们假设你正在找一份软件工程师的工作，对“香蕉公司”很感兴趣。所以你联系了他们的HR，给了他你的联系电话。他保证如果有任何职位空缺都会通知你。这里还有几个候选人也你一样很感兴趣。所以职位空缺大家都会知道，如果你回应了他们的通知，他们就会联系你面试。<br>所以，以上和“观察者模式”有什么关系呢？这里的“香蕉公司”就是Subject，用来维护Observers（和你一样的候选人），为某些event（比如职位空缺）来通知（notify）观察者。<br>观察者模式在软件设计中是一个对象，维护一个依赖列表，当任何状态发生改变自动通知它们。<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E5%8F%91%E5%B8%83-%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F/1.jpg" alt="观察者模式"><br>所以，如果你想在你的软件或者应用中实现观察者模式，你可以遵循上图这个流程。</li></ol><h2 id="发布-订阅模式"><a href="#发布-订阅模式" class="headerlink" title="发布-订阅模式"></a>发布-订阅模式</h2><p>在观察者模式中的Subject就像一个发布者（Publisher），而观察者（Observer）完全可以看作一个订阅者（Subscriber）。subject通知观察者时，就像一个发布者通知他的订阅者。这也就是为什么很多书和文章使用“发布-订阅”概念来解释观察者设计模式。但是这里还有另外一个流行的模式叫做发布-订阅设计模式。它的概念和观察者模式非常类似。最大的区别是：<br>在发布-订阅模式，消息的发送方，叫做发布者（publishers），消息不会直接发送给特定的接收者（订阅者）。<br>意思就是发布者和订阅者不知道对方的存在。需要一个第三方组件，叫做信息中介，它将订阅者和发布者串联起来，它过滤和分配所有输入的消息。换句话说，发布-订阅模式用来处理不同系统组件的信息交流，即使这些组件不知道对方的存在。<br>那么如何过滤消息的呢？事实上这里有几个过程，最流行的方法是：基于主题以及基于内容。好了，就此打住，如果你感兴趣，可以去维基百科了解。<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E5%8F%91%E5%B8%83-%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F/2.jpg" alt="发布-订阅者模式"><br>所以，我用下图表示这两个模式最重要的区别：<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E5%8F%91%E5%B8%83-%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F/3.jpg" alt="二者对比"><br>我们把这些差异快速总结一下：</p><ul><li>在观察者模式中，观察者是知道Subject的，Subject一直保持对观察者进行记录。然而，在发布订阅模式中，发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。</li><li>在发布订阅模式中，组件是松散耦合的，正好和观察者模式相反。</li><li>观察者模式大多数时候是同步的，比如当事件触发，Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的（使用消息队列）。</li><li>观察者模式需要在单个应用程序地址空间中实现，而发布-订阅更像交叉应用模式。</li></ul>]]>
    </content>
    <id>http://shchome.top/2026/04/16/%E5%8F%91%E5%B8%83-%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F/</id>
    <link href="http://shchome.top/2026/04/16/%E5%8F%91%E5%B8%83-%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F/"/>
    <published>2026-04-16T02:17:00.000Z</published>
    <summary>
      <![CDATA[<p>经常在一些文章中看到–发布-订阅模式又叫做观察者模式，久而久之，我也认为这两个设计模式是一样的，也就是叫法有所不同而已，直到最近进行了一个深入学习，才发现二者还是有区别的。</p>
<h2 id="观察者模式"><a href="#观察者模式" class="headerl]]>
    </summary>
    <title>发布-订阅模式</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="框架" scheme="http://shchome.top/categories/%E6%A1%86%E6%9E%B6/"/>
    <category term="Vue" scheme="http://shchome.top/tags/Vue/"/>
    <content>
      <![CDATA[<p>最近完成了一个拖拽分组的需求，在里面用到了作用域插槽，感觉这是一种优秀的组件化方案，所以在此做个记录。</p><hr><p>关于Vue插槽的知识，本篇文章不做过多的描述，没有接触过的童靴请移步<a href="https://cn.vuejs.org/v2/guide/components-slots.html">Vue.js</a></p><h2 id="业务需求"><a href="#业务需求" class="headerlink" title="业务需求"></a>业务需求</h2><p>任意多个模型，每个模型中都有若干个字段，字段可在页面上添加，类似于数据库，有多张表且每张表字段任意。现在要求用拖拽的方式给字段分组。</p><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p>做个拖拽分组需求的小伙伴相比都知道，首先我们要构造一个树形结构</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">- </span>模型</span><br><span class="line"><span class="bullet">-- </span>分组</span><br><span class="line"><span class="bullet">--- </span>字段</span><br></pre></td></tr></table></figure><p>刚开始实现的时候，将字段信息写成一个组件Field.vue，而在Group.vue中用一个v-for来处理字段的显示，在index.vue中用v-for处理分组信息的显示。</p><figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Group.vue</span></span><br><span class="line">&lt;<span class="keyword">div</span> <span class="keyword">class</span>=<span class="string">&quot;field-list-content&quot;</span> v-<span class="keyword">for</span>=<span class="string">&quot;(item, index) in fieldListData&quot;</span> :key=<span class="string">&quot;index&quot;</span>&gt;</span><br><span class="line">    &lt;Field :fieldData=<span class="string">&quot;item&quot;</span> /&gt;</span><br><span class="line">&lt;/<span class="keyword">div</span>&gt;</span><br></pre></td></tr></table></figure><p>按照这种方法也能够实现，但是如果按照这种方法我也不会写这篇博客了，下面我们来看看优化的方案。</p><h2 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h2><p>通常情况下普通的插槽是父组件使用插槽过程中传入东西决定了插槽的内容。但作用域插槽却可以拿到子组件中的数据<br>在子组件中创建slot并通过 v-bind 绑定数据 prop 的形式传入数据：</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;slot <span class="symbol">:data=<span class="string">&quot;data&quot;</span>&gt;&lt;/slot&gt;</span></span><br></pre></td></tr></table></figure><p>基于作用域插槽的这个特性，将以上代码改写</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.vue</span></span><br><span class="line">&lt;field-list</span><br><span class="line">    <span class="keyword">class</span>=<span class="string">&quot;group_content&quot;</span></span><br><span class="line">    :fieldListData=<span class="string">&quot;item.fieldArray ? item.fieldArray : []&quot;</span></span><br><span class="line">    group=<span class="string">&quot;field&quot;</span></span><br><span class="line">    <span class="meta">@addGroupItem</span>=<span class="string">&quot;onAddGroupItem(item)&quot;</span></span><br><span class="line">    <span class="meta">@moveFieldFinish</span>=<span class="string">&quot;dealWithFieldGroups&quot;</span></span><br><span class="line">&gt;</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">template</span> <span class="attr">v-slot</span>=<span class="string">&quot;scope&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">field</span> <span class="attr">:fieldData</span>=<span class="string">&quot;scope.row&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br><span class="line">&lt;/field-list&gt;</span><br></pre></td></tr></table></figure><p>而Group组件内部改造成slot接收来自父组件的字段信息组件，并且将所有逻辑处理代码提升到父组件，实现组件和业务剥离。这也是组件化的精髓。</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Group</span>.vue</span><br><span class="line">&lt;draggable <span class="symbol">:list=<span class="string">&quot;fieldListData&quot;</span></span> v-bind=<span class="string">&quot;dragOptions&quot;</span> <span class="variable">@end</span>=<span class="string">&quot;dealWithFieldGroups&quot;</span>&gt;</span><br><span class="line">    &lt;div class=<span class="string">&quot;field-list-content&quot;</span> v-<span class="keyword">for</span>=<span class="string">&quot;(item, index) in fieldListData&quot;</span> <span class="symbol">:key=<span class="string">&quot;index&quot;</span>&gt;</span></span><br><span class="line">      &lt;slot <span class="symbol">:row=<span class="string">&quot;item&quot;</span>&gt;&lt;/slot&gt;</span></span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">    &lt;div</span><br><span class="line">      style=<span class="string">&quot;width: 100%;&quot;</span></span><br><span class="line">      slot=<span class="string">&quot;footer&quot;</span></span><br><span class="line">      class=<span class="string">&quot;group_detail&quot;</span></span><br><span class="line">      v-<span class="keyword">if</span>=<span class="string">&quot;fieldListData.length === 0&quot;</span></span><br><span class="line">      <span class="variable">@click</span>=<span class="string">&quot;onAddGroupItem&quot;</span></span><br><span class="line">    &gt;</span><br><span class="line">      &lt;el-button type=<span class="string">&quot;text&quot;</span> class=<span class="string">&quot;add_detail&quot;</span>&gt;立即添加&lt;/el-button&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">&lt;/draggable&gt;</span><br></pre></td></tr></table></figure><p>至于字段信息组件，只需要展示即可。</p><figure class="highlight handlebars"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="language-xml">// Field.vue</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;field_content&quot;</span> <span class="attr">:title</span>=<span class="string">&quot;fieldData.label&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">icon</span> <span class="attr">type</span>=<span class="string">&quot;field_info&quot;</span> <span class="attr">className</span>=<span class="string">&quot;icon&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">icon</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;item&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      </span><span class="template-variable">&#123;&#123; <span class="name">fieldData.label</span> &#125;&#125;</span><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p>最终实现的效果：<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%8F%92%E6%A7%BD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF/1.gif"></p><hr><p>本文主要的介绍的是作用域插槽的场景，作用域插槽适合的场景是至少包含三级以上的组件层级，是一种优秀的组件化方案！</p>]]>
    </content>
    <id>http://shchome.top/2026/04/09/Vue%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%8F%92%E6%A7%BD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF/</id>
    <link href="http://shchome.top/2026/04/09/Vue%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%8F%92%E6%A7%BD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF/"/>
    <published>2026-04-09T12:44:00.000Z</published>
    <summary>
      <![CDATA[<p>最近完成了一个拖拽分组的需求，在里面用到了作用域插槽，感觉这是一种优秀的组件化方案，所以在此做个记录。</p>
<hr>
<p>关于Vue插槽的知识，本篇文章不做过多的描述，没有接触过的童靴请移步<a href="https://cn.vuejs.org/v2/guide/]]>
    </summary>
    <title>作用域插槽的应用场景</title>
    <updated>2026-05-24T03:45:48.282Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="JavaScript" scheme="http://shchome.top/categories/JavaScript/"/>
    <category term="JavaScript" scheme="http://shchome.top/tags/JavaScript/"/>
    <content>
      <![CDATA[<p>讲了好长时间的闭包前置知识，今天终于说到了闭包。正如前面文章中说到的，闭包涉及到的知识点很多，如果直接阅读本文，可能你会看不太懂，因此，为了更好地理解本文，建议你去看看前面几篇文章。</p><hr><h2 id="什么是闭包？"><a href="#什么是闭包？" class="headerlink" title="什么是闭包？"></a>什么是闭包？</h2><p>废话不多说，我们先来看看MDN对闭包的定义：</p><blockquote><p>简单讲，闭包就是指有权访问另一个函数作用域中的变量的函数。<br>MDN 上面这么说：闭包是一种特殊的对象。它由两部分构成：函数，以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。</p></blockquote><p>但是，在网上找了好多资料，也翻了好几本相关书籍，它们对闭包都有各种各样的定义。但是个人最认同的是《你不知道的JavaScript》中的描述：</p><blockquote><p>当函数可以记住并访问所在的词法作用域时，就产生了闭包，即使函数是在当前词法作用域之外执行。</p></blockquote><p>虽然其它的说法都没有错，但闭包应该是一种现象，你不用刻意去创建，因为闭包在代码中随处可见，只是你还不知道当时你写的那一段代码其实就产生了闭包。</p><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/JavaScript%E4%B9%8B%E9%97%AD%E5%8C%85/01.jpg"></p><h2 id="闭包分析"><a href="#闭包分析" class="headerlink" title="闭包分析"></a>闭包分析</h2><p>上面已经说到了，<strong>当函数可以记住并访问函数所在的词法作用域</strong>，就产生了闭包。<br>看一段代码</p><figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> scope = <span class="string">&#x27;global scope&#x27;</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">checkScope</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> scope = <span class="string">&#x27;local scope&#x27;</span></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">foo</span> <span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> scope</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> foo;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> foo = checkScope();</span><br><span class="line">foo()</span><br></pre></td></tr></table></figure><p>这段代码是不是很熟悉？没错，这就是我们上一篇文章<a href="http://shchome.top/2019/05/02/JavaScript%E9%97%AD%E5%8C%85%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86--%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/">JavaScript闭包之执行上下文</a>最后着重分析的那段代码。但是仔细看看，这两段代码好像又有些不同，下面我们简要的分析下这段代码的执行过程。</p><ol><li>执行全局代码，创建全局执行上下文。</li></ol><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">g<span class="attr">lobalContext</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">VO:</span> &#123;</span><br><span class="line">        <span class="params">arguments:</span> &#123;</span><br><span class="line">            <span class="params">length:</span> <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="params">scope:</span> undefined，</span><br><span class="line">        <span class="params">foo:</span> undefined</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="params">Scope:</span> globalContext.VO,</span><br><span class="line">    <span class="params">this:</span> undefined</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>全局上下文压入执行上下文栈</li></ol><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> [</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="3"><li>全局执行上下文初始化</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">globalContext = &#123;</span><br><span class="line">    <span class="type">VO</span>: &#123;</span><br><span class="line">        arguments: &#123;</span><br><span class="line">            length: <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        scope: &#x27;global scope&#x27;,</span><br><span class="line">        checkScope: reference to function checkScope()<span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="type">Scope</span>: globalContext.<span class="type">VO</span>,</span><br><span class="line">    this: window</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>初始化的同时，checkScope函数被创建</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">checkScopeContext = &#123;</span><br><span class="line">    <span class="type">AO</span>: &#123;</span><br><span class="line">        arguments：&#123;</span><br><span class="line">            length: <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        scope: undefined,</span><br><span class="line">        foo: reference to function foo() <span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="type">Scope</span>: [<span class="type">AO</span>, globalContext.<span class="type">VO</span>],</span><br><span class="line">    this: undefined</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="5"><li>checkScope函数上下文被压入执行上下文栈</li></ol><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">ECStack</span> = [</span><br><span class="line">    checkScopeContext,</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="6"><li>checkScope执行上下文初始化</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">checkScopeContext = &#123;</span><br><span class="line">    <span class="type">AO</span>: &#123;</span><br><span class="line">        arguments：&#123;</span><br><span class="line">            length: <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        scope: &#x27;local scope,</span><br><span class="line">        foo: reference to function foo() <span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="type">Scope</span>: [<span class="type">AO</span>, globalContext.<span class="type">VO</span>],</span><br><span class="line">    this: window</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="7"><li>checkScope函数执行，返回foo函数的引用，checkScope从执行上下文栈中弹出</li></ol><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> [</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="8"><li>执行foo函数，创建foo函数执行上下文</li></ol><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">f<span class="attr">ooContext</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">AO:</span> &#123;</span><br><span class="line">        <span class="params">arguments:</span> &#123;</span><br><span class="line">            <span class="params">length:</span> <span class="number">0</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="params">Scope:</span> [AO, checkScopeContext.AO, globalContext.VO],</span><br><span class="line">    <span class="params">this:</span> undefined</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="9"><li>foo函数执行上下文被压入执行栈</li></ol><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">ECStack</span> = [</span><br><span class="line">    fooContext,</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="10"><li>foo函数执行上下文初始化</li></ol><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">f<span class="attr">ooContext</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">AO:</span> &#123;</span><br><span class="line">        <span class="params">arguments:</span> &#123;</span><br><span class="line">            <span class="params">length:</span> <span class="number">0</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="params">Scope:</span> [AO, checkScopeContext.AO, globalContext.VO],</span><br><span class="line">    <span class="params">this:</span> window</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="11"><li>执行foo函数，查找变量scope</li></ol><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-<span class="string">&#x27;scope&#x27;</span></span><br><span class="line">--- fooContext<span class="selector-class">.AO</span>  <span class="comment">// not found</span></span><br><span class="line">--- checkScope<span class="selector-class">.AO</span>  <span class="comment">// found -&gt; &#x27;local scope&#x27;</span></span><br></pre></td></tr></table></figure><ol start="12"><li>foo函数执行完毕，foo函数执行上下文从执行栈中弹出</li></ol><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> [</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>看到这儿，小伙伴们应该思考一个问题：<br>当 foo函数执行的时候，checkscope函数上下文已经从执行上下文栈中被弹出了，怎么还会读取到checkscope作用域下的 scope值呢？<br>其实原因很简单，foo函数执行上下文维护了一个作用域链</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">fooContext.Scope</span> = [AO, checkScopeContext.AO, globalContext.VO]</span><br></pre></td></tr></table></figure><p>所以即使checkScope函数执行完毕，但是JavaScript依然会让checkScope.AO存活在内存中，foo函数依旧可以通过作用域链找到它。<br>还记得我们上面对闭包的定义吗？</p><blockquote><p>当函数可以记住并访问所在的词法作用域时，就产生了闭包，即使函数是在当前词法作用域之外执行。</p></blockquote><p>我们再来看看foo函数，它是不是就记住了checkScope的作用域，因此上面的foo函数就是一个闭包。<br>闭包在计算机科学中也只是一个普通的概念，大家不要去想得太复杂。</p><h2 id="闭包经典例子"><a href="#闭包经典例子" class="headerlink" title="闭包经典例子"></a>闭包经典例子</h2><p>在闭包中，有一个很经典的例子:</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">for</span><span class="params">(var i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++)</span></span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="built_in">function</span>() &#123;</span><br><span class="line">        console<span class="selector-class">.log</span>(i)</span><br><span class="line">    &#125;, <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这段代码中，我们对其预期的输出是0 ~ 9，但真正的输出结果却是10次10，这是因为当setTimeout中的匿名函数执行时，循环已经结束，此时全局上下文的VO：</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">g<span class="attr">lobalContext.VO</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">arguments:</span> &#123;</span><br><span class="line">        <span class="params">length:</span> <span class="number">0</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="params">i:</span> <span class="number">10</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>究其原因：i是声明在全局作用域中的，定时器中的匿名函数也是执行在全局作用域中，那当然是每次都输出11了。<br>知道了原因，解决起来就简单多了，我们可以让i在每次循环的过程中都产生一个私有作用域</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">    (<span class="keyword">function</span> (<span class="params">i</span>) &#123;</span><br><span class="line">        setTimeout(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="built_in">console</span>.<span class="built_in">log</span>(i)</span><br><span class="line">        &#125;, <span class="number">1000</span>)</span><br><span class="line">    &#125;)(i)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样当setTimeout中的匿名函数执行时，全局上下文的VO:</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">g<span class="attr">lobalContext.VO</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">arguments:</span> &#123;</span><br><span class="line">        <span class="params">length:</span> <span class="number">0</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="params">i:</span> <span class="number">10</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和修改之前一样，完全没变化，但是此时setTimeout中的匿名函数的作用域链：</p><figure class="highlight coq"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">Scope</span> = [AO, 匿名函数<span class="keyword">Context</span>.AO, globalContext.VO]</span><br></pre></td></tr></table></figure><p>而匿名函数的AO:</p><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">匿名函数Context.AO = <span class="punctuation">&#123;</span></span><br><span class="line"><span class="symbol">    arguments:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="number">0</span>: <span class="number">0</span>,</span><br><span class="line"><span class="symbol">        length:</span> <span class="number">1</span></span><br><span class="line">    <span class="punctuation">&#125;</span>,</span><br><span class="line"><span class="symbol">    i:</span> <span class="number">0</span> <span class="comment">//每次作为参数传来的值，这里以第一次为例。 </span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>所以查找i的过程：</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-<span class="string">&#x27;i&#x27;</span></span><br><span class="line">--- 匿名函数<span class="built_in">Context</span>(setTimeout内部)  <span class="comment">// not found</span></span><br><span class="line">--- 匿名函数Context  <span class="comment">// found -&gt; 0</span></span><br></pre></td></tr></table></figure><h2 id="闭包的应用"><a href="#闭包的应用" class="headerlink" title="闭包的应用"></a>闭包的应用</h2><p>关于闭包的应用，小伙伴可以去看看这篇文章<a href="https://juejin.im/post/5cabde85e51d456e6e3891e0#heading-5">为了前端的深度-闭包概念与应用</a>。</p><h2 id="闭包的缺陷"><a href="#闭包的缺陷" class="headerlink" title="闭包的缺陷"></a>闭包的缺陷</h2><ul><li>闭包的缺点就是常驻内存会增大内存使用量，并且使用不当很容易造成内存泄露。</li><li>如果不是因为某些特殊任务而需要闭包，在没有必要的情况下，在其它函数中创建函数是不明智的，因为闭包对脚本性能具有负面影响，包括处理速度和内存消耗。</li></ul><hr><p>说到了这儿，闭包基本上就全部过了一遍，在这个过程中，我翻了无数的书和博客，收获很大。这是我第一次写系列文章，感觉写的不怎么好，但这也算是一次经验吧！相信以后一定能写出好的文章。<br><img src="https://user-gold-cdn.xitu.io/2019/4/8/169fc27e0fdf4d09?imageslim"></p><p>本文完</p><p>参考文章：<br><a href="https://juejin.im/post/5b081f8d6fb9a07a9b3664b6#heading-4">闭包详解一</a><br><a href="http://www.cnblogs.com/TomXu/archive/2012/01/31/2330252.html">深入理解JavaScript系列（16）：闭包（Closures）</a><br><a href="https://juejin.im/post/5cabde85e51d456e6e3891e0#heading-5">为了前端的深度-闭包概念与应用</a></p>]]>
    </content>
    <id>http://shchome.top/2026/04/02/JavaScript%E4%B9%8B%E9%97%AD%E5%8C%85/</id>
    <link href="http://shchome.top/2026/04/02/JavaScript%E4%B9%8B%E9%97%AD%E5%8C%85/"/>
    <published>2026-04-02T00:33:00.000Z</published>
    <summary>
      <![CDATA[<p>讲了好长时间的闭包前置知识，今天终于说到了闭包。正如前面文章中说到的，闭包涉及到的知识点很多，如果直接阅读本文，可能你会看不太懂，因此，为了更好地理解本文，建议你去看看前面几篇文章。</p>
<hr>
<h2 id="什么是闭包？"><a href="#什么是闭包？" cl]]>
    </summary>
    <title>JavaScript 闭包</title>
    <updated>2026-05-24T03:45:48.281Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="JavaScript" scheme="http://shchome.top/categories/JavaScript/"/>
    <category term="JavaScript" scheme="http://shchome.top/tags/JavaScript/"/>
    <content>
      <![CDATA[<p>在上篇<a href="http://shchome.top/2019/04/26/JavaScript%E9%97%AD%E5%8C%85%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86--%E6%8F%90%E5%8D%87/">《JavaScript闭包之提升》</a>我们提到过，JavaScript引擎是一段一段的分析和执行代码，那么这个’一段一段’是怎么划分的呢？</p><hr><h1 id="执行上下文"><a href="#执行上下文" class="headerlink" title="执行上下文"></a>执行上下文</h1><p>这就要说到JavaScript的执行上下文(execution context)了， 当JavaScript代码运行时，执行环境很重要，主要分为三种：</p><ul><li>全局代码 – 代码默认执行环境。</li><li>函数代码 – 函数内部执行环境。</li><li>eval代码 – eval内部的文本被执行时创建的执行环境。<br>前面我们也说过，JavaScript在执行一段代码前，会进行一系列的准备工作，而这里的‘准备工作’，就是创建执行上下文。</li></ul><h1 id="执行上下文栈"><a href="#执行上下文栈" class="headerlink" title="执行上下文栈"></a>执行上下文栈</h1><p>在JavaScript中，你可以有任意多个执行上下文，而且在JavaScript代码中，执行上下文的个数也会有很多，比如说每次调用一个函数，就会创建一个新的执行上下文，并且还会多一个私有作用域，这么多的执行上下文，又该怎么管理呢？<br>所以JavaScript引入了执行上下文栈(ECStack)来专门管理执行上下文。<br>现在我们来看看这样的一段代码：</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">let a = <span class="string">&#x27;Hello World!&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">first</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(<span class="string">&#x27;Inside first function&#x27;</span>);</span><br><span class="line">  <span class="built_in">second</span>();</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(<span class="string">&#x27;Again inside first function&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">second</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(<span class="string">&#x27;Inside second function&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">first</span>();</span><br><span class="line"><span class="built_in">console</span>.<span class="built_in">log</span>(<span class="string">&#x27;Inside Global Execution Context&#x27;</span>);</span><br></pre></td></tr></table></figure><p>为了模拟执行上下文栈的行为，让我们定义执行上下文栈是一个数组</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> []</span><br></pre></td></tr></table></figure><p>当JavaScript引擎开始解释执行代码时，首先它会创建一个全局的执行上下文并压入执行上下文栈，而且只有当整个代码执行完毕后，执行上下文栈才会被清空，所有执行上下文栈中总有一个全局上下文。</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> [</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>当函数被调用后，就会创建一个函数的执行上下文，并且压入执行上下文栈。</p><figure class="highlight autohotkey"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ECStack = [</span><br><span class="line"><span class="built_in">    secondContext,</span></span><br><span class="line"><span class="built_in">    firstContext,</span></span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>当函数执行完毕后，执行上下文从栈中弹出。</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> [</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>JavaScript继续执行其它的代码（如果有的话），但是在执行上下文栈中总有一个globalContext。<br>为了更加让人容易理解，这里附上执行上下文栈变化过程。<br><img src="https://user-gold-cdn.xitu.io/2018/9/20/165f539572076fe3?imageslim"></p><h1 id="怎么创建执行上下文"><a href="#怎么创建执行上下文" class="headerlink" title="怎么创建执行上下文"></a>怎么创建执行上下文</h1><p>到这里，我们已经知道JavaScript如何来管理执行上下文了，现在我们就来说说JavaScript引擎怎么来创建执行上下文的。<br>对于每个执行上下文，都有三个重要属性</p><ul><li>变量对象</li><li>作用域链</li><li>this<br>下面我们分别来说说这三个属性的创建。</li></ul><h2 id="变量对象"><a href="#变量对象" class="headerlink" title="变量对象"></a>变量对象</h2><p>变量对象存储了执行上下文中的变量和函数声明。</p><blockquote><p>变量对象(Variable Object，缩写为VO)是一个与执行上下文相关的特殊对象，它存储着在上下文中声明的以下内容：</p><ul><li>变量 (var, 变量声明);</li><li>函数声明 (FunctionDeclaration, 缩写为FD);</li><li>函数的形参</li></ul></blockquote><p>对于所有的执行上下文来说，变量对象的一些操作(如变量初始化)和行为都是共通的。下面我们来聊聊全局上下文中的变量对象和函数上下文中的变量对象。</p><h3 id="全局上下文"><a href="#全局上下文" class="headerlink" title="全局上下文"></a>全局上下文</h3><p>在讲全局上下文之前，我们先来了解一个概念–全局对象，在W3C中对全局上下文有这样的定义</p><blockquote><p>全局对象是预定义的对象，作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象，可以访问所有其他所有预定义的对象、函数和属性。<br>在顶层 JavaScript 代码中，可以用关键字 this 引用全局对象。因为全局对象是作用域链的头，这意味着所有非限定性的变量和函数名都会作为该对象的属性来查询。<br>例如，当JavaScript 代码引用 parseInt() 函数时，它引用的是全局对象的 parseInt 属性。全局对象是作用域链的头，还意味着在顶层 JavaScript 代码中声明的所有变量都将成为全局对象的属性。</p></blockquote><p>如果看不懂的话没关系，下面我们来仔细说说<br>全局对象(Global object) 是在进入任何执行上下文之前就已经创建了的对象；<br>这个对象只存在一份，它的属性在程序中任何地方都可以访问，全局对象的生命周期终止于程序退出那一刻。<br>全局对象初始在初始创建的时候将Math、String、Date等作为自身属性初始化，同时也可以创建额外创建其它对象作为属性。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">global</span> = &#123;</span><br><span class="line">    <span class="title class_">Math</span>,</span><br><span class="line">    <span class="title class_">String</span>,</span><br><span class="line">    <span class="title class_">Date</span>,</span><br><span class="line">    <span class="attr">a</span>: <span class="literal">undefined</span></span><br><span class="line">    <span class="attr">b</span>: reference to <span class="keyword">function</span> <span class="title function_">b</span>(<span class="params"></span>)&#123;&#125;,</span><br><span class="line">    ...</span><br><span class="line">    ...</span><br><span class="line">    <span class="attr">window</span>: <span class="variable language_">global</span> <span class="comment">//引用自身</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当访问全局对象属性时通常会省略前缀，这是因为全局对象时不能直接通过名称访问的，但是我们仍然可以通过this来访问全局对象，也可以递归引用自身，例如上面的window</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="property">a</span> = <span class="number">20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a)       <span class="comment">// 20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">a</span>)  <span class="comment">// 20</span></span><br><span class="line"><span class="title class_">String</span>(<span class="number">10</span>); <span class="comment">// 就是global.String(10);</span></span><br></pre></td></tr></table></figure><p>花了这么长时间介绍全局对象，其实就是想说：<br>全局上下文中的变量对象就是全局对象</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">VO(globalContext) <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> global<span class="comment">;</span></span><br></pre></td></tr></table></figure><h3 id="函数上下文"><a href="#函数上下文" class="headerlink" title="函数上下文"></a>函数上下文</h3><p>在函数上下文中，变量对象(VO)是不能直接访问的，我们用活动对象(Activation Object， 简称AO)来扮演VO的角色。</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">VO(functionContext) <span class="operator">=</span><span class="operator">=</span><span class="operator">=</span> AO<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>活动对象是在进入函数上下文之后被创建的，只有当进入到一个函数上下文中，这个执行上下文的变量对象才会被激活，所以叫做活动对象。<br>活动对象通过arguments属性初始化的，arguments属性的值是Arguments对象。</p><h3 id="执行顺序"><a href="#执行顺序" class="headerlink" title="执行顺序"></a>执行顺序</h3><p>之前我们说过，JavaScript执行代码会分为编译和运行两个阶段，执行上下文也是这样。执行上下文代码被分为</p><ol><li>进入执行上下文</li><li>执行代码</li></ol><h4 id="进入执行上下文"><a href="#进入执行上下文" class="headerlink" title="进入执行上下文"></a>进入执行上下文</h4><p>在进入执行上下文阶段，还没有执行代码，但是变量的定义和提升都是在这时候完成的。<br>此时VO中包含以下属性：</p><ul><li>函数所有的形参</li><li>函数声明</li><li>变量声明</li></ul><p>下面我们来看一个例子：</p><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">function foo(a, b) &#123;</span><br><span class="line">    <span class="keyword">var</span> c = <span class="number">3</span>;</span><br><span class="line">    function d() <span class="meta">&#123;...&#125;</span></span><br><span class="line">    <span class="keyword">var</span> e = function e() <span class="meta">&#123;...&#125;</span></span><br><span class="line">    ....</span><br><span class="line">&#125;</span><br><span class="line">foo(<span class="number">2</span>)</span><br></pre></td></tr></table></figure><p>在进入执行上下文后，这时候的AO可以表示为：</p><figure class="highlight nestedtext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">AO = &#123;</span></span><br><span class="line"><span class="attribute">    arguments</span><span class="punctuation">:</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attribute">0</span><span class="punctuation">:</span> <span class="string">2;</span></span><br><span class="line">        <span class="attribute">length</span><span class="punctuation">:</span> <span class="string">1</span></span><br><span class="line">    <span class="attribute">&#125;,</span></span><br><span class="line"><span class="attribute">    d</span><span class="punctuation">:</span> <span class="string">reference to function d()&#123;&#125;,</span></span><br><span class="line">    <span class="attribute">a</span><span class="punctuation">:</span> <span class="string">2,</span></span><br><span class="line">    <span class="attribute">b</span><span class="punctuation">:</span> <span class="string">undefined,</span></span><br><span class="line">    <span class="attribute">c</span><span class="punctuation">:</span> <span class="string">undefined,</span></span><br><span class="line">    <span class="attribute">e</span><span class="punctuation">:</span> <span class="string">undefined</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这之后，会进入处理执行上下文的第二阶段–执行代码。</p><h4 id="代码执行"><a href="#代码执行" class="headerlink" title="代码执行"></a>代码执行</h4><p>在代码执行阶段，会顺序执行代码，该赋值的赋值，该引用的应用，还是以上面的代码为例，当代码执行完：</p><figure class="highlight nestedtext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">AO = &#123;</span></span><br><span class="line"><span class="attribute">    arguments</span><span class="punctuation">:</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attribute">0</span><span class="punctuation">:</span> <span class="string">2,</span></span><br><span class="line">        <span class="attribute">length</span><span class="punctuation">:</span> <span class="string">1</span></span><br><span class="line">    <span class="attribute">&#125;,</span></span><br><span class="line"><span class="attribute">    d</span><span class="punctuation">:</span> <span class="string">reference to function d()&#123;&#125;,</span></span><br><span class="line">    <span class="attribute">a</span><span class="punctuation">:</span> <span class="string">2,</span></span><br><span class="line">    <span class="attribute">b</span><span class="punctuation">:</span> <span class="string">undefined,</span></span><br><span class="line">    <span class="attribute">c</span><span class="punctuation">:</span> <span class="string">3,</span></span><br><span class="line">    <span class="attribute">e</span><span class="punctuation">:</span> <span class="string">reference to function e()&#123;&#125;,</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到这里变量对象的创建过程就介绍完了。<br>注意： 不是使用var声明的变量不会存在于变量对象中，例如：</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">a</span> <span class="operator">=</span> <span class="number">10</span><span class="comment">;</span></span><br><span class="line">var b <span class="operator">=</span> <span class="number">20</span><span class="comment">;</span></span><br><span class="line">console.log(a)<span class="comment">;    // 10</span></span><br><span class="line">console.log(b)<span class="comment">;    // 20</span></span><br></pre></td></tr></table></figure><p>上面例子很容易让人产生误解，虽然同样是声明一个变量，然后给变量赋值，但是此时的变量对象表示为：</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">V<span class="attr">O</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">b:</span> undefined</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是因为变量对象是在进入执行上下文后初始化的，这个阶段JavaScript引擎会编译代码，提升函数声明和变量声明，此时不存在变量a，变量a是在代码执行时声明并且赋值的。所以变量对象中不会存在a。</p><h2 id="作用域链-Scope-Chain"><a href="#作用域链-Scope-Chain" class="headerlink" title="作用域链(Scope Chain)"></a>作用域链(Scope Chain)</h2><p>在JavaScript中，函数对象和其它对象一样，拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性，在这些内部属性中，其中有一个是[[scope]]，由 ECMA-262 标准第三版定义，该内部属性包含了函数被创建的作用域中变量对象的集合，这个集合被称为函数的作用域链，它决定了哪些数据能被函数访问。<br>下面我们来仔细说说作用域链的初始化过程，这与函数的生命周期有关</p><h3 id="函数的生命周期"><a href="#函数的生命周期" class="headerlink" title="函数的生命周期"></a>函数的生命周期</h3><p>函数的声明周期分为创建和激活两个阶段</p><h4 id="函数创建"><a href="#函数创建" class="headerlink" title="函数创建"></a>函数创建</h4><p>上面作用域链的定义中指出，作用域链就是所有变量对象的集合。之前我们也说过，函数的作用域是在函数定义的时候就决定的，所以在函数创建的时候作用域链就已经存在了。<br>举个栗子：</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">bar</span>(<span class="params"></span>) &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">bar()</span><br></pre></td></tr></table></figure><p>函数创建时，各自的[[scope]]为：</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">bar.<span class="string">[[scope]]</span> = &#123;</span><br><span class="line">    globalContext.VO</span><br><span class="line">&#125;</span><br><span class="line">foo.<span class="string">[[scope]]</span> = &#123;</span><br><span class="line">    barContext.AO,</span><br><span class="line">    globalContext.VO</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="函数激活"><a href="#函数激活" class="headerlink" title="函数激活"></a>函数激活</h4><p>进入上下文创建VO&#x2F;AO之后，就会将活动对象添加到作用域链的前端。</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Scope = [AO].<span class="built_in">concat</span>(<span class="string">[[Scope]]</span>);</span><br></pre></td></tr></table></figure><p>最里层的变量对象总是在最前面，这样就解释了在查找变量时从里到外的过程。<br>我们用一个稍微复杂的例子描述作用域链变化过程：</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> b = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params">c</span>) &#123;</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">bar</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">var</span> d = <span class="number">30</span>;</span><br><span class="line">        <span class="built_in">console</span>.<span class="built_in">log</span>(a+b+c+d)</span><br><span class="line">    &#125;</span><br><span class="line">    bar()</span><br><span class="line">&#125;</span><br><span class="line">foo(<span class="number">6</span>)</span><br></pre></td></tr></table></figure><p>执行过程如下：  </p><ol><li>全局上下文变量对象:</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">globalContext.<span class="type">VO</span> = &#123;</span><br><span class="line">    a: <span class="number">10</span>,</span><br><span class="line">    b: <span class="number">20</span>,</span><br><span class="line">    foo: reference to function foo() <span class="meta">&#123;...&#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>执行foo函数，创建foo函数执行上下文，foo函数执行上下文被压入执行上下文栈</li></ol><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">ECStack</span> = [</span><br><span class="line">    globalContext,</span><br><span class="line">    fooContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="3"><li>foo函数不会立即执行，需要做一系列准备工作，首先利用<br>[[scope]]属性创建作用域链</li></ol><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">fooContext = &#123;</span><br><span class="line">    Scope: foo.<span class="string">[[scope]]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>用arguments对象创建活动对象，随后初始化活动对象</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fooContext = &#123;</span><br><span class="line">    <span class="type">AO</span>: &#123;</span><br><span class="line">        arguments: &#123;</span><br><span class="line">            <span class="number">0</span>: <span class="number">6</span>,</span><br><span class="line">            length: <span class="number">1</span></span><br><span class="line">        &#125;,</span><br><span class="line">        bar: reference to function bar() <span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="5"><li>将活动对象压入foo作用域链顶端</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">fooContext = &#123;</span><br><span class="line">    <span class="type">AO</span>: &#123;</span><br><span class="line">        arguments: &#123;</span><br><span class="line">            <span class="number">0</span>: <span class="number">6</span>,</span><br><span class="line">            length: <span class="number">1</span></span><br><span class="line">        &#125;,</span><br><span class="line">        bar: reference to function bar() <span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">Scope</span>: [<span class="type">AO</span>, globalContext.<span class="type">VO</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="6"><li>执行bar函数，创建bar函数执行上下文，将bar函数执行上下文压入执行上下文栈。</li></ol><figure class="highlight autohotkey"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ECStack = [</span><br><span class="line"><span class="built_in">    barContext,</span></span><br><span class="line"><span class="built_in">    fooContext,</span></span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="7"><li>bar函数执行上下文初始化，步骤和初始化foo函数执行上下文一样：  <ol><li>复制[[scope]]属性创建作用域链</li><li>利用arguments创建活动对象</li><li>初始化活动对象</li><li>将活动对象压入作用域链顶端</li></ol></li></ol><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">b<span class="attr">arContext</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">AO:</span> &#123;</span><br><span class="line">        <span class="params">arguments:</span> &#123;</span><br><span class="line">            <span class="params">length:</span> <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="params">d:</span> undefined</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="params">Scope:</span> [AO, fooContext.AO, globalContext.VO]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="8"><li>函数开始执行，完成变量的赋值，并沿着作用域查找所需变量。</li></ol><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">-<span class="string">&#x27;a&#x27;</span></span><br><span class="line">--- barContext<span class="selector-class">.AO</span>  <span class="comment">// not found</span></span><br><span class="line">--- fooContext<span class="selector-class">.AO</span>  <span class="comment">// not found</span></span><br><span class="line">--- globalContext<span class="selector-class">.VO</span> <span class="comment">// found -&gt; 10</span></span><br><span class="line"></span><br><span class="line">-<span class="string">&#x27;b&#x27;</span></span><br><span class="line">--- barContext<span class="selector-class">.AO</span>  <span class="comment">// not found</span></span><br><span class="line">--- fooContext<span class="selector-class">.AO</span>  <span class="comment">// not found</span></span><br><span class="line">--- globalContext<span class="selector-class">.VO</span> <span class="comment">// found -&gt; 20 </span></span><br><span class="line"></span><br><span class="line">-<span class="string">&#x27;c&#x27;</span></span><br><span class="line">--- barContext<span class="selector-class">.AO</span>  <span class="comment">// not found</span></span><br><span class="line">--- fooContext<span class="selector-class">.AO</span>  <span class="comment">// found -&gt; 6</span></span><br><span class="line"></span><br><span class="line">-<span class="string">&#x27;d&#x27;</span></span><br><span class="line">--- barContext<span class="selector-class">.AO</span>  <span class="comment">//found -&gt; 30</span></span><br></pre></td></tr></table></figure><ol start="9"><li>函数执行完毕，函数执行上下文依次从执行上下文栈中弹出。</li></ol><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> [</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>以上就是作用域链的创建和初始化过程，需要重点理解，不过在闭包中我们也会重点讲解，理解了作用域链闭包就容易多了。</p><h2 id="this"><a href="#this" class="headerlink" title="this"></a>this</h2><p>关于this，可以参考之前的博客<a href="http://shchome.top/2018/12/25/%E7%90%86%E8%A7%A3JavaScript%E4%B8%AD%E7%9A%84this%E3%80%81call%E3%80%81bind%E3%80%81new/">《理解JavaScript中的this、call、apply、bind》</a>，由于篇幅有限，这里就不做累述了。</p><h1 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h1><p>说完了执行上下文，我们再分析一段代码加深理解：</p><figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> scope = <span class="string">&#x27;global scope&#x27;</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">checkScope</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> scope = <span class="string">&#x27;local scope&#x27;</span></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">foo</span> <span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> scope</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> foo();</span><br><span class="line">&#125;</span><br><span class="line">checkScope()</span><br></pre></td></tr></table></figure><p>以上代码来自《JavaScript权威指南》，借用这个例子我们来分析它的执行过程</p><ol><li>执行全局代码，创建全局执行上下文。</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">globalContext = &#123;</span><br><span class="line">    <span class="type">VO</span>: &#123;</span><br><span class="line">        arguments: &#123;</span><br><span class="line">            length: <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        scope: undefined，</span><br><span class="line">        checkScope: reference to function checkScope()<span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="type">Scope</span>: globalContext.<span class="type">VO</span>,</span><br><span class="line">    this: undefined</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>全局上下文压入执行上下文栈</li></ol><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> [</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="3"><li>全局上下文初始化</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">globalContext = &#123;</span><br><span class="line">    <span class="type">VO</span>: &#123;</span><br><span class="line">        arguments: &#123;</span><br><span class="line">            length: <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        scope: &#x27;global scope&#x27;,</span><br><span class="line">        checkScope: reference to function checkScope()<span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="type">Scope</span>: globalContext.<span class="type">VO</span>,</span><br><span class="line">    this: window</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>初始化的同时，checkScope函数上下文被创建</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">checkScopeContext = &#123;</span><br><span class="line">    <span class="type">AO</span>: &#123;</span><br><span class="line">        arguments：&#123;</span><br><span class="line">            length: <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        scope: undefined,</span><br><span class="line">        foo: reference to function foo() <span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="type">Scope</span>: [<span class="type">AO</span>, globalContext.<span class="type">VO</span>],</span><br><span class="line">    this: undefined</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="5"><li>checkScope函数执行上下文被压入执行上下文栈</li></ol><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">ECStack</span> = [</span><br><span class="line">    checkScopeContext,</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="6"><li>checkScope执行上下文初始化</li></ol><figure class="highlight nim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">checkScopeContext = &#123;</span><br><span class="line">    <span class="type">AO</span>: &#123;</span><br><span class="line">        arguments：&#123;</span><br><span class="line">            length: <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        scope: &#x27;local scope,</span><br><span class="line">        foo: reference to function foo() <span class="meta">&#123;...&#125;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="type">Scope</span>: [<span class="type">AO</span>, globalContext.<span class="type">VO</span>],</span><br><span class="line">    this: window</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="7"><li>checkScope函数上下文初始化的同时，foo函数执行上下文被创建</li></ol><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">f<span class="attr">ooContext</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">AO:</span> &#123;</span><br><span class="line">        <span class="params">arguments:</span> &#123;</span><br><span class="line">            <span class="params">length:</span> <span class="number">0</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="params">Scope:</span> [AO, checkScopeContext.AO, globalContext.VO],</span><br><span class="line">    <span class="params">this:</span> undefined</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="8"><li>foo函数执行上下文被压入执行栈</li></ol><figure class="highlight autohotkey"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ECStack = [</span><br><span class="line"><span class="built_in">    fooContext,</span></span><br><span class="line"><span class="built_in">    checkScopeContext,</span></span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="9"><li>foo函数执行上下文初始化</li></ol><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">f<span class="attr">ooContext</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="params">AO:</span> &#123;</span><br><span class="line">        <span class="params">arguments:</span> &#123;</span><br><span class="line">            <span class="params">length:</span> <span class="number">0</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="params">Scope:</span> [AO, checkScopeContext.AO, globalContext.VO],</span><br><span class="line">    <span class="params">this:</span> checkScope</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="10"><li>执行foo函数，查找变量scope</li></ol><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-<span class="string">&#x27;scope&#x27;</span></span><br><span class="line">--- fooContext<span class="selector-class">.AO</span>  <span class="comment">// not found</span></span><br><span class="line">--- checkScope<span class="selector-class">.AO</span>  <span class="comment">// found -&gt; &#x27;local scope&#x27;</span></span><br></pre></td></tr></table></figure><ol start="11"><li>foo函数执行完毕，函数执行上下文依次从执行栈中弹出</li></ol><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">ECStack</span> <span class="operator">=</span> [</span><br><span class="line">    globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>以上分析过程是这一系列文章的重中之重，希望小伙伴们一定要弄懂。</p><hr><p>本篇文章主要谈了下函数中一个非常重要的概念-执行上下文，由执行上下文又延伸到了执行上下文的管理，以及执行上下文的创建。下篇文章我们就来说说闭包，拿下这个‘巨无霸’。</p><p>本文完</p><p>参考文章：<br><a href="https://www.cnblogs.com/TomXu/archive/2012/01/16/2309728.html#4213450">深入理解JavaScript系列（12）：变量对象（Variable Object）</a><br><a href="https://www.cnblogs.com/TomXu/archive/2012/01/18/2312463.html#4209090">深入理解JavaScript系列（14）：作用域链(Scope Chain)</a></p>]]>
    </content>
    <id>http://shchome.top/2026/03/25/JavaScript%E9%97%AD%E5%8C%85%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86--%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/</id>
    <link href="http://shchome.top/2026/03/25/JavaScript%E9%97%AD%E5%8C%85%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86--%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/"/>
    <published>2026-03-25T14:08:00.000Z</published>
    <summary>
      <![CDATA[<p>在上篇<a href="http://shchome.top/2019/04/26/JavaScript%E9%97%AD%E5%8C%85%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86--%E6%8F%90%E5%8D%87/">《JavaScr]]>
    </summary>
    <title>JavaScript闭包之执行上下文</title>
    <updated>2026-05-24T03:45:48.281Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="JavaScript" scheme="http://shchome.top/categories/JavaScript/"/>
    <category term="JavaScript" scheme="http://shchome.top/tags/JavaScript/"/>
    <content>
      <![CDATA[<p>接着上一篇文章<a href="http://shchome.top/2019/04/19/%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/">JavaScript闭包之作用域和词法作用域</a>，今天我们继续来说说闭包的前置知识–提升。</p><hr><p>在上一篇文章<a href="http://shchome.top/2019/04/19/%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/">《JavaScript闭包之作用域和词法作用域》</a>的最后，我们有这样的一个例子</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(a);    <span class="comment">// 1</span></span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(b);     <span class="comment">// undefined</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line">foo();</span><br><span class="line"><span class="keyword">var</span>  b = <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>当时只是简单提了提升，今天我们就来看看提升</p><h2 id="顺序执行？"><a href="#顺序执行？" class="headerlink" title="顺序执行？"></a>顺序执行？</h2><p>相信大部分人在写JavaScript时，都存在一个潜意识–JavaScript代码时一行一行的，毕竟：</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">bar</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="built_in">console</span>.<span class="built_in">log</span>(a)</span><br><span class="line">&#125;</span><br><span class="line">bar()</span><br></pre></td></tr></table></figure><p>但实际上说JavaScript代码时顺序执行的并不完全正确，我们来看看一些特殊情况</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">a</span> <span class="operator">=</span> <span class="number">2</span><span class="comment">;</span></span><br><span class="line">var a<span class="comment">;</span></span><br><span class="line">console.log(a)</span><br></pre></td></tr></table></figure><p>你认为此时console.log(a)会输出什么？<br>很多开发者会认为此时输出undefined， 因为在第二步重新声明了变量a， 但是并没有给a赋值，所以a被赋值了默认值undefined。但是，真正的输出还是2。<br>我们再来看看另外一段代码</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.<span class="built_in">log</span>(a)</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span></span><br></pre></td></tr></table></figure><p>受上一个例子的影响，很容易就认为和上一个例子出现同样的行为，结果还是输出2， 或者还有部分人会认为因为a没有提前定义，所以输出RefenerceError异常，但是这两种主观意识都是错误的，此时真正输出的是undefined。<br>为什么会出现这样的情况呢？编译器疯了吧！！！</p><h2 id="变量提升"><a href="#变量提升" class="headerlink" title="变量提升"></a>变量提升</h2><p>刷过面试题的小伙伴一定知道，JavaScript代码存在一个预编译的过程，在这个过程中，在这个过程中，编译器会一段一段的分析和执行，而不是所谓的一行一行的。在上一篇文章中，我们提到LHS和RHS这两个概念时说到，var a &#x3D; 2这个过程有一次RHS和一次LHS(不理解为什么的童靴可以看看上篇博客<a href="http://shchome.top/2019/04/19/%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/">JavaScript闭包之作用域和词法作用域</a>)，<br>RHS是查找a的值，LHS是将2赋值给查找到的变量a。从这儿我们就可以看出来，var a &#x3D; 2;分成了两部分执行，var a和a &#x3D; 2。第一个定义声明是在编译时完成的，第二个赋值声明是在执行时完成的。<br>所以上面的例子就可以改写为：</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">var a<span class="comment">;</span></span><br><span class="line"><span class="attribute">a</span> <span class="operator">=</span> <span class="number">2</span><span class="comment">;</span></span><br><span class="line">console.log(a)</span><br></pre></td></tr></table></figure><p>这个过程就像是将声明移动了作用域的最上面。这个过程就叫做提升。<br>注意：变量的提升是在编译阶段完成的。代码执行到赋值语句时才会给声明的变量赋值。</p><h2 id="函数提升"><a href="#函数提升" class="headerlink" title="函数提升"></a>函数提升</h2><p>在JavaScript中，不仅仅是变量会提升，函数在编译阶段也会提升</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">foo</span>();    <span class="comment">// 1</span></span><br><span class="line"><span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面代码中，foo函数声明就被提升了，所以在第一行调用就可以正常执行。</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">foo();      <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(a)</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>了解了变量提升，我们可以很容易的知道函数输出undefined。在JavaScript编译的过程中，foo函数的声明被提升了，同时提升的还有foo函数的作用域，并且作用域中也发生了变量提升。所以我们可以看成这种形式</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">funcation foo() &#123;</span><br><span class="line">  var a<span class="comment">;</span></span><br><span class="line">  console.log(a)<span class="comment">;</span></span><br><span class="line">  a <span class="operator">=</span> <span class="number">2</span><span class="comment">;</span></span><br><span class="line">&#125;<span class="comment">;</span></span><br><span class="line">foo()<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>但是函数的提升仅限于函数声明，函数表达式并不会被提升</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">bar()  <span class="comment">// TypeError</span></span><br><span class="line"><span class="keyword">var</span> bar = <span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意这里是TypeError, 而不是ReferenceError。在编译阶段，foo被声明并且提升了，但是并没有赋值，只有运行时才会被赋值foo函数声明，所以此时bar还是undefined，foo()对undefined进行函数调用从而导致非法操作，所以最后抛出TypeError。</p><h2 id="谁先谁后"><a href="#谁先谁后" class="headerlink" title="谁先谁后"></a>谁先谁后</h2><p>说完了变量提升和函数提升之后，你可能会有个疑问，函数提升和变量提升谁先发生。<br>下面我们先来看一段代码</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">foo();  <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> foo;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">foo = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(<span class="number">2</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意：尽管var foo出现在function foo() {…} 之前，但它是个重复声明，在这儿会被忽略。所以在编译阶段，先声明的是函数，然后才是变量。<br>在变量声明时，有这样的规则：变量提升时，如果存在同名的变量，则直接覆盖吗，如果存在同名的函数声明，则忽略变量声明。</p><h2 id="为什么有提升？"><a href="#为什么有提升？" class="headerlink" title="为什么有提升？"></a>为什么有提升？</h2><p>世界上没有无缘无故的事情，JavaScript也是一样，提升在JavaScript中意义重大，下面我们来看看这样的一个例子</p><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">function foo () &#123;</span><br><span class="line">  <span class="built_in">bar</span>()</span><br><span class="line">&#125;;</span><br><span class="line">bar ();</span><br><span class="line">function bar () &#123;</span><br><span class="line">  <span class="built_in">foo</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码只是一个简单的循环引用的例子，试想一下，如果没有提升，上面的代码肯定不能正常执行，但是有了变量提升，循环引用也可以实现了。</p><hr><p>本文介绍了JavaScript提升的概念，不是很难，但是很重要。接下来重头戏到了，欢迎关注。<br>本文完</p>]]>
    </content>
    <id>http://shchome.top/2026/03/17/JavaScript%E9%97%AD%E5%8C%85%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86--%E6%8F%90%E5%8D%87/</id>
    <link href="http://shchome.top/2026/03/17/JavaScript%E9%97%AD%E5%8C%85%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86--%E6%8F%90%E5%8D%87/"/>
    <published>2026-03-17T01:41:00.000Z</published>
    <summary>
      <![CDATA[<p>接着上一篇文章<a href="http://shchome.top/2019/04/19/%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/">JavaScr]]>
    </summary>
    <title>JavaScript闭包之提升</title>
    <updated>2026-05-24T03:45:48.281Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="JavaScript" scheme="http://shchome.top/categories/JavaScript/"/>
    <category term="JavaScript" scheme="http://shchome.top/tags/JavaScript/"/>
    <content>
      <![CDATA[<p>接着上一篇文章<a href="http://shchome.top/2019/04/12/%E8%87%AA%E6%89%A7%E8%A1%8C%E5%87%BD%E6%95%B0%E5%92%8C%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0/">JavaScript闭包之自执行函数和匿名函数</a>，今天我们继续来说说闭包的前置知识–作用域和词法作用域。</p><hr><h2 id="作用域"><a href="#作用域" class="headerlink" title="作用域"></a>作用域</h2><p>作用域是编程语言中的一个重要概念，目前大部分编程语言都有作用域这个概念。JavaScript当然也不例外。那么什么是作用域呢？<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/JavaScript%E9%97%AD%E5%8C%85%E4%B9%8B%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/1.jpg"><br>下面我们就先来说说JavaScript的作用域<br>简单的来说，<strong>作用域就是定义的一组用来查找变量的规则</strong>，那么JavaScript是如何来查找变量的呢？<br>我们先来看个例子：</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(a)  <span class="comment">// 1</span></span><br><span class="line">&#125;</span><br><span class="line">foo()</span><br></pre></td></tr></table></figure><p>在执行foo()的时候，JavaScript引擎去查找定义a的地方，诶，在上方就是的，于是不继续找了直接打印。<br>再来看一个例子：</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(a)  <span class="comment">// 1</span></span><br><span class="line">&#125;</span><br><span class="line">foo()</span><br></pre></td></tr></table></figure><p>在执行foo()的时候，JavaScript引擎试图在函数内部查找a的值，但是遗憾的是没有找到，JavaScript引擎这个时候就很聪明了，在里面找不到我就不能去外面找嘛，诶，还真让我找到了，在函数外面的第一层里面就有一个a值，找到了我就不继续找了，直接去打印吧。<br>注意上面的两段代码，它们之间一个共同点就是查找变量，第一段代码在函数内部直接就查找到了，第二段代码是在函数外部查找到的。<br>把上面这段话正式一点的说就是：第一段代码是在<strong>函数作用域</strong>查找到了变量a，第二段代码是在<strong>全局作用域</strong>查找到了变量a。所以作用域就是查找变量的地方。<br>至于第二段代码查找变量的过程，有一个由里向外的过程，好像是顺着一条链条从下到上查找，这条链条我们就称之为作用域链，关于作用域链我们后面会详细讲解。  </p><h2 id="静态作用域和动态作用域"><a href="#静态作用域和动态作用域" class="headerlink" title="静态作用域和动态作用域"></a>静态作用域和动态作用域</h2><p>先来看看维基百科给静态作用域和动态作用域的定义：</p><blockquote><p>静态作用域又叫做词法作用域，采用词法作用域的变量叫词法变量。函数的作用域在函数定义的时候就决定了。<br>采用动态作用域的变量叫做动态变量。函数的作用域是在函数调用的时候才决定的。  </p></blockquote><p>让我们来看个例子就能明白它们两个的区别了。</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> value = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(value)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">bar</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> value = <span class="number">2</span>;</span><br><span class="line">  foo()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">bar()</span><br></pre></td></tr></table></figure><p>我们用假设法来分析一下这个函数的执行，假设JavaScript采用的是静态作用域，那么函数的作用域在函数定义的时候就已经确定了，当执行foo()函数的时候，先在foo()函数内部查找是否有变量value，如果没有就到全局作用域中查找，那么此时打印出来的就应该是1。<br>而如果JavaScript采用的是动态作用域，函数的作用域是在函数调用的时候决定的，那么当执行函数foo()的时候，先从foo()函数内部查找是否有变量value，如果没有就去函数调用的上一级作用域中查找，一级级的查找，直到全局作用域，此时函数就应该打印2。<br>让我们执行函数<br><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/JavaScript%E9%97%AD%E5%8C%85%E4%B9%8B%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/ScreenShot00002.png"><br>通过函数的执行结果，和我们分析的第一段结果一样，所以JavaScript采用的是静态作用域，也就是词法作用域。</p><h2 id="变量的查找类型"><a href="#变量的查找类型" class="headerlink" title="变量的查找类型"></a>变量的查找类型</h2><p>在JavaScript中，变量的查找有两种类型–LHS和RHS。<br>LHS，RHS 这两个术语就是出现在引擎对变量进行查询的时候。在《你不知道的Javascript(上)》也有很清楚的描述。在这里，我想引用freecodecamp 上面的回答来解释：</p><blockquote><p>LHS &#x3D; 变量赋值或写入内存。想象为将文本文件保存到硬盘中。<br>RHS &#x3D; 变量查找或从内存中读取。想象为从硬盘打开文本文件。  </p></blockquote><p><strong>两者的特性：</strong></p><ul><li>都会在所有作用域中查询</li><li>严格模式下，找不到所需的变量时，引擎都会抛出ReferenceError异常。</li><li>非严格模式下，LHR稍微比较特殊: 会自动创建一个全局变量。</li><li>查询成功时，如果对变量的值进行不合理的操作，比如：对一个非函数类型的值进行函数调用，引擎会抛出TypeError异常。</li></ul><p>我们来看一个简单的例子：</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(a);    <span class="comment">// 1</span></span><br><span class="line">  <span class="built_in">console</span>.<span class="built_in">log</span>(b);     <span class="comment">// undefined</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line">foo();</span><br><span class="line"><span class="keyword">var</span>  b = <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>这段代码在预编译的时候存在一个变量提升的过程，这个以后会详细讲解，这里并不妨碍我们理解LHS和RHS。<br>预编译后：</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">var a<span class="comment">;</span></span><br><span class="line">var b<span class="comment">;</span></span><br><span class="line">function foo () &#123;</span><br><span class="line">  console.log(a)<span class="comment">;    // 1</span></span><br><span class="line">  console.log(b)<span class="comment">;     // undefined</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="attribute">a</span> <span class="operator">=</span> <span class="number">1</span><span class="comment">;</span></span><br><span class="line">foo()<span class="comment">;</span></span><br><span class="line"><span class="attribute">b</span> <span class="operator">=</span> <span class="number">1</span><span class="comment">;</span></span><br></pre></td></tr></table></figure><p>我们直接来看查找过程，在查找变量a和变量b时用的是RHS(读取内存)，而查找a的时候a已经被赋值a &#x3D; 1，这步操作也就是LHS(写入内存)，而b此时只是被定义了还没有被赋值，所以查找到的只是undefined。  </p><hr><p>本文介绍了JavaScript的词法作用域以及两种变量查找方式，下一篇文章我们将重点介绍下JavaScript中的提升。  </p><p>本文完</p>]]>
    </content>
    <id>http://shchome.top/2026/03/09/%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/</id>
    <link href="http://shchome.top/2026/03/09/%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/"/>
    <published>2026-03-09T11:13:00.000Z</published>
    <summary>
      <![CDATA[<p>接着上一篇文章<a href="http://shchome.top/2019/04/12/%E8%87%AA%E6%89%A7%E8%A1%8C%E5%87%BD%E6%95%B0%E5%92%8C%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0/]]>
    </summary>
    <title>JavaScript闭包之作用域和词法作用域</title>
    <updated>2026-05-24T03:45:48.281Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="框架" scheme="http://shchome.top/categories/%E6%A1%86%E6%9E%B6/"/>
    <category term="Vue" scheme="http://shchome.top/tags/Vue/"/>
    <content>
      <![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>最近又开始了一个新的项目，最近一直都在做项目的前期准备工作，借着这样的机会，我们来唠嗑唠嗑前端项目从0到1过程。今天我就先来说说前端动态路由和鉴权这块。大神勿喷。</p><hr><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>第一次看到动态路由是在研究<a href="https://github.com/PanJiaChen/vue-element-admin">vue-element-admin</a>代码的时候。之前我接触的系统都是后台返回给前端一个路由表，然后前端直接渲染在页面上就可以了，但是看到了大佬的代码后，感觉这种思路很好，毕竟现在前后端的分离了，路由和权限这些信息应该由前端来处理。所以我第一次尝试了前端动态鉴权。虽然逻辑有点复杂，但是最终我还是实现了。</p><h2 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h2><ol><li>每次路由跳转都判断用户是否登录，登录了才会进行后续操作，否则直接跳到登录页面</li><li>浏览器中已经保存了用户登录信息，但是请求进入的页面是登录页面，直接进入到首页，实现免登录的功能</li><li>判断vuex中是否存在路由配置信息，如果存在直接放行，否则进行下一步操作。</li><li>如果vuex中不存在路由配置信息，就需要去后台请求当前用户对应角色的权限信息，根据权限信息生成可访问的路由表，并存储在vuex中。</li><li>合并路由，将初始路由和生成的动态路由进行拼接。</li></ol><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><ol><li>在router.js中定义两份路由，一份是初始路由，另一份是系统功能路由，需要动态加载<br>基础路由：</li></ol><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 基础路由</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> const constantRouterMap = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&#x27;/login&#x27;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;login&#x27;</span>,</span><br><span class="line">    <span class="attr">hidden</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">component</span>: <span class="function"><span class="params">()</span> =&gt;</span> <span class="keyword">import</span>(<span class="string">&#x27;@/pages/login/Login&#x27;</span>)</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&#x27;/404&#x27;</span>,</span><br><span class="line">    <span class="attr">component</span>: <span class="function"><span class="params">()</span> =&gt;</span> <span class="keyword">import</span>(<span class="string">&#x27;@/pages/exception/404&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">];</span><br></pre></td></tr></table></figure><p>系统功能路由(截取部分)：</p><figure class="highlight xl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 系统功能路由，动态加载</span></span><br><span class="line"><span class="comment"> * 需要根据实际情况自己配置</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">export const ansycRouterMap = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="built_in">path</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">    <span class="keyword">name</span>: <span class="string">&#x27;index&#x27;</span>,</span><br><span class="line">    component: MenuView,</span><br><span class="line">    meta: &#123; <span class="built_in">title</span>: <span class="string">&#x27;首页&#x27;</span> &#125;,</span><br><span class="line">    redirect: <span class="string">&#x27;/service/servicePage&#x27;</span>,</span><br><span class="line">    children: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="built_in">path</span>: <span class="string">&#x27;/service/servicePage&#x27;</span>,</span><br><span class="line">        <span class="keyword">name</span>: <span class="string">&#x27;servicePage&#x27;</span>,</span><br><span class="line">        component: () =&gt; <span class="keyword">import</span>(&#x27;@/pages/service/ServicePage&#x27;),</span><br><span class="line">        meta: &#123;</span><br><span class="line">          <span class="built_in">title</span>: <span class="string">&#x27;首页&#x27;</span>,</span><br><span class="line">          icon: <span class="string">&#x27;dashboard&#x27;</span>,</span><br><span class="line">          keepAlive: <span class="literal">true</span>,</span><br><span class="line">          permission: [<span class="string">&#x27;SERVICE_CATALOG&#x27;</span>]</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">            <span class="comment">// 变更</span></span><br><span class="line">      &#123;</span><br><span class="line">        <span class="built_in">path</span>: <span class="string">&#x27;/change&#x27;</span>,</span><br><span class="line">        <span class="keyword">name</span>: <span class="string">&#x27;change&#x27;</span>,</span><br><span class="line">        component: RouterView,</span><br><span class="line">        redirect: <span class="string">&#x27;/change/changeList&#x27;</span>,</span><br><span class="line">        meta: &#123;</span><br><span class="line">          <span class="built_in">title</span>: <span class="string">&#x27;变更管理&#x27;</span>,</span><br><span class="line">          icon: <span class="string">&#x27;credit-card&#x27;</span>,</span><br><span class="line">          permission: [<span class="string">&#x27;MOD_MANAGE&#x27;</span>]</span><br><span class="line">        &#125;,</span><br><span class="line">        children: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="built_in">path</span>: <span class="string">&#x27;/change/changeList&#x27;</span>,</span><br><span class="line">            <span class="keyword">name</span>: <span class="string">&#x27;changeList&#x27;</span>,</span><br><span class="line">            component: () =&gt; <span class="keyword">import</span>(&#x27;@/pages/modify/ModifyList&#x27;),</span><br><span class="line">            meta: &#123;</span><br><span class="line">              <span class="built_in">title</span>: <span class="string">&#x27;变更列表&#x27;</span>,</span><br><span class="line">              keepAlive: <span class="literal">true</span>,</span><br><span class="line">              permission: [<span class="string">&#x27;MOD_LIST&#x27;</span>]</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="built_in">path</span>: <span class="string">&#x27;/change/addChange&#x27;</span>,</span><br><span class="line">            <span class="keyword">name</span>: <span class="string">&#x27;addChange&#x27;</span>,</span><br><span class="line">            hidden: <span class="literal">true</span>,</span><br><span class="line">            component: () =&gt; <span class="keyword">import</span>(&#x27;@/pages/modify/CreateChange&#x27;),</span><br><span class="line">            meta: &#123; <span class="built_in">title</span>: <span class="string">&#x27;新增变更&#x27;</span>, permission: [<span class="string">&#x27;MOD_LIST&#x27;</span>] &#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="built_in">path</span>: <span class="string">&#x27;/change/changeInfo&#x27;</span>,</span><br><span class="line">            <span class="keyword">name</span>: <span class="string">&#x27;changeInfo&#x27;</span>,</span><br><span class="line">            hidden: <span class="literal">true</span>,</span><br><span class="line">            component: () =&gt; <span class="keyword">import</span>(&#x27;@/pages/modify/ChangeInfo&#x27;),</span><br><span class="line">            meta: &#123; <span class="built_in">title</span>: <span class="string">&#x27;变更详情&#x27;</span>, permission: [<span class="string">&#x27;MOD_LIST&#x27;</span>] &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        ]</span><br><span class="line">      &#125;,</span><br><span class="line">      ]</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="built_in">path</span>: <span class="string">&#x27;*&#x27;</span>,</span><br><span class="line">    redirect: <span class="string">&#x27;/service/servicePage&#x27;</span>,</span><br><span class="line">    hidden: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">];</span><br></pre></td></tr></table></figure><ol start="2"><li>permission.js</li></ol><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 路由守卫</span></span><br><span class="line">router.<span class="title function_ invoke__">beforeEach</span>((to, <span class="keyword">from</span>, next) =&gt; &#123;</span><br><span class="line">  <span class="comment">// 开启页面上方进度条</span></span><br><span class="line">  NProgress.<span class="title function_ invoke__">start</span>();</span><br><span class="line">  <span class="comment">// 判断用户是否登录</span></span><br><span class="line">  <span class="comment">// 用户登录成功后台会返回一个token，这里我将token保存在localStorage中，</span></span><br><span class="line">  <span class="comment">// 并设置一个过期时间，和后台过期时间保持一致</span></span><br><span class="line">  <span class="keyword">if</span> (Vue.ls.<span class="title function_ invoke__">get</span>(ACCESS_TOKEN)) &#123;</span><br><span class="line">    <span class="comment">// 浏览器中已经有用户登录信息，并且用户要去登录页面</span></span><br><span class="line">    <span class="comment">// 直接放行，跳转到系统首页，实现免登录</span></span><br><span class="line">    <span class="keyword">if</span> (to.path === <span class="string">&#x27;/login&#x27;</span>) &#123;</span><br><span class="line">      <span class="title function_ invoke__">next</span>(&#123; <span class="attr">path</span>: <span class="string">&#x27;/&#x27;</span> &#125;);</span><br><span class="line">      <span class="comment">// 关闭页面上方进度条</span></span><br><span class="line">      NProgress.<span class="title function_ invoke__">done</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 判断vuex中是否有登录信息</span></span><br><span class="line">      <span class="keyword">if</span> (Store.getters.addRouters === <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 后台请求用户信息</span></span><br><span class="line">        <span class="comment">// 特地封装的一个接口，包括用户的基本信息和角色权限信息</span></span><br><span class="line">        Store.<span class="title function_ invoke__">dispatch</span>(<span class="string">&#x27;getUserInfo&#x27;</span>)</span><br><span class="line">          .<span class="title function_ invoke__">then</span>(res =&gt; &#123;</span><br><span class="line">            <span class="keyword">if</span> (res.code === <span class="string">&#x27;111111&#x27;</span>) &#123;</span><br><span class="line">              <span class="keyword">const</span> <span class="variable constant_">roles</span> = res.<span class="keyword">object</span>;</span><br><span class="line">              <span class="keyword">if</span> (roles.userPrivilegeCodes &amp;&amp; roles.userPrivilegeCodes.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                Store.<span class="title function_ invoke__">dispatch</span>(<span class="string">&#x27;GenerateRoutes&#x27;</span>, &#123; roles &#125;).<span class="title function_ invoke__">then</span>(() =&gt; &#123;</span><br><span class="line">                  <span class="comment">// 根据roles权限生成可访问的路由表</span></span><br><span class="line">                  <span class="comment">// 动态添加可访问路由表</span></span><br><span class="line">                  router.<span class="title function_ invoke__">addRoutes</span>(Store.getters.addRouters);</span><br><span class="line">                  <span class="keyword">const</span> <span class="variable constant_">redirect</span> = <span class="title function_ invoke__">decodeURIComponent</span>(<span class="keyword">from</span>.query.redirect || to.path);</span><br><span class="line">                  <span class="keyword">if</span> (redirect === to.path) &#123;</span><br><span class="line">                    <span class="comment">// hack方法 确保addRoutes已完成,</span></span><br><span class="line">                    <span class="comment">// set the replace: true so the navigation will not leave a history record</span></span><br><span class="line">                    <span class="title function_ invoke__">next</span>(&#123; ...to, <span class="attr">replace</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">                  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="title function_ invoke__">next</span>(&#123; <span class="attr">path</span>: redirect &#125;);</span><br><span class="line">                  &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">              &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 用户没有对应的角色权限信息</span></span><br><span class="line">                notification.<span class="title function_ invoke__">error</span>(&#123;</span><br><span class="line">                  <span class="attr">message</span>: <span class="string">&#x27;错误&#x27;</span>,</span><br><span class="line">                  <span class="attr">description</span>: <span class="string">&#x27;当前用户无权限菜单，请联系系统管理员&#x27;</span></span><br><span class="line">                &#125;);</span><br><span class="line">                Store.<span class="title function_ invoke__">dispatch</span>(<span class="string">&#x27;LogOut&#x27;</span>).<span class="title function_ invoke__">then</span>(() =&gt; &#123;</span><br><span class="line">                  <span class="title function_ invoke__">next</span>(&#123; <span class="attr">path</span>: <span class="string">&#x27;/login&#x27;</span>, <span class="attr">query</span>: &#123; <span class="attr">redirect</span>: to.fullPath &#125; &#125;);</span><br><span class="line">                  NProgress.<span class="title function_ invoke__">done</span>();</span><br><span class="line">                &#125;);</span><br><span class="line">              &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">              <span class="comment">// 获取用户信息失败</span></span><br><span class="line">              notification.<span class="title function_ invoke__">error</span>(&#123;</span><br><span class="line">                <span class="attr">message</span>: <span class="string">&#x27;错误&#x27;</span>,</span><br><span class="line">                <span class="attr">description</span>: <span class="string">&#x27;获取用户信息失败，请重试&#x27;</span></span><br><span class="line">              &#125;);</span><br><span class="line">              Store.<span class="title function_ invoke__">dispatch</span>(<span class="string">&#x27;LogOut&#x27;</span>).<span class="title function_ invoke__">then</span>(() =&gt; &#123;</span><br><span class="line">                <span class="title function_ invoke__">next</span>(&#123; <span class="attr">path</span>: <span class="string">&#x27;/login&#x27;</span>, <span class="attr">query</span>: &#123; <span class="attr">redirect</span>: to.fullPath &#125; &#125;);</span><br><span class="line">                NProgress.<span class="title function_ invoke__">done</span>();</span><br><span class="line">              &#125;);</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;)</span><br><span class="line">          .<span class="keyword">catch</span>(() =&gt; &#123;</span><br><span class="line">            notification.<span class="title function_ invoke__">error</span>(&#123;</span><br><span class="line">              <span class="attr">message</span>: <span class="string">&#x27;错误&#x27;</span>,</span><br><span class="line">              <span class="attr">description</span>: <span class="string">&#x27;获取用户信息失败，请重试&#x27;</span></span><br><span class="line">            &#125;);</span><br><span class="line">            Store.<span class="title function_ invoke__">dispatch</span>(<span class="string">&#x27;LogOut&#x27;</span>).<span class="title function_ invoke__">then</span>(() =&gt; &#123;</span><br><span class="line">              <span class="title function_ invoke__">next</span>(&#123; <span class="attr">path</span>: <span class="string">&#x27;/login&#x27;</span>, <span class="attr">query</span>: &#123; <span class="attr">redirect</span>: to.fullPath &#125; &#125;);</span><br><span class="line">              NProgress.<span class="title function_ invoke__">done</span>();</span><br><span class="line">            &#125;);</span><br><span class="line">          &#125;);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_ invoke__">next</span>();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 浏览器中没有cookie信息</span></span><br><span class="line">    <span class="comment">// 判断跳转路由是否免登录</span></span><br><span class="line">    <span class="comment">// 免登录直接放行</span></span><br><span class="line">    <span class="comment">// 否则返回登录页面(也可跳到提示页面)</span></span><br><span class="line">    <span class="keyword">if</span> (whiteList.<span class="title function_ invoke__">includes</span>(to.name)) &#123;</span><br><span class="line">      <span class="title function_ invoke__">next</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="title function_ invoke__">next</span>(&#123; <span class="attr">path</span>: <span class="string">&#x27;/login&#x27;</span>, <span class="attr">query</span>: &#123; <span class="attr">redirect</span>: to.fullPath &#125; &#125;);</span><br><span class="line">      NProgress.<span class="title function_ invoke__">done</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 路由跳转完成之后的回调</span></span><br><span class="line">router.<span class="title function_ invoke__">afterEach</span>(() =&gt; &#123;</span><br><span class="line">  <span class="comment">// 关闭加载条</span></span><br><span class="line">  NProgress.<span class="title function_ invoke__">done</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>以上代码是动态路由实现的核心代码，主要是使用了vue-router提供的两个钩子函数，代码逻辑判断比较多，极易出现路由循环跳转从而引起爆栈问题。所以在写的时候一定要注意。<br>下面我们再来看看vuex中核心的构造路由表的代码</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  constantRouterMap,</span><br><span class="line">  ansycRouterMap</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;@/router/config/router.config&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">hasPermission</span>(<span class="params">permission, route</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (route.<span class="property">meta</span> &amp;&amp; route.<span class="property">meta</span>.<span class="property">permission</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> flag = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, len = permission.<span class="property">length</span>; i &lt; len; i++) &#123;</span><br><span class="line">      flag = route.<span class="property">meta</span>.<span class="property">permission</span>.<span class="title function_">includes</span>(permission[i]);</span><br><span class="line">      <span class="keyword">if</span> (flag) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">filterAsyncRouter</span>(<span class="params">routerMap, roles</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> routerMap.<span class="title function_">filter</span>(<span class="function"><span class="params">route</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">hasPermission</span>(roles.<span class="property">userPrivilegeCodes</span>, route)) &#123;</span><br><span class="line">      <span class="keyword">if</span> (route.<span class="property">children</span> &amp;&amp; route.<span class="property">children</span>.<span class="property">length</span>) &#123;</span><br><span class="line">        <span class="comment">// 递归调用，确保子元素能够被加载到</span></span><br><span class="line">        route.<span class="property">children</span> = <span class="title function_">filterAsyncRouter</span>(route.<span class="property">children</span>, roles);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> permission = &#123;</span><br><span class="line">  <span class="attr">state</span>: &#123;</span><br><span class="line">    <span class="attr">routers</span>: constantRouterMap,</span><br><span class="line">    <span class="attr">addRouters</span>: []</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">actions</span>: &#123;</span><br><span class="line">    <span class="title class_">GenerateRoutes</span>(&#123; commit &#125;, data) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> &#123; roles &#125; = data;</span><br><span class="line">        <span class="keyword">const</span> accessedRouters = <span class="title function_">filterAsyncRouter</span>(ansycRouterMap, roles);</span><br><span class="line">        <span class="title function_">commit</span>(<span class="string">&#x27;SET_ROUTERS&#x27;</span>, accessedRouters);</span><br><span class="line">        <span class="title function_">resolve</span>();</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mutations</span>: &#123;</span><br><span class="line">    <span class="attr">SET_ROUTERS</span>: <span class="function">(<span class="params">state, routers</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 侧边栏路由</span></span><br><span class="line">      state.<span class="property">addRouters</span> = routers;</span><br><span class="line">      <span class="comment">// 路由表</span></span><br><span class="line">      state.<span class="property">routers</span> = constantRouterMap.<span class="title function_">concat</span>(routers);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> permission;</span><br></pre></td></tr></table></figure><p>以上就是动态路由的核心代码，当然如果你看到这儿还是一脸懵逼，你可以去我的github看看，哪里我已经准备好了一个完整demo <a href="https://github.com/chengshi2017/vue-config-web">vue-config-web</a>，欢迎star。</p><hr><p>本文完</p>]]>
    </content>
    <id>http://shchome.top/2026/03/01/Vue%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1%E4%B8%8E%E9%89%B4%E6%9D%83/</id>
    <link href="http://shchome.top/2026/03/01/Vue%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1%E4%B8%8E%E9%89%B4%E6%9D%83/"/>
    <published>2026-03-01T02:22:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>最近又开始了一个新的项目，最近一直都在做项目的前期准备工作，借着这样的机会，我们来唠嗑唠嗑前端项目从0到1过程。今天我就先来说说前端动态路由]]>
    </summary>
    <title>Vue动态路由的实现（基于vue-cli3）</title>
    <updated>2026-05-24T03:45:48.281Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="taro框架" scheme="http://shchome.top/categories/taro%E6%A1%86%E6%9E%B6/"/>
    <category term="小程序" scheme="http://shchome.top/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    <content>
      <![CDATA[<p>好久没写博客了……今天一时兴起，准备把最近做的东西记录下来。以后要重拾写博客的好习惯。手动狗头，今天先来介绍下最近做过的支付宝小程序接入轻会员的流程。</p><h2 id="什么是轻会员？？"><a href="#什么是轻会员？？" class="headerlink" title="什么是轻会员？？"></a>什么是轻会员？？</h2><p>废话不多说，直接上官方的介绍：</p><blockquote><p>“花芝轻会员”是一款轻量级的差异化会员营销工具，集成了花呗和芝麻信用两大能力。商家可让用户“先享受权益，后支付会员费”，体验作为轻会员和普通会员之间的差异化服务。使用户切实享受会员带来的实惠、打消资金安全和消费陷阱顾虑；使商家会员经营更简单，促进会员留存和复购。 </p></blockquote><h2 id="支付宝小程序接入轻会员步骤"><a href="#支付宝小程序接入轻会员步骤" class="headerlink" title="支付宝小程序接入轻会员步骤"></a>支付宝小程序接入轻会员步骤</h2><p>接入轻会员，主要需要三个步骤：</p><h4 id="签约轻会员"><a href="#签约轻会员" class="headerlink" title="签约轻会员"></a>签约轻会员</h4><p>参考《轻会员商家接入指南》</p><h4 id="完成轻会员模板配置"><a href="#完成轻会员模板配置" class="headerlink" title="完成轻会员模板配置"></a>完成轻会员模板配置</h4><p>参考《轻会员商家接入指南》</p><h4 id="将轻会员代码插入到小程序中"><a href="#将轻会员代码插入到小程序中" class="headerlink" title="将轻会员代码插入到小程序中"></a>将轻会员代码插入到小程序中</h4><p>在模板配置完成后，我们可以获得模板的templateId，所以在我们代码中，只需要唤起轻会员插件即可。</p><figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">const &#123; config &#125; = this.state</span><br><span class="line">    my.navigateToMiniService(&#123;</span><br><span class="line">      serviceId: <span class="string">&#x27;2019072365974237&#x27;</span>, <span class="regexp">//</span> 插件id,固定值勿改</span><br><span class="line">      servicePage: <span class="string">&#x27;pages/hz-enjoy/main/index&#x27;</span>, <span class="regexp">//</span> 插件页面地址,固定值勿改</span><br><span class="line">      extraData: &#123;</span><br><span class="line">        <span class="string">&#x27;alipay.huabei.hz-enjoy.templateId&#x27;</span>: config.templateId,</span><br><span class="line">        <span class="string">&#x27;alipay.huabei.hz-enjoy.partnerId&#x27;</span>: config.partnerId,</span><br><span class="line">      &#125;,</span><br><span class="line">      success: <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        navigateUtil.toHome()</span><br><span class="line">      &#125;,</span><br><span class="line">      fail: <span class="function"><span class="params">(res)</span> =&gt;</span> &#123;</span><br><span class="line">        Taro.showToast(res.data)</span><br><span class="line">      &#125;,</span><br><span class="line">      complete: <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        navigateUtil.toHome()</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;)</span><br></pre></td></tr></table></figure><p>代码中的templateId即是我们在第二步中配置模板生成的ID, partnerId为商家支付宝Id(不是用户的Id)。</p><h2 id="轻会员结算成功通知商户"><a href="#轻会员结算成功通知商户" class="headerlink" title="轻会员结算成功通知商户"></a>轻会员结算成功通知商户</h2><p>轻会员在页面上用户是无法感知的，优惠金额只有在用户结算时才显示在支付宝收银台上。为了方便获取优惠金额，用户支付完成后，异步获取优惠金额也是一个很重要的步骤。<br>支付宝考虑到这一点，也提供了相应的API。主要用于支付完成后异步通知商户信息。<br>这里主要给出两种实现方式：</p><ol><li>Java</li></ol><figure class="highlight wren"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">AlipayClient</span> <span class="variable">alipayClient</span> <span class="operator">=</span> <span class="variable">new</span> <span class="title class_">DefaultAlipayClient</span>(<span class="string">&quot;https://openasyncapi.alipay.com/gateway.do&quot;</span>,<span class="string">&quot;app_id&quot;</span>,<span class="string">&quot;your private_key&quot;</span>,<span class="string">&quot;json&quot;</span>,<span class="string">&quot;GBK&quot;</span>,<span class="string">&quot;alipay_public_key&quot;</span>,<span class="string">&quot;RSA2&quot;</span>);</span><br><span class="line"><span class="title class_">AlipayPcreditHuabeiAuthSettleSuccessRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="variable">new</span> <span class="title class_">AlipayPcreditHuabeiAuthSettleSuccessRequest</span>();</span><br><span class="line"><span class="variable">request</span>.<span class="property">setBizContent</span>(<span class="string">&quot;&#123;&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>auth_opt_id<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>2018041721001001230571065758<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>trade_no<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>2013112011001004330000121536<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>agreement_no<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>20170502000610755993<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>agreement_status<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>N<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>out_request_no<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>8077735255938032<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>auth_scene<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>EASY_MEMBER<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>rest_freeze_amount<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>0.00<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>pay_amount<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>3.00<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>alipay_user_id<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>2088101117955611<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>out_seller_id<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>123023415243<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>gmt_trans<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>2018-03-15 11:23:04<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>addition_operate_type<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>SIGN_OFF<span class="char escape_">\&quot;</span>,&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;<span class="char escape_">\&quot;</span>addition_operate_result<span class="char escape_">\&quot;</span>:<span class="char escape_">\&quot;</span>Y<span class="char escape_">\&quot;</span>&quot;</span> <span class="operator">+</span></span><br><span class="line"><span class="string">&quot;  &#125;&quot;</span>);</span><br><span class="line"><span class="title class_">AlipayPcreditHuabeiAuthSettleSuccessResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="variable">alipayClient</span>.<span class="property">execute</span>(<span class="variable">request</span>);</span><br><span class="line"><span class="keyword">if</span>(<span class="variable">response</span>.<span class="property">isSuccess</span>())&#123;</span><br><span class="line"><span class="title class_">System</span>.<span class="property">out</span>.<span class="property">println</span>(<span class="string">&quot;调用成功&quot;</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="title class_">System</span>.<span class="property">out</span>.<span class="property">println</span>(<span class="string">&quot;调用失败&quot;</span>);</span><br></pre></td></tr></table></figure><ol start="2"><li>HTTP请求</li></ol><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">https</span>://openasyncapi.alipay.com/gateway.do?timestamp=<span class="number">2013</span>-<span class="number">01</span>-<span class="number">01</span> <span class="number">08</span>:<span class="number">08</span>:<span class="number">08</span>&amp;method=alipay.pcredit.huabei.auth.settle.success&amp;app_id=<span class="number">17043</span>&amp;sign_type=RSA2&amp;sign=ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE&amp;version=<span class="number">1</span>.<span class="number">0</span>&amp;charset=GBK&amp;biz_content=&#123;<span class="string">&quot;pay_amount&quot;</span>:<span class="string">&quot;3.00&quot;</span>,<span class="string">&quot;alipay_user_id&quot;</span>:<span class="string">&quot;2088101117955611&quot;</span>,<span class="string">&quot;rest_freeze_amount&quot;</span>:<span class="string">&quot;0.00&quot;</span>,<span class="string">&quot;out_seller_id&quot;</span>:<span class="string">&quot;123023415243&quot;</span>,<span class="string">&quot;auth_opt_id&quot;</span>:<span class="string">&quot;2018041721001001230571065758&quot;</span>,<span class="string">&quot;addition_operate_result&quot;</span>:<span class="string">&quot;Y&quot;</span>,<span class="string">&quot;agreement_no&quot;</span>:<span class="string">&quot;20170502000610755993&quot;</span>,<span class="string">&quot;agreement_status&quot;</span>:<span class="string">&quot;N&quot;</span>,<span class="string">&quot;gmt_trans&quot;</span>:<span class="string">&quot;2018-03-15 11:23:04&quot;</span>,<span class="string">&quot;trade_no&quot;</span>:<span class="string">&quot;2013112011001004330000121536&quot;</span>,<span class="string">&quot;addition_operate_type&quot;</span>:<span class="string">&quot;SIGN_OFF&quot;</span>,<span class="string">&quot;out_request_no&quot;</span>:<span class="string">&quot;8077735255938032&quot;</span>,<span class="string">&quot;auth_scene&quot;</span>:<span class="string">&quot;EASY_MEMBER&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>公共请求参数：</p><table><thead><tr><th>参数</th><th align="center">类型</th><th align="center">是否必填</th><th align="center">最大长度</th><th align="center">描述</th></tr></thead><tbody><tr><td>notify_id</td><td align="center">String</td><td align="center">是</td><td align="center">50</td><td align="center">通知Id</td></tr><tr><td>utc_timestamp</td><td align="center">String</td><td align="center">是</td><td align="center">13</td><td align="center">消息发送时的服务端时间</td></tr><tr><td>msg_method</td><td align="center">String</td><td align="center">是</td><td align="center">100</td><td align="center">消息接口名称</td></tr><tr><td>app_id</td><td align="center">String</td><td align="center">是</td><td align="center">20</td><td align="center">消息接受方的应用id</td></tr><tr><td>msg_type</td><td align="center">String</td><td align="center">是</td><td align="center">5</td><td align="center">消息类型。目前支持类型：sys：系统消息；usr，用户消息；app，应用消息</td></tr><tr><td>msg_uid</td><td align="center">String</td><td align="center">否</td><td align="center">20</td><td align="center">消息归属的商户支付宝uid。用户消息和应用消息时非空</td></tr><tr><td>msg_app_id</td><td align="center">String</td><td align="center">否</td><td align="center">20</td><td align="center">消息归属方的应用id。应用消息时非空</td></tr><tr><td>version</td><td align="center">String</td><td align="center">是</td><td align="center">5</td><td align="center">版本号(1.1版本为标准消息)</td></tr><tr><td>biz_content</td><td align="center">String</td><td align="center">是</td><td align="center"></td><td align="center">消息报文</td></tr><tr><td>sign</td><td align="center">String</td><td align="center">是</td><td align="center"></td><td align="center">签名</td></tr><tr><td>sign_type</td><td align="center">String</td><td align="center">是</td><td align="center">10</td><td align="center">签名类型</td></tr><tr><td>encrypt_type</td><td align="center">String</td><td align="center">否</td><td align="center">10</td><td align="center">加密算法</td></tr><tr><td>charset</td><td align="center">String</td><td align="center">是</td><td align="center">10</td><td align="center">编码集，该字符集为验签和解密所需要的字符集</td></tr><tr><td>notify_type</td><td align="center">String</td><td align="center">否</td><td align="center">20</td><td align="center">通知类型，1.1接口没有该参数</td></tr><tr><td>notify_time</td><td align="center">String</td><td align="center">否</td><td align="center">19</td><td align="center">通知时间</td></tr><tr><td>auth_app_id</td><td align="center">String</td><td align="center">否</td><td align="center">20</td><td align="center">1.1没有此参数授权方的应用id</td></tr></tbody></table><p>消息的基本格式：</p><table><thead><tr><th>参数</th><th align="center">类型</th><th align="center">是否必填</th><th align="center">最大长度</th><th align="center">描述</th></tr></thead><tbody><tr><td>auth_opt_id</td><td align="center">String</td><td align="center">是</td><td align="center">64</td><td align="center">支付宝侧授权操作单据id</td></tr><tr><td>trade_no</td><td align="center">String</td><td align="center">否</td><td align="center">64</td><td align="center">支付宝交易号，当支付金额&gt;0时才会有支付宝交易号2018112011001004330000121536，否则就为空</td></tr><tr><td>agreement_no</td><td align="center">String</td><td align="center">是</td><td align="center">64</td><td align="center">支付宝系统中用以唯一标识用户签约记录的编号</td></tr><tr><td>agreement_status</td><td align="center">String</td><td align="center">是</td><td align="center">10</td><td align="center">协议状态。Y表示状态有效，P表示失效中，N表示状态失效</td></tr><tr><td>out_request_no</td><td align="center">String</td><td align="center">是</td><td align="center">64</td><td align="center">商户本次操作的请求流水号，用于标识请求流水的唯一性，不能包含除中文、英文、数字以外的字符，需要保证在商户端不重复</td></tr><tr><td>auth_scene</td><td align="center">String</td><td align="center">是</td><td align="center">64</td><td align="center">花芝轻会员签约场景，商户和支付宝签约时确定，商户接入时需要咨询技术支持。</td></tr><tr><td>rest_freeze_amount</td><td align="center">String</td><td align="center">是</td><td align="center">16</td><td align="center">完成本次操作时，用户资金池余额快照。仅作提示用，请勿用于核对，并发情况下数值有可能不准确。两位小数，单位元</td></tr><tr><td>pay_amount</td><td align="center">String</td><td align="center">是</td><td align="center">16</td><td align="center">支付金额，单位元</td></tr><tr><td>alipay_user_id</td><td align="center">String</td><td align="center">是</td><td align="center">128</td><td align="center">买家在支付宝的用户id</td></tr><tr><td>out_seller_id</td><td align="center">String</td><td align="center">是</td><td align="center">64</td><td align="center">商户的支付宝sellerId</td></tr><tr><td>gmt_trans</td><td align="center">String</td><td align="center">是</td><td align="center">20</td><td align="center">业务成功时间</td></tr><tr><td>addition_operate_type</td><td align="center">String</td><td align="center">是</td><td align="center">20</td><td align="center">支付完成后的协议追加动作，比如解约SIGN_OFF</td></tr><tr><td>addition_operate_result</td><td align="center">String</td><td align="center">是</td><td align="center">20</td><td align="center">表示此次业务是否成功，Y：成功，N：失败</td></tr></tbody></table><p>以上介绍了请求的一些基本参数，接下来就只需要在支付宝开放平台消息服务中设置消息模板，具体操作可参考<a href="https://docs.open.alipay.com/10381/">支付宝开放平台消息服务 </a>中的From蚂蚁消息服务使用。</p><p>待续…….</p>]]>
    </content>
    <id>http://shchome.top/2026/02/20/%E6%94%AF%E4%BB%98%E5%AE%9D%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%8E%A5%E5%85%A5%E8%8A%B1%E8%8A%9D%E8%BD%BB%E4%BC%9A%E5%91%98/</id>
    <link href="http://shchome.top/2026/02/20/%E6%94%AF%E4%BB%98%E5%AE%9D%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%8E%A5%E5%85%A5%E8%8A%B1%E8%8A%9D%E8%BD%BB%E4%BC%9A%E5%91%98/"/>
    <published>2026-02-20T13:57:00.000Z</published>
    <summary>
      <![CDATA[<p>好久没写博客了……今天一时兴起，准备把最近做的东西记录下来。以后要重拾写博客的好习惯。手动狗头，今天先来介绍下最近做过的支付宝小程序接入轻会员的流程。</p>
<h2 id="什么是轻会员？？"><a href="#什么是轻会员？？" class="headerlink"]]>
    </summary>
    <title>支付宝小程序接入花芝轻会员</title>
    <updated>2026-05-24T03:45:48.281Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeremy</name>
    </author>
    <category term="前端" scheme="http://shchome.top/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="浏览器" scheme="http://shchome.top/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/"/>
    <content>
      <![CDATA[<p>上篇文章中<a href="https://www.shchome.top/2019/06/02/%E8%B7%A8%E5%9F%9F%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/">跨域的解决方案</a>，我们简单的介绍了下跨域的原因以及使用postMessage解决跨域，这篇我们我们再来介绍几种前端跨域的解决方案</p><hr><h2 id="JSONP"><a href="#JSONP" class="headerlink" title="JSONP"></a>JSONP</h2><p>JSON和JSONP虽然只有一个字母之差，但是它们却压根不是一回事：<br>JSON是一种数据交换格式，而JSONP是一种非官方的跨域数据交互协议。它们一个是描述信息的格式，一个是信息传递双方约定的方法。</p><h4 id="JSONP的产生"><a href="#JSONP的产生" class="headerlink" title="JSONP的产生"></a>JSONP的产生</h4><p>上篇文章中说到过，ajax在调用跨域请求时，存在跨域无权限访问的情况，但是后来伟大的程序员又发现，Web页面上调用js文件不受跨域问题的影响。不仅如此，我们还发现凡是拥有src这个属性的标签都拥有跨域的能力，比如<code>&lt;script&gt;</code>、<code>&lt;img&gt;</code>、<code>&lt;iframe&gt;)</code>。<br>于是我们判断，当前阶段如果想要跨域访问数据时，我们可以把数据外面包裹上一层js代码，供客户端调用和统一处理。<br>为了方便客户端使用数据，逐渐形成了一种非正式的传输协议JSONP，该协议的一个要点就是允许用户传递一个callback给服务端，然后服务端返回数据时会将这个callback参数作为函数名包裹JSON数据，这样客户端就可以随意定制自己的函数来自动处理返回数据了。</p><h4 id="JSONP的使用"><a href="#JSONP的使用" class="headerlink" title="JSONP的使用"></a>JSONP的使用</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;http://domain/api?param1=a&amp;param2=b&amp;callback=jsonp&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">function</span> <span class="title function_">jsonp</span>(<span class="params">data</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span> </span><br></pre></td></tr></table></figure><p>JSONP使用简单而且兼容性不错，但是只限于get请求。<br>看过JSONP库源码的童靴想必知道，JSONP常见的代码实现其实就是document.createElement(‘script’)生成一个script标签，然后插入到body中而已。所以JSONP的实现原理就是创建一个scrip标签，然后再把需要请求的api地址放到scr里，这个请求只能是get方法，不可能是post方法。</p><h2 id="跨域资源共享（CORS）"><a href="#跨域资源共享（CORS）" class="headerlink" title="跨域资源共享（CORS）"></a>跨域资源共享（CORS）</h2><p>在出现跨域资源共享之前，我们只能通过JSONP来解决跨域问题。上面我们也说到过，JSONP只能支持GET请求，在前端日益复杂的今天，仅仅只靠JSONP怎么可能，所以这个时候，CORS应运而生了。</p><h3 id="CORS原理"><a href="#CORS原理" class="headerlink" title="CORS原理"></a>CORS原理</h3><p>支持CORS的浏览器，一旦发现ajax在做跨域请求时，会进行一些特殊的处理，对于已经实现CORS接口的服务端，接收请求并做出回应。<br>浏览器对跨域请求进行了一个简单的区分–简单请求和非简单请求</p><h4 id="简单请求"><a href="#简单请求" class="headerlink" title="简单请求"></a>简单请求</h4><p>若一个请求同时满足以下条件，则将其视为简单请求。</p><ul><li>使用的方法<ul><li>HEAD</li><li>GET</li><li>POST</li></ul></li><li>HTTP请求头信息为以下几种字段<ul><li>Accept</li><li>Accept-Lanuage</li><li>Content-Lanuage</li><li>Last-Event-ID</li><li>Content-Type:<br>application&#x2F;x-www-form-urlencoded、 multipart&#x2F;form-data、text&#x2F;plain</li></ul></li></ul><p>当浏览器判断一个请求是简单请求后，会在Request Header中添加Origin(协议+域名+端口)字段，表示我们的请求源，CORS会将该字段作为跨域的标志。</p><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E8%B7%A8%E5%9F%9F%E7%9A%84%E5%87%A0%E7%A7%8D%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/Jietu20190617-174854.png"></p><p>CORS接收到此请求后，首先会判断origin是否在允许源（由服务端决定）范围之内，如果验证通过，服务端会在Response Header 添加 Access-Control-Allow-Origin、Access-Control-Allow-Credentials等字段。</p><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E8%B7%A8%E5%9F%9F%E7%9A%84%E5%87%A0%E7%A7%8D%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/Jietu20190617-175200.png"></p><p>浏览器收到Response后会判断自己是否在Access-Control-Allow-Origin允许源中，如果不存在，会抛出“同源检测异常”。</p><h4 id="非简单请求"><a href="#非简单请求" class="headerlink" title="非简单请求"></a>非简单请求</h4><p>不满足以上两个条件的请求成为非简单请求。<br>对于非简单请求，浏览器首先发出类型为OPTIONS的预检请求，主请求和预检请求地址相同，CORS服务端对预检请求处理，并在Response Header中添加标识字段，客户端接收到预检请求的返回值进行一次预检请求的判断，如果判断通过则发起主请求。</p><p><img src="https://shcblog-image.oss-cn-shanghai.aliyuncs.com/pages/%E8%B7%A8%E5%9F%9F%E7%9A%84%E5%87%A0%E7%A7%8D%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/Jietu20190617-180529.png"></p><p>这里我们可以看到，浏览器连续发送了两个请求，第一个就是预检请求，类型为OPTIONS，第二个请求才是我们发出的请求。<br>预检请求通过后，主请求开始发送。</p><p>所以，CORS的非简单请求只是在简单请求的基础上加了一个预检请求而已。</p><h4 id="CORS的使用"><a href="#CORS的使用" class="headerlink" title="CORS的使用"></a>CORS的使用</h4><p>CORS在前端的使用很简单，只需要在构建axios实例时加上一行代码</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">axios.defaults.withCredentials</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>要想完整的使用CORS解决跨域问题，还需要后端童靴的支持，可以说实现CORS的关键是后端，只要后端实现了CORS，就实现了跨域。</p><h2 id="Vue2-0-proxyTable跨域"><a href="#Vue2-0-proxyTable跨域" class="headerlink" title="Vue2.0 proxyTable跨域"></a>Vue2.0 proxyTable跨域</h2><p>proxyTable是Vue-cli中的一种跨域方式，主要依赖的是http-proxy-middleware中间件。实现的原理也很简单，其本质是本地开了一个服务器dev-server（所以proxyTable只能在开发环境中使用），浏览器先将跨域请求发给自己的服务端，由自己的服务端再转发给要跨域的服务端，做一层代理来实现跨域。</p><h4 id="proxyTable的使用"><a href="#proxyTable的使用" class="headerlink" title="proxyTable的使用"></a>proxyTable的使用</h4><p>这里以Vue-cli3.0举例。在Vue-cli3.0中，webpack的配置主要写在vue.config.js中</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="params">devServer:</span> &#123;</span><br><span class="line">    <span class="params">proxy:</span> &#123;</span><br><span class="line">        &#x27;<span class="operator">/</span><span class="params">cmdb&#x27;:</span> &#123;</span><br><span class="line">            <span class="params">target:</span> &#x27;http:<span class="operator">//</span><span class="number">10.4</span>.<span class="number">170.42</span>:<span class="number">6060</span><span class="operator">/</span>cmdb&#x27;,</span><br><span class="line">            <span class="params">changeOrigin:</span> <span class="literal">true</span>,</span><br><span class="line">            <span class="params">pathRewrite:</span> &#123;</span><br><span class="line">                &#x27;^<span class="operator">/</span><span class="params">cmdb&#x27;:</span> &#x27;<span class="operator">/</span>cmdb&#x27;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后在我们请求数据或者axios封装时，只需要使用’&#x2F;cmdb’即可。</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">axios.defaults.baseURL</span> = <span class="string">&#x27;/cmdb&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="nginx代理跨域"><a href="#nginx代理跨域" class="headerlink" title="nginx代理跨域"></a>nginx代理跨域</h2><p>上面我们说到的proxyTable只能够在开发环境下使用，那么我们在生产环境上怎么办呢？ nginx反向代理无疑是一种很好的解决方案。<br>那么nginx的反向代理为什么能够实现跨域呢？<br>首先，我们直接在浏览器上输入地址，是不会产生跨域问题，只有在某域名页面，由该页面发起的接口请求才可能跨域。nginx就相当于这个浏览器，它接收到外部对它的请求(注意：nginx只会接收别人对它的请求，但是并不会拦截请求)，在类似浏览器的地址栏中去请求某个接口，最后将请求的内容返回回去。</p><h4 id="nginx-配置"><a href="#nginx-配置" class="headerlink" title="nginx 配置"></a>nginx 配置</h4><p>nginx解决跨域需要配置url请求规则，主要配置信息都在nginx.conf文件中</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">location</span> /cmdb/ &#123;</span><br><span class="line">    <span class="attribute">proxy_pass</span> http://10.4.170.42:8003;</span><br><span class="line">    <span class="attribute">proxy_redirect</span> <span class="literal">off</span>;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> Host <span class="variable">$host</span>;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>nginx解决跨域问题在前端开发中也经常使用，因此需要重点掌握。</p><hr><p>本文主要介绍了几种常见的前端跨域方法，当然跨域的方法远远不止这几种，感兴趣的同学可以参考<a href="https://segmentfault.com/a/1190000011145364">前端常见跨域解决方案（全）</a></p><p>参考文章：<br><a href="https://segmentfault.com/a/1190000011145364">前端常见跨域解决方案（全）</a><br><a href="https://juejin.im/post/5c23993de51d457b8c1f4ee1">九种跨域方式实现原理（完整版）</a></p>]]>
    </content>
    <id>http://shchome.top/2026/02/12/%E8%B7%A8%E5%9F%9F%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%88%E7%BB%AD%EF%BC%89/</id>
    <link href="http://shchome.top/2026/02/12/%E8%B7%A8%E5%9F%9F%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%88%E7%BB%AD%EF%BC%89/"/>
    <published>2026-02-12T00:29:00.000Z</published>
    <summary>
      <![CDATA[<p>上篇文章中<a href="https://www.shchome.top/2019/06/02/%E8%B7%A8%E5%9F%9F%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/">跨域的解决方案</a>，我们简单的介绍了下跨]]>
    </summary>
    <title>跨域的解决方法(续)</title>
    <updated>2026-05-24T03:45:48.281Z</updated>
  </entry>
</feed>
