<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Yuki</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://blog.yuki.sh/</id>
  <link href="https://blog.yuki.sh/" rel="alternate"/>
  <link href="https://blog.yuki.sh/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Yuki</rights>
  <subtitle>Yuki 的个人博客，用来记录一些奇奇怪怪的东西。(*/ω＼*)</subtitle>
  <title>Yuki 妙妙屋</title>
  <updated>2026-05-08T01:03:33.378Z</updated>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="项目" scheme="https://blog.yuki.sh/categories/project/"/>
    <category term="代码" scheme="https://blog.yuki.sh/tags/code/"/>
    <category term="娱乐" scheme="https://blog.yuki.sh/tags/fun/"/>
    <content>
      <![CDATA[<p>游戏 <a href="https://www.dlsite.com/maniax/work/=/product_id/RJ338582.html">『妹!せいかつ～ファンタジー～』</a> 是由 <a href="https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG11531.html">いぬすく</a> 社团于 2022 年在 DLsite 上发售的一款养成类 SLG，在台湾地区也被称为<strong>黑白妹 2</strong>（<strong>黑白妹 1</strong> 指的是 <a href="https://www.dlsite.com/maniax/work/=/product_id/RJ258445.html">『妹!せいかつ～モノクローム～』</a>，不过两者除了都是黑白画风外，在剧情上并没什么关联）。</p><p>其中<strong>黑白妹 1</strong>已于 2021 年发售了简中版，DLsite 官方将「妹!せいかつ」这个词直译为了「妹！生活」。出版商 <a href="https://kaguragamer.com/">Kagura Games</a> 又于 2024 年初在 Steam 代理发行了这款游戏，将其翻译为「妹相随」。</p><span id="more"></span><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Steam 游戏 <a href="https://store.steampowered.com/app/3062990/DLC/">DLC</a> 已于上个月发售，这本应该是件令人兴奋的事，但随之而来的却是各种各样的问题。存档不兼容，就连神乐官方给出的 <a href="https://kaguragamer.com/wp-content/uploads/2024/12/%E5%A6%B9%E7%9B%B8%E9%9A%8FDLC%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98.pdf">处理方案</a>，都是删除云存档、并重装游戏，来解决游戏中出现的各种 Bug。</p><p>在此之前我有很多存档，在游戏中花费了大量的时间和精力。因不想再次重复枯燥的流程，加上近期加班没有多余的游戏时间，便在一气之下做出了这个修改器。</p><p><img src="/posts/05064242ec86/application.png" class="lazyload" data-srcset="/posts/05064242ec86/application.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="application"></p><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><table><thead><tr><th align="left">热键</th><th align="left">功能</th><th>描述</th></tr></thead><tbody><tr><td align="left"><code>F1</code></td><td align="left">见面 5 秒结束战斗</td><td>战斗阶段直接获得胜利</td></tr><tr><td align="left"><code>F2</code></td><td align="left">AT 力场</td><td>免疫战斗时受到的伤害</td></tr><tr><td align="left"><code>F9</code></td><td align="left">砸瓦鲁多</td><td>停止游戏内的时间流动</td></tr><tr><td align="left"><code>F10</code></td><td align="left">无限洗澡</td><td>洗澡不受次数限制</td></tr><tr><td align="left"><code>F11</code></td><td align="left">无限泡茶</td><td>泡茶不受次数限制</td></tr></tbody></table><p>修改器在 V1 时仅支持 Steam 平台，现在 V2 使用了 Lua 进行重构与窗口手绘，已经完美兼容 DLsite 和 Steam 双平台。</p><p>在此之前，旧版本无法兼容 DLsite，这其实是神乐的问题。我们打开 Steam 的游戏安装目录可以看到有 <code>Game.exe</code> 和 <code>GamePro.exe</code> 两个运行程序。通过 Steam 内部来启动游戏，执行的是 <code>Game.exe</code> 程序，而你手动执行 <code>GamePro.exe</code> 文件也是可以正常启动游戏的，但它们之间的<strong>基址</strong>并不一致。</p><p>DLsite 日语原版只有 <code>GamePro.exe</code> 一个执行文件，<code>Game.exe</code> 是先前老版本使用的启动器，两者就连程序图标都不一样，不清楚为什么神乐要保留并且使用 <code>Game.exe</code>。</p><h2 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h2><p>你可以 <a href="/posts/05064242ec86/ImoutoLifeFantasyTrainer.exe">点击这里</a> 开始下载，要是你所在的地区有网络波动，下载文件速度过慢，可以尝试使用 <a href="https://xueelf.lanzoup.com/ir8kf3e557ni">蓝奏云</a>。</p><p>如果你的电脑里安装了杀毒软件，可能还要添加白名单来避免被误删。修改器的原理是通过访问游戏的内存实现数据篡改，对于操作系统而言属于敏感操作，误报是正常现象，可以放心使用。</p><p>该项目基于 Cheat Engine 开发，所有代码已在 <a href="https://github.com/xueelf/ImoutoLifeFantasyTrainer">GitHub</a> 开源，如果你想要自定义功能项，可以直接打开 <code>launch.CT</code> 修改对应的脚本。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>在打开修改器后，如果你此时还未启动游戏，窗口会开始播放加载动画，当打开游戏之后，如果你听到了那个<strong>熟悉的声音</strong>，就证明进程已经成功加载了。</p><h3 id="道具修改"><a href="#道具修改" class="headerlink" title="道具修改"></a>道具修改</h3><p>可以通过顶部菜单栏修改各式各样的角色数值以及道具物品。</p><h3 id="功能面板"><a href="#功能面板" class="headerlink" title="功能面板"></a>功能面板</h3><p>通过鼠标左键点击窗口面板右侧的区域栏，可以打开与关闭对应功能性，你也能直接使用对应的键盘快捷键。</p><h3 id="触手"><a href="#触手" class="headerlink" title="触手"></a>触手</h3><p>触手是一个在游戏后期通关大冒险后才能获得的道具，无法通过商店购买，你可以直接在菜单栏解锁。</p><h3 id="妹妹心情"><a href="#妹妹心情" class="headerlink" title="妹妹心情"></a>妹妹心情</h3><p>妹妹的心情数值在游戏内存里的实际变量是 <code>0–10</code>，对应了六个不同的阶段，下拉栏菜单的数值为双数（0、2、4、6、8、10），也就是说，当妹妹的心情值是单数（1、3、5、7、9）时，下拉栏不会有对应的默认勾选项。</p><h3 id="秒-5"><a href="#秒-5" class="headerlink" title="秒 5"></a>秒 5</h3><p>需要注意的是，「秒 5」建议在<strong>战斗开始前</strong>或<strong>己方回合</strong>时开启，能避免出现一些意料之外的情况。该功能在开启后，只要进入了战斗阶段，不管是对方的回合还是己方的回合，都会直接获得战斗胜利。</p><p>部分战斗会有剧情对话，如果此时在<strong>对方的回合</strong>突然强制获胜，会导致游戏图层异常。例如对话框正常显示，但战斗 UI 还没有消失，不过不会影响游戏运行就是了，会在遇到游戏事件重新绘制 UI 时恢复正常。</p><p>如果遇到了战斗无法获胜的场景，先确认游戏内是否已经进入<strong>战斗开始阶段</strong>（也就是那一句 <em>battle start</em>）。部分剧情在战斗开始前会有一段对话或者引导提示，此时需要鼠标点击一下界面，游戏内才会出现 <em>battle start</em> 的标语。</p><p>据 Steam 的部分留言反馈，「秒 5」功能无法正常使用，但我在 Windows 10 和 Windows 11 下都测试过了，无法复现。由于我的使用习惯，电脑系统始终在第一时间保持着最新版本，Windows 也是购买的正版，无法 100% 还原各式各样的运行环境。</p><p>为此，我又新增了「AT 力场」功能。如果无法使用修改器直接获得胜利，可以使用该功能避免战败，通过战斗正常取胜。</p><h3 id="砸瓦鲁多"><a href="#砸瓦鲁多" class="headerlink" title="砸瓦鲁多"></a>砸瓦鲁多</h3><p>时停只会影响在家里的互动时间，公会、逛街等一系列事件会强制切换场景，锁了时间也没用。</p><h3 id="无限泡茶"><a href="#无限泡茶" class="headerlink" title="无限泡茶"></a>无限泡茶</h3><p>是否喝过茶和是否上过厕所在游戏里是两种不同的状态，该功能只会解除泡茶限制，妹妹只会去一趟厕所。</p><h2 id="攻略"><a href="#攻略" class="headerlink" title="攻略"></a>攻略</h2><p>在开发反编译的过程中，我发现了许多有意思的事情，虽然算不上什么正经攻略，不过还是希望能多多少少给大家带来一些帮助。</p><h3 id="关于隐藏数值"><a href="#关于隐藏数值" class="headerlink" title="关于隐藏数值"></a>关于隐藏数值</h3><p>游戏里对妹妹的大部分操作，都会被记录在案（说的就是你，hentai！），例如夜晚叫醒妹妹的次数、喂妹妹喝媚药的次数、闯入厕所的次数，以及各种 H 的开发程度等。这类隐藏数值与 Sex 的次数、射出量等统计数据不同，会影响到妹妹的行为、对话，还有胖次界面的人物介绍。</p><p>而这其中有一个 Bug，当在夜袭的时候选择让妹妹趴着用屁股夹住，虽然没有插入，但还是会增加相关 H 的隐藏数值。当该数值达到一定程度时，会解锁很多 Sex 相关剧情，例如在浴室时插入、被早安骑、晚饭时先享用妹妹等，都不会导致破瓜，中出也不会怀孕。</p><p>我也有考虑过要不要向作者反馈这个 Bug，但是……吧……我一个给游戏拆了包写修改器的人，跑去联系作者还要求修 Bug，这不太合适吧（</p><h3 id="关于避孕药"><a href="#关于避孕药" class="headerlink" title="关于避孕药"></a>关于避孕药</h3><p>这其实是翻译闹出的乌龙，在日语原版里，道具的名字是「アフターピル」，而 Steam 英语版里是「After Pill」，已经讲的很清楚了是<strong>事后药</strong>，而不是事前药，要注意使用时机。</p><p>之前有考虑过要不要给修改器做多语言切换，所以参考了各个版本的翻译文本，最后放弃了，懒（也许哪天心血来潮了又会突然更新吧）。</p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><p>修改器使用到了游戏内的拆包资源（侧边图、程序图标、字体、音效等），版权归相关方所属，本项目仅供学习用途，请勿肆意宣传滥用。</p><h3 id="关于使用修改器的态度，神乐官方给出的解释原文如下："><a href="#关于使用修改器的态度，神乐官方给出的解释原文如下：" class="headerlink" title="关于使用修改器的态度，神乐官方给出的解释原文如下："></a>关于使用修改器的态度，神乐官方给出的解释原文如下：</h3><blockquote><p>Q：我可以开修改器或者加速游戏吗？<br>A：可以，但是出了 Bug 请自行解决，我们无法提供技术支持。</p></blockquote><h3 id="关于快捷键为什么要使用-F-区："><a href="#关于快捷键为什么要使用-F-区：" class="headerlink" title="关于快捷键为什么要使用 F 区："></a>关于快捷键为什么要使用 F 区：</h3><p>在 V1 时，修改器窗口直接使用了 CE 默认生成模板，快捷键默认数字小键盘，市面上很多基于 CE 的修改器也都是这样做的，因为很方便。但是 CE 提供的默认模板实在是太丑了，而且我的笔记本和另一个 87 配列键盘压根没有副键盘区，用台式的时候又会经常误触（我用 108 配列习惯敲数字区），所以最终在 V2 选择了 F 区的方案。</p><p>在照顾不同键盘用户和防误触的同时，又因为游戏内的功能按键也是 F 区，可以很好地在交互上做到逻辑统一，还顺便给功能区做了点击交互，方便鼠标用户。</p><h2 id="更新"><a href="#更新" class="headerlink" title="更新"></a>更新</h2><div class="timeline"><div class="timenode"><div class="meta"><p><p>2025-12-21</p></p></div><div class="body"><ul><li>使用 Lua 进行重构与窗口手绘，代码开源</li><li>优化了游戏进程监听逻辑</li><li>添加了游戏未启动时的动画</li><li>添加了游戏关闭时的图片与标语</li><li>添加了侧边栏图片的随机切换</li><li>更新了顶部菜单栏列表，可以修改更多数值了</li><li>新增了「AT 力场」热键功能</li><li>新增了「砸瓦鲁多」热键功能</li><li>新增了「无限洗澡」热键功能</li><li>新增了「无限泡茶」热键功能</li><li>暂时移除了「三倍速」功能</li><li>界面交互替换为了游戏素材音效</li></ul></div></div><div class="timenode"><div class="meta"><p><p>2025-02-03</p></p></div><div class="body"><ul><li>为热键添加激活文本高亮</li><li>优化道具栏数量的同步刷新机制</li><li>修复字体丢失的问题</li></ul></div></div><div class="timenode"><div class="meta"><p><p>2025-01-21</p></p></div><div class="body"><ul><li>移除「爆金币」热键功能</li><li>移除「0721」热键功能</li><li>新增顶部菜单栏，可修改物品数量</li><li>优化启动器进程检索逻辑</li><li>添加彩蛋 <psw>如果在启动修改器后一段时间内不运行游戏的话……</psw></li></ul></div></div><div class="timenode"><div class="meta"><p><p>2025-01-12</p></p></div><div class="body"><ul><li>添加「0721」热键功能</li><li>添加「爆金币」热键功能</li><li>添加「三倍速」热键功能</li><li>添加「秒 5」热键功能</li><li>添加游戏未启动时的弹窗提示</li></ul></div></div></div>]]>
    </content>
    <id>https://blog.yuki.sh/posts/05064242ec86/</id>
    <link href="https://blog.yuki.sh/posts/05064242ec86/"/>
    <published>2025-01-11T19:21:33.000Z</published>
    <summary>
      <![CDATA[<p>游戏 <a href="https://www.dlsite.com/maniax/work/=/product_id/RJ338582.html">『妹!せいかつ～ファンタジー～』</a> 是由 <a href="https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG11531.html">いぬすく</a> 社团于 2022 年在 DLsite 上发售的一款养成类 SLG，在台湾地区也被称为<strong>黑白妹 2</strong>（<strong>黑白妹 1</strong> 指的是 <a href="https://www.dlsite.com/maniax/work/=/product_id/RJ258445.html">『妹!せいかつ～モノクローム～』</a>，不过两者除了都是黑白画风外，在剧情上并没什么关联）。</p>
<p>其中<strong>黑白妹 1</strong>已于 2021 年发售了简中版，DLsite 官方将「妹!せいかつ」这个词直译为了「妹！生活」。出版商 <a href="https://kaguragamer.com/">Kagura Games</a> 又于 2024 年初在 Steam 代理发行了这款游戏，将其翻译为「妹相随」。</p>]]>
    </summary>
    <title>妹相随～黑白世界的缤纷冒险～修改器</title>
    <updated>2026-05-08T01:03:33.378Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="项目" scheme="https://blog.yuki.sh/categories/project/"/>
    <category term="代码" scheme="https://blog.yuki.sh/tags/code/"/>
    <category term="API" scheme="https://blog.yuki.sh/tags/api/"/>
    <category term="娱乐" scheme="https://blog.yuki.sh/tags/fun/"/>
    <content>
      <![CDATA[<p>就在前几天，发生了件令我十分头疼的事情。我搭建的 P 站反代服务被人恶意调用，在<strong>国内</strong>请求了百万张 R18 图片，险些造成严重的后果，不过还好我及时进行了处理。</p><p>正当我松了口气，准备来张涩图奖励下自己的时候，机器人却一直提示图片请求失败。进首页一看，用了快 3 年的 API 因为作者被爬虫抓烦了，决定停止服务。<del>这下心情更不好了.jpg</del></p><span id="more"></span><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><div class="note message red"><p>请不要用爬虫抓我的接口，你的行为只会给别人带来困扰。</p></div><p>既然 API 挂掉了，那么再去换一个不就好了，为什么要自己从头去写？</p><p>因为随机图片本身并没什么技术难点，我只花了半天不到的时间就写完了。而且很难找到能满足我使用需求的 API，就算找到了也无法保证意外会在哪天到来。</p><p>除了通过 API 获取随机图片外，你还可以直接 <a href="https://pixiv.yuki.sh/">访问首页</a> 来查询（关键字、搜图）P 站作品的相关信息。（暂未适配移动端）</p><h2 id="为什么不是……"><a href="#为什么不是……" class="headerlink" title="为什么不是……"></a>为什么不是……</h2><p>我们都知道，上 Google 随便一搜就能找到好几个 Pixiv 服务，那么这个 API 有什么特别之处？</p><p>最初，我只是因为在群里做了一个 QQ 机器人，顺手添加的涩图功能，却没想到好评如潮。但是随着时间的推移，这也暴露出了许许多多的问题。虽然群内确实因为涩图增加了一部分的趣味性，但由于发送的图片不可控，再加上内鬼举报以及各种不可抗之力，导致 Bot 被频繁风控。</p><p>所以我在录入图片的时候，遵循着下列几条事项：</p><ul><li>无露点（胖次、南北半球）<del>泳装除外</del></li><li>无性暗示（撩裙角、推倒）</li><li>无过激行为（舔脚、kiss 拉丝、0721）</li></ul><p>在保证图片质量的同时，又能让其适合分享至各个社交平台，而不用担心会造成不良影响。</p><p>这个时候可能就有人要问了，那你这里的图不是一点都不涩嘛！当我们看到可爱的女孩子，并从内心深处发出「好可爱呀」的电波时，目的就已经达到了，不是么？<psw>当然，如果能在稍微带点涩涩那就更好了，但这并不适合出现在公共场合。</psw></p><p><img src="/posts/976864126ca2/konoha.png" class="lazyload" data-srcset="/posts/976864126ca2/konoha.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="konoha"></p><h2 id="随机图片"><a href="#随机图片" class="headerlink" title="随机图片"></a>随机图片</h2><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET https://pixiv.yuki.sh/api/recommend</span><br></pre></td></tr></table></figure><table><thead><tr><th align="left">参数名</th><th align="left">数据类型</th><th align="left">默认值</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left">type</td><td align="left">string</td><td align="left"><code>redirect</code></td><td align="left"><code>redirect</code> | <code>json</code></td></tr><tr><td align="left">size</td><td align="left">string</td><td align="left"><code>original</code></td><td align="left"><code>mini</code> | <code>thumb</code> | <code>small</code> | <code>regular</code> | <code>original</code></td></tr><tr><td align="left">proxy</td><td align="left">string</td><td align="left"><code>i.yuki.sh</code></td><td align="left">将会替换 <code>urls</code> 中的 <code>i.pximg.net</code> 源地址</td></tr></tbody></table><p>如果 <code>type</code> 传入 <code>redirect</code>，访问 API 将会直接 302 跳转图片地址，<code>json</code> 则返回数据结果集。</p><h2 id="信息查询"><a href="#信息查询" class="headerlink" title="信息查询"></a>信息查询</h2><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET https://pixiv.yuki.sh/api/illust</span><br></pre></td></tr></table></figure><table><thead><tr><th align="left">参数名</th><th align="left">数据类型</th><th align="left">默认值</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left">id</td><td align="left">string</td><td align="left"></td><td align="left">Pixiv 作品 ID</td></tr><tr><td align="left">proxy</td><td align="left">string</td><td align="left"><code>i.yuki.sh</code></td><td align="left">将会替换 <code>urls</code> 中的 <code>i.pximg.net</code> 源地址</td></tr></tbody></table><p>要是你查询的图片含有 <code>R-18</code> 的标签，那么 <code>urls</code> 将全部返回 <code>null</code>。</p>]]>
    </content>
    <id>https://blog.yuki.sh/posts/976864126ca2/</id>
    <link href="https://blog.yuki.sh/posts/976864126ca2/"/>
    <published>2023-12-20T11:12:40.000Z</published>
    <summary>
      <![CDATA[<p>就在前几天，发生了件令我十分头疼的事情。我搭建的 P 站反代服务被人恶意调用，在<strong>国内</strong>请求了百万张 R18 图片，险些造成严重的后果，不过还好我及时进行了处理。</p>
<p>正当我松了口气，准备来张涩图奖励下自己的时候，机器人却一直提示图片请求失败。进首页一看，用了快 3 年的 API 因为作者被爬虫抓烦了，决定停止服务。<del>这下心情更不好了.jpg</del></p>]]>
    </summary>
    <title>随手做了一个 Pixiv 图片小工具</title>
    <updated>2026-05-08T01:03:33.377Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="笔记" scheme="https://blog.yuki.sh/categories/note/"/>
    <category term="代码" scheme="https://blog.yuki.sh/tags/code/"/>
    <content>
      <![CDATA[<p>在上个月，我更改了已使用近 9 年的网名，还顺便换了一个新邮箱（感觉自己萌萌哒），这本应该是一件值得开心的事才对……</p><p>但是直到今天，我才突然发现，我的 GitHub 墙居然不绿了！</p><span id="more"></span><h2 id="Why"><a href="#Why" class="headerlink" title="Why?"></a>Why?</h2><p>因为 GitHub 的绿墙，是通过账号所绑定的邮箱，找到仓库与之对应的 Git commit 记录并展示的。</p><p>也就是说，如果我们需要 GitHub 展示出自己的提交记录，就必须要在账号内添加与之对应的邮箱地址。</p><p>但是如果在哪一天，我们的邮箱不再使用了，或者是用户名被修改了，应该如何去纠正已 push 的 commit 信息？</p><h2 id="commit-–amend"><a href="#commit-–amend" class="headerlink" title="commit –amend"></a>commit –amend</h2><p><code>git commit --amend</code> 命令，是纠正此类错误信息最简单的方法。它用于追加提交信息，让你合并缓存区的修改和上一次 commit，而不会创建一条新的提交记录。除此之外，还可以用来编辑上一次的 commit 描述。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git commit --amend --author=&lt;author&gt; -m &lt;msg&gt;</span><br></pre></td></tr></table></figure><p>不过这有一个问题，它只能用于编辑<strong>最后一次的提交</strong>，所以无奈只能去寻找别的出路。</p><h2 id="rebase"><a href="#rebase" class="headerlink" title="rebase"></a>rebase</h2><p><code>git rebase</code> 是一个非常强大的命令，它通常被拿来与 <code>git merge</code> 作对比。与 <code>git merge</code> 一致，<code>git rebase</code> 的目的，也是将一个分支的更改并入到另外一个分支中去。但不同点在于，它可以<strong>编辑历史</strong>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase -i &lt;options&gt;</span><br></pre></td></tr></table></figure><p>当我们使用 rebase 命令进行变基操作后，就可以将 <code>pick</code> 修改为 <code>reword</code> 或者 <code>edit</code> 来编辑对应 commit 信息。</p><p>但是这也有一个问题，咱这 3 年来有几千条 commit 记录，一个个的去 rebase 这是不是有点……</p><h2 id="filter-branch"><a href="#filter-branch" class="headerlink" title="filter-branch"></a>filter-branch</h2><p>在查阅了大量资料后，我发现了 <code>git filter-branch</code> 这样一条命令。它是专门用于 Git 历史修正的，能自定义过滤器，修改每一个树下与之相关的提交信息。</p><p>Jesus，这与前面的两个命令比起来，简直就是降维打击。也难怪在官方文档一栏中，会将 <code>filter-branch</code> 称之为 <strong>The Nuclear Option（核武器级选项）</strong>。</p><p>事不宜迟，赶紧开始来修改用户名和邮箱地址：</p><figure class="highlight shell"><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">git filter-branch --commit-filter &#x27;</span><br><span class="line">        if [ &quot;$GIT_AUTHOR_EMAIL&quot; = &quot;dc_yuki@qq.com&quot; ];</span><br><span class="line">        then</span><br><span class="line">                GIT_AUTHOR_NAME=&quot;Yuki&quot;;</span><br><span class="line">                GIT_AUTHOR_EMAIL=&quot;admin@yuki.sh&quot;;</span><br><span class="line">                git commit-tree &quot;$@&quot;;</span><br><span class="line">        else</span><br><span class="line">                git commit-tree &quot;$@&quot;;</span><br><span class="line">        fi&#x27; HEAD</span><br><span class="line">git push -f origin master</span><br></pre></td></tr></table></figure><p>好耶，墙又绿了~ ヾ(≧▽≦*)o</p><p><img src="/posts/5cb1b70bc6ec/strive.png" class="lazyload" data-srcset="/posts/5cb1b70bc6ec/strive.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="strive"></p><h2 id="小趣闻"><a href="#小趣闻" class="headerlink" title="小趣闻"></a>小趣闻</h2><p>在我查阅了大量资料，快写完这篇文章的时候，突然发现…… Git 的官方文档，专门有一栏阐述了如何去修改历史记录，与我前面总结的基本无异（摔）。（╯°□°）╯︵ ┻━┻</p><p>还真是印证了 yyx 的那一句话，多看文档.jpg</p><p><img src="/posts/5cb1b70bc6ec/readme.png" class="lazyload" data-srcset="/posts/5cb1b70bc6ec/readme.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="readme"></p>]]>
    </content>
    <id>https://blog.yuki.sh/posts/5cb1b70bc6ec/</id>
    <link href="https://blog.yuki.sh/posts/5cb1b70bc6ec/"/>
    <published>2023-12-09T04:07:27.000Z</published>
    <summary>
      <![CDATA[<p>在上个月，我更改了已使用近 9 年的网名，还顺便换了一个新邮箱（感觉自己萌萌哒），这本应该是一件值得开心的事才对……</p>
<p>但是直到今天，我才突然发现，我的 GitHub 墙居然不绿了！</p>]]>
    </summary>
    <title>修改 Git 提交记录中的用户名及邮箱信息</title>
    <updated>2026-05-08T01:03:33.375Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="笔记" scheme="https://blog.yuki.sh/categories/note/"/>
    <category term="代码" scheme="https://blog.yuki.sh/tags/code/"/>
    <category term="JavaScript" scheme="https://blog.yuki.sh/tags/javascript/"/>
    <content>
      <![CDATA[<p>在 Java 中，我们可以通过 <code>getStackTrace()</code> 来获取调用栈信息。若是 PHP 就更简单了，直接调用 <code>debug_backtrace()</code> 函数就可以打印出各类数据。</p><p>可是，在 JavaScript 中的表现又如何？说到底，作为一门不依赖于操作系统的脚本语言，究竟能不能<strong>主动获取</strong>到堆栈的相关信息？</p><span id="more"></span><h2 id="什么是调用栈？"><a href="#什么是调用栈？" class="headerlink" title="什么是调用栈？"></a>什么是调用栈？</h2><p>调用栈到底是什么？又能用来做些什么？在什么情况下会用到？</p><p>说来也是巧合，在这之前，我从未想过用 JavaScript 去操作堆栈，因为在平时的工作中，哪怕是后端开发也会很少接触到，更别说是前端了。</p><p>这里就不大费口舌去解释一些底层原理了，简单来说，调用栈是解释器追踪函数执行流的一种机制。当执行环境中调用了多个函数时，通过这种机制，我们能够追踪到哪个函数正在执行，执行的函数体中又调用了哪个函数。</p><p>当函数被调用的时候，解析器就会把这个函数给添加到调用栈中，如果函数里面还有调用其他函数的话，内部被调用的函数就会继续被添加到调用栈中去，最后当函数执行结束便会从调用栈清除。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">greeting</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">sayHi</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sayHi</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;hello world&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">greeting</span>();</span><br></pre></td></tr></table></figure><p>例如上列的代码块，执行顺序是这样的：</p><p>① 执行到函数 <code>greeting()</code> 的时候，把 <code>greeting()</code> 添加到调用栈列表中并执行<br>② 执行 <code>greeting()</code> 函数中调用 <code>sayHi()</code> 函数之前的代码<br>③ 执行到 <code>sayHi()</code> 函数的时候，将 <code>sayHi()</code> 函数添加到调用栈并执行<br>④ <code>sayHi()</code> 函数执行结束，从代码栈中清除，并执行 <code>greeting()</code> 函数之后的代码<br>⑤ <code>greeting()</code> 执行完毕，从调用栈清除<br>⑥ 继续执行接下来的代码</p><p>如果调用栈空间被占满了，就会引发<strong>堆栈溢出</strong>。这也是为什么在实际开发中，不鼓励使用递归的原因，因为一旦处理不当就会导致严重的后果。</p><h2 id="举个栗子"><a href="#举个栗子" class="headerlink" title="举个栗子"></a>举个栗子</h2><p>既然知道了调用栈可以用来追踪函数，在什么情况下会使用到？为了更加直观的说明问题，这里我们可以拿火影来举例，使用 TypeScript 编写方便区分数据类型。 <del>（不会吧不会吧，不会有人没看过火影吧？）</del></p><h3 id="血轮眼"><a href="#血轮眼" class="headerlink" title="血轮眼"></a>血轮眼</h3><p>写轮眼，作为只有宇智波一族获得遗传的血继限界，它并非是天生获得的，而是需要后天开眼，而写轮眼也分为多个阶段，普通（单勾、双勾、三勾）、万花筒、永恒万花筒、轮回眼。</p><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="keyword">enum</span> <span class="title class_">SharinganStage</span> &#123;</span><br><span class="line">  <span class="comment">/** 未开眼 */</span></span><br><span class="line">  none = <span class="number">0</span>,</span><br><span class="line">  <span class="comment">/** 一勾 */</span></span><br><span class="line">  ichi,</span><br><span class="line">  <span class="comment">/** 双勾 */</span></span><br><span class="line">  ni,</span><br><span class="line">  <span class="comment">/** 三勾 */</span></span><br><span class="line">  san,</span><br><span class="line">  <span class="comment">/** 万花筒 */</span></span><br><span class="line">  mangekyou,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>随着持有者的情绪波动，血轮眼会自我进化，而进化至万花筒以上的阶段又会有其独特的瞳术，例如月读、天照等，所以我们创建 <code>Sharingan</code> 类。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Sharingan</span> &#123;</span><br><span class="line">  <span class="comment">/** 阶段 */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="attr">stage</span>: <span class="title class_">SharinganStage</span>;</span><br><span class="line">  <span class="comment">/** 瞳术 */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="attr">skill</span>?: <span class="title class_">Function</span>;</span><br><span class="line"></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">stage</span> = <span class="title class_">SharinganStage</span>.<span class="property">none</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 class="keyword">public</span> <span class="title function_">evolution</span>(): <span class="built_in">void</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">stage</span>++;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">stage</span> === <span class="title class_">SharinganStage</span>.<span class="property">mangekyou</span>) &#123;</span><br><span class="line">      <span class="comment">// <span class="doctag">TODO:</span> 获取随机瞳术</span></span><br><span class="line">      <span class="comment">// this.skill = getRandomSkill();</span></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><h3 id="瞳术"><a href="#瞳术" class="headerlink" title="瞳术"></a>瞳术</h3><p>可以看到，<code>skill</code> 并不是非空字段，那么瞳术要从何而来？其实这里的瞳术我们可以简单理解为就是一个 <code>function</code> 函数，根据项目的实际需求可以有以下两种设计方案</p><p>第一种可以单独创建一个文件夹，专门用来存放各类 <strong>index.ts</strong> 文件，这有点类似 <strong>component</strong> 或 <strong>plugin</strong> 的模式：</p><figure class="highlight text"><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><br><span class="line">├─ tsukuyomi.ts        月读</span><br><span class="line">├─ amaterasu.ts        天照</span><br><span class="line">├─ kamui.ts            神威</span><br><span class="line">└─ kagutsuchi.ts       加具土命</span><br></pre></td></tr></table></figure><p>第二种可以直接在 <strong>index.ts</strong> 创建对应函数并将其存入数组：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">tsukuyomi</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">amaterasu</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">kamui</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">kagutsuchi</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> skills = [tsukuyomi, amaterasu, kamui, kagutsuchi];</span><br></pre></td></tr></table></figure><p>现在我们就可以根据代码实际情况，来编写出 <code>getRandomSkill</code> 函数，调用即可随机获取一种瞳术并返回。</p><h3 id="二柱子"><a href="#二柱子" class="headerlink" title="二柱子"></a>二柱子</h3><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><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"><span class="keyword">class</span> <span class="title class_">Ninja</span> &#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="attr">leftEye</span>: <span class="built_in">unknown</span>;</span><br><span class="line">  <span class="keyword">public</span> <span class="attr">rightEye</span>: <span class="built_in">unknown</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="keyword">public</span> <span class="attr">name</span>: <span class="built_in">string</span></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">kill</span>(<span class="attr">ninja</span>: <span class="title class_">Ninja</span>): <span class="built_in">void</span> &#123;</span><br><span class="line">    ninja.<span class="title function_">die</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">die</span>(): <span class="built_in">void</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">say</span>(<span class="string">&#x27;人被杀就会死&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">say</span>(<span class="attr">word</span>: <span class="built_in">string</span>): <span class="built_in">void</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(word);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而宇智波一族出生就继承了血轮眼，在其产生极为强烈的情感时就会让血轮眼进化，而杀死至亲之人就是其中之一，我们接着继承 <code>Ninja</code> 类创建宇智波类</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Uchiha</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Ninja</span> &#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="attr">leftEye</span>: <span class="title class_">Sharingan</span>;</span><br><span class="line">  <span class="keyword">public</span> <span class="attr">rightEye</span>: <span class="title class_">Sharingan</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="attr">name</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(<span class="string">`宇智波<span class="subst">$&#123;name&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">leftEye</span> = <span class="keyword">new</span> <span class="title class_">Sharingan</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">rightEye</span> = <span class="keyword">new</span> <span class="title class_">Sharingan</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/** 重写 kill 方法 */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">kill</span>(<span class="attr">ninja</span>: <span class="title class_">Ninja</span>): <span class="built_in">void</span> &#123;</span><br><span class="line">    ninja.<span class="title function_">die</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">dokidoki</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">dokidoki</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">leftEye</span>.<span class="title function_">evolution</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">rightEye</span>.<span class="title function_">evolution</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="现在是幻想时间"><a href="#现在是幻想时间" class="headerlink" title="现在是幻想时间"></a>现在是幻想时间</h3><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><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">const</span> sasuke = <span class="keyword">new</span> <span class="title class_">Uchiha</span>(<span class="string">&#x27;佐助&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> itachi = <span class="keyword">new</span> <span class="title class_">Uchiha</span>(<span class="string">&#x27;鼬&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 二柱子成功获得了天照与加具土命</span></span><br><span class="line">sasuke.<span class="title function_">kill</span>(itachi);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obito = <span class="keyword">new</span> <span class="title class_">Uchiha</span>(<span class="string">&#x27;带土&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 二柱子发动了天照 ⚛_⚛</span></span><br><span class="line">sasuke.<span class="property">leftEye</span>.<span class="property">skill</span>!();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 哼哼哼啊啊啊啊啊啊啊啊</span></span><br><span class="line">obito.<span class="title function_">say</span>(<span class="string">&#x27;烫烫烫锟斤拷&#x27;</span>);</span><br><span class="line"><span class="comment">// 带土发动了神威 ✇_✇</span></span><br><span class="line">obito.<span class="property">rightEye</span>.<span class="property">skill</span>!();</span><br><span class="line"><span class="comment">// 带土活了下来，天照又没能烫死人</span></span><br></pre></td></tr></table></figure><p>乍一看没什么问题对吧？但是代码并不能像岸本老贼那样按剧本出牌，在程序（观众）眼里，二柱子根本就不知道自己开眼后获得了什么瞳术。<del>不过在见到带土后，二柱子也确实不知道釉把天照给了自己。</del></p><h3 id="我要五彩斑斓的黑"><a href="#我要五彩斑斓的黑" class="headerlink" title="我要五彩斑斓的黑"></a>我要五彩斑斓的黑</h3><p>不知道是什么瞳术就不知道嘛，知道这玩意又有啥用？</p><p>问得好，也许对程序员（读者）而言，这种事情怎么样都无所谓。但是岸本（产品）不一样，你永远都不知道下一话会添加哪些新的设定（需求），剧情会有什么新的展开，一切都充满了未知。</p><p>例如这个时候，就有了一个新的设定，在发动瞳术的时候需要<strong>大声喊出瞳术的名字</strong>。在我们之前写好的代码中，二柱子如何去获取到 <code>kill</code> 的相关信息？</p><p>要实现也很简单，方法也有许许多多，但这些的前提，都是在理想情况下：</p><ul><li>我可以在 <code>Sharingan</code> 类下面直接追加 <code>name</code> 属性。</li><li>我也可以在 <code>getRandomSkill</code> 调用时返回 <code>name</code> 字段。</li><li>我甚至还能创建 <code>getSkill</code> 函数，在 <code>evolution</code> 调用时随机生成 <code>name</code> 主动获取指定函数。</li><li>…</li></ul><p>这样会有什么问题？首先 <code>Sharingan</code> 的 <code>skill</code> 属性本身就是不是非空字段，我们并不知道 <code>skill</code> 会在何时被生成、何时被获取以及何时被调用。</p><p>如果在 <code>skill</code> 为空的情况下擅自调用，基于现有的代码，必然要做大量的修改，添加大量的条件语句，来防止意料之外的情况发生。而且前提是 <code>skill</code> 必须返回的是一个函数，如果 <code>getRandomSkill</code> 返回的不是一个函数，而是一个模块又会怎么样？</p><p>所以我们静下心来仔细一想，<code>skill</code> 被执行的共同点是什么？文件名或函数名就是其 <code>name</code> 的唯一标识，是不是我们并不用将一个无关紧要的字符串传过来传过去？如果能直接获取到调用栈，是不是也不用将以前已有的逻辑推倒重做？</p><p>正是因为在某次开发的过程中，我遇到了类似的情况，所以就有了操作调用栈这个想法。当函数调用另一个函数的时候，获取调用栈直接输出被调用者的信息，这是最简单也是最便捷的方案。</p><h2 id="如何获取调用栈"><a href="#如何获取调用栈" class="headerlink" title="如何获取调用栈"></a>如何获取调用栈</h2><p>我当时立即在 MDN 查阅了 stack、trace 等关键字的相关 API，不过没有找到令人满意的结果。JavaScript 并没有提供主动获取调用栈的方法，但是我找到了 <code>Error.prototype.stack</code> 这么一个奇怪的东西。</p><p>该特性<strong>并不是 JavaScript 的标准</strong>，最初是由 Firefox 定义，作为 <code>Error</code> 对象的栈属性为其提供了一种函数追踪方式。虽然没有标准的定义，但后来 Safari、Opera、Chrome 也都定义了自己的 <code>error.stack</code> 格式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">trace</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;stack&#x27;</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error.<span class="property">stack</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">function</span> <span class="title function_">b</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">trace</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_">a</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">b</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">a</span>();</span><br></pre></td></tr></table></figure><p>例如上面这段代码，我们可以直接得到调用栈相关信息，下面分别是 <strong>Chrome</strong> 与 <strong>Firefox</strong> 的输出：</p><figure class="highlight tex"><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">Error: stack</span><br><span class="line">  at trace (index.js:3:13)</span><br><span class="line">  at b (index.js:11:5)</span><br><span class="line">  at a (index.js:15:5)</span><br></pre></td></tr></table></figure><figure class="highlight tex"><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">trace@file:///index.js:3:13</span><br><span class="line">b@file:///index.js:11:5</span><br><span class="line">a@file:///index.js:15:5</span><br></pre></td></tr></table></figure><h2 id="自定义输出格式"><a href="#自定义输出格式" class="headerlink" title="自定义输出格式"></a>自定义输出格式</h2><p>虽然通过 <code>error.stack</code> 我们成功获取到了调用栈信息，但是新的问题又出现了，各大 runtime 对于 <code>stack</code> 并没有一个标准。可以看到上面输出的文本格式都有所不同，而且仅仅通过字符串，也并不满足我们的需求。</p><p>正当我快要放弃的时候，我又发现了一个奇怪的东西。V8 提供了一个 <code>Error.prepareStackTrace()</code> 的函数，允许用户提供自定义的堆栈跟踪格式。</p><p>这不正是我们想要的么！而且 V8 还提供了各种各样的方法，不管是函数名、文件名、包括代码行数和内部属性，都可以全部获取。有了 <code>prepareStackTrace()</code> 我们甚至可以实现各种各样比较 cool 的东西，例如之后我自己实现了一个 module 模块热更新。</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="keyword">function</span> <span class="title function_">getStack</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> orig = <span class="title class_">Error</span>.<span class="property">prepareStackTrace</span>;</span><br><span class="line">  <span class="title class_">Error</span>.<span class="property">prepareStackTrace</span> = <span class="function">(<span class="params">_, stack</span>) =&gt;</span> stack;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> error = <span class="keyword">new</span> <span class="title class_">Error</span>();</span><br><span class="line">  <span class="keyword">const</span> stack = error.<span class="property">stack</span>;</span><br><span class="line">  <span class="title class_">Error</span>.<span class="property">prepareStackTrace</span> = orig;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> stack;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样，我们就可以使用 <code>getStack()</code> 获取调用栈信息了。需要注意的是，<code>Error.prepareStackTrace</code> 一定要在获取 <code>stack</code> 后将其还原，不然你整个项目的 <code>Error</code> 格式就全变了。</p><h2 id="callsite"><a href="#callsite" class="headerlink" title="callsite"></a>callsite</h2><p>上面那段代码在我的项目里已经使用了 3 年，最近在写这篇文章的时候，无意间发现了一个名为 <a href="https://github.com/tj/callsite">callsite</a> 的库。虽然已经 7 年没更新，但是周下载量居然有 2,000,000+，一看居然还是 tj 大佬的作品。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> orig = <span class="title class_">Error</span>.<span class="property">prepareStackTrace</span>;</span><br><span class="line">  <span class="title class_">Error</span>.<span class="property">prepareStackTrace</span> = <span class="keyword">function</span> (<span class="params">_, stack</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> stack;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">var</span> err = <span class="keyword">new</span> <span class="title class_">Error</span>();</span><br><span class="line">  <span class="title class_">Error</span>.<span class="title function_">captureStackTrace</span>(err, <span class="variable language_">arguments</span>.<span class="property">callee</span>);</span><br><span class="line">  <span class="keyword">var</span> stack = err.<span class="property">stack</span>;</span><br><span class="line">  <span class="title class_">Error</span>.<span class="property">prepareStackTrace</span> = orig;</span><br><span class="line">  <span class="keyword">return</span> stack;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>源码非常简单，只有上面 9 行，和我自己写的几乎没有区别，不由得感叹人家大佬 7 年前就玩转 JavaScript 了，看来我的学习之路还远远不够呀~</p>]]>
    </content>
    <id>https://blog.yuki.sh/posts/ff591b515b27/</id>
    <link href="https://blog.yuki.sh/posts/ff591b515b27/"/>
    <published>2023-01-04T13:47:40.000Z</published>
    <summary>
      <![CDATA[<p>在 Java 中，我们可以通过 <code>getStackTrace()</code> 来获取调用栈信息。若是 PHP 就更简单了，直接调用 <code>debug_backtrace()</code> 函数就可以打印出各类数据。</p>
<p>可是，在 JavaScript 中的表现又如何？说到底，作为一门不依赖于操作系统的脚本语言，究竟能不能<strong>主动获取</strong>到堆栈的相关信息？</p>]]>
    </summary>
    <title>如何在 JavaScript 中获取调用栈</title>
    <updated>2026-05-08T01:03:33.375Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="教程" scheme="https://blog.yuki.sh/categories/course/"/>
    <category term="娱乐" scheme="https://blog.yuki.sh/tags/fun/"/>
    <content>
      <![CDATA[<p>为什么要去美化 steam 主页？这是一个值得考虑的问题。</p><p>不管是以前的 QQ 空间，还是现在的 Github，哪怕是自己的电脑桌面，只要当自己闲下心来，看着这些位置空荡荡的什么东西都没有，都会不经想着去折腾一番。亦或者是人的本能？毕竟爱美之心人皆有之，就算是猛男，也许在内心的某处也有着一颗少女心（笑）</p><span id="more"></span><h2 id="少废话，先看东西！"><a href="#少废话，先看东西！" class="headerlink" title="少废话，先看东西！"></a>少废话，先看东西！</h2><p><img src="/posts/daf9288c81ba/artwork.gif" class="lazyload" data-srcset="/posts/daf9288c81ba/artwork.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="artwork"></p><h2 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h2><p>我们都知道，steam 并不能自定义修改个人资料背景，所使用的背景只能是通过游戏掉落或者点数商店购买的特定图片。</p><p>而且就算换上了特定的背景图也毫无美感，中间永远是被默认的展柜所遮盖，所以大多数背景图都是左右角色图，中间置空的排版。</p><p><img src="/posts/daf9288c81ba/shop.png" class="lazyload" data-srcset="/posts/daf9288c81ba/shop.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="shop"></p><p>这个时候就需要用到 steam 的创意工坊，我们可以使用 ps 通过特定的坐标来裁剪图片并上传艺术作品，最后在个人主页将作品集展示即可达到 banner 的效果。<del>好了，相信你已经学会如何美化个人主页了，本次教程到此结束，感谢大家的观看。</del></p><h2 id="才怪呢"><a href="#才怪呢" class="headerlink" title="才怪呢"></a>才怪呢</h2><p>原理看似很简单，但要想真正达到满意的效果却很困难，不然为什么我要单独写一篇 steam 的美化教程咧？</p><p>不管是桌面美化，还是 Github 美化，无非是安装软件，修改文件等操作，几乎都是大同小异有手就行，steam 却稍微有那么<strong>亿点点</strong>不一样。</p><p>例如随手在网上搜索 steam 美化的相关教程，你搜到的最终效果可能是类似这样的</p><p>二等分</p><p><img src="/posts/daf9288c81ba/bisection.gif" class="lazyload" data-srcset="/posts/daf9288c81ba/bisection.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="bisection"></p><p>五等分</p><p><img src="/posts/daf9288c81ba/quinquesection.gif" class="lazyload" data-srcset="/posts/daf9288c81ba/quinquesection.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="quinquesection"></p><p>甚至还有十等分，不过这种比较少见，这里我就不作图演示了，大家知道有这么个操作就行。<del>一个个做实在是太麻烦了。</del></p><p>虽然上面的效果与文章开头的展示有所差异，但乍一看也蛮可爱的，似乎并没有任何影响，但实际上这种等分操作都是已经**「过时」**的方法。</p><p>这种切片的美化方式主要是利用**「创意工坊展柜」<strong>与</strong>「游戏截图展柜」<strong>实现的，于文章开头给出的</strong>「艺术作品展柜」**有着以下区别：</p><ul><li>必须测量特定的坐标轴对图片进行切割</li><li>需要修改图片十六进制数据</li><li>上传图片时需要 js 注入，否则无法正常选取</li><li>会在顶部栏显示展柜字段，若是创意工坊还会将所有作品提交数显示</li></ul><p>操作步骤繁琐不说，如果仅仅是想实现 banner 效果还会占用展柜的位置，例如你只是想单纯的在第一行显示动态展柜，第二行显示创意作品或者截图，并不能做到相互兼容，除非用点数扩容。</p><h2 id="为什么会有如此差异"><a href="#为什么会有如此差异" class="headerlink" title="为什么会有如此差异"></a>为什么会有如此差异</h2><p>因为在以前 steam 并没有提供能够将单个作品独占一行展示的方案，我印象中大概是一年前的一次更新，加入了「精选艺术作品展柜」才能将图片完整的展示出来，而不用去切割留下几条黑边。</p><p>尽管过去了这么久，可现在网上搜到的教程几乎还是以前的方法，也没有对图片切割做出过多的说明和解释，于是便有了这篇文章。</p><p>我还是昨天无意间才知道淘宝上还有人专门收费做主页美化，有黑边的普通套餐 10r+，没黑边的是高级套餐，50r 不等，过于生草 w</p><p>这么一说，是不是不带黑边的就一定比带黑边的要好？</p><p>当然不是，任何事物存在即合理。图片上传是有大小限制的，有时候因为 gif 图尺寸过大，但又不想让画面丢失，所以也会出现需要切割的情况。不过图片太大了过分切割也会导致一些问题，例如加载时间不一致会导致 gif 动画没有<strong>同时播放</strong>。</p><h2 id="制作图片作品"><a href="#制作图片作品" class="headerlink" title="制作图片作品"></a>制作图片作品</h2><p>作品主要分两种，一种是拿任意图片，上传后直接放到展柜，比较简单，但看着会比较违和。另一种是将 steam 原背景切图，在切片上添加你喜欢的任意素材，虽然比较麻烦但可以达到背景一致，也比较耐看。</p><p>不会用 ps 怎么办？网上有很多别人做好的素材，找素材跟着下面的教程上传就行,如果想制作动图，可以找绿幕素材在 pr 进行合成。<del>更简单的连素材都不用上传，去别人创意工坊窃一个。</del></p><p>我会先介绍创意工坊和截图的上传，如果不想图片留黑边，可以直接跳到 <a href="#%E8%89%BA%E6%9C%AF%E4%BD%9C%E5%93%81">艺术作品</a> 查阅。</p><p>在开始前，你需要先在<strong>浏览器打开</strong> <a href="https://steamcommunity.com/sharedfiles/edititem/767/3/">上传页面</a>，不论你的图片是想添加到创意工坊还是截图或者艺术作品，都是通过这个地址上传，最终通过 js 注入修改参数，所以不能在 steam 客户端操作，因为无法调用控制台。</p><p>你可能会需要用到的网站：</p><ul><li><a href="https://hexed.it/">https://hexed.it/</a> 图片十六进制在线编辑</li><li><a href="https://ezgif.com/">https://ezgif.com/</a> gif 在线编辑（压缩、视频转 gif 等）</li><li><a href="https://steam.design/">https://steam.design/</a> steam 壁纸在线切片</li></ul><h3 id="创意工坊作品"><a href="#创意工坊作品" class="headerlink" title="创意工坊作品"></a>创意工坊作品</h3><p>在此之前，请确保你有基础的 p 图知识。这里不教怎么使用 ps，只给出具体坐标参数，可以在 b 站或油管上搜索相关教程。</p><h4 id="切图"><a href="#切图" class="headerlink" title="切图"></a>切图</h4><p>选择一张图片，将宽度调整为 <strong>766 px</strong>，新建参考线板面，列数 5，装订线 <strong>4 px</strong>，切割五等分。</p><h4 id="改图"><a href="#改图" class="headerlink" title="改图"></a>改图</h4><p>该步骤存疑，我搜遍了外网也没有找到一个可靠的说法，据说部分用户不修改原图像数据会导致显示异常。</p><p>修改十六进制数据这一步大多数人应该都是可以跳过的，离谱的是百度上搜出来的修改参数和谷歌上搜到的还不一样。这里以外网提供的参数为主，我们先打开 <a href="https://hexed.it/">hexed</a> 网页：</p><ol><li>点击左上角「打开文件」，并加载所需的图像：<br><img src="/posts/daf9288c81ba/hexed_open.png" class="lazyload" data-srcset="/posts/daf9288c81ba/hexed_open.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="hexed open"></li><li>滚动到页面最底部：<br><img src="/posts/daf9288c81ba/hexed_bottom.gif" class="lazyload" data-srcset="/posts/daf9288c81ba/hexed_bottom.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="hexed bottom"></li><li>将文件的最后一个字节替换为 <strong>21</strong> 后，点击「导出」保存修改后的图像：<br><img src="/posts/daf9288c81ba/hexed_replace.gif" class="lazyload" data-srcset="/posts/daf9288c81ba/hexed_replace.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="hexed replace"><br><img src="/posts/daf9288c81ba/hexed_export.png" class="lazyload" data-srcset="/posts/daf9288c81ba/hexed_export.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="hexed export"></li></ol><p>对剩余的四张图像重复此步骤</p><h4 id="进入控制台"><a href="#进入控制台" class="headerlink" title="进入控制台"></a>进入控制台</h4><p>在浏览器按下 <strong>f12</strong>（safari 是 <strong>option + command + c</strong>） 进入开发者工具，选择 <strong>console</strong> 进入控制台。</p><p>确保图片上传完毕，在控制台输入下列代码后按下回车，在页面点击「保存并继续」，以下代码会将图片作为<strong>创意工坊作品</strong>上传：</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></pre></td><td class="code"><pre><span class="line">$J(<span class="string">&#x27;[name=consumer_app_id]&#x27;</span>).<span class="title function_">val</span>(<span class="number">480</span>);</span><br><span class="line">$J(<span class="string">&#x27;[name=file_type]&#x27;</span>).<span class="title function_">val</span>(<span class="number">0</span>);</span><br><span class="line">$J(<span class="string">&#x27;[name=visibility]&#x27;</span>).<span class="title function_">val</span>(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>审核完毕后，在首页设置直接选择「创意工坊展柜」即可</p><h3 id="截图作品"><a href="#截图作品" class="headerlink" title="截图作品"></a>截图作品</h3><p>如果你不会切图，在看了上面的教程后，可能会让你头晕目眩。不会没关系，「截图展柜」与「艺术作品展柜」都是二等分，可直接在 <a href="https://steam.design/">steam.design</a> 下载素材不用手动切。</p><p>如果你习惯了使用 ps 不想在线下载素材，也可以将左图宽切为 <strong>506 px</strong>，右图宽 <strong>100 px</strong>，高度无要求。</p><h4 id="进入控制台-1"><a href="#进入控制台-1" class="headerlink" title="进入控制台"></a>进入控制台</h4><p>在浏览器按下 <strong>f12</strong>（safari 是 <strong>option + command + c</strong>） 进入开发者工具，选择 <strong>console</strong> 进入控制台。</p><p>确保图片上传完毕，在控制台输入下列代码后按下回车，在页面点击「保存并继续」，以下代码会将图片作为<strong>截图作品</strong>上传：</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></pre></td><td class="code"><pre><span class="line">$J(<span class="string">&#x27;#image_width&#x27;</span>).<span class="title function_">val</span>(<span class="number">1000</span>).<span class="title function_">attr</span>(<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">$J(<span class="string">&#x27;#image_height&#x27;</span>).<span class="title function_">val</span>(<span class="number">1</span>).<span class="title function_">attr</span>(<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">$J(<span class="string">&#x27;[name=file_type]&#x27;</span>).<span class="title function_">val</span>(<span class="number">5</span>);</span><br></pre></td></tr></table></figure><p>审核完毕后，在首页设置直接选择「截图展柜」即可。</p><h3 id="艺术作品"><a href="#艺术作品" class="headerlink" title="艺术作品"></a>艺术作品</h3><p>艺术作品与截图的尺寸相同，都是二等分，也可直接在 <a href="https://steam.design/">steam.design</a> 下载素材无须手动切图，也不用其它额外操作。</p><h4 id="进入控制台-2"><a href="#进入控制台-2" class="headerlink" title="进入控制台"></a>进入控制台</h4><p>在浏览器按下 <strong>f12</strong>（safari 是 <strong>option + command + c</strong>） 进入开发者工具，选择 <strong>console</strong> 进入控制台。</p><p>确保图片上传完毕，在控制台输入下列代码后按下回车，在页面点击「保存并继续」，以下代码会将图片的<strong>尺寸改变</strong>：</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">$J(<span class="string">&#x27;#image_width&#x27;</span>).<span class="title function_">val</span>(<span class="number">1000</span>).<span class="title function_">attr</span>(<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">$J(<span class="string">&#x27;#image_height&#x27;</span>).<span class="title function_">val</span>(<span class="number">1</span>).<span class="title function_">attr</span>(<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;&#x27;</span>);</span><br></pre></td></tr></table></figure><p>审核完毕后，在首页设置选择「艺术作品展柜」或「精选艺术作品展柜」即可，对应二等分和完整图。</p><h3 id="关于略缩图"><a href="#关于略缩图" class="headerlink" title="关于略缩图"></a>关于略缩图</h3><p>为什么上传的艺术品和截图在展柜选取和作品编辑页面只有一条细细的黑线？</p><p>因为使用代码强制修改了图片的宽高，所以不能显示略缩图，这是正常的。</p><p>图片宽高不改行不行？</p><p>可以不改，而且图片上传后在首页也可以正常显示，代码中带 <code>width</code> 与 <code>height</code> 的部分就是修改宽高值，去掉也没影响。但会导致图片<strong>无法自适应</strong>整个展柜的大小，左右会留有间距。</p><h3 id="其它补充"><a href="#其它补充" class="headerlink" title="其它补充"></a>其它补充</h3><p>「评测指南」也可以使用类似的方式实现动态展柜，不过比较少见。指南的上传代码如下，步骤可参考前面的教程，在这里不过多赘述。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$J(<span class="string">&#x27;[name=consumer_app_id]&#x27;</span>).<span class="title function_">val</span>(<span class="number">480</span>);</span><br><span class="line">$J(<span class="string">&#x27;[name=file_type]&#x27;</span>).<span class="title function_">val</span>(<span class="number">9</span>);</span><br><span class="line">$J(<span class="string">&#x27;[name=visibility]&#x27;</span>).<span class="title function_">val</span>(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>当然，除了在上面提到的代码段，你可能也见过下面的这种：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> num = <span class="variable language_">document</span>.<span class="title function_">getElementsByName</span>(<span class="string">&#x27;image_width&#x27;</span>)[<span class="number">0</span>].<span class="property">value</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementsByName</span>(<span class="string">&#x27;image_height&#x27;</span>)[<span class="number">0</span>].<span class="property">value</span> = num - (num - <span class="number">1</span>);</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementsByName</span>(<span class="string">&#x27;image_width&#x27;</span>)[<span class="number">0</span>].<span class="property">value</span> = num * <span class="number">100</span>;</span><br></pre></td></tr></table></figure><p>上列代码段其实等价于</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">$J(<span class="string">&#x27;#image_width&#x27;</span>).<span class="title function_">val</span>(<span class="number">1000</span>).<span class="title function_">attr</span>(<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">$J(<span class="string">&#x27;#image_height&#x27;</span>).<span class="title function_">val</span>(<span class="number">1</span>).<span class="title function_">attr</span>(<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;&#x27;</span>);</span><br></pre></td></tr></table></figure><p>两者没有任何区别，只是写法不同，都是强制修改图片尺寸让其在展柜能自适应，你想用哪种都行。</p><p>如何判断控制台输入的代码是否生效？只要不是返回数字 0 你的代码段都是正常执行的。</p><h2 id="隐藏作品名称"><a href="#隐藏作品名称" class="headerlink" title="隐藏作品名称"></a>隐藏作品名称</h2><p>在上传作品之后你可能发现了，艺术作品和截图作品展柜在最下方会默认显示作品名，并且当我们尝试将作品名称设置空格也不行。</p><p>这是因为 steam 在前端，对于作品名有 <code>trim</code> 处理。什么？你说前端？没错，后端<strong>并没有校验</strong>，所以我们完全可以在作品上传时，使用下列代码将作品名置空。</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">v_trim = <span class="function"><span class="params">_</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> _;</span><br><span class="line">&#125;;</span><br><span class="line">$J(<span class="string">&#x27;#title&#x27;</span>).<span class="title function_">val</span>(<span class="string">&#x27; \n&#x27;</span> + <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="title class_">Array</span>(<span class="number">126</span>), <span class="function"><span class="params">_</span> =&gt;</span> <span class="string">&#x27;\t&#x27;</span>).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>));</span><br></pre></td></tr></table></figure><p><code>v_trim</code> 是 steam 的 <code>trim</code> 函数，将其 return 值修改就可以使用 <code>\n</code> 将输入框填满并达到隐藏作品名的效果。</p><p>如果你此前就已经设置了作品名也不用删掉图片重新上传，这段代码在「作品编辑」页面一样适用，可以在控制台输入代码后直接保存。</p>]]>
    </content>
    <id>https://blog.yuki.sh/posts/daf9288c81ba/</id>
    <link href="https://blog.yuki.sh/posts/daf9288c81ba/"/>
    <published>2022-12-05T11:18:03.000Z</published>
    <summary>
      <![CDATA[<p>为什么要去美化 steam 主页？这是一个值得考虑的问题。</p>
<p>不管是以前的 QQ 空间，还是现在的 Github，哪怕是自己的电脑桌面，只要当自己闲下心来，看着这些位置空荡荡的什么东西都没有，都会不经想着去折腾一番。亦或者是人的本能？毕竟爱美之心人皆有之，就算是猛男，也许在内心的某处也有着一颗少女心（笑）</p>]]>
    </summary>
    <title>手把手教你如何美化 Steam 主页</title>
    <updated>2026-05-08T01:03:33.214Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="笔记" scheme="https://blog.yuki.sh/categories/note/"/>
    <category term="代码" scheme="https://blog.yuki.sh/tags/code/"/>
    <category term="JavaScript" scheme="https://blog.yuki.sh/tags/javascript/"/>
    <content>
      <![CDATA[<p>在 Javascript 里，所有的数据类型都是「对象」(object)，这一点与 Java 非常相似，所以很多人都会误以为 JavaScript 是一门面向对象的语言。</p><p>但从严格意义上来讲，Javascript 并不是一门面向对象语言，而是<strong>基于对象</strong>，也可以说是基于原型。Javascript 并没有「类」(class) 的概念，也没有提供像抽象、继承、重载等有关面向对象语言的许多特性。那么，Javascript 是如何通过 object 来实现 class 类似表现的？</p><span id="more"></span><h2 id="对象原型"><a href="#对象原型" class="headerlink" title="对象原型"></a>对象原型</h2><p>可以说 JavaScript 既是一个面向对象的语言又是一个面向过程的语言。</p><p>在 JavaScript 中，通过<strong>在运行时</strong>，给<strong>空对象</strong>附加方法和属性来创建对象，与编译语言如 C++ 和 Java 中常见的通过语法来定义类相反。对象构造后，它可以用作是创建相似对象的原型。</p><p>前面我们有说到，Javascript 里面所有的数据类型都是对象。在这其中，每个对象上都有 <code>__proto__</code> 和 <code>constructor</code> 这两个属性。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> array = [<span class="number">114</span>, <span class="number">514</span>];</span><br><span class="line"><span class="keyword">const</span> number = <span class="number">1919810</span>;</span><br><span class="line"><span class="keyword">const</span> string = <span class="string">&#x27;哼哼哼啊&#x27;</span>;</span><br><span class="line"></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="comment"> * const array = new Array(114, 514);</span></span><br><span class="line"><span class="comment"> * const number = new Number(1919810);</span></span><br><span class="line"><span class="comment"> * const string = new String(&#x27;哼哼哼啊&#x27;);</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(number <span class="keyword">instanceof</span> <span class="title class_">Object</span>); <span class="comment">// -&gt; true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array <span class="keyword">instanceof</span> <span class="title class_">Object</span>); <span class="comment">// -&gt; true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(string <span class="keyword">instanceof</span> <span class="title class_">Object</span>); <span class="comment">// -&gt; true</span></span><br></pre></td></tr></table></figure><p>函数也是对象，较为特殊的是，除了 <code>__proto__</code> 和 <code>constructor</code>，函数还有 <code>prototype</code> 这个独有属性。<code>prototype</code> 是显式原型，<code>__proto__</code> 是隐式原型。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 等价于 var foo = new Function();</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Foo</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Foo</span> <span class="keyword">instanceof</span> <span class="title class_">Object</span>); <span class="comment">// -&gt; true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Foo</span>.<span class="property">__proto__</span> === <span class="title class_">Function</span>.<span class="property"><span class="keyword">prototype</span></span>); <span class="comment">// -&gt; true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Foo</span>.<span class="property"><span class="keyword">prototype</span></span>); <span class="comment">// -&gt; &#123;constructor: ƒ Foo(), [[Prototype]]: Object&#125;</span></span><br></pre></td></tr></table></figure><h2 id="原型链"><a href="#原型链" class="headerlink" title="原型链"></a>原型链</h2><p>对象的 <code>__proto__</code> 属性，是在创建对象时自动添加的，默认值为其构造函数的 <code>prototype</code>。函数的 <code>prototype</code> 属性是定义时自动添加的，默认为 <code>&#123;&#125;</code> 空对象，一层一层的 <code>__proto__</code> 便形成了<strong>原型链</strong>。</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></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;&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Foo</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">test</span> = <span class="keyword">function</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 world&#x27;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> foo = <span class="keyword">new</span> <span class="title class_">Foo</span>();</span><br><span class="line">foo.<span class="title function_">test</span>(); <span class="comment">// -&gt; hello world</span></span><br></pre></td></tr></table></figure><p><code>prototype</code> 的作用，就是包含可以由特定类型的所有实例共享的属性和方法，也就是让该函数所实例化的对象们，都可以找到公用的属性和方法。当我们试图去访问一个对象上的属性时，如果不存在，便会顺着原型链一直往下找，直到找到为止。</p><p>所以，我们可以在字符串上调用 <code>replace()</code>、<code>trim()</code>，能在数组上调用 <code>join()</code>、<code>push()</code>，正是因为顺着原型链找到了对应的方法。如果比较熟悉链表 (linked list)，那么理解起来会特别快。</p><p>值得一提的是，<code>Object</code> 的原型是 <code>null</code>，因为它是原型链的<strong>最顶端</strong>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">__proto__</span> === <span class="literal">null</span>); <span class="comment">// -&gt; true</span></span><br></pre></td></tr></table></figure><h2 id="prototype-与-proto"><a href="#prototype-与-proto" class="headerlink" title="[[prototype]] 与 __proto__"></a><code>[[prototype]]</code> 与 <code>__proto__</code></h2><p>前面我们有说到，所有对象都有 <code>__proto__</code> 属性，可是在浏览器里打印却显示的是 <code>[[prototype]]</code> 而非 <code>__proto__</code>。</p><p>其实 <code>[[prototype]]</code> 和 <code>__proto__</code> 意义相同，均表示对象的内部属性，其值指向对象原型。前者在一些书籍、规范中表示一个对象的原型属性，后者则是在浏览器实现中指向对象原型。</p><p><code>__proto__</code> 并不是语言本身的特性，这是各大厂商具体实现时添加的私有属性。虽然目前很多现代浏览器的 JavaScript 引擎中都提供了这个私有属性，但依旧不建议在生产中使用该属性，避免对环境产生依赖。生产环境中，建议只使用 <code>Object.getPrototypeOf()</code> 方法来获取实例对象的原型。</p><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><p><code>constructor</code> 属性也是对象才拥有的，它是从一个对象指向一个函数，含义就是指向该对象的构造函数。</p><p>每个对象都有构造函数，不过 <code>Function</code> 对象比较特殊，它的构造函数就是它自己（因为 <code>Function</code> 可以看成是一个函数，也可以是一个对象），所有函数最终都是由 <code>Function()</code> 构造函数得来，所以 <code>constructor</code> 属性的指向就是 <code>Function()</code>。</p><h2 id="ES6"><a href="#ES6" class="headerlink" title="ES6"></a>ES6</h2><p>在 ES6 中，新引入了 <code>class</code> 关键字，让定义类更接近传统写法，不过其本质也只是构造函数的另一种写法，最后还是绕不开原型链。</p><p>有兴趣你可以查阅 <a href="/posts/372766c45423/" title="深入浅出 function 与 class">深入浅出 function 与 class</a> 一栏。</p>]]>
    </content>
    <id>https://blog.yuki.sh/posts/0d9c07689977/</id>
    <link href="https://blog.yuki.sh/posts/0d9c07689977/"/>
    <published>2022-11-03T01:34:48.000Z</published>
    <summary>
      <![CDATA[<p>在 Javascript 里，所有的数据类型都是「对象」(object)，这一点与 Java 非常相似，所以很多人都会误以为 JavaScript 是一门面向对象的语言。</p>
<p>但从严格意义上来讲，Javascript 并不是一门面向对象语言，而是<strong>基于对象</strong>，也可以说是基于原型。Javascript 并没有「类」(class) 的概念，也没有提供像抽象、继承、重载等有关面向对象语言的许多特性。那么，Javascript 是如何通过 object 来实现 class 类似表现的？</p>]]>
    </summary>
    <title>prototype、__proto__ 与 constructor 分析</title>
    <updated>2026-05-08T01:03:33.214Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="笔记" scheme="https://blog.yuki.sh/categories/note/"/>
    <category term="代码" scheme="https://blog.yuki.sh/tags/code/"/>
    <category term="JavaScript" scheme="https://blog.yuki.sh/tags/javascript/"/>
    <content>
      <![CDATA[<p>如果你使用过 Swift 或者 C#，那么一定对 <code>var</code> 非常熟悉。它是 variable 或者是 variation 的简写，在编程语言中多用于定义变量的关键字，在一些操作系统中也能见到它的身影。</p><p>在 JavaScript（以下简称 JS）中也可以使用 <code>var</code> 来声明变量，不仅限于此，还有 <code>let</code>、<code>const</code> 共三种方式创建变量。而如今却很少在 JS 中见到 <code>var</code> 的身影，这又是为什么？</p><span id="more"></span><h2 id="var-的由来"><a href="#var-的由来" class="headerlink" title="var 的由来"></a>var 的由来</h2><p><code>var</code> 在使用上与 <code>let</code> 十分相似，大部分情况下我们可以直接用 <code>let</code> 来代替 <code>var</code> 或者用 <code>var</code> 去代替 <code>let</code>，都能达到预期的效果。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;周树人&#x27;</span>;</span><br><span class="line"><span class="keyword">let</span> pseudonym = <span class="string">&#x27;鲁迅&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name); <span class="comment">// -&gt; 周树人</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(pseudonym); <span class="comment">// -&gt; 鲁迅</span></span><br></pre></td></tr></table></figure><p>但实际上，<code>var</code> 却是一座「屎山」。在 JS 设计初期，作为一门网页脚本语言，并未做过多的考虑（从设计到初步实现仅用了 10 天）。但随着时间的推移，项目中大量使用 <code>var</code> 会导致许多诡异的问题，这从而促使了 <code>let</code> 与 <code>const</code> 的诞生。</p><p>现如今，虽然在各大项目中已经很难在看到 <code>var</code> 的身影，但在百度上或者公司的祖传代码里你依然能找到它。如果在百度搜代码，看到有人还在教你使用 <code>var</code> 甚至是 DreamWeaver 写代码… 直接关掉吧，不用看了，那篇文章只是在浪费你的时间。</p><p>这不由得让人想起当年教我的 Java 老师，用的 MyEclipse 2012 + Tomcat 1.6 + JDK 1.6 开发，每次有人提出疑问都蜜汁自信说自己有 10 年开发经验，不可能出错。</p><psw>低情商：用的都是 10 年前的老技术</psw><psw>高情商：10 年开发经验高级工程师</psw><psw>他甚至连 class 都不 new！什么数据都直接塞 Map 里去传值，写 SQL 不换行，还一脸自信的说 你们看我多厉害，一行就写完了（指三张表联查 + 一大堆条件语句不换行）</psw><p>当然，也并不是说使用 <code>var</code> 就完全是一件错事，任何事物存在就有着自身的价值。Babel 以及 TypeScript 等工具在特定情况下，编译出的代码都或多或少会使用到 <code>var</code> 来声明变量，因为有着极强的兼容性。不过，除开这种情况使用 <code>var</code> 就完全没必要了。<del>我可从没说过只要用 var 写代码的都是辣鸡啊，你们不要乱说啊。</del></p><h2 id="作用域"><a href="#作用域" class="headerlink" title="作用域"></a>作用域</h2><p>在 ES6 之前，JS 的变量作用域是非常笼统的，只有<strong>全局变量</strong>与<strong>函数变量</strong>。</p><p>而 <code>var</code> 坑就坑在它没有块级作用域，用 <code>var</code> 声明变量的变量不是函数作用域就是全局作用域。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><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;&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// -&gt; 10</span></span><br></pre></td></tr></table></figure><p>上列就是一个最简单的例子，<code>for</code> 循环都结束了，却仍然可以访问到变量 <code>i</code>。试想一下，如果变量名不是用的 i，而是开发中最为常见的 item 会怎么样？</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;GBK&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Document<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>li1<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>li2<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span>li3<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">var</span> lis = <span class="variable language_">document</span>.<span class="title function_">getElementsByTagName</span>(<span class="string">&#x27;li&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; lis.<span class="property">length</span>; i++) &#123;</span></span><br><span class="line"><span class="language-javascript">        lis[i].<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">          lis[i].<span class="property">style</span>.<span class="property">background</span> = <span class="string">&#x27;skyblue&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">        &#125;);</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><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>再来看这段代码，可以说这段代码在以前是非常常见的，如果你接触编程比较早甚至还觉得非常亲切，<psw>为了复古我甚至都在 script 里加了 type=text/javascript，charset 是万恶的 GBK。</psw> 要实现的功能也很简单，为每个 <code>li</code> 标签加上点击事件，当该 <code>li</code> 被点击时修改当前的背景颜色。</p><p>看着没什么问题对吧？但当你点击 li 元素后，控制台会提示 <code>TypeError: Cannot read properties of undefined (reading &#39;style&#39;)</code> 的错误信息。</p><p>这算是一个老生常谈的问题了，只要以前写过 ES5 都遇到过，也都知道该怎么避免。</p><p>当该段代码执行时仅仅是为标签元素绑定了 click 事件，但并未执行该函数。当页面元素被点击后会从 lis 集合里寻找下标为 i 的元素并修改颜色。但这时 <code>i</code> 的值是多少呢？没错，3，但并不存在下标为 3 的 <code>li</code> 元素，所以就抛 <code>undefined</code> 了。</p><p>要解决这个问题也很简单，使用闭包，因为 <code>var</code> 会受到<strong>函数作用域</strong>的影响。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">var</span> lis = <span class="variable language_">document</span>.<span class="title function_">getElementsByTagName</span>(<span class="string">&#x27;li&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; lis.<span class="property">length</span>; i++) &#123;</span></span><br><span class="line"><span class="language-javascript">    (<span class="keyword">function</span> (<span class="params">j</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      lis[j].<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        lis[j].<span class="property">style</span>.<span class="property">background</span> = <span class="string">&#x27;skyblue&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">      &#125;);</span></span><br><span class="line"><span class="language-javascript">    &#125;)(i);</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>哦，我的老伙计，尽管这段代码能顺利跑起来了，但这只是简单的修改样式。我发誓，如果，我是说如果，要是代码逻辑稍微复杂一点，后面项目维护起来，那一定和写小程序一样难受，就像隔壁苏珊太太的苹果派那样糟糕。</p><h2 id="ES6-YES"><a href="#ES6-YES" class="headerlink" title="ES6 YES"></a>ES6 YES</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><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">const</span> lis = <span class="variable language_">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;li&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; lis.<span class="property">length</span>; i++) &#123;</span></span><br><span class="line"><span class="language-javascript">    lis[j].<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      lis[j].<span class="property">style</span>.<span class="property">background</span> = <span class="string">&#x27;skyblue&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;);</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>这是一种比较现代的写法，<code>let</code> 和 <code>const</code> 都拥有块级作用域，所以完全避免了变量污染的问题。</p><h2 id="重复声明"><a href="#重复声明" class="headerlink" title="重复声明"></a>重复声明</h2><p>除了作用域导致的变量污染，<code>var</code> 还有一个奇怪的特性，就是变量允许重复声明。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> liangfen = <span class="number">1</span>;</span><br><span class="line"><span class="comment">// 我说你吃了两碗凉粉那就是两碗</span></span><br><span class="line"><span class="keyword">var</span> liangfen = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(liangfen); <span class="comment">// -&gt; 2</span></span><br></pre></td></tr></table></figure><p>使用 <code>var</code>，我们可以重复声明一个变量，不管多少次都行。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SyntaxError: Identifier &#x27;liangfen&#x27; has already been declared</span></span><br><span class="line"><span class="keyword">let</span> liangfen = <span class="number">1</span>;</span><br><span class="line"><span class="comment">// 我就吃了一碗粉你凭什么说我吃了两碗？</span></span><br><span class="line"><span class="keyword">let</span> liangfen = <span class="number">2</span>;</span><br></pre></td></tr></table></figure><p>而使用 <code>let</code> 会直接提示语法错误，非常的银杏。</p><h2 id="变量提升"><a href="#变量提升" class="headerlink" title="变量提升"></a>变量提升</h2><p>在 JS 代码开始执行的时候，就会预先处理 <code>var</code> 的变量。也就是说，使用 <code>var</code> 声明的变量会在其<strong>作用域</strong>的开头被定义，与它在代码中定义的位置无关。</p><p>要注意的是，这不包括变量嵌套在函数中的情况，因为函数不管定义在代码的哪一行，都只有在被调用时才会执行。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">message = <span class="string">&#x27;hello world&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(message);</span><br><span class="line"><span class="keyword">var</span> message;</span><br></pre></td></tr></table></figure><p>它与下面这种情况是等价的（var message 被上移至作用域开头）</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="keyword">var</span> message;</span><br><span class="line">message = <span class="string">&#x27;hello world&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(message);</span><br></pre></td></tr></table></figure><p>甚至与这种情况也一样（代码块会被忽略）</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">message = <span class="string">&#x27;hello world&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="literal">false</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> message;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(message);</span><br></pre></td></tr></table></figure><p>这种行为被称之为「提升」(英文为 hoisting 或 raising)，因为所有的 <code>var</code> 都被提升到了作用域的顶部。</p><p>声明会被提升，但是赋值不会。</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="variable language_">console</span>.<span class="title function_">log</span>(message);</span><br><span class="line"><span class="keyword">var</span> message = <span class="string">&#x27;hello world&#x27;</span>;</span><br></pre></td></tr></table></figure><p><code>var message = &#39;hello world&#39;</code> 这行代码包含两个行为：</p><ol><li>使用 <code>var</code> 声明变量</li><li>使用 <code>=</code> 给变量赋值</li></ol><p>变量的声明，在代码刚开始执行的时候，就被提升处理了，但是赋值操作始终是在它出现的地方才起作用。所以这段代码实际上是这样工作的：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> message;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(message); <span class="comment">// -&gt; undefined</span></span><br><span class="line"><span class="comment">// 赋值</span></span><br><span class="line">message = <span class="string">&#x27;hello world&#x27;</span>;</span><br></pre></td></tr></table></figure><p>因为所有的 <code>var</code> 声明都是在作用域开头处理的，我们可以在任何地方引用它们。但是在它们被赋值之前都是 <code>undefined</code>。</p><p>所以就算你写出了这种代码，它也不会报错。</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="variable language_">console</span>.<span class="title function_">log</span>(message); <span class="comment">// -&gt; undefined</span></span><br><span class="line"><span class="keyword">var</span> message;</span><br></pre></td></tr></table></figure><p>而使用 <code>let</code> 会直接提示结构错误，非常的银杏。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ReferenceError: Cannot access &#x27;message&#x27; before initialization</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(message);</span><br><span class="line"><span class="keyword">let</span> message;</span><br></pre></td></tr></table></figure><h2 id="暂时性死区"><a href="#暂时性死区" class="headerlink" title="暂时性死区"></a>暂时性死区</h2><p>只要块级作用域内存在 <code>let</code> 关键字，它所声明的变量就绑定了这个区域，不再受外部的影响。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ReferenceError: Cannot access &#x27;name&#x27; before initialization</span></span><br><span class="line"><span class="keyword">var</span> name;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">  name = <span class="string">&#x27;周树人&#x27;</span>;</span><br><span class="line">  <span class="keyword">let</span> name;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ES6 明确规定，如果区块中存在 <code>let</code> 和 <code>const</code> 关键字，这个区块对这些关键字声明的变量，从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量，就会报错。</p><p>在代码块内，使用 <code>let</code> 关键字声明变量之前，该变量都是不可用的。这在语法上，称为「暂时性死区」（temporal dead zone，简称 TDZ）。</p><p>有些死区比较隐蔽，不太容易发现。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">naming</span>(<span class="params">fullname = <span class="string">`<span class="subst">$&#123;surname&#125;</span>树人`</span>, surname = <span class="string">&#x27;周&#x27;</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> fullname;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;naming start&#x27;</span>); <span class="comment">// -&gt; naming start</span></span><br><span class="line"><span class="comment">// ReferenceError: Cannot access &#x27;surname&#x27; before initialization at naming</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">naming</span>());</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;naming end&#x27;</span>);</span><br></pre></td></tr></table></figure><p>上面代码与前面的有所不同，直到 <code>naming</code> 函数被调用前，程序都是可以正常运行的，不会出现报错，因为其通过了<strong>语法编译</strong>。</p><p>没错，尽管 JS 是一门弱类型的脚本语言，但它并不是解释型语言，其也是有语法编译的，很多人都不知道这一点<del>PHP：你礼貌么？</del>。前面的几个示例中，代码还没开始执行就报语法错误了，这就是最简单的证明。</p><p>而 <code>naming()</code> 在这里报错，是因为参数 <code>fullname</code> 默认值用到了另一个参数 <code>surname</code>，而此时 <code>surname</code> 还没有声明，属于死区。如果将这两个参数的顺序调整，就不会报错，因为此时变量已经声明了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">naming</span>(<span class="params">surname = <span class="string">&#x27;周&#x27;</span>, fullname = <span class="string">`<span class="subst">$&#123;surname&#125;</span>树人`</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> fullname;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;naming start&#x27;</span>); <span class="comment">// -&gt; naming start</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">naming</span>()); <span class="comment">// -&gt; 周树人</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;naming end&#x27;</span>); <span class="comment">// -&gt; naming end</span></span><br></pre></td></tr></table></figure><h2 id="常量"><a href="#常量" class="headerlink" title="常量"></a>常量</h2><p>在 ES6 之前，JS 内所有数据都是<strong>变量</strong>，可以修改任意值。而 <code>const</code> 关键字用来声明<strong>常量</strong>，一旦被声明，值将无法更改。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// TypeError: Assignment to constant variable.</span></span><br><span class="line"><span class="keyword">const</span> name = <span class="string">&#x27;周樟寿&#x27;</span>;</span><br><span class="line">name = <span class="string">&#x27;周树人&#x27;</span>;</span><br></pre></td></tr></table></figure><p>为什么 Object 和 Array 可以随意修改值？因为这两类是引用类型，并非基础数据类型，引用类型内部的值不管怎么变，其<strong>引用地址</strong>都是不变的。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> wifes = [<span class="string">&#x27;镜华&#x27;</span>];</span><br><span class="line"><span class="keyword">const</span> harem = wifes;</span><br><span class="line"></span><br><span class="line">wifes.<span class="title function_">push</span>(<span class="string">&#x27;美美&#x27;</span>);</span><br><span class="line">wifes.<span class="title function_">push</span>(<span class="string">&#x27;未奏希&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(harem); <span class="comment">// -&gt; [&#x27;镜华&#x27;, &#x27;美美&#x27;, &#x27;未奏希&#x27;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(harem === wifes); <span class="comment">// -&gt; true</span></span><br></pre></td></tr></table></figure><p>当然，这是 JS 的基础知识，由此可以引申出<strong>深拷贝</strong>与<strong>浅拷贝</strong>，但并不与本文章相关，便不在此做过多的赘述。<del>先挖个坑，才不是因为懒呢。</del></p>]]>
    </content>
    <id>https://blog.yuki.sh/posts/fdfa570d5245/</id>
    <link href="https://blog.yuki.sh/posts/fdfa570d5245/"/>
    <published>2022-11-02T05:54:08.000Z</published>
    <summary>
      <![CDATA[<p>如果你使用过 Swift 或者 C#，那么一定对 <code>var</code> 非常熟悉。它是 variable 或者是 variation 的简写，在编程语言中多用于定义变量的关键字，在一些操作系统中也能见到它的身影。</p>
<p>在 JavaScript（以下简称 JS）中也可以使用 <code>var</code> 来声明变量，不仅限于此，还有 <code>let</code>、<code>const</code> 共三种方式创建变量。而如今却很少在 JS 中见到 <code>var</code> 的身影，这又是为什么？</p>]]>
    </summary>
    <title>诡计多端的 var</title>
    <updated>2026-05-08T01:03:33.213Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="笔记" scheme="https://blog.yuki.sh/categories/note/"/>
    <category term="代码" scheme="https://blog.yuki.sh/tags/code/"/>
    <category term="JavaScript" scheme="https://blog.yuki.sh/tags/javascript/"/>
    <content>
      <![CDATA[<p>在 JavaScript（以下简称 JS）中，面向对象编程有着使用 <code>function</code> 构造函数或 <code>class</code> 来配合 <code>new</code> 生成实例对象的两种方式。</p><p>尽管我们知道，传统构造函数是 ES5 中的写法，而 <code>class</code> 则是在 ES6 中新引入的关键字，那么这两种方式到底有什么区别？</p><span id="more"></span><h2 id="class-的由来"><a href="#class-的由来" class="headerlink" title="class 的由来"></a>class 的由来</h2><p>在 ES5 中，与传统的面向对象语言（比如 C++ 和 Java）相比，JS 在生成实例对象的写法上有着很大的不同。</p><p>例如下面的例子：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Phone</span>(<span class="params">brand</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">brand</span> = brand;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Phone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">call</span> = <span class="keyword">function</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;The &quot;hello world&quot; from %s.&#x27;</span>, <span class="variable language_">this</span>.<span class="property">brand</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> xiaomi = <span class="keyword">new</span> <span class="title class_">Phone</span>(<span class="string">&#x27;xiaomi&#x27;</span>);</span><br><span class="line">xiaomi.<span class="title function_">call</span>(); <span class="comment">// -&gt; The &quot;hello world&quot; from xiaomi.</span></span><br></pre></td></tr></table></figure><psw>为什么要拿手机来举例子？嗯……随手写的，并没有什么特别深的含义。</psw><psw>Do you guys not have phones?</psw><p>而 ES6 提供了更接近传统语言的写法，引入了类的概念。上面的代码用 <code>class</code> 关键字来编写，就是下面这样。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Phone</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">brand</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">brand</span> = brand;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">call</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">`The &quot;hello world&quot; from <span class="subst">$&#123;<span class="variable language_">this</span>.brand&#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="keyword">const</span> huawei = <span class="keyword">new</span> <span class="title class_">Phone</span>(<span class="string">&#x27;huawei&#x27;</span>);</span><br><span class="line">huawei.<span class="title function_">call</span>(); <span class="comment">// -&gt; The &quot;hello world&quot; from huawei.</span></span><br></pre></td></tr></table></figure><p>可以看到，尽管在写法上有所差异，但上述代码的表现行为是完全一致的。我们可以将 <code>class</code> 看作是构造函数的一个语法糖，不过使用 <code>class</code> 能让代码逻辑更加清晰，使其看起来更像面向对象编程的语法。</p><h2 id="语法糖，但不完全语法糖"><a href="#语法糖，但不完全语法糖" class="headerlink" title="语法糖，但不完全语法糖"></a>语法糖，但不完全语法糖</h2><p>很多开发者在学习初期，会认为 <code>class</code> 仅仅是一个语法糖，因为在不使用它的情况下，完全可以用 <code>function</code> 来实现同样的效果。不过，这两者并不能完全画等号。</p><p>我们通过 <code>class</code> 创建的，是一种特殊的函数对象，其内部的 <code>[[FunctionKind]]</code> 属性被标记为了 <code>&quot;classConstructor&quot;</code>，而 JS 引擎会在许多地方检查 <code>[[IsClassConstructor]]</code> 属性。</p><p>虽然我们无法直接在代码里观察到这些内部槽（internal slots），但可以通过其表现行为来佐证。</p><h3 id="调用方式不同"><a href="#调用方式不同" class="headerlink" title="调用方式不同"></a>调用方式不同</h3><p>与 <code>function</code> 不同，<code>class</code> 必须配合 <code>new</code> 使用，而构造函数是普通函数，不使用 <code>new</code> 也能直接调用。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Smartphone</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">crackWalnuts</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;You are sure?&#x27;</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">// Uncaught TypeError: Class constructor Smartphone cannot be invoked without &#x27;new&#x27;</span></span><br><span class="line"><span class="title class_">Smartphone</span>();</span><br><span class="line"><span class="title class_">Smartphone</span>.<span class="title function_">crackWalnuts</span>();</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Dumbphone</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Dumbphone</span>.<span class="property">crackWalnuts</span> = <span class="keyword">function</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;Crack!&#x27;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Dumbphone</span>();</span><br><span class="line"><span class="title class_">Dumbphone</span>.<span class="title function_">crackWalnuts</span>(); <span class="comment">// -&gt; Crack!</span></span><br></pre></td></tr></table></figure><p>可以看到，尽管 <code>Dumbphone</code> 添加了静态方法，但它依然可以作为函数正常执行，不受任何影响。</p><h3 id="严格模式"><a href="#严格模式" class="headerlink" title="严格模式"></a>严格模式</h3><p>使用 <code>class</code> 创建的类，其内部代码默认启用严格模式。构造函数、静态方法、原型方法、getter 和 setter，都是在严格模式下强制执行，无法手动关闭。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Dumbphone</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">with</span> (<span class="variable language_">console</span>) &#123;</span><br><span class="line">    <span class="title function_">log</span>(<span class="string">&#x27;I am Dumbphone.&#x27;</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">new</span> <span class="title class_">Dumbphone</span>(); <span class="comment">// -&gt; &quot;I am Dumbphone.&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Smartphone</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="comment">// Uncaught SyntaxError: Strict mode code may not include a with statement</span></span><br><span class="line">    <span class="title function_">with</span> (<span class="variable language_">console</span>) &#123;</span><br><span class="line">      <span class="title function_">log</span>(<span class="string">&#x27;I am Smartphone.&#x27;</span>);</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><h3 id="方法不可枚举"><a href="#方法不可枚举" class="headerlink" title="方法不可枚举"></a>方法不可枚举</h3><p>使用 <code>class</code> 会将其原型上的方法的 <code>enumerable</code> 标志定义为 <code>false</code>，使得它们不可被枚举，而通过给 <code>prototype</code> 赋值的方式定义的方法默认是可枚举的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Dumbphone</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Dumbphone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">openCover</span> = <span class="keyword">function</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;打开后盖就能看到我的电池，咱还能再装回去 (*/ω＼*)&#x27;</span>);</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_">Smartphone</span> &#123;</span><br><span class="line">  <span class="title function_">openCover</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;杂鱼❤杂鱼❤～打不开后盖的杂鱼～没有充电器就只能等着关机的杂鱼～&#x27;</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="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Dumbphone</span>.<span class="property"><span class="keyword">prototype</span></span>); <span class="comment">// -&gt; &#123; constructor: ƒ, openCover: ƒ &#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Smartphone</span>.<span class="property"><span class="keyword">prototype</span></span>); <span class="comment">// -&gt; &#123; constructor: ƒ, openCover: ƒ &#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">keys</span>(<span class="title class_">Dumbphone</span>.<span class="property"><span class="keyword">prototype</span></span>)); <span class="comment">// -&gt; [&#x27;openCover&#x27;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">keys</span>(<span class="title class_">Smartphone</span>.<span class="property"><span class="keyword">prototype</span></span>)); <span class="comment">// -&gt; []</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">getOwnPropertyNames</span>(<span class="title class_">Smartphone</span>.<span class="property"><span class="keyword">prototype</span></span>)); <span class="comment">// -&gt; [&#x27;constructor&#x27;, &#x27;openCover&#x27;]</span></span><br></pre></td></tr></table></figure><h2 id="function-的继承"><a href="#function-的继承" class="headerlink" title="function 的继承"></a>function 的继承</h2><p>在看过了前面的内容，相信你对 JS 的类已经有了一个大致的了解。但这个时候你一定有所疑问，构造函数是如何做到继承的？</p><psw>呐，构造函数的继承有四样写法，你知道么？</psw>  <psw>我教给你，记着！这些应该记着。将来做程序员的时候，开发要用。</psw><h3 id="原型链继承"><a href="#原型链继承" class="headerlink" title="原型链继承"></a>原型链继承</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Phone</span>(<span class="params">brand</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">brand</span> = brand;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Phone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">call</span> = <span class="keyword">function</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;The &quot;hello world&quot; from %s.&#x27;</span>, <span class="variable language_">this</span>.<span class="property">brand</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_">XiaomiPhone</span>(<span class="params">model</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">model</span> = model;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">XiaomiPhone</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">Phone</span>(<span class="string">&#x27;Xiaomi&#x27;</span>);</span><br><span class="line"><span class="title class_">XiaomiPhone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">constructor</span> = <span class="title class_">XiaomiPhone</span>;</span><br><span class="line"><span class="title class_">XiaomiPhone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">slogan</span> = <span class="keyword">function</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;为发烧而生&#x27;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> xiaomi = <span class="keyword">new</span> <span class="title class_">XiaomiPhone</span>(<span class="string">&#x27;13 Ultra&#x27;</span>);</span><br></pre></td></tr></table></figure><p>思路：</p><ol><li>将子类的原型对象指向父类的实例对象。</li></ol><p>缺点：</p><ol><li>子类实例化时无法为父类构造函数传入不同参数。</li><li>子类原型中包含的引用值会在所有实例之间共享。</li></ol><h3 id="构造函数继承"><a href="#构造函数继承" class="headerlink" title="构造函数继承"></a>构造函数继承</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Phone</span>(<span class="params">brand, price</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">brand</span> = brand;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">price</span> = price;</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">call</span> = <span class="keyword">function</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;The &quot;hello world&quot; from %s.&#x27;</span>, <span class="variable language_">this</span>.<span class="property">brand</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">function</span> <span class="title function_">XiaomiPhone</span>(<span class="params">model, price</span>) &#123;</span><br><span class="line">  <span class="title class_">Phone</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Xiaomi&#x27;</span>, price);</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">model</span> = model;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">XiaomiPhone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">slogan</span> = <span class="keyword">function</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;为发烧而生&#x27;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> xiaomi = <span class="keyword">new</span> <span class="title class_">XiaomiPhone</span>(<span class="string">&#x27;13 Ultra&#x27;</span>, <span class="number">5999</span>);</span><br></pre></td></tr></table></figure><p>思路：</p><ol><li>让父类构造函数在子类内部运行。</li><li>将父类内部的 <code>this</code> 指向子类的实例。</li></ol><p>缺点：</p><ol><li>父类方法必须定义在构造函数中。</li><li>子类不能访问父类原型上定义的属性与方法。</li></ol><h3 id="组合继承（经典写法）"><a href="#组合继承（经典写法）" class="headerlink" title="组合继承（经典写法）"></a>组合继承（经典写法）</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Phone</span>(<span class="params">brand, price</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">brand</span> = brand;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">price</span> = price;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Phone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">call</span> = <span class="keyword">function</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;The &quot;hello world&quot; from %s.&#x27;</span>, <span class="variable language_">this</span>.<span class="property">brand</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_">XiaomiPhone</span>(<span class="params">model, price</span>) &#123;</span><br><span class="line">  <span class="title class_">Phone</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Xiaomi&#x27;</span>, price);</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">model</span> = model;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">XiaomiPhone</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_">Phone</span>.<span class="property"><span class="keyword">prototype</span></span>);</span><br><span class="line"><span class="title class_">XiaomiPhone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">constructor</span> = <span class="title class_">XiaomiPhone</span>;</span><br><span class="line"><span class="title class_">XiaomiPhone</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">slogan</span> = <span class="keyword">function</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;为发烧而生&#x27;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> xiaomi = <span class="keyword">new</span> <span class="title class_">XiaomiPhone</span>(<span class="string">&#x27;13 Ultra&#x27;</span>, <span class="number">5999</span>);</span><br></pre></td></tr></table></figure><p>这种方式综合了「构造函数继承」和「原型链继承」的优点，是 ES5 中最经典的继承方式（之一）。</p><p>关于构造函数的继承方式，我们可以在网上看到有很多种写法，但从严格意义上来讲，我个人认为其继承机制，基本归纳为上述三种。其它常见的各种写法，都是以上三类方式的变体或组合。例如寄生继承，就是在这其中结合了函数工厂模式。</p><h2 id="class-extends"><a href="#class-extends" class="headerlink" title="class extends"></a>class extends</h2><p>在 ES6 发布之后，类的继承有了质的提升。如果熟悉别的编程语言，那么对 <code>extends</code> 这个关键字一定不会陌生。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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="keyword">class</span> <span class="title class_">Phone</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">brand, price</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">brand</span> = brand;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">price</span> = price;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">call</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">`The &quot;hello world&quot; from <span class="subst">$&#123;<span class="variable language_">this</span>.brand&#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="keyword">class</span> <span class="title class_">XiaomiPhone</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Phone</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">model, price</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(<span class="string">&#x27;Xiaomi&#x27;</span>, price);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">model</span> = model;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">slogan</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;为发烧而生&#x27;</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> xiaomi = <span class="keyword">new</span> <span class="title class_">XiaomiPhone</span>(<span class="string">&#x27;13 Ultra&#x27;</span>, <span class="number">5999</span>);</span><br></pre></td></tr></table></figure><p>泰裤辣！</p><p><img src="/posts/372766c45423/extends.png" class="lazyload" data-srcset="/posts/372766c45423/extends.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="extends"></p>]]>
    </content>
    <id>https://blog.yuki.sh/posts/372766c45423/</id>
    <link href="https://blog.yuki.sh/posts/372766c45423/"/>
    <published>2022-11-01T05:10:53.000Z</published>
    <summary>
      <![CDATA[<p>在 JavaScript（以下简称 JS）中，面向对象编程有着使用 <code>function</code> 构造函数或 <code>class</code> 来配合 <code>new</code> 生成实例对象的两种方式。</p>
<p>尽管我们知道，传统构造函数是 ES5 中的写法，而 <code>class</code> 则是在 ES6 中新引入的关键字，那么这两种方式到底有什么区别？</p>]]>
    </summary>
    <title>深入浅出 function 与 class</title>
    <updated>2026-05-08T01:03:33.207Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yuki</name>
    </author>
    <category term="教程" scheme="https://blog.yuki.sh/categories/course/"/>
    <category term="代码" scheme="https://blog.yuki.sh/tags/code/"/>
    <category term="API" scheme="https://blog.yuki.sh/tags/api/"/>
    <category term="娱乐" scheme="https://blog.yuki.sh/tags/fun/"/>
    <content>
      <![CDATA[<p>众所周知，Pixiv（以下简称 p 站）因各种不可抗力，在部分地区无法正常访问站点，并且图片服务器域名 <code>i.pximg.net</code> 具有盗链保护，只要 <code>Referer</code> 不是来自 p 站的请求，都会返回 403 状态码。那么，我们如何才能通过 URL 直接访问到图片资源？</p><span id="more"></span><h2 id="为什么要代理图片？"><a href="#为什么要代理图片？" class="headerlink" title="为什么要代理图片？"></a>为什么要代理图片？</h2><p>你可能首先就想到了反向代理，可 p 站都被墙了这么多年，既然都准备走反代了，为什么还要单独针对图片？直接搭建反代站点不是更好么？</p><p>首先，反代站点存在着风险，可能会导致 p 站向主机商发送投诉邮件，甚至还会导致 VPS 等服务被终止。其次是包括我在内的大多数人，都只有简单的看<psw>色</psw>图需求，没有特别复杂的使用场景。</p><p>类似例如 <a href="https://api.lolicon.app/">lolicon</a>、<a href="https://www.acgmx.com/">acgmx</a> 等许多基于 p 站的涩图 API，返回的也都是官方图片链接，并不能直接访问，所以便有了单独针对图片代理的需求。</p><h2 id="第三方代理"><a href="#第三方代理" class="headerlink" title="第三方代理"></a>第三方代理</h2><p>若不会自己搭建，也可以直接使用第三方服务。<a href="https://pixiv.cat/">pixiv.cat</a> 就是一个专门用于 p 站图片代理的站点，甚至能直接通过图片 id 请求获取插画。</p><p>我最初也是使用的该服务，不过后续因主域名在我所在的地区被墙，导致服务全挂掉了（我当时还以为是服务器坏了，排查了近十分钟才发现是代理的问题），便有了自建的想法。</p><h2 id="反向代理"><a href="#反向代理" class="headerlink" title="反向代理"></a>反向代理</h2><p>如果你有自己的服务器，可以使用 nginx 进行代理。</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><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="attribute">proxy_cache_path</span> /path/to/cache levels=<span class="number">1</span>:<span class="number">2</span> keys_zone=pximg:<span class="number">10m</span> max_size=<span class="number">10g</span> inactive=<span class="number">7d</span> use_temp_path=<span class="literal">off</span>;</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">443</span> ssl http2;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">ssl_certificate</span> /path/to/ssl_certificate.crt;</span><br><span class="line">    <span class="attribute">ssl_certificate_key</span> /path/to/ssl_certificate.key;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">server_name</span> i.pixiv.cat;</span><br><span class="line">    <span class="attribute">access_log</span> <span class="literal">off</span>;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> / &#123;</span><br><span class="line">    <span class="attribute">proxy_cache</span> pximg;</span><br><span class="line">    <span class="attribute">proxy_pass</span> https://i.pximg.net;</span><br><span class="line">    <span class="attribute">proxy_cache_revalidate</span> <span class="literal">on</span>;</span><br><span class="line">    <span class="attribute">proxy_cache_use_stale</span> <span class="literal">error</span> timeout updating http_500 http_502 http_503 http_504;</span><br><span class="line">    <span class="attribute">proxy_cache_lock</span> <span class="literal">on</span>;</span><br><span class="line">    <span class="attribute">add_header</span> X-Cache-Status <span class="variable">$upstream_cache_status</span>;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> Host i.pximg.net;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> Referer <span class="string">&quot;https://www.pixiv.net/&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">proxy_cache_valid</span> <span class="number">200</span> <span class="number">7d</span>;</span><br><span class="line">    <span class="attribute">proxy_cache_valid</span> <span class="number">404</span> <span class="number">5m</span>;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>没有服务器也没关系，我们可以利用 Cloudflare 的 Workers 白嫖，每日免费提供 10 万次请求，完全满足日常使用。<del>总不至于不够用吧？别冲了，人要冲死了。</del></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">fetch</span>(<span class="params">request</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> url = <span class="keyword">new</span> <span class="title function_">URL</span>(request.<span class="property">url</span>);</span><br><span class="line">    url.<span class="property">hostname</span> = <span class="string">&#x27;i.pximg.net&#x27;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> proxyRequest = <span class="keyword">new</span> <span class="title class_">Request</span>(url, request);</span><br><span class="line">    proxyRequest.<span class="property">headers</span>.<span class="title function_">set</span>(<span class="string">&#x27;Referer&#x27;</span>, <span class="string">&#x27;https://www.pixiv.net/&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">fetch</span>(proxyRequest);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>你如果看过其它提供反向代理的教程，可能已经发现了，我写的 Workers 代码与别人贴的不太一样。这是因为 Workers 早就不推荐使用 CommonJS 规范了，官方文档的示例代码也全都更改为了 ESM 规范。</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></pre></td><td class="code"><pre><span class="line"><span class="title function_">addEventListener</span>(<span class="string">&#x27;fetch&#x27;</span>, <span class="function"><span class="params">event</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> url = <span class="keyword">new</span> <span class="title function_">URL</span>(event.<span class="property">request</span>.<span class="property">url</span>);</span><br><span class="line">  url.<span class="property">hostname</span> = <span class="string">&#x27;i.pximg.net&#x27;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> request = <span class="keyword">new</span> <span class="title class_">Request</span>(url, event.<span class="property">request</span>);</span><br><span class="line">  event.<span class="title function_">respondWith</span>(</span><br><span class="line">    <span class="title function_">fetch</span>(request, &#123;</span><br><span class="line">      <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="title class_">Referer</span>: <span class="string">&#x27;https://www.pixiv.net/&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;),</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这种写法其实也可以，但它已经<strong>过时</strong>了，所以我并不推荐。</p><h2 id="我要色色！"><a href="#我要色色！" class="headerlink" title="我要色色！"></a>我要色色！</h2><p>配置相关 DNS 解析后，就可以直接通过 URL 直接访问 p 站图片了。</p><p>p 站上的原始链接（直接打开或在其他网站使用会返回 403）：<br><a href="https://i.pximg.net/img-master/img/2021/06/10/18/09/04/90457556_p0_master1200.jpg">https://i.pximg.net/img-master/img/2021/06/10/18/09/04/90457556_p0_master1200.jpg</a><br><img src="https://i.pximg.net/img-master/img/2021/06/10/18/09/04/90457556_p0_master1200.jpg" class="lazyload" data-srcset="https://i.pximg.net/img-master/img/2021/06/10/18/09/04/90457556_p0_master1200.jpg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="原始图片链接（无法正常显示）"></p><p>反向代理（i.yuki.sh）（能正常访问）：<br><a href="https://i.yuki.sh/img-master/img/2021/06/10/18/09/04/90457556_p0_master1200.jpg">https://i.yuki.sh/img-master/img/2021/06/10/18/09/04/90457556_p0_master1200.jpg</a><br><img src="https://i.yuki.sh/img-master/img/2021/06/10/18/09/04/90457556_p0_master1200.jpg" class="lazyload" data-srcset="https://i.yuki.sh/img-master/img/2021/06/10/18/09/04/90457556_p0_master1200.jpg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="kokkoro" width="300" /></p><p>嘛，如果你看完文章还是嫌麻烦，也可以直接把 <code>i.yuki.sh</code> 拿去用，给个 blog 点个 star 就行了（笑）</p><h2 id="罗翔说法"><a href="#罗翔说法" class="headerlink" title="罗翔说法"></a>罗翔说法</h2><p>张三在网上，用别人提供的 API 服务，居然在 QQ 群里发 R18 的图片，请问张三这一行为触犯了哪项法律？</p><p><img src="/posts/599ec3ed8eda/offend.png" class="lazyload" data-srcset="/posts/599ec3ed8eda/offend.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="offend"></p><p>技术虽无罪，但还是提醒一下，发送的图片请务必遵守你所在国家及其地区的法律法规，别被请喝茶了。</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><div class="timeline"><div class="timenode"><div class="meta"><p><p>2022-10-03</p></p></div><div class="body"><p>出于几个月前发生的事情（关注 p 站的应该都知道，有人用别人搭建的反代服务盈利），特别强调一下，本来就靠爱发电，某些人别把开源环境弄得越来越恶臭。</p></div></div><div class="timenode"><div class="meta"><p><p>2023-12-16</p></p></div><div class="body"><p>这个周末，我还是和往常一样，惬意地吹着暖气，打着 gal。就在这时，我突然收到了一封来自服务商的邮件，不看不知道，一看吓一跳，我的反代服务突然出现了<strong>大量的</strong>请求。</p><p>是被 DDoS 了？咱平时也没做坏事呀… 带着这个疑问，我立即查看了后台数据，看到 7&#x2F;s 的访问频率已经持续了一个小时。这么低的频率看来并不是被恶意攻击了，而且 IP 也没发现异常，这让我松了一口气。为了保证 API 能够正常使用，我立马将服务做了升级，随后继续打着黄油。</p><p>万万没想到，这种状况一直持续了近 24 小时。起初我其实并不介意，有人能用我的代理服务是一件很开心的事情，因为我做的东西帮到了他人。不过在好奇心的驱使下（到底是谁能冲这么久），我去看了眼日志，当时血压就上来了，喵了个咪的居然一直在请求 <strong>R18</strong> 的图片。</p><div class="img-wrap"><div class="img-bg"><img class="img lazyload" src="calling_card.png" class="lazyload" data-srcset="calling_card.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="image"/></div></div><p>我立即制作了这张图片，用作 API 的固定返回结果，并要求当事人与我取得联系。</p><p>说实话，我从没这么生气过，用着别人的代理服务一直发 R18 的图片，一天请求了百万次都是小事，但是 IP 全部来自国内，各种不堪入目的图片出现在公共场合，这是一件很自豪的事情么？关注 gal 圈的应该都知道，上个月就是因为发生了类似的事件，导致站长受了牵连。</p><p>直到今天，当事人也没有与我取得联系，在群友的帮助下，我大概了解到了事情的经过。起因是一个链接在各大 QQ 群疯传，专门用来获取 R18 图片，而那个 API 正是使用了我的代理服务。当时我正在气头上，准备去找当事人理论理论，点进 blog 一看… 嗯，年纪挺小的一小孩，好像也就没那么生气了。毕竟不知者无罪，也难怪胆子那么大，这件事也算是给我自己起到了一个警示。</p></div></div><div class="timenode"><div class="meta"><p><p>2023-10-20</p></p></div><div class="body"><p>反代服务 <code>pixiv.yuki.sh</code> 已更换为 <code>i.yuki.sh</code>，原地址将作为随机图片 API 来使用，详见 <a href="/posts/976864126ca2/" title="随手做了一个 Pixiv 图片小工具">随手做了一个 Pixiv 图片小工具</a>。</p></div></div></div>]]>
    </content>
    <id>https://blog.yuki.sh/posts/599ec3ed8eda/</id>
    <link href="https://blog.yuki.sh/posts/599ec3ed8eda/"/>
    <published>2022-10-03T06:47:33.000Z</published>
    <summary>
      <![CDATA[<p>众所周知，Pixiv（以下简称 p 站）因各种不可抗力，在部分地区无法正常访问站点，并且图片服务器域名 <code>i.pximg.net</code> 具有盗链保护，只要 <code>Referer</code> 不是来自 p 站的请求，都会返回 403 状态码。那么，我们如何才能通过 URL 直接访问到图片资源？</p>]]>
    </summary>
    <title>Pixiv 图片反向代理</title>
    <updated>2026-05-08T01:03:33.204Z</updated>
  </entry>
</feed>
