<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Vastiny</title>
  
  
  <link href="https://vastiny.com/atom.xml" rel="self"/>
  
  <link href="https://vastiny.com/"/>
  <updated>2026-03-23T20:16:32.429Z</updated>
  <id>https://vastiny.com/</id>
  
  <author>
    <name>yantze</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>发现悬崖及时离开</title>
    <link href="https://vastiny.com/post/ohygb2p83n56p4d0.html"/>
    <id>https://vastiny.com/post/ohygb2p83n56p4d0.html</id>
    <published>2025-12-07T00:55:39.000Z</published>
    <updated>2026-03-23T20:16:32.429Z</updated>
    
    <content type="html"><![CDATA[<p>前几天我看一个采访戒毒者的视频，里面讲到他自己戒毒的经历，“在戒毒所其实不会想着去吸毒，但只要一出戒毒所，就再也控制不住，你脑子里面会调动大部分精力去想所有拿到毒品的渠道和方式”。在后来他所在的地区毒品渠道全面被铲除后，就停止吸毒了，到目前为止已经 4 年。</p><p>看完后有所感触，其实毒品是一个极端的案例，但生活中，无处不在的有一些不健康的沉迷事物。比如我看网络小说，大学被同学推荐后，我就迷上了这种网络小说，后来一发不可收拾，大量时间都花在网络小说上，断断续续挣扎地看了很多年。</p><p>现在我周末有时候会看一些动漫或者电视剧，或者玩游戏，我都是尽量对一些容易沉迷的事物有所戒心。目前已知有两种方式是最容易脱离沉迷的：</p><ul><li>最开始选择的时候，只要多想个一两分钟，中断马上看的反馈，就知道还有其它相对好玩的，或者有意思的，知道这个如果上瘾，就要沉迷很久；</li><li>在沉迷过程中，比如网络小说看到一半，发现有点没意思，或者知道后面要做什么了，这个时候才好脱身。</li></ul><p>如果是开始计划，我把这个看完就不看了，其实 90% 的情况是没办法真的不看的，因为你闭环了一个圈后，还想再闭环一次。平时吃东西吃到饱后会有反馈吃饱了不想吃的链路，但大脑看这些东西的时候，其实负反馈很少，或者需要看足够久才会有负反馈，比如看腻了，身体疲惫，或者其它一些负反馈，不然就会一直看。</p><p>抛开哪些能让人沉迷，实践中发现有几个情况能缓解沉迷一件事情的方法：</p><h5 id="运动"><a href="#运动" class="headerlink" title="运动"></a>运动</h5><p>运动能极大的抛开你沉迷的东西，以前人类狩猎，如果没有抓到猎物，大脑还是会给奖励内啡肽的，不然就没有动力进行下一次的狩猎（有据可查，需要找资料）。所以前一个正反馈循环，和运动这个正反馈循环，这两个循环的对抗确实有一定的概率能增强对沉迷并且自责的事物进行抵抗。</p><h5 id="引领"><a href="#引领" class="headerlink" title="引领"></a>引领</h5><p>有时候确实很难靠自律走出沉迷的漩涡，这个时候需要有人拉一把，把当前的事物换一个环境，这样能很好的避免沉迷，比如上面那个吸毒的案例，只要还进没有那种让你沉迷的条件，就能客观的戒掉沉迷。</p><h3 id="免疫抵抗"><a href="#免疫抵抗" class="headerlink" title="免疫抵抗"></a>免疫抵抗</h3><p>当你认为一个事情有毒，并且熬过来后，大脑和身体就会产生抵抗。但身体和大脑本身又有一些来者不拒的情况。所以就像前面说的，当你意识到前面是悬崖的时候，你对未来可能发生沉迷的事情，只要多想几步，就能避免陷进去几个小时，甚至一天。比如刷短视频，或者玩游戏一天。要做到这一点，具备这几个点效果会比较好：</p><ol><li>每天有一点的运动量，这样能清醒地快速的对大脑有掌控力</li><li>每天和朋友有链接，比如出去玩，或者有一定的生活目标</li><li>关注“环”，找到生活中很多的健康的正反馈，比如创造工具，规划生活，去找到内心平静的事情，并且不断的强化它。</li><li>每件事情先有规划，不在规划内的，记录下来，放到下次（虽然就没有下次了）</li></ol><p>让你沉迷的，总是那种回路短，正反馈强烈且快速的情况容易让身体大脑记住。日常的事情其实很少有这种沉迷的情况，就算有也是频率极低的，一般都是链路长、反馈少甚至失败的情况。链路长的事情本身就很难坚持，但只要找到里面的正反馈，不管今天，还是明天，总会有契机，走到环上去的。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;前几天我看一个采访戒毒者的视频，里面讲到他自己戒毒的经历，“在戒毒所其实不会想着去吸毒，但只要一出戒毒所，就再也控制不住，你脑子里面会调动大部分精力去想所有拿到毒品的渠道和方式”。在后来他所在的地区毒品渠道全面被铲除后，就停止吸毒了，到目前为止已经 4 年。&lt;/p&gt;
&lt;p&gt;</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>我的 Blog SSG 技术栈演进记录</title>
    <link href="https://vastiny.com/post/xtg7shu8xpgy38rk.html"/>
    <id>https://vastiny.com/post/xtg7shu8xpgy38rk.html</id>
    <published>2025-12-03T02:24:59.000Z</published>
    <updated>2026-03-23T20:16:32.430Z</updated>
    
    <content type="html"><![CDATA[<p><font style="color:rgb(0, 0, 0);">都忘记是啥时候了，应该是在博客盛行的尾声，有了自己第一个博客。用了 Blogspot、WordPress、博客园等主流专业博客平台，后面看到一些新奇的平台感觉有个性，就转向 Bitcron(Farbox) 这类小众博客服务商，付费使用约 7 年后，他们说要停运。就想着挺喜欢写小东西的，就自己维护，开启了博客搭建的探索之路。</font></p><h1 id="一、Bitcron-博客服务商"><a href="#一、Bitcron-博客服务商" class="headerlink" title="一、Bitcron 博客服务商"></a><font style="color:rgb(0, 0, 0);">一、Bitcron 博客服务商</font></h1><h3 id="核心特性"><a href="#核心特性" class="headerlink" title="核心特性"></a><font style="color:rgb(0, 0, 0);">核心特性</font></h3><p><font style="color:rgb(0, 0, 0);">支持 Markdown 格式写作，内容可自动发布到 Bitcron 关联网站，且允许自定义域名。</font></p><h3 id="完整发布链路"><a href="#完整发布链路" class="headerlink" title="完整发布链路"></a><font style="color:rgb(0, 0, 0);">完整发布链路</font></h3><ol><li><font style="color:rgb(0, 0, 0);">本地完成 Markdown 内容撰写；</font></li><li><font style="color:rgb(0, 0, 0);">通过 Dropbox 网盘自动同步至 Bitcron 后台；</font></li><li><font style="color:rgb(0, 0, 0);">选择 Bitcron 提供的多种主题；</font></li><li><font style="color:rgb(0, 0, 0);">1 分钟内自动渲染生成博客页面。</font></li></ol><h3 id="优势"><a href="#优势" class="headerlink" title="优势"></a><font style="color:rgb(0, 0, 0);">优势</font></h3><ul><li><font style="color:rgb(0, 0, 0);">主题设计精美，开箱即用，风格小众且适配性好，经简单调整可满足多数使用场景，支持图片主题、幻灯片等类型，具备自定义能力；</font></li><li><font style="color:rgb(0, 0, 0);">发布方式多样，包含本地 Markdown 同步 Dropbox、命令行同步、邮件发布、在线 WeEditor 发布、微信发布、WebDav 同步发布等；</font></li><li><font style="color:rgb(0, 0, 0);">网站访问速度和稳定性较好，采用多服务器同步部署，缓解国内外访问不稳定问题，基础配置及服务与主流博客平台基本一致；</font></li><li><font style="color:rgb(0, 0, 0);">针对小众用户需求，提供加密、付费等特色功能。</font></li></ul><h3 id="不足"><a href="#不足" class="headerlink" title="不足"></a><font style="color:rgb(0, 0, 0);">不足</font></h3><ul><li><font style="color:rgb(0, 0, 0);">主题自定义配置操作繁琐，无实时预览功能，需通过 Dropbox 同步后刷新页面确认效果，调整效率较低；</font></li><li><font style="color:rgb(0, 0, 0);">围绕 Markdown 构建全链路博客服务，若定位小众产品，团队维护压力较大，用户基数有限导致收益不足，存在成本与收益的平衡难题；</font></li><li><font style="color:rgb(0, 0, 0);">配套开发的全功能 Markdown 编辑器 MarkEditor，支持发布到 Bitcron 及同步 Evernote 等功能，但后期维护不足。推测因采用 QT 技术开发调试性价比低，且团队后续推出另一款收费编辑器，分散了精力，导致该工具迭代停滞。</font></li></ul><h3 id="小思"><a href="#小思" class="headerlink" title="小思"></a><font style="color:rgb(0, 0, 0);">小思</font></h3><p><font style="color:rgb(0, 0, 0);">由于未参与平台运营决策，无法对维护团队的选择做绝对评判。后续了解到这个团队转向课程变现业务，对博客服务的投入相应减少。从技术架构来看，Bitcron 持续维护成本较高，但对之前的用户而言，省时省力尝试一下也还可以，但如果要完全自定义曲线就会陡然增高。</font></p><h1 id="二、Serverless-Hexo-博客"><a href="#二、Serverless-Hexo-博客" class="headerlink" title="二、Serverless + Hexo 博客"></a><font style="color:rgb(0, 0, 0);">二、Serverless + Hexo 博客</font></h1><h3 id="搭建背景"><a href="#搭建背景" class="headerlink" title="搭建背景"></a><font style="color:rgb(0, 0, 0);">搭建背景</font></h3><p><font style="color:rgb(0, 0, 0);">早年曾尝试 Jekyll + Github 的博客方案，因国内访问受限放弃，但认可 “仅维护 Markdown 文章” 的静态生成模式。恰逢 Serverless 技术兴起，且本人熟悉相关技术栈，遂启动该方案搭建。</font></p><h3 id="核心逻辑"><a href="#核心逻辑" class="headerlink" title="核心逻辑"></a><font style="color:rgb(0, 0, 0);">核心逻辑</font></h3><p><font style="color:rgb(0, 0, 0);">从语雀拉取内容并转换为 Markdown 格式，通过自主开发的 Serverless Hexo 博客生成器处理后，生成 HTML 等静态文件，最终发布到 OSS（对象存储服务）。</font></p><h3 id="优势-1"><a href="#优势-1" class="headerlink" title="优势"></a><font style="color:rgb(0, 0, 0);">优势</font></h3><ul><li><font style="color:rgb(0, 0, 0);">创作便捷性高，语雀编辑体验良好，排版规范，支持画图功能，可在手机、电脑、微信等多端编辑发布文档；</font></li><li><font style="color:rgb(0, 0, 0);">服务成本较低，OSS 与 Serverless 服务计费灵活，支持本地调试主题，修改后即时生效；</font></li><li><font style="color:rgb(0, 0, 0);">基于成熟的 Hexo 框架，可复用市面各类主题，支持简单微调适配需求。</font></li></ul><h3 id="不足-1"><a href="#不足-1" class="headerlink" title="不足"></a><font style="color:rgb(0, 0, 0);">不足</font></h3><ul><li><font style="color:rgb(0, 0, 0);">Hexo 框架魔改调试难度大，其原生为本地生成架构，适配 Serverless 时需开发虚拟文件系统封装层。部分 Hexo 插件未采用框架自带文件层，需修改全局变量、核心代码及插件逻辑，导致维护成本增加；</font></li><li><font style="color:rgb(0, 0, 0);">线上调试困难，Serverless 服务的线上问题在本地环境难以复现，后期排查故障耗时较长；</font></li><li><font style="color:rgb(0, 0, 0);">服务稳定性不足，语雀接口、OSS 发布均存在限流限制，且响应时长不稳定，发布后需手动验证博客内容是否正常显示。</font></li></ul><h3 id="小思-1"><a href="#小思-1" class="headerlink" title="小思"></a><font style="color:rgb(0, 0, 0);">小思</font></h3><p><font style="color:rgb(0, 0, 0);">该方案以自用为核心，初期围绕 Serverless 生态进行适配改造。后期因维护成本高、精力有限，博客更新停滞，项目搁置 3 年。</font></p><h1 id="三、Serverless-Hexo-博客迭代版"><a href="#三、Serverless-Hexo-博客迭代版" class="headerlink" title="三、Serverless + Hexo 博客迭代版"></a><font style="color:rgb(0, 0, 0);">三、Serverless + Hexo 博客迭代版</font></h1><p><font style="color:rgb(0, 0, 0);">针对上一版方案维护成本高的问题，核心原因在于架构复杂，但仍倾向于 Serverless 的技术特性，花费两个周末的部分时间进行重构，计划将 Hexo 生成代码封装为 Library 简化架构，然后按需调用。</font></p><ul><li>使用 pnpm 的 monorepo，实现模块的解耦</li><li>使用 Serverless function 直接调用，避免把 Serverless 项目变得更加复杂</li><li>Hexo 内部文件访问，语雀 api 的继承，都使用单独的包进行封装，再 patch 到对应的包，实现修改包也能同步官网更新</li></ul><p><font style="color:rgb(0, 0, 0);">虽然按照上面的重构，但还有比较多问题，核心还是 Serverless 调试不方便，本质还是 沙箱不是很适合本地调试，远程调试非常依赖基础设施，但明显现在云的基础设施并不怎么样。最终避免费更多精力，走到不应以技术为核心导向的地步，应该需优先匹配实际需求。</font></p><h1 id="四、Hexo-博客-Github-Action-OSS"><a href="#四、Hexo-博客-Github-Action-OSS" class="headerlink" title="四、Hexo 博客 + Github Action + OSS"></a><font style="color:rgb(0, 0, 0);">四、Hexo 博客 + Github Action + OSS</font></h1><h3 id="搭建思路"><a href="#搭建思路" class="headerlink" title="搭建思路"></a><font style="color:rgb(0, 0, 0);">搭建思路</font></h3><p><font style="color:rgb(0, 0, 0);">复盘前期方案，明确核心优化目标是降低维护成本、解决线上调试难的问题。结合 Hexo 原生适配本地运行的特性，引入 Github Action 构建新架构。</font></p><h3 id="优势-2"><a href="#优势-2" class="headerlink" title="优势"></a><font style="color:rgb(0, 0, 0);">优势</font></h3><ul><li><font style="color:rgb(0, 0, 0);">触发机制简洁，通过 Serverless 编写少量代码实现接口，用于触发 Github Action 运行，比较适合 Serverless 的轻量特性；</font></li><li><font style="color:rgb(0, 0, 0);">调试便捷，文章生成全流程日志可在 Github Action 后台查看，便于定位问题；</font></li><li><font style="color:rgb(0, 0, 0);">支持内容缓存，Github Action 可缓存上次同步内容，提升生成速度，减少限流情况；</font></li><li><font style="color:rgb(0, 0, 0);">主题调试一致性高，本地与线上环境差异小，修改后验证效率高。</font></li></ul><h3 id="不足-2"><a href="#不足-2" class="headerlink" title="不足"></a><font style="color:rgb(0, 0, 0);">不足</font></h3><p><font style="color:rgb(0, 0, 0);">生成速度仍有优化空间，最快需 50 秒左右完成生成流程，不过速度在静态站点生成（SSG）场景下基本可满足使用需求。</font></p><h1 id="五、小结"><a href="#五、小结" class="headerlink" title="五、小结"></a><font style="color:rgb(0, 0, 0);">五、小结</font></h1><p><font style="color:rgb(0, 0, 0);">当前采用的 Hexo + Github Action + OSS 方案，与最初的 Bitcron 方案相比，虽未实现一体化整合的高效性，但通过整合各平台优势，成为小众场景下维护成本较低的可行方案。实际使用中，当朋友反馈博客主题或配置问题时，通过 Cursor Remote Agent 在手机端完成 PR 提交、合并及重新发布操作还是非常适应当前的时代发展</font><font style="color:rgb(0, 0, 0);">🤣</font><font style="color:rgb(0, 0, 0);">。其实很好地反应了根据具体的需求做定制化的设计是很重要的。</font></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;font style=&quot;color:rgb(0, 0, 0);&quot;&gt;都忘记是啥时候了，应该是在博客盛行的尾声，有了自己第一个博客。用了 Blogspot、WordPress、博客园等主流专业博客平台，后面看到一些新奇的平台感觉有个性，就转向 Bitcron(Farbox) </summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>九溪烟树徒步</title>
    <link href="https://vastiny.com/post/lc6mpwcvvx19pe9c.html"/>
    <id>https://vastiny.com/post/lc6mpwcvvx19pe9c.html</id>
    <published>2025-04-19T21:49:16.000Z</published>
    <updated>2026-03-23T20:16:36.354Z</updated>
    
    <content type="html"><![CDATA[<p>#本周大自然</p><p>徒步路线：法喜寺 - 三分岔 - 龙井村 - 九溪十八涧</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fr75IRksqtRofnS-YhVWM9q-rV7O.jpeg"></p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fu-PyGQVuufRUj4cc55MyBr2JgET.jpeg"></p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/lqBTGCwf-ZrfaYAI3di1t59KEqGC.jpeg"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;#本周大自然&lt;/p&gt;
&lt;p&gt;徒步路线：法喜寺 - 三分岔 - 龙井村 - 九溪十八涧&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fr75IRksqtRofnS-YhVWM9</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>重新生成博客</title>
    <link href="https://vastiny.com/post/zw8oitqzsey4hbbx.html"/>
    <id>https://vastiny.com/post/zw8oitqzsey4hbbx.html</id>
    <published>2025-04-13T02:14:12.000Z</published>
    <updated>2026-03-23T20:16:35.290Z</updated>
    
    <content type="html"><![CDATA[<p>简单来说还是用 hexo 生成静态网站，但额外增加了数据源来自语雀，以及发布到阿里云 oss 上，变为静态的博客站点。整个生成流程如下：</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/FrAXzskcQoUlGtMuO1a90Spcg3kb.jpeg"></p><p>核心的架构思路是分层,实现数据 \ 代码和渲染分离：</p><ul><li>数据层:语雀</li><li>博客代码生成: GitHub</li><li>页面代码: OSS 提供 html 服务</li></ul><p>生产流程走 GitHub Action， 调试执行记录也方便查看。特别是在当前 AI Coding 的环境下，直接使用 Cursor Remote Agent 就可以在手机上直接修改博客代码，自动提交 PR 并重新发布。画图使用画板或者其它脑图等都很方便,在博客生成时会自动转为图片。博客文章也可以在手机语雀上写完自动发布。整体的生态已经相对成熟。</p><p>另外善于利用这些成熟的工具，稳定性有很大的保证，语雀放数据&#x2F;代码放 Github&#x2F;博客放 OSS，利用了所有擅长的产品。</p><p>博客代码仓库：<a href="https://github.com/yantze/blog">https：&#x2F;&#x2F;github。com&#x2F;yantze&#x2F;blog</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;简单来说还是用 hexo 生成静态网站，但额外增加了数据源来自语雀，以及发布到阿里云 oss 上，变为静态的博客站点。整个生成流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_i</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>常用 Vim 技巧</title>
    <link href="https://vastiny.com/post/qpg4etqqimrbao2g.html"/>
    <id>https://vastiny.com/post/qpg4etqqimrbao2g.html</id>
    <published>2024-03-12T03:33:01.000Z</published>
    <updated>2026-03-23T20:16:32.430Z</updated>
    
    <content type="html"><![CDATA[<p>常常使用但容易忘的 Vim 使用技巧。 ee4</p><h2 id="移动"><a href="#移动" class="headerlink" title="移动"></a>移动</h2><ul><li>移到屏幕顶行后的 n 行</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nH</span><br></pre></td></tr></table></figure><ul><li>移到屏幕底行的 n 行</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nL</span><br></pre></td></tr></table></figure><ul><li>让当前行居中，z<cr> 让当前行居顶部</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">zz</span><br></pre></td></tr></table></figure><ul><li>折叠</li></ul><figure class="highlight shell"><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">zo 展开一层，zO 递归展开</span><br><span class="line">zc 折叠一层，zC 递归折叠</span><br></pre></td></tr></table></figure><ul><li>快速选词</li></ul><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">c3w, 而不是 dws， vws</span><br></pre></td></tr></table></figure><pre><code>- &lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;实现删除当前单词 daw 或者 diw，如果删除后要插入，就 ciw，或者 caw，不过为了均衡手指的点击，建议用 diw 和 ciw&lt;/font&gt;- &lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;比如 w 是快速移动一个单词，W 是快速移动一个“字”，那什么才是字呢？ 空格隔开的算啥？ &quot;&#39;;: 这些符号隔开的算啥？我的经验是，这些都不必去记，如果一个操作你需要思考的话，速度就已经慢下来了，还影响你的思路。你的脑子应该用在考虑代码逻辑上，而不是用在怎么把代码打出来。拿刚刚这个例子来说吧，就简单的理解成“W 是更快的 w ”就好啦！&lt;/font&gt;</code></pre><ul><li>goto<ul><li>指定位置</li></ul></li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">goto <span class="number">21490</span></span><br></pre></td></tr></table></figure><pre><code>- goto 指定位置（命令行）</code></pre><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></pre></td><td class="code"><pre><span class="line">vim +21490 script.py</span><br><span class="line">vim -c &quot;goto 1553&quot; soft/lsof.md</span><br><span class="line">vim -c &quot;:goto 1553&quot; soft/lsof.md</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">grep -b -r <span class="string">&quot;str&quot;</span> . 获取 byte-offset</span></span><br></pre></td></tr></table></figure><blockquote><p>+{command} 或者 -c {command}</p><p>{command} will be executed after the first file has been read. {command} is interpreted as an Ex command. If the {command} contains spaces it must be enclosed in double quotes (this depends on the shell that is used).</p></blockquote><ul><li>行排序</li></ul><figure class="highlight shell"><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="meta prompt_">%</span><span class="language-bash">!<span class="built_in">sort</span> -t, -k3</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">占位符为 <span class="string">&quot;,&quot;</span>，第三列的内容</span></span><br></pre></td></tr></table></figure><h3 id="常用快捷键"><a href="#常用快捷键" class="headerlink" title="常用快捷键"></a>常用快捷键</h3><ul><li>使用 Ctrl + o 可以上一个文件</li><li>使用 Ctrl+Shift+o 就向前</li><li><font style="color:rgb(51, 51, 51);">手册中的 yank 其实就是 copy 的意思 </font><a href="https://www.zhihu.com/question/68713673/answer/273169409">ref</a><ul><li><font style="color:rgb(51, 51, 51);">Yanking is just a Vim name for copying. The “c” letter was already used for the change operator, and “y” was still available. Calling this operator “yank” made it easier to remember to use the “y” key.</font></li></ul></li><li><font style="color:rgb(51, 51, 51);">fx - jump to next occurrence of character x</font></li><li><font style="color:rgb(51, 51, 51);">tx - jump to before next occurrence of character x</font></li><li><font style="color:rgb(51, 51, 51);">Fx - jump to previous occurence of character x</font></li><li><font style="color:rgb(51, 51, 51);">Tx - jump to after previous occurence of character</font></li><li><font style="color:rgb(51, 51, 51);">; - repeat previous f, t, F or T movement</font></li><li><font style="color:rgb(51, 51, 51);">, - repeat previous f, t, F or T movement, backwards 这两个都被其它快捷键覆盖了</font></li><li><font style="color:rgb(51, 51, 51);">vaw : aw - mark a word</font></li><li><font style="color:rgb(51, 51, 51);">Registers (信息在 ~&#x2F;.viminfo)</font><ul><li><font style="color:rgb(51, 51, 51);">:reg - show registers content</font></li><li><font style="color:rgb(51, 51, 51);">“xy - yank into register x</font></li><li><font style="color:rgb(51, 51, 51);">“xp - paste contents of register x</font></li></ul></li><li><font style="color:rgb(51, 51, 51);">Marks</font><ul><li><font style="color:rgb(51, 51, 51);">:marks - list of marks</font></li></ul></li></ul><h2 id="修改"><a href="#修改" class="headerlink" title="修改"></a>修改</h2><ul><li>删除当前行开始的 11 行</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">d11d</span><br></pre></td></tr></table></figure><ul><li>删除重复行的几种解决方法</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">:</span>sort</span><br><span class="line"><span class="punctuation">:</span>g/^\(.*\)$\n\<span class="number">1</span>$/d</span><br><span class="line">或者</span><br><span class="line"><span class="punctuation">:</span>sort</span><br><span class="line"><span class="punctuation">:</span>%s/^\(.*\)\(\n\<span class="number">1</span>\)\+$/\<span class="number">1</span>/</span><br></pre></td></tr></table></figure><ul><li>替代 multiple cursors</li></ul><p>选择 text object</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">/var&lt;cr&gt;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">cgn</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">输入替代内容</span></span><br><span class="line">&lt;esc&gt;</span><br></pre></td></tr></table></figure><pre><code>- 接着一直按`.`就可以了- 如果 2. 这种，就可以隔两个继续 redo</code></pre><blockquote><p>Multiple cursors plugin <a href="https://github.com/mg979/vim-visual-multi">https://github.com/mg979/vim-visual-multi</a></p></blockquote><h3 id="快速编辑函数"><a href="#快速编辑函数" class="headerlink" title="快速编辑函数"></a>快速编辑函数</h3><p>快速编辑函数</p><ul><li>删除一个单词并进入插入模式</li></ul><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">cw</span><br></pre></td></tr></table></figure><ul><li>删除一行并进入插入模式</li></ul><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">cc</span><br></pre></td></tr></table></figure><ul><li>修改函数的第一个参数</li></ul><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">f(lct,</span><br></pre></td></tr></table></figure><ul><li>删除最后一个参数</li></ul><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">f)dF,</span><br></pre></td></tr></table></figure><ul><li>删除花括号的内容，并进入写入模式。和 vi{s 类似，但可以少按一个按钮</li></ul><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">ci&#123;</span><br></pre></td></tr></table></figure><p>ya{ 复制括号所有的内容，所以以此类推 yi{ 是复制不包括花括号的内容，总结为 abc 公式</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></pre></td><td class="code"><pre><span class="line">a 可选：c、d、y、v</span><br><span class="line">b 可选：a、i</span><br><span class="line">c 可选：（、[、&#123;、w、l、s 或者其它</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">a 可选：d</span><br><span class="line">b 可选：a、i、s（删除选中两遍的内容）</span><br><span class="line">c 可选：（、[、&#123;</span><br></pre></td></tr></table></figure><h3 id="正则异同"><a href="#正则异同" class="headerlink" title="正则异同"></a>正则异同</h3><ul><li>正则和其它 JavaScript 正则相比，<code>(</code>需要转义</li></ul><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">:%s/\(.\&#123;4\&#125;\)/0x\1 /g</span><br></pre></td></tr></table></figure><ul><li>删除包含的部分，以及取反</li></ul><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></pre></td><td class="code"><pre><span class="line">:g/^/m</span><br><span class="line">:v/prototype/d</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">:g! 与 v 相同</span></span><br></pre></td></tr></table></figure><ul><li>删除多余空行(包括 vscode)</li></ul><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">^s*(?=r?$)\n</span><br></pre></td></tr></table></figure><ul><li>正则文档 <a href="https://vimdoc.sourceforge.net/htmldoc/pattern.html">https://vimdoc.sourceforge.net/htmldoc/pattern.html</a></li><li>vim regex 101 <a href="https://vimregex.com/">https://vimregex.com</a></li></ul><h2 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h2><ul><li>在文件打开命令前面加上 noautocmd 或者 noau，可以用开阻止打开 zip 结构</li></ul><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">vim -c &#x27;noau e foo.xlsx&#x27;</span><br></pre></td></tr></table></figure><ul><li>复制到系统粘贴板</li></ul><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></pre></td><td class="code"><pre><span class="line">noremap &lt;Leader&gt;y &quot;*y</span><br><span class="line">noremap &lt;Leader&gt;p &quot;*p</span><br><span class="line">noremap &lt;Leader&gt;Y &quot;+y</span><br><span class="line">noremap &lt;Leader&gt;P &quot;+p</span><br><span class="line"></span><br><span class="line">可以设置默认的</span><br><span class="line">set clipboard=unnamed</span><br></pre></td></tr></table></figure><ul><li>经常把退出 vim 的 :q 敲成 q: 然后进入 vim 的 command-line-window</li><li>编码：vim 重载编码</li></ul><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></pre></td><td class="code"><pre><span class="line">set fenc=cp936</span><br><span class="line">set enc=cp936 设置当前文档的编码，并且立即生效，但效果不是很彻底，要用 :e ++enc=cp936 , 具体可以查文档 :help ++enc</span><br><span class="line">set ff=unix mac dos，这三种，代表了换行的格式</span><br></pre></td></tr></table></figure><ul><li><font style="color:rgb(51, 51, 51);">让文件保存不要自动添加 eol (Automatic Newline At End Of File)</font><ul><li><font style="color:rgb(51, 51, 51);">默认情况下 vim 会加 eol 的标记，0x0A，但像 VSCode 就没有加</font></li><li><font style="color:rgb(51, 51, 51);">只有在 vim -b file.txt 的情况下，执行 :set noeol 才能去掉，nofixeol 无效</font></li></ul></li></ul><h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><ul><li>multiple cursors <a href="https://medium.com/@schtoeffel/you-don-t-need-more-than-one-cursor-in-vim-2c44117d51db">ref</a></li><li>快速编辑函数 <a href="https://twitter.com/linuxtoy/status/1302228511621394439">ref</a></li><li>反向获取数据 <a href="https://vi.stackexchange.com/questions/2105/how-to-reverse-the-order-of-lines">ref</a></li><li>video 主要是讲 nav code 的三个工具， 以后可以看看他的配置 <a href="https://www.youtube.com/watch?v=knSFZCKMy20">https://www.youtube.com/watch?v=knSFZCKMy20</a></li><li>创建自己的 ack.vim <a href="https://gist.github.com/manasthakur/5afd3166a14bbadc1dc0f42d070bd746">https://gist.github.com/manasthakur/5afd3166a14bbadc1dc0f42d070bd746</a></li><li>不错的插件列表<ul><li>搜索 vim 插件 <a href="https://vimawesome.com/">https://vimawesome.com</a></li><li>curl restful 插件 <a href="https://github.com/diepm/vim-rest-console">https://github.com/diepm/vim-rest-console</a></li></ul></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;常常使用但容易忘的 Vim 使用技巧。 ee4&lt;/p&gt;
&lt;h2 id=&quot;移动&quot;&gt;&lt;a href=&quot;#移动&quot; class=&quot;headerlink&quot; title=&quot;移动&quot;&gt;&lt;/a&gt;移动&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;移到屏幕顶行后的 n 行&lt;/li&gt;
&lt;/ul&gt;
&lt;figure c</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Serverless Blog 使用手册</title>
    <link href="https://vastiny.com/post/wlz6bwfhtaf4e4az.html"/>
    <id>https://vastiny.com/post/wlz6bwfhtaf4e4az.html</id>
    <published>2023-10-15T02:17:49.000Z</published>
    <updated>2026-03-23T20:16:36.664Z</updated>
    
    <content type="html"><![CDATA[<h3 id="发布文章到网站"><a href="#发布文章到网站" class="headerlink" title="发布文章到网站"></a>发布文章到网站</h3><p>文章在点击发布或者更新后，会自动发布到中，如果出现异常，可以通过访问下面的链接手动生成：</p><p><a href="http://hexo.vastiny.com/generate/vastiny-com">http://hexo.vastiny.com/generate/vastiny-com</a></p><h3 id="自定义文章元信息"><a href="#自定义文章元信息" class="headerlink" title="自定义文章元信息"></a>自定义文章元信息</h3><p>可以通过每篇文章最前面添加下面的 front matter，自定义当前文章的标题、创建日期、隐私状态、多语言等。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">title<span class="punctuation">:</span> 文章标题</span><br><span class="line">date<span class="punctuation">:</span> <span class="number">2024</span><span class="number">-05</span><span class="number">-20</span> <span class="number">23</span><span class="punctuation">:</span><span class="number">24</span></span><br><span class="line">status<span class="punctuation">:</span> private</span><br><span class="line">lang<span class="punctuation">:</span> en</span><br><span class="line">---</span><br></pre></td></tr></table></figure><p>具体在一个文章里面就是这样：</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fg-UAi9F2yUhgB2mV-K_0mTNIUq1.png"></p><h3 id="支持英文网站"><a href="#支持英文网站" class="headerlink" title="支持英文网站"></a>支持英文网站</h3><p>当前 leedom 以及 leedom2 已经适配好了多语言，自己通过添加导航 xxx&#x2F;en&#x2F; 就可以访问，比如 <a href="https://vastiny.com/en/">https://vastiny.com/en/</a> 一定要加后面的 <code>/</code>才能访问。如果要写一篇英文文章， 不会出现在中文页面，需要定制英文文章的元信息：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">lang<span class="punctuation">:</span>en</span><br><span class="line">---</span><br><span class="line">正文内容</span><br></pre></td></tr></table></figure><p>然后要配置好网站元信息（完整的信息示例在<a href="#OXVFl">下面</a>）：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="attr">&quot;language&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;zh-CN&quot;</span><span class="punctuation">,</span> <span class="string">&quot;en&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  ...</span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>之后，网站就会在 &#x2F;en&#x2F; 的路径下展示英文文章。</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/FgOqA_jMWWZhaAH9Ojhz3ompDvSH.png"></p><h3 id="自定义网站配置"><a href="#自定义网站配置" class="headerlink" title="自定义网站配置"></a>自定义网站配置</h3><p>使用 Hexo 的 _config.yml 转为 语雀的 json 网站配置：</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fg7Y6AGRpYvMvDKKW0_MyY7hzXaE.png"></p><ol><li>在知识库中新建一个文件</li><li>添加文章元信息，设置 status 为 private ，避免出现在网站中</li><li>添加一个代码块，配置网站元信息</li><li>设置当前文章的路径是 config，可以通过 文档设置 -&gt; 高级选项 -&gt; 路径，进行配置</li><li>发布当前文章即可</li></ol><p>配置示例：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Vastiny&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;subtitle&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Hi 与天&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;keywords&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yantze&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;language&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;zh-CN&quot;</span><span class="punctuation">,</span> <span class="string">&quot;en&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;timezone&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Asia/Shanghai&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://vastiny.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permalink&quot;</span><span class="punctuation">:</span> <span class="string">&quot;post/:name.html&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permalink_defaults&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;default_category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;other&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;theme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;leedom2&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;theme_config_override&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;favicon&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/images/favicon.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;touch_icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/images/favicon.png&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;avatar&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;author_photo&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/images/avatar.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;author_nickname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Vastiny&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;search&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;placeholder&quot;</span><span class="punctuation">:</span> <span class="string">&quot;搜索&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;link&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;github&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;github&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/yantze&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;_blank&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;twitter&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;twitter&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://twitter.com/ivastiny&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;_blank&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;footer&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Copyright © Yantze 2023-3&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://vastiny.com&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Theme by Leedom | Powered by Hexo&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/leedom92/hexo-theme-leedom&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;google_analytics&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;发布文章到网站&quot;&gt;&lt;a href=&quot;#发布文章到网站&quot; class=&quot;headerlink&quot; title=&quot;发布文章到网站&quot;&gt;&lt;/a&gt;发布文章到网站&lt;/h3&gt;&lt;p&gt;文章在点击发布或者更新后，会自动发布到中，如果出现异常，可以通过访问下面的链接手动生成：&lt;/p&gt;
&lt;</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>黑魔法：如何让 Mac 只保留一个输入法</title>
    <link href="https://vastiny.com/post/omo7cs3h0mm3g3ct.html"/>
    <id>https://vastiny.com/post/omo7cs3h0mm3g3ct.html</id>
    <published>2023-04-17T08:06:13.000Z</published>
    <updated>2026-03-23T20:16:32.430Z</updated>
    
    <content type="html"><![CDATA[<p>因为 Mac 上使用的一直都是 Rime 输入法，也叫鼠须管，中英文一个键就可以切换。如果保留一个以上的输入法，总是会在切换应用后，输入法自动切换为系统默认的输入法 ABC，导致不确定是哪个输入法，也不知道是不是中文输入状态。所以之前一直使用的方法是修改 <code>com.apple.HIToolbox.plist</code> 文件，让输入法 Rime 保留为唯一的输入法。这里最新的系统 macOS Ventura 13 在升级输入法后发现设置失效了，这里重新记录一下成功的案例。</p><h2 id="操作步骤"><a href="#操作步骤" class="headerlink" title="操作步骤"></a>操作步骤</h2><h4 id="1-备份-com-apple-HIToolbox-plist-文件"><a href="#1-备份-com-apple-HIToolbox-plist-文件" class="headerlink" title="1. 备份 com.apple.HIToolbox.plist 文件"></a>1. 备份 com.apple.HIToolbox.plist 文件</h4><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">cp ~/Library/Preferences/com.apple.HIToolbox.plist&#123;,_1&#125;</span><br></pre></td></tr></table></figure><p>会拷贝 <code>~/Library/Preferences/com.apple.HIToolbox.plist</code> 文件到同目录下的 <code>~/Library/Preferences/com.apple.HIToolbox.plist_1</code>。</p><h4 id="2-将当前活跃输入法选为「英文」输入法"><a href="#2-将当前活跃输入法选为「英文」输入法" class="headerlink" title="2. 将当前活跃输入法选为「英文」输入法"></a>2. 将当前活跃输入法选为「英文」输入法</h4><h4 id="3-转换-plist-文件为-xml-格式"><a href="#3-转换-plist-文件为-xml-格式" class="headerlink" title="3. 转换 plist 文件为 xml 格式"></a>3. 转换 plist 文件为 xml 格式</h4><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">plutil -convert xml1 ~/Library/Preferences/com.apple.HIToolbox.plist</span><br></pre></td></tr></table></figure><p>因为 plist 有两种格式，一种是二进制的，直接用编辑器打开是乱码，所以需要用工具转为 xml 格式，方便编辑。</p><h4 id="4-修改-com-apple-HIToolbox-plist-文件"><a href="#4-修改-com-apple-HIToolbox-plist-文件" class="headerlink" title="4. 修改 com.apple.HIToolbox.plist 文件"></a>4. 修改 com.apple.HIToolbox.plist 文件</h4><p>用编辑器打开 <code>com.apple.HIToolbox.plist</code>，删除掉 AppleEnabledInputSources 键下不需要的输入法 dict。我这里删除后只剩下这些。可以不用拷贝，直接编辑系统上的文件到类似的内容即可。然后保存文件</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">plist</span> <span class="keyword">PUBLIC</span> <span class="string">&quot;-//Apple//DTD PLIST 1.0//EN&quot;</span> <span class="string">&quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">&quot;1.0&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>AppleCurrentKeyboardLayoutInputSourceID<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span>com.apple.keylayout.ABC<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>AppleDictationAutoEnable<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">integer</span>&gt;</span>0<span class="tag">&lt;/<span class="name">integer</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>AppleEnabledInputSources<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>Bundle ID<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span>com.apple.CharacterPaletteIM<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>InputSourceKind<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span>Non Keyboard Input Method<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>AppleInputSourceHistory<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>Bundle ID<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span>im.rime.inputmethod.Squirrel<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>Input Mode<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span>im.rime.inputmethod.Squirrel.Hans<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>InputSourceKind<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span>Input Mode<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="5-重启系统"><a href="#5-重启系统" class="headerlink" title="5. 重启系统"></a>5. 重启系统</h4><p>保存文件后，其它都不要操作，直接重启 Mac 系统。启动后就会发现菜单栏只剩下一个输入法了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;因为 Mac 上使用的一直都是 Rime 输入法，也叫鼠须管，中英文一个键就可以切换。如果保留一个以上的输入法，总是会在切换应用后，输入法自动切换为系统默认的输入法 ABC，导致不确定是哪个输入法，也不知道是不是中文输入状态。所以之前一直使用的方法是修改 &lt;code&gt;com</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>如何调整个人倦怠的讲座</title>
    <link href="https://vastiny.com/post/mazgem6be2hytnp3.html"/>
    <id>https://vastiny.com/post/mazgem6be2hytnp3.html</id>
    <published>2023-04-15T04:26:59.000Z</published>
    <updated>2026-03-23T20:16:36.701Z</updated>
    
    <content type="html"><![CDATA[<p>好久没听过讲座了，越来越觉得有深度的知识才是可靠的，减少看「新」闻，增加深度的内容，才能是让收益最大的方式。这次听的是一位临床的心理学教授的讲座，有不少可以借鉴的地方。</p><h2 id="倦怠的现状"><a href="#倦怠的现状" class="headerlink" title="倦怠的现状"></a>倦怠的现状</h2><p>工作目标自己也要知道高峰区才是最理想的，过多就会倦怠的情况。</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fmm_jL9tXfPlXcZ8YP8bJOIb2_zA.png"></p><h3 id="倦怠之后，特别容易发脾气"><a href="#倦怠之后，特别容易发脾气" class="headerlink" title="倦怠之后，特别容易发脾气"></a>倦怠之后，特别容易发脾气</h3><p>会因为一些小事，点爆火药桶。本来可以一笑了之的事情，会因为可能会升级矛盾，导致恶性循环。</p><h3 id="公墓里面，男女占比-7-3"><a href="#公墓里面，男女占比-7-3" class="headerlink" title="公墓里面，男女占比 7:3"></a>公墓里面，男女占比 7:3</h3><p>公墓里面，有那种夫妻一起，活着的没有染色的字碑，去世的是有染色的墓碑。讲师亲自去数了一下，是 7:3。讲到一个可能就是男性承受的倦怠或者压力常常在内憋着，女生可以有更多的手段去释放出来，比如说更多的话。</p><h3 id="工作压力大，可以去楼下小酌一杯"><a href="#工作压力大，可以去楼下小酌一杯" class="headerlink" title="工作压力大，可以去楼下小酌一杯"></a>工作压力大，可以去楼下小酌一杯</h3><p>如果碰到压力太大或者倦怠的情况，脱离当前的环境，去外面走走。但有些人可能连这个自由都没有，那就是 13~18 岁的学生，因为他们没有意识到能出去，每节课都说，要集中注意力。</p><h3 id="人为什么会疲劳"><a href="#人为什么会疲劳" class="headerlink" title="人为什么会疲劳"></a>人为什么会疲劳</h3><p>早上吃饭，水喝多了一点，过一会儿有小便的想法，但在路上，是膀胱受到了刺激，外周神经给你的中枢神经传导了一个信号。但中枢通过处理又给外周下了指令，不行，憋住。然后这样过多的利用中枢去控制外周，就会疲劳。工作中也有很多不能随心所欲的东西，过多的控制就会疲劳倦怠。</p><h2 id="缓解的方法"><a href="#缓解的方法" class="headerlink" title="缓解的方法"></a>缓解的方法</h2><p>讲师讲述别人工作中的疲劳解决方法：</p><ol><li>中午不吃碳水，只吃蔬菜等简餐，避免碳水摄入过多犯困后导致的精神萎靡。</li><li>中午去正念，在阳光开阔的环境下正念，达到放松的精神的状态。</li></ol><p>讲师提到自己用的方法：</p><p>晚上疲惫的时候，走到操场，放听不懂的音乐，避免音乐印象情绪，只听节奏就好，比如西班牙语很多的歌节奏挺好的。按照节奏开始快步走，配速配到六公里每小时。然后不要和任何人说话，把手机收好不看手机，持续时间四十分钟以上。频率每周两三次，保持几周时间，就会发现很不一样。</p><h2 id="正念"><a href="#正念" class="headerlink" title="正念"></a>正念</h2><p>然后讲师终于切入正题，正念是如何缓解倦怠的，这里面就包括了前面提到的正念行走，保持关注行走本身。下面是常见的一些正念探索分类。</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fvk783dlAaboaoP_hXRPG7qtklyA.jpeg"></p><p>关于正念饮食，就是吃饭的时候，只关注吃饭本身，不要说话，也不要看手机，只要关注到菜的味道，和自己。讲师还讲了一个自己的关于吃的事情。自己是江苏人，喜欢吃爆鳝面。怎么找到好吃的爆鳝面，得先找到鳝鱼。所以把车开到余杭平遥。就看哪个农民在那里抓鳝鱼，抓了后就跟着他他那个的小三轮车，观察他把鳝鱼送到哪家面馆就进哪家面馆。</p><p>这个例子感觉很棒，因为这种我一直认为，建立联接，才是你觉得这个菜好吃的原因。好吃不支持口味，也包含菜品背后的故事。比如前几周清明节，同事送了青团给我们吃，我当时觉得这个也一般（至少比盒马的好吃），为什么还会流传至今。后来深思了一下，文化传统，在这样的节日下吃这样的食物，是相当惬意的。当你知道你吃菜的来龙去买，你才会知道，下一次吃这个菜的缘起了。</p><p>另外城市生活中，有一类人（比如我）吃健康餐，是因为好吃吗？不一定，是因为健康。因为吃的是健康的食物，所以感到心情愉悦。所以吃东西，好吃只是日常吃东西的一个原因，也有文化、健康等因素。</p><p>讲师提到自己观鸟的爱好，大概一半的时间都是在讲自己观鸟的经历。因为观鸟，要去到各个地方，也是他了解到的正念旅行。讲到早睡早起有两个原因，也挺有意思的：</p><ol><li>观鸟带来的</li><li>城市老人和乡村老人的区别</li></ol><p>城市老人每天一睁眼，就会想着我要去那，今天要干什么，城市老人晚年都是吃药去世在 ICU。而乡村老人一般都是鸡鸣而起，然后就忙各种农活或者到处都是朋友，这种场景会更容易一些，所以一般都是自然死亡。所以早睡早起也是这种充实的一个环节，能让自己一天很充实。</p><p>有时候我也认为，不是因为早睡早起好，就要这样，而是因为自己的爱好，自己的其它活动让自己早睡早起。</p><h2 id="可操作的地方"><a href="#可操作的地方" class="headerlink" title="可操作的地方"></a>可操作的地方</h2><p>下面是一些我听讲座总结和结合自己的观点，总结的几个可以操作的地方：</p><ol><li>**正念行走，**就专注走的本身，其它的不要去想，如前面提到的行走方法；</li><li>**饮食，**专注饮食本身，工作之余不要让太多念头充满脑子；</li><li>**阅读，**沉浸在书里面，睡前最好是传记等不要引起情绪波动的类型的书。</li></ol><p>避免浮躁就是减少碎片化的时间，这个是我在生活中的一个实践。比如下班回来后，我一般情况不会去看短视频，这种碎片的内容，一般都是把 B 站的长视频，或者干脆就看电视剧，这样一个小时就能让自己好一些。而不是看短视频后啥都没有的事后感。</p><p>所以看出来了吧，最核心的还是，找到自己沉浸式的爱好，然后你在爱好中，不再去思考其它，专注当下。讲师的观鸟就是，他因此会早起早睡，因此会去世界各地去旅行，同样也能缓解自己的倦怠。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;好久没听过讲座了，越来越觉得有深度的知识才是可靠的，减少看「新」闻，增加深度的内容，才能是让收益最大的方式。这次听的是一位临床的心理学教授的讲座，有不少可以借鉴的地方。&lt;/p&gt;
&lt;h2 id=&quot;倦怠的现状&quot;&gt;&lt;a href=&quot;#倦怠的现状&quot; class=&quot;headerlin</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>新的网站，老网站地址表</title>
    <link href="https://vastiny.com/post/vfiz5q.html"/>
    <id>https://vastiny.com/post/vfiz5q.html</id>
    <published>2023-02-11T04:34:00.000Z</published>
    <updated>2026-03-23T20:16:37.045Z</updated>
    
    <content type="html"><![CDATA[<p>由于原来的博客不能很好的定制化，可操作不强，换到了新的博客系统。考虑到 url 不能转移，列了技术类的参考地址表。也可以直接跳到旧版博客 <a href="http://m.vastiny.com/">http://m.vastiny.com</a>。旧版博客有效期至 2023 年底。1</p><table><thead><tr><th>原地址</th><th>新路径</th></tr></thead><tbody><tr><td>&#x2F;post&#x2F;tech&#x2F;strace</td><td><a href="/post/zp99g9c6uk0qose5.html">查阅系统调用 strace 和 instruments</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;electron-lifecycle</td><td><a href="/post/lpgv9519maf1pnzl.html">Electron 生命周期事件</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;electron-asar</td><td><a href="/post/nq27csq2khcbfnv6.html">asar 解析</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;electron-screenshot</td><td><a href="/post/hyukway2qxvmwwt5.html">Electron 截屏方案分析</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;electron-remote</td><td><a href="/post/rrxdfu1790k1o5p2.html">分析并实现一个简单 Electron 的 remote obj</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;vscode-nodejs-deasync</td><td><a href="/post/vnhivhifxpzuf7uz.html">分析异步转同步库 deasync</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;vscode-nodejs-addon</td><td><a href="/post/rhz0d1ortvulkhiw.html">VSCode 配置 NodeJS C&#x2F;C++ Addon Debug 环境</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;fs-lockfile</td><td><a href="/post/cprw9kz9b2xdxqvf.html">应用内文件锁</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;word-count</td><td><a href="/post/va4m9k1gcurig6s6.html">字数统计</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;ubuntu-screen-sharing</td><td><a href="/post/rp863grv413esgtm.html">Ubuntu Screen Sharing</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;electron-protocol</td><td><a href="/post/zcc3bc57emb3pgw5.html">Electron 的 Protocol 实例解析</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;mac-do-not-distrib</td><td><a href="/post/zhswz1y94nrcakq1.html">隐藏所有通知 ── 快捷的开关勿扰模式</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;fix-zip</td><td><a href="/post/mifr11u1273e8tx9.html">ZIP 文件常用结构分析和修复</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;spell-intro</td><td><a href="/post/oztrd86o5traio0c.html">使用 Spell 实践深度学习，几乎零配置开始使用</a></td></tr><tr><td>&#x2F;post&#x2F;fork-for-mac-and-windows-git-client</td><td><a href="/post/gf50wi09k8scm39l.html">Fork for Mac and Windows（Git 客户端）</a></td></tr><tr><td>&#x2F;post&#x2F;remote-control</td><td><a href="/post/eggd8y4zums46aqf.html">怎样简单的用浏览器控制电脑</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;macos-wkwebview</td><td><a href="/post/agspoe66xfgdnr2s.html">macOS 的 WKWebView 基本使用</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;aws-error</td><td><a href="/post/zfg7kx3bm5r0m2hs.html">AWS 更新导致的一次错误</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;reset-microphone</td><td><a href="/post/fzaxgz3n5t75udcb.html">Mojave 的隐私设置和重置权限方法</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;ubuntu-xfce</td><td><a href="/post/nr492hcr4s8tde9f.html">在 Ubuntu Server 上安装简洁版 Xfce Desktop</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;hammerspoon_part2_launcher_and_switcher</td><td><a href="/post/nbcwh2d4pvuct6ln.html">Hammerspoon Part 2: Launcher and Switcher &#x2F; 启动器</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;hammerspoon_part1_window_manager</td><td><a href="/post/nz0zmgbslglwgpoe.html">Hammerspoon Part 1: Window Manager &#x2F; 窗口管理</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;recover_safari_history</td><td><a href="/post/wr5eczaiq86bflgk.html">导出 Safari 历史记录</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;shared_docker_container_and_auto_build</td><td><a href="/post/oyklgx4zdfkw5eu6.html">Docker 容器互联和自动构建</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;nginx_webdav</td><td><a href="/post/aigrwbobhqgeugan.html">用 Nginx 建立 WebDAV 盘</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;fake-gps</td><td><a href="/post/ev5ibd3azsvfc7g7.html">Fake GPS for iOS &#x2F; 在 iOS 伪造 GPS</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;frontend-library</td><td><a href="/post/olmb9ll9sc097k0e.html">jQuery 、 Zepto 、 Bootstrap、PureCSS 模块定制大小比较</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;an-zhuang-centos-i3wm-zhuo-mian-huan-jing</td><td><a href="/post/dha2lt2vp5tm57sh.html">安装 CentOS + i3wm 桌面环境</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;nc</td><td><a href="/post/tmwgdbg9gysm8bid.html">nc and socat</a></td></tr><tr><td>&#x2F;post&#x2F;tech&#x2F;emacs</td><td><a href="/post/xedhbltsut0lgxsi.html">Emacs Guide</a></td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;由于原来的博客不能很好的定制化，可操作不强，换到了新的博客系统。考虑到 url 不能转移，列了技术类的参考地址表。也可以直接跳到旧版博客 &lt;a href=&quot;http://m.vastiny.com/&quot;&gt;http://m.vastiny.com&lt;/a&gt;。旧版博客有效期至 20</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>网站配置</title>
    <link href="https://vastiny.com/post/config.html"/>
    <id>https://vastiny.com/post/config.html</id>
    <published>2023-02-04T04:35:23.000Z</published>
    <updated>2026-03-23T20:16:36.626Z</updated>
    
    <content type="html"><![CDATA[<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Vastiny&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;subtitle&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Hi 与天，这是你写博客的地方。技术、思考。&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;keywords&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yantze&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;timezone&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Asia/Shanghai&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://vastiny.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permalink&quot;</span><span class="punctuation">:</span> <span class="string">&quot;post/:name.html&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permalink_defaults&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;default_category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;other&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;theme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;leedom2&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;theme_config_override&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;favicon&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/images/favicon.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;touch_icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/images/favicon.png&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;avatar&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;author_photo&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/images/avatar.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;author_nickname&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Vastiny&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;search&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;placeholder&quot;</span><span class="punctuation">:</span> <span class="string">&quot;搜索&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;link&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;github&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;github&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/yantze&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;_blank&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;twitter&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;twitter&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://twitter.com/ivastiny&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;_blank&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;footer&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Copyright © Yantze 2023-3&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://vastiny.com&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Theme by Leedom | Powered by Hexo&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/leedom92/hexo-theme-leedom&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;google_analytics&quot;</span><span class="punctuation">:</span> <span class="string">&quot;G-WJ1ZY9QR8J&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;figure class=&quot;highlight json&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>新的网站，新的起点</title>
    <link href="https://vastiny.com/post/new-start.html"/>
    <id>https://vastiny.com/post/new-start.html</id>
    <published>2022-06-12T00:04:13.000Z</published>
    <updated>2026-03-23T20:16:38.464Z</updated>
    
    <content type="html"><![CDATA[<p>本站点现由 Serverless 自动化部署，下面介绍一下具有的能力：</p><h2 id="新的站点"><a href="#新的站点" class="headerlink" title="新的站点"></a>新的站点</h2><h4 id="1-提供基础的参数"><a href="#1-提供基础的参数" class="headerlink" title="1. 提供基础的参数"></a>1. 提供基础的参数</h4><ul><li>获取语雀的 token 码</li><li>生成 oss 对应的 AK&#x2F;SK</li><li>以及绑定域名到 oss 中</li></ul><h4 id="2-站点配置"><a href="#2-站点配置" class="headerlink" title="2. 站点配置"></a>2. 站点配置</h4><ul><li>提供 hexo 对应的主题，比如 <a href="https://github.com/leedom92/hexo-theme-leedom">leedom</a></li><li>提供一份网站的配置，包括个人社交信息、网站名称等配置、favicon 个性化图标</li></ul><h4 id="2-在语雀编写文档"><a href="#2-在语雀编写文档" class="headerlink" title="2. 在语雀编写文档"></a>2. 在语雀编写文档</h4><ul><li>提供一个 webhook 链接，在写文章后会自动生成到站点</li></ul><h2 id="文章格式"><a href="#文章格式" class="headerlink" title="文章格式"></a>文章格式</h2><p>文档支持 <a href="https://hexo.io/zh-cn/docs/front-matter.html">Front-matter</a></p><h4 id="1-自定义日期"><a href="#1-自定义日期" class="headerlink" title="1. 自定义日期"></a>1. 自定义日期</h4><p>可以直接输入 date: 后面有详细的日期，然后输入 <code>---&lt;回车&gt;</code>，变成下面这样，在保存后，站点的文章就会是这个日期</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/FhKMJYrSk8jDITT61YOrX_bA8TwW.png"></p><h4 id="2-设置当前文章是否要发布"><a href="#2-设置当前文章是否要发布" class="headerlink" title="2. 设置当前文章是否要发布"></a>2. 设置当前文章是否要发布</h4><p>status 设置为 private 将不会发布文章内容</p><figure class="highlight markdown"><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="section">## status: private</span></span><br><span class="line"></span><br><span class="line">正文内容</span><br></pre></td></tr></table></figure><h2 id="自定义网站配置"><a href="#自定义网站配置" class="headerlink" title="自定义网站配置"></a>自定义网站配置</h2>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本站点现由 Serverless 自动化部署，下面介绍一下具有的能力：&lt;/p&gt;
&lt;h2 id=&quot;新的站点&quot;&gt;&lt;a href=&quot;#新的站点&quot; class=&quot;headerlink&quot; title=&quot;新的站点&quot;&gt;&lt;/a&gt;新的站点&lt;/h2&gt;&lt;h4 id=&quot;1-提供基础的参数&quot;&gt;&lt;a </summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>查阅系统调用 strace 和 instruments</title>
    <link href="https://vastiny.com/post/zp99g9c6uk0qose5.html"/>
    <id>https://vastiny.com/post/zp99g9c6uk0qose5.html</id>
    <published>2021-05-08T16:15:00.000Z</published>
    <updated>2026-03-23T20:16:36.875Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Strace-相关"><a href="#Strace-相关" class="headerlink" title="Strace 相关"></a>Strace 相关</h1><h2 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h2><h3 id="背景："><a href="#背景：" class="headerlink" title="背景："></a>背景：</h3><p>在 Linux 世界，进程不能直接访问硬件设备，当进程需要访问硬件设备(比如读取磁盘文件，接收网络数据等等)时，必须由用户态模式切换至内核态模式，通过系统调用访问硬件设备。strace 可以跟踪到一个进程产生的系统调用,包括参数，返回值，执行消耗的时间。</p><p>strace 常用来跟踪进程执行时的系统调用和所接收的信号，当然 strace 对于代码里的死循环是解决不了的。比如你发现一个进程 CPU 100% 了，strace 恐怕是解决不了的。因为 strace 是看系统调用，一般都是 IO 类操作，既然是 IO 密集，那 CPU 一定不可能是 100%。</p><h3 id="语法："><a href="#语法：" class="headerlink" title="语法："></a>语法：</h3><ul><li>strace -f -F command<ul><li>比如 <code>strace php abc.php</code></li><li>比如 <code>strace node abc.js</code></li><li>-f -F 同事跟踪 fork 和 vfork 出来的进程</li></ul></li><li>strace -o output.txt -T -tt -e trace&#x3D;all -p process-pid<ul><li>-e trace&#x3D;all 跟踪 pid 所有的系统调用</li><li>-T 统计系统调用花费的时间</li><li>-tt 开始时间(更多选项看 man strace)</li></ul></li></ul><h3 id="例子："><a href="#例子：" class="headerlink" title="例子："></a>例子：</h3><p><code>uptime</code> 会读取 <code>/proc/uptime</code> 和 <code>/proc/loadavg</code> 获取详细信息，怎么去判断的呢？</p><figure class="highlight bash"><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">$ strace <span class="built_in">uptime</span> 2&gt;&amp;1 | grep open</span><br><span class="line">...</span><br><span class="line">open(<span class="string">&quot;/proc/uptime&quot;</span>, O_RDONLY)          = 3</span><br><span class="line">open(<span class="string">&quot;/var/run/utmp&quot;</span>, O_RDONLY|O_CLOEXEC) = 4</span><br><span class="line">open(<span class="string">&quot;/proc/loadavg&quot;</span>, O_RDONLY)         = 4</span><br></pre></td></tr></table></figure><p>也可以使用<code>strace -e open uptime</code> 获得更加简便的，也可以通过 <code>strace -e trace=file -f uptime</code>获取结果相对复杂。</p><h2 id="macOS"><a href="#macOS" class="headerlink" title="macOS"></a>macOS</h2><p>macOS 也有类似</p><h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><ul><li>dtruss [-acdeflhoLs] [-t syscall] { -p PID | -n name | command }</li></ul><blockquote><p>安全问题：sudo dtruss [dtruss_options] -f sudo -u $USER myprogram [program_options]</p></blockquote><ul><li>iosnoop: snoop I&#x2F;O events as they occur. Uses DTrace.</li><li>opensnoop, execsnoop, 这个可以找到使用 dtrace 相关的都不能用<code>man -k dtrace</code><br>这些都是关于对应的系统调用，但都需要关闭系统保护，不然直接诶调用 dtrace 就会出现下面的内容：</li></ul><figure class="highlight plaintext"><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">dtrace: error on enabled probe ID 1712 (ID 564: syscall::sysctl:return): invalid user access in action #5 at DIF offset 0</span><br><span class="line">dtrace: error on enabled probe ID 1712 (ID 564: syscall::sysctl:return): invalid user access in action #5 at DIF offset 0</span><br><span class="line">dtrace: error on enabled probe ID 1930 (ID 832: syscall::proc_info:return): invalid user access in action #5 at DIF offset 0</span><br><span class="line">dtrace: error on enabled probe ID 1930 (ID 832: syscall::proc_info:return): invalid user access in action #5 at DIF offset 0</span><br><span class="line">dtrace: error on enabled probe ID 1712 (ID 564: syscall::sysctl:return): invalid user access in action #5 at DIF offset 0</span><br><span class="line">dtrace: error on enabled probe ID 1712 (ID 564: syscall::sysctl:return): invalid user access in action #5 at DIF offset 0</span><br><span class="line">dtrace: error on enabled probe ID 1930 (ID 832: syscall::proc_info:return): invalid user access in action #5 at DIF offset 0</span><br></pre></td></tr></table></figure><p>所以使用 XCode 提供的 instruments 才是更好的方案:</p><h3 id="instruments-语法"><a href="#instruments-语法" class="headerlink" title="instruments 语法"></a>instruments 语法</h3><ul><li>instruments [-w device] [-t template] [-D document] [-l timeLimit] [-i #] [[-p pid] | [application [-e variable value] [argument …]]]</li></ul><h3 id="instruments-例子："><a href="#instruments-例子：" class="headerlink" title="instruments 例子："></a>instruments 例子：</h3><p>使用 instruments 来查看调用：</p><ul><li>instruments -D result.trace -t ‘System Trace’ &#x2F;usr&#x2F;bin&#x2F;uptime</li><li>open result.trace</li></ul><p>就可以看到对应的底层调用<br><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/FrRlRlA6dfWkS-KqkTpRH6_89AGC.png"></p><h3 id="关于-xctrace"><a href="#关于-xctrace" class="headerlink" title="关于 xctrace"></a>关于 xctrace</h3><p>这个是用来替换 instruments 的，用法先看看 man xctrace 吧，功能跟 instruments 类似。</p><h2 id="strace、dtrace-和-dtruss-的介绍"><a href="#strace、dtrace-和-dtruss-的介绍" class="headerlink" title="strace、dtrace 和 dtruss 的介绍"></a>strace、dtrace 和 dtruss 的介绍</h2><ul><li>strace(Linux) - trace system calls and signals</li><li>dtrace(macOS) - dynamic tracing compiler and tracing utility</li><li>dtruss(macOS) - process syscall details. Uses DTrace.</li></ul><h2 id="Ref"><a href="#Ref" class="headerlink" title="Ref"></a>Ref</h2><ul><li>博客中非常多关于系统底层的分析：<a href="http://www.brendangregg.com/overview.html">http://www.brendangregg.com/overview.html</a></li><li>关于介绍 uptime: <a href="https://peteris.rocks/blog/htop/#source-code">https://peteris.rocks/blog/htop/#source-code</a></li><li>关于 PHP 的 debug: <a href="http://rango.swoole.com/archives/340">http://rango.swoole.com/archives/340</a></li><li>更多参数相关的介绍： <a href="https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/strace.html">https://linuxtools-rst.readthedocs.io/zh_CN&#x2F;latest&#x2F;tool&#x2F;strace.html</a></li><li>关于 dtruss 安全：<a href="https://opensourcehacker.com/2011/12/02/osx-strace-equivalent-dtruss-seeing-inside-applications-what-they-do-and-why-they-hang/">https://opensourcehacker.com/2011/12/02/osx-strace-equivalent-dtruss-seeing-inside-applications-what-they-do-and-why-they-hang/</a></li><li>更多关于 dtrace 详细的介绍： <a href="http://dtrace.org/blogs/brendan/2011/10/10/top-10-dtrace-scripts-for-mac-os-x/">http://dtrace.org/blogs/brendan/2011/10/10/top-10-dtrace-scripts-for-mac-os-x/</a></li><li>介绍 instruments： <a href="https://etcnotes.com/posts/system-call/">https://etcnotes.com/posts/system-call/</a></li><li>Instruments 手册：<a href="https://help.apple.com/instruments/mac/current/#/dev7b09c84f5">https://help.apple.com/instruments/mac/current/#/dev7b09c84f5</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Strace-相关&quot;&gt;&lt;a href=&quot;#Strace-相关&quot; class=&quot;headerlink&quot; title=&quot;Strace 相关&quot;&gt;&lt;/a&gt;Strace 相关&lt;/h1&gt;&lt;h2 id=&quot;Linux&quot;&gt;&lt;a href=&quot;#Linux&quot; class=&quot;header</summary>
      
    
    
    
    
    <category term="linux" scheme="https://vastiny.com/tags/linux/"/>
    
    <category term="macOS" scheme="https://vastiny.com/tags/macOS/"/>
    
    <category term="debug" scheme="https://vastiny.com/tags/debug/"/>
    
  </entry>
  
  <entry>
    <title>Electron 生命周期事件</title>
    <link href="https://vastiny.com/post/lpgv9519maf1pnzl.html"/>
    <id>https://vastiny.com/post/lpgv9519maf1pnzl.html</id>
    <published>2021-01-31T13:12:05.000Z</published>
    <updated>2026-03-23T20:16:36.541Z</updated>
    
    <content type="html"><![CDATA[<p>常见软件有 <code>ready</code> <code>close</code> <code>closed</code> 这些事件，但 electron 的事件也非常的多，如果想要更加深入的了解 electron 整个生命周期的流程，需要对应用里面的生命周期，窗口的生命周期以及页面内的生命周期的时机有一个清晰的理解。</p><p>一图胜千言：<br><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fkndmdg2Be9JJq-T_9d9qSRZI3AY.png"></p><h2 id="一、设计启动退出的事件介绍"><a href="#一、设计启动退出的事件介绍" class="headerlink" title="一、设计启动退出的事件介绍"></a>一、设计启动退出的事件介绍</h2><p>这里把这些事件分成三部分，App 事件、BrowserWindow 事件以及 Renderer 进程中的 Web 事件：</p><h3 id="1-App-事件"><a href="#1-App-事件" class="headerlink" title="1. App 事件"></a>1. App 事件</h3><ul><li><code>on(&#39;will-finish-launching&#39;, (event: Event) =&gt; &#123;&#125;)</code></li><li><code>on(&#39;ready&#39;, (event: Event, launchInfo: Record&lt;string, any&gt;) =&gt; &#123;&#125;)</code></li><li><code>on(&#39;open-file&#39;, (event: Event, path: string) =&gt; &#123;&#125;)</code> 【macOS】</li></ul><p>应该在 ready 之前对 open-file 进行监听。如果想要自己接管文件的打开，应该 event.preventDefault()<br>触发条件：</p><ul><li>应用已经打开，并且通过扩展名或者 open 命令打开文件的时候，触发</li><li>拖放一个文件到 Dock 但应用还没有运行的时候触发</li></ul><p>Windows 电脑中，需要通过主进程的 process.argv 进行解析</p><ul><li><code>on(&#39;open-url&#39;, (event: Event, url: string) =&gt; &#123;&#125;)</code>【macOS】</li></ul><p>事件 <code>open-url</code>  是处理通过系统通过 electron 应用打开 url 时触发，如果想要自己接管打开 url，应该 <code>event.preventDefault()</code> 。并且要在 info.plist 中定义 url scheme，原话是这么说的：Your application’s Info.plist file must define the URL scheme within the CFBundleURLTypes key, and set NSPrincipalClass to AtomApplication.</p><ul><li><code>on(&#39;activate&#39;, (event: Event, hasVisibleWindows: boolean) =&gt; &#123;&#125;)</code> 【macOS】</li></ul><p>事件 <code>activate</code>  只会在【首次启动应用程序】、【尝试在应用程序已运行时】或【单击应用程序的坞站或任务栏图标时】重新激活它。</p><ul><li><code>on(&#39;did-become-active&#39;, (event: Event) =&gt; &#123;&#125;)</code> 【macOS】</li></ul><p>事件 <code>did-become-active</code> 则会在切换到这个应用的时候触发，比如没有窗口的应用或者程序第一次启动。</p><ul><li><code>on(&#39;session-created&#39;, (session: Session) =&gt; &#123;&#125;)</code></li><li><code>on(&#39;web-contents-created&#39;, (event: Event, window: BrowserWindow) =&gt; &#123;&#125;)</code></li><li><code>on(&#39;browser-window-created&#39;, (event: Event, window: BrowserWindow) =&gt; &#123;&#125;)</code></li></ul><p>创建一个窗口，都是依次以 <code>session-created</code> , <code>web-contents-created</code> , <code>browser-window-created</code>  创建。但不知道为什么在 app <code>ready</code>  事件后，又触发了事件 <code>web-contents-created</code> 。</p><ul><li><code>on(&#39;browser-window-focus&#39;, (event: Event, window: BrowserWindow) =&gt; &#123;&#125;)</code></li><li><code>on(&#39;second-instance&#39;, (event: Event, argv: string[], workingDirectory: string) =&gt; &#123;&#125;)</code></li><li><code>on(&#39;window-all-closed&#39;, ()=&gt;&#123;&#125;)</code></li></ul><p>这个事件只有一个窗口一个窗口的关闭后，才会触发，其它情况，比如 <code>app.quit</code> 、 <code>cmd+q</code>  或者菜单的退出，或者任何其它方式的退出软件都不会触发。</p><ul><li><code>on(&#39;before-quit&#39;, (event: Event) =&gt; &#123;&#125;)</code></li></ul><p>如果应用关闭是被 <code>autoUpdater.quitAndInstall()</code>  发起的，那么在所有窗口触发 <code>close</code>  之后 才会触发 <code>before-quit</code>  并关闭所有窗口。</p><ul><li><code>on(&#39;quit&#39;, (event: Event, exitCode: number) =&gt; &#123;&#125;)</code></li></ul><p>在 Windows 系统中，如果应用程序因系统关机&#x2F;重启或用户注销而关闭，那么 <code>before-quit</code>  和 <code>quit</code>  事件不会被触发。</p><h4 id=""><a href="#" class="headerlink" title=""></a></h4><h3 id="2-BrowserWindow-事件"><a href="#2-BrowserWindow-事件" class="headerlink" title="2. BrowserWindow 事件"></a>2. BrowserWindow 事件</h3><ul><li><code>on(&#39;close&#39;, (event: Event) =&gt;&#123;&#125;)</code></li><li><code>on(&#39;closed&#39;, () =&gt; &#123;&#125;)</code></li><li><code>on(&#39;ready-to-show&#39;, () =&gt; &#123;&#125;)</code>  还没有显示的时候就发起</li></ul><h3 id="3-Renderer-进程"><a href="#3-Renderer-进程" class="headerlink" title="3. Renderer 进程"></a>3. Renderer 进程</h3><ul><li><code>window.onunload</code></li><li><code>window.onbeforeunload</code>  返回非 undefined 就会中断 主进程 close 事件</li><li><code>document.addEventListener(&#39;DOMContentLoaded&#39;)</code></li><li><code>document.addEventListener(&#39;visibilitychange&#39;)</code></li></ul><h2 id="二、退出场景"><a href="#二、退出场景" class="headerlink" title="二、退出场景"></a>二、退出场景</h2><h3 id="1-正常退出"><a href="#1-正常退出" class="headerlink" title="1. 正常退出"></a>1. 正常退出</h3><ul><li><code>Cmd+q</code>  或者菜单中的退出按钮。</li><li><code>app.quit()</code></li><li><code>autoupdater.quitAndInstall()</code></li><li><code>app.reluanch()</code></li></ul><h3 id="2-异常退出"><a href="#2-异常退出" class="headerlink" title="2. 异常退出"></a>2. 异常退出</h3><p>比如常见的在主进程调用 <code>process.crash()</code> 。</p><h3 id="3-SIG-信号退出"><a href="#3-SIG-信号退出" class="headerlink" title="3. SIG 信号退出"></a>3. SIG 信号退出</h3><p>我们常见的命令行退出软件的方式有 <code>ctrl+c</code> ，命令行给进程发送了 <code>SIGKILL</code>  信号，其实还有其它常见关闭进程的方式，可以通过 <code>kill</code>  对进程发送信号，比如 <code>kill -s KILL 24567</code>  或者 <code>kill -9 24567</code> ：</p><figure class="highlight plaintext"><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">1       HUP (hang up)</span><br><span class="line">2       INT (interrupt)</span><br><span class="line">3       QUIT (quit)</span><br><span class="line">6       ABRT (abort)</span><br><span class="line">9       KILL (non-catchable, non-ignorable kill)</span><br><span class="line">14      ALRM (alarm clock)</span><br><span class="line">15      TERM (software termination signal)</span><br></pre></td></tr></table></figure><h3 id="4-具体的退出例子"><a href="#4-具体的退出例子" class="headerlink" title="4. 具体的退出例子"></a>4. 具体的退出例子</h3><ol><li>正常命令行启动，通过 <code>Cmd+q</code>  退出</li></ol><figure class="highlight plaintext"><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">==&gt; app-event: will-finish-launching &lt;===</span><br><span class="line">==&gt; app-event: session-created &lt;===</span><br><span class="line">==&gt; app-event: web-contents-created &lt;===</span><br><span class="line">==&gt; app-event: browser-window-created &lt;===</span><br><span class="line">==&gt; app-event: ready &lt;===</span><br><span class="line">==&gt; app-event: did-become-active &lt;===</span><br><span class="line">==&gt; app-event: web-contents-created &lt;===</span><br><span class="line">==&gt; html-event: DOMContentLoaded &lt;===</span><br><span class="line">==&gt; html-event: load &lt;===</span><br><span class="line">==&gt; window-event: ready-to-show &lt;===</span><br><span class="line"></span><br><span class="line">==&gt; app-event: before-quit &lt;===</span><br><span class="line">==&gt; window-event: close &lt;===</span><br><span class="line">==&gt; html-event: beforeunload &lt;===</span><br><span class="line">==&gt; app-event: will-quit &lt;===</span><br><span class="line">==&gt; app-event: quit &lt;===</span><br><span class="line">==&gt; window-event: closed &lt;===</span><br></pre></td></tr></table></figure><p><code>==&gt; window-event: closed &lt;===</code>  这个事件最后才发送是因为 closed 完全是异步的，closed 不会因为没有执行完就阻塞，应用会把所有异步执行完后才退出进程。</p><p>可以通过流程图更加清晰的表达：<br>【流程图】</p><ol start="2"><li>正常启动，通过 <code>ctrl+c</code>  退出</li></ol><figure class="highlight plaintext"><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><br><span class="line">==&gt; window-event: ready-to-show &lt;===</span><br><span class="line"></span><br><span class="line">^C==&gt; app-event: before-quit &lt;===</span><br></pre></td></tr></table></figure><p>启动后，所有事件正常，但退出通过 <code>ctrl+c</code>  中断应用， electron 就只发出了 <code>before-quit</code>  事件。试试其它方法：</p><ul><li><code>SIGHUP</code>  和正常的退出流程一致</li><li><code>SIGINT</code>  效果同上，也是 <code>ctrl+c</code>  发出的信号</li><li><code>SIGQUIT</code> 会导致整个应用完全没有任何反应就退出</li><li><code>SIGABRT</code>  效果同上</li><li><code>SIGKILL</code>  效果同上</li><li><code>SIGSEGV</code> 效果同上，也是 <code>process.crash()</code>  发出的信号</li><li><code>SIGTERM</code>  和正常的退出流程大概一致，也是 <code>app.quit()</code>  的退出方式</li></ul><ol start="3"><li>正常启动，通过 <code>app.quit()</code>  退出，这里发现通过这种方式退出，有一些不太一样的地方：</li></ol><figure class="highlight plaintext"><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><br><span class="line">==&gt; window-event: ready-to-show &lt;===</span><br><span class="line"></span><br><span class="line">==&gt; app-event: before-quit &lt;===</span><br><span class="line">==&gt; window-event: close &lt;===</span><br><span class="line">==&gt; html-event: beforeunload &lt;===</span><br><span class="line">==&gt; html-event: unload &lt;===</span><br><span class="line">==&gt; app-event: will-quit &lt;===</span><br><span class="line">==&gt; app-event: quit &lt;===</span><br><span class="line">==&gt; window-event: closed &lt;===</span><br></pre></td></tr></table></figure><p>这里多了一个 unload 事件，也可以看出，其实整个应用通过 <code>app.quit</code>  退出才是正常的退出流程， <code>cmd+q</code>  也会导致部分流程会被忽略退出。然后使用 <code>app.reluanch()</code>  也是和 <code>cmd+q</code>  类似，不会触发 <code>unload</code>  事件。</p><ol start="4"><li>正常启动，通过 beforeunload 中断退出</li></ol><p>通过 beforeunload 中调用 <code>event.returnValue = false</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="variable language_">window</span>.<span class="property">onbeforeunload</span> = <span class="function">(<span class="params">event: Event</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">log</span>(<span class="string">&quot;beforeunload&quot;</span>);</span><br><span class="line">  event.<span class="property">returnValue</span> = <span class="literal">true</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>通过 <code>cmd+q</code>  会重复调用下面三个事件：</p><figure class="highlight plaintext"><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">==&gt; app-event: before-quit &lt;===</span><br><span class="line">==&gt; window-event: close &lt;===</span><br><span class="line">==&gt; html-event: beforeunload &lt;===</span><br></pre></td></tr></table></figure><p>如果是直接关闭窗口：</p><figure class="highlight plaintext"><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">==&gt; window-event: close &lt;===</span><br><span class="line">==&gt; html-event: beforeunload &lt;===</span><br></pre></td></tr></table></figure><ol start="5"><li>正常启动，通过 BrowserWindow <code>close</code>  事件中断，就只会触发 <code>close</code>  事件：</li></ol><figure class="highlight plaintext"><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">==&gt; window-event: close &lt;===</span><br><span class="line">==&gt; window-event: close &lt;===</span><br><span class="line">==&gt; window-event: close &lt;===</span><br></pre></td></tr></table></figure><blockquote><p>BrowserWindow <code>closed</code>  事件是没有 event 的。</p></blockquote><ol start="6"><li>正常启动，通过 app <code>before-quit</code>  中断，直接按 <code>cmd+q</code>  是不会关闭窗口。</li></ol><figure class="highlight plaintext"><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">==&gt; app-event: before-quit &lt;===</span><br><span class="line">==&gt; app-event: before-quit &lt;===</span><br><span class="line">==&gt; app-event: before-quit &lt;===</span><br></pre></td></tr></table></figure><blockquote><p>通过 <code>ctrl+c</code> 或者上面的一些 SIG 信号关闭，是会忽视这个中断关闭的操作。</p></blockquote><ol start="7"><li>正常启动，通过 app <code>will-quit</code>  中断，直接按 <code>cmd+q</code>  会关闭所有窗口，但程序还是激活状态。</li></ol><figure class="highlight plaintext"><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">==&gt; app-event: before-quit &lt;===</span><br><span class="line">==&gt; app-event: will-quit &lt;===</span><br><span class="line">==&gt; app-event: before-quit &lt;===</span><br><span class="line">==&gt; app-event: will-quit &lt;===</span><br><span class="line">==&gt; app-event: before-quit &lt;===</span><br><span class="line">==&gt; app-event: will-quit &lt;===</span><br></pre></td></tr></table></figure><blockquote><p>通过 <code>ctrl+c</code> 或者上面的一些 SIG 信号关闭，是会忽视这个中断关闭的操作。</p></blockquote><h2 id="三、启动场景"><a href="#三、启动场景" class="headerlink" title="三、启动场景"></a>三、启动场景</h2><p>软件常见的启动有很多，比如通过 <code>open</code>  或者 Windows 的 <code>start</code>  启动，或者通过 url scheme 然后系统启动，或者拖动文档到 dock 或者 tray 启动。下面来说说启动软件后，软件参数和环境的路径。</p><h3 id="1-普通启动"><a href="#1-普通启动" class="headerlink" title="1. 普通启动"></a>1. 普通启动</h3><p>一般双击软件启动，会经过 <code>will-finish-launching</code>  和 <code>ready</code> ，然后正常进入应用界面。</p><h3 id="2-单例模式下的应用启动"><a href="#2-单例模式下的应用启动" class="headerlink" title="2. 单例模式下的应用启动"></a>2. 单例模式下的应用启动</h3><p>在应用启动的时候，检测 <code>app.requestSingleInstanceLock()</code>  是否是单例模式，如果是就退出，并且发送一个事件给 <code>second-instance</code> ，从这里再获取退出的应用的 argv 和 cwd。</p><blockquote><p>其实这里检测是否是单例模式的方法是，看看 <code>app.getPath(&#39;userData&#39;)</code>  下是否有 lock 文件。</p></blockquote><h3 id="3-通过命令行启动"><a href="#3-通过命令行启动" class="headerlink" title="3. 通过命令行启动"></a>3. 通过命令行启动</h3><p>命令行执行 <code>/path/to/app --arg1 value1 --arg2 value2 document/path</code> ，会在应用启动的时候，通过 <code>process.argv</code>  和 <code>process.cwd</code></p><h3 id="4-通过-url-scheme-启动"><a href="#4-通过-url-scheme-启动" class="headerlink" title="4. 通过 url scheme 启动"></a>4. 通过 url scheme 启动</h3><p>通过 <code>app.setAsDefaultProtocolClient(&#39;electron-test&#39;)</code>  注册 url scheme，然后在 app 事件 <code>open-url</code>  的 url 参数获取 url 信息：</p><figure class="highlight plaintext"><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">==&gt; app-event: did-become-active &lt;===</span><br><span class="line">==&gt; app-event: open-url &lt;=== electron-test://happy?abc=eee#ii=aa</span><br></pre></td></tr></table></figure><h3 id="5-通过拖拽到-dock-或者-tray-启动"><a href="#5-通过拖拽到-dock-或者-tray-启动" class="headerlink" title="5. 通过拖拽到 dock 或者 tray 启动"></a>5. 通过拖拽到 dock 或者 tray 启动</h3><p>通过 dock 拖拽启动，需要在 <code>info.plist</code> 中声明支持的文件类型，比如 electronBuilder 可以通过 <code>extendInfo</code>  字段中，声明 <code>CFBundleDocumentTypes</code>  注册支持的类型。<br>触发的是事件 <code>open-file</code> 。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;常见软件有 &lt;code&gt;ready&lt;/code&gt; &lt;code&gt;close&lt;/code&gt; &lt;code&gt;closed&lt;/code&gt; 这些事件，但 electron 的事件也非常的多，如果想要更加深入的了解 electron 整个生命周期的流程，需要对应用里面的生命周期，窗口的生</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>asar 解析</title>
    <link href="https://vastiny.com/post/nq27csq2khcbfnv6.html"/>
    <id>https://vastiny.com/post/nq27csq2khcbfnv6.html</id>
    <published>2020-08-21T15:19:00.000Z</published>
    <updated>2026-03-23T20:16:32.540Z</updated>
    
    <content type="html"><![CDATA[<p>这是一个在 Electron 中常用于把多个文件打包到一个 asar 文件的文件归档格式。缩写简单可以理解为 A Simple Archive，类似于 tar 或者 zip 这种格式。asar 文件简单的包含三个部分，第一部分占 8 bytes 描述文件头的长度，第二部分是一个 json 文件格式，描述包含了哪些文件，第三部分是文件二进制的排列。</p><p>下面是一个 asar 文件头的 json 文件信息，通过这种格式信息，能完整的分析出有哪些文件和文件的目录结构:</p><figure class="highlight plaintext"><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">&#123;</span><br><span class="line">  &quot;header&quot;: &#123;</span><br><span class="line">    &quot;files&quot;: &#123;</span><br><span class="line">      &quot;child-dir&quot;: &#123;</span><br><span class="line">        &quot;files&quot;: &#123;</span><br><span class="line">          &quot;index.js&quot;: &#123;</span><br><span class="line">            &quot;size&quot;: 27,</span><br><span class="line">            &quot;offset&quot;: &quot;0&quot;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;README.md&quot;: &#123;</span><br><span class="line">        &quot;size&quot;: 13,</span><br><span class="line">        &quot;offset&quot;: &quot;27&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;headerSize&quot;: 120</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面来分析一个如何从 asar 中读取一个文件。</p><h2 id="前置环境"><a href="#前置环境" class="headerlink" title="前置环境"></a>前置环境</h2><p>做出一个类似这样的文件结构：</p><figure class="highlight plaintext"><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">└── project</span><br><span class="line">    ├── README.md</span><br><span class="line">    └── child-dir</span><br><span class="line">        └── index.js</span><br></pre></td></tr></table></figure><p>然后执行，就会产生一个 project.asar 的包</p><figure class="highlight plaintext"><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">npm install asar -g</span><br><span class="line">asar pack project project.asar</span><br></pre></td></tr></table></figure><h2 id="文件头信息"><a href="#文件头信息" class="headerlink" title="文件头信息"></a>文件头信息</h2><p>可以通过一个 nodejs 脚本代码来获取文件信息：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&quot;fs&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> sizeBuf = <span class="title class_">Buffer</span>.<span class="title function_">alloc</span>(<span class="number">8</span>);</span><br><span class="line"><span class="keyword">const</span> fd = fs.<span class="title function_">openSync</span>(<span class="string">&quot;./project.asar&quot;</span>);</span><br><span class="line"></span><br><span class="line">fs.<span class="title function_">readSync</span>(fd, sizeBuf, <span class="number">0</span>, <span class="number">8</span>, <span class="literal">null</span>);</span><br><span class="line"><span class="comment">// fs.readSync(fd, buffer, offset, length, position)</span></span><br><span class="line"><span class="comment">// position 指定从那个地方开始读，如果是 null，就会从文件内置的 position 中读取，并且更新 更新 position</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// sizeBuff 保存了文件头的大小，需要通过 chromium-pickle-js 读取前 32 位无符号整型</span></span><br><span class="line"><span class="keyword">const</span> pickle = <span class="built_in">require</span>(<span class="string">&quot;chromium-pickle-js&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> sizePickle = pickle.<span class="title function_">createFromBuffer</span>(sizeBuf);</span><br><span class="line"><span class="keyword">const</span> headerSize = sizePickle.<span class="title function_">createIterator</span>().<span class="title function_">readUInt32</span>();</span><br><span class="line"><span class="comment">// headerSize: 120</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> headerBuf = <span class="title class_">Buffer</span>.<span class="title function_">alloc</span>(headerSize);</span><br><span class="line"><span class="comment">// headerBuf.length: 120</span></span><br><span class="line"></span><br><span class="line">fs.<span class="title function_">readSync</span>(fd, headerBuf, <span class="number">0</span>, headerSize, <span class="literal">null</span>);</span><br><span class="line"><span class="keyword">const</span> headerPickle = pickle.<span class="title function_">createFromBuffer</span>(headerBuf);</span><br><span class="line"><span class="keyword">const</span> header = headerPickle.<span class="title function_">createIterator</span>().<span class="title function_">readString</span>();</span><br><span class="line">fs.<span class="title function_">closeSync</span>(fd);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> headInfo = &#123;</span><br><span class="line">  <span class="attr">header</span>: <span class="title class_">JSON</span>.<span class="title function_">parse</span>(header),</span><br><span class="line">  headerSize,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(headInfo, <span class="literal">null</span>, <span class="number">2</span>));</span><br></pre></td></tr></table></figure><p>最后就会生成跟上面一样的文件结构：</p><figure class="highlight plaintext"><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">&#123;</span><br><span class="line">  &quot;header&quot;: &#123;</span><br><span class="line">    &quot;files&quot;: &#123;</span><br><span class="line">      &quot;child-dir&quot;: &#123;</span><br><span class="line">        &quot;files&quot;: &#123;</span><br><span class="line">          &quot;index.js&quot;: &#123;</span><br><span class="line">            &quot;size&quot;: 27,</span><br><span class="line">            &quot;offset&quot;: &quot;0&quot;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;README.md&quot;: &#123;</span><br><span class="line">        &quot;size&quot;: 13,</span><br><span class="line">        &quot;offset&quot;: &quot;27&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;headerSize&quot;: 120</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="文件读取"><a href="#文件读取" class="headerlink" title="文件读取"></a>文件读取</h2><p>下面来试试读取其中的 <code>index.js</code> 文件，先指定一个路径 <code>child-dir/index.js</code>。定位到文件大小 27bytes，偏移量为 0。</p><figure class="highlight plaintext"><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">const fileInfo = &#123;</span><br><span class="line">  size: 27,</span><br><span class="line">  offset: 0,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">const buffer = Buffer.alloc(fileInfo.size)</span><br><span class="line">const fd2 = fs.openSync(&#x27;./project.asar&#x27;, &#x27;r&#x27;)</span><br><span class="line">const offset = 8 + headInfo.headerSize + parseInt(fileInfo.offset) // offset: 120</span><br><span class="line">fs.readSync(fd2, buffer, 0, fileInfo.size, offset)</span><br><span class="line">fs.closeSync(fd2)</span><br><span class="line"></span><br><span class="line">console.log(&#x27;file content:&#x27;, buffer.toString(&#x27;utf8&#x27;))</span><br><span class="line">// file content: console.log(&#x27;hello world&#x27;)</span><br></pre></td></tr></table></figure><p>看来可以读取到文件内容了。</p><h2 id="结束"><a href="#结束" class="headerlink" title="结束"></a>结束</h2><p>其实可以直接通过编辑器打开 project.asar 文件，里面基本就是没有压缩的文件，是一个很简单的单文件档案。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>文件中的代码都是改编自 asar 原来的代码，有兴趣也可以看看：</p><ul><li><a href="https://github.com/electron/asar">https://github.com/electron/asar</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这是一个在 Electron 中常用于把多个文件打包到一个 asar 文件的文件归档格式。缩写简单可以理解为 A Simple Archive，类似于 tar 或者 zip 这种格式。asar 文件简单的包含三个部分，第一部分占 8 bytes 描述文件头的长度，第二部分是</summary>
      
    
    
    
    
    <category term="electron" scheme="https://vastiny.com/tags/electron/"/>
    
    <category term="javascript" scheme="https://vastiny.com/tags/javascript/"/>
    
    <category term="nodejs" scheme="https://vastiny.com/tags/nodejs/"/>
    
  </entry>
  
  <entry>
    <title>Electron 截屏方案分析</title>
    <link href="https://vastiny.com/post/hyukway2qxvmwwt5.html"/>
    <id>https://vastiny.com/post/hyukway2qxvmwwt5.html</id>
    <published>2020-03-06T18:20:00.000Z</published>
    <updated>2026-03-23T20:16:34.403Z</updated>
    
    <content type="html"><![CDATA[<p>实现一个在 Electron 内截屏的功能，大概有六种实现方式，讨论一下选择哪个方案。最后再说一下高分屏的截图如何变得清晰。</p><h2 id="可选方案"><a href="#可选方案" class="headerlink" title="可选方案"></a>可选方案</h2><h3 id="第一种，使用-Canvas-截图"><a href="#第一种，使用-Canvas-截图" class="headerlink" title="第一种，使用 Canvas 截图"></a>第一种，使用 Canvas 截图</h3><p>这种技术有一个非常有名的库：<a href="https://github.com/niklasvh/html2canvas">html2canvas</a>，可以支持相对比较简单的 DOM 截图</p><blockquote><p>目前一直是处于实验状态（very experimental state）,如果理解这个库的限制，那么是一个还不错的选择</p></blockquote><p>原理是使用 <code>BoundCurves</code> 对 DOM 进行 path 绘制。例如下面一段选自 html2canvas 库中的代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="property">topLeftPaddingBox</span> =</span><br><span class="line">  tlh &gt; <span class="number">0</span> || tlv &gt; <span class="number">0</span></span><br><span class="line">    ? <span class="title function_">getCurvePoints</span>(</span><br><span class="line">        bounds.<span class="property">left</span> + borderLeftWidth,</span><br><span class="line">        bounds.<span class="property">top</span> + borderTopWidth,</span><br><span class="line">        <span class="title class_">Math</span>.<span class="title function_">max</span>(<span class="number">0</span>, tlh - borderLeftWidth),</span><br><span class="line">        <span class="title class_">Math</span>.<span class="title function_">max</span>(<span class="number">0</span>, tlv - borderTopWidth),</span><br><span class="line">        <span class="variable constant_">CORNER</span>.<span class="property">TOP_LEFT</span></span><br><span class="line">      )</span><br><span class="line">    : <span class="keyword">new</span> <span class="title class_">Vector</span>(bounds.<span class="property">left</span> + borderLeftWidth, bounds.<span class="property">top</span> + borderTopWidth);</span><br></pre></td></tr></table></figure><p>计算每一个 DOM 的形状，再把这些数据，比如 topLeftPaddingBox 放到 Stack 中，最后放在队列中，一个一个的绘制。</p><p><strong>优势</strong></p><ul><li>当然是兼容性非常好, 支持 canvas 的就行</li></ul><p><strong>劣势</strong></p><ul><li>因为是要把现存的 DOM 搬到 canvas 中，所以只能识别库支持的 css 属性，比如 transform 不支持。</li><li>获取资源要同源，如果有其它跨域资源，布局会混乱</li><li>大量计算工作，性能损耗较大</li></ul><h3 id="第二种，使用-SVG-构图"><a href="#第二种，使用-SVG-构图" class="headerlink" title="第二种，使用 SVG 构图"></a>第二种，使用 SVG 构图</h3><p>如下简单的例子，可以把 DOM 通过 svg 的 foreignObject 放到 canvas 中，就可以截图了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这段代码出自它处</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> canvas = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;canvas&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> ctx = canvas.<span class="title function_">getContext</span>(<span class="string">&quot;2d&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> data =</span><br><span class="line">  <span class="string">&#x27;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;200&quot; height=&quot;200&quot;&gt;&#x27;</span> +</span><br><span class="line">  <span class="string">&#x27;&lt;foreignObject width=&quot;100%&quot; height=&quot;100%&quot;&gt;&#x27;</span> +</span><br><span class="line">  <span class="string">&#x27;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;font-size:40px&quot;&gt;&#x27;</span> +</span><br><span class="line">  <span class="string">&quot;&lt;em&gt;I&lt;/em&gt; like&quot;</span> +</span><br><span class="line">  <span class="string">&#x27;&lt;span style=&quot;color:white; text-shadow:0 0 2px blue;&quot;&gt;&#x27;</span> +</span><br><span class="line">  <span class="string">&quot;cheese&lt;/span&gt;&quot;</span> +</span><br><span class="line">  <span class="string">&quot;&lt;/div&gt;&quot;</span> +</span><br><span class="line">  <span class="string">&quot;&lt;/foreignObject&gt;&quot;</span> +</span><br><span class="line">  <span class="string">&quot;&lt;/svg&gt;&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">DOMURL</span> = <span class="variable language_">window</span>.<span class="property">URL</span> || <span class="variable language_">window</span>.<span class="property">webkitURL</span> || <span class="variable language_">window</span>;</span><br><span class="line"><span class="keyword">const</span> img = <span class="keyword">new</span> <span class="title class_">Image</span>();</span><br><span class="line"><span class="keyword">const</span> svg = <span class="keyword">new</span> <span class="title class_">Blob</span>([data], &#123; <span class="attr">type</span>: <span class="string">&quot;image/svg+xml;charset=utf-8&quot;</span> &#125;);</span><br><span class="line"><span class="keyword">const</span> url = <span class="variable constant_">DOMURL</span>.<span class="title function_">createObjectURL</span>(svg);</span><br><span class="line">img.<span class="property">onload</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  ctx.<span class="title function_">drawImage</span>(img, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">  <span class="variable constant_">DOMURL</span>.<span class="title function_">revokeObjectURL</span>(url);</span><br><span class="line">&#125;;</span><br><span class="line">img.<span class="property">src</span> = url;</span><br></pre></td></tr></table></figure><p>做得好的是 <a href="https://github.com/cburgmer/rasterizeHTML.js">rasterizeHTML.js</a>，同时可以支持直接填写 url，然后获取后自己截图。</p><blockquote><p>rasterizeHTML.js 和 html2canvas 的区别<br>之前是 rasterizeHTML.js 使用 foreignObject，而 html2canvas 自己从最基础的开始。<br>推荐直接使用 foreignObject 更加安全，目前 html2canvas 和 rasterizeHTML 都兼容使用 foreignObject (html2canvas 看源代码里面已经支持了）。</p></blockquote><h3 id="第三种，使用-Chrome-的-getDisplayMedia"><a href="#第三种，使用-Chrome-的-getDisplayMedia" class="headerlink" title="第三种，使用 Chrome 的 getDisplayMedia"></a>第三种，使用 Chrome 的 getDisplayMedia</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> captureStream = <span class="keyword">await</span> navigator.<span class="property">mediaDevices</span>.<span class="title function_">getDisplayMedia</span>(&#123;</span><br><span class="line">  <span class="attr">audio</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">video</span>: &#123;</span><br><span class="line">    <span class="attr">width</span>: &#123; <span class="attr">max</span>: <span class="number">1280</span> &#125;,</span><br><span class="line">    <span class="attr">height</span>: &#123; <span class="attr">max</span>: <span class="number">720</span> &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>比如在 Chrome 中执行，会弹出下面的窗口，然后开启屏幕分享：<br>![](_image&#x2F;Screen Shot 2020-03-07 at 00.10.21.png)</p><p>这种可以实现录制整个屏幕，或者某个 Tab，从而分享画面，不过录音这个功能会碰到一些阻碍。</p><p><strong>优势</strong></p><ul><li>实现非网页内的内容也可以截取</li></ul><p><strong>劣势</strong></p><ul><li>需要用户手动允许系统的屏幕录像权限</li><li>获得的视频需要截取</li></ul><h3 id="第四种，使用-Electron-的-webContents-capturePage"><a href="#第四种，使用-Electron-的-webContents-capturePage" class="headerlink" title="第四种，使用 Electron 的 webContents.capturePage"></a>第四种，使用 Electron 的 webContents.capturePage</h3><p>Electron 内置了一个截取网页内的接口 <a href="https://www.electronjs.org/docs/api/web-contents#contentscapturepagerect">webContents.capturePage</a> ，如下：</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">const</span> remote = <span class="built_in">require</span>(<span class="string">&quot;electron&quot;</span>).<span class="property">remote</span>;</span><br><span class="line"><span class="keyword">const</span> webContents = remote.<span class="title function_">getCurrentWebContents</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> nativeImage = <span class="keyword">await</span> webContents.<span class="title function_">capturePage</span>(&#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">0</span>,</span><br><span class="line">  <span class="attr">y</span>: <span class="number">0</span>,</span><br><span class="line">  <span class="attr">width</span>: <span class="number">1000</span>,</span><br><span class="line">  <span class="attr">height</span>: <span class="number">2000</span>,</span><br><span class="line">&#125;);</span><br><span class="line">remote.<span class="built_in">require</span>(<span class="string">&quot;fs&quot;</span>).<span class="title function_">writeFile</span>(<span class="variable constant_">TEMP_URL</span>, nativeImage.<span class="title function_">toPng</span>());</span><br></pre></td></tr></table></figure><p><strong>优势</strong></p><ul><li>简单，快捷</li><li>支持截取完整的页面</li></ul><p><strong>劣势</strong></p><ul><li>只能截取当前页面的内容</li></ul><h3 id="第五种，使用-Electron-DesktopCapturer-module"><a href="#第五种，使用-Electron-DesktopCapturer-module" class="headerlink" title="第五种，使用 Electron DesktopCapturer module"></a>第五种，使用 Electron DesktopCapturer module</h3><h4 id="五的第一种："><a href="#五的第一种：" class="headerlink" title="五的第一种："></a>五的第一种：</h4><p><a href="https://www.electronjs.org/docs/api/desktop-capturer">DesktopCapturer</a> 模块里面可以通过设置录屏的 video，然后通过 canvas 获取录制屏幕数据。前面的 <code>getDisplayMedia</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><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; desktopCapturer &#125; = <span class="built_in">require</span>(<span class="string">&quot;electron&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> sources = <span class="keyword">await</span> desktopCapturer.<span class="title function_">getSources</span>(&#123;</span><br><span class="line">  <span class="attr">types</span>: [<span class="string">&quot;window&quot;</span>, <span class="string">&quot;screen&quot;</span>],</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; sources.<span class="property">length</span>; ++i) &#123;</span><br><span class="line">  <span class="keyword">if</span> (sources[i].<span class="property">name</span> === <span class="string">&quot;Electron&quot;</span>) &#123;</span><br><span class="line">    navigator.<span class="property">mediaDevices</span></span><br><span class="line">      .<span class="title function_">getUserMedia</span>(&#123;</span><br><span class="line">        <span class="attr">audio</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="attr">video</span>: &#123;</span><br><span class="line">          <span class="attr">mandatory</span>: &#123;</span><br><span class="line">            <span class="attr">chromeMediaSource</span>: <span class="string">&quot;desktop&quot;</span>,</span><br><span class="line">            <span class="attr">chromeMediaSourceId</span>: sources[i].<span class="property">id</span>,</span><br><span class="line">            <span class="attr">minWidth</span>: <span class="number">1280</span>,</span><br><span class="line">            <span class="attr">maxWidth</span>: <span class="number">1280</span>,</span><br><span class="line">            <span class="attr">minHeight</span>: <span class="number">720</span>,</span><br><span class="line">            <span class="attr">maxHeight</span>: <span class="number">720</span>,</span><br><span class="line">          &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;)</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">stream</span>) =&gt;</span> <span class="title function_">handleStream</span>(stream))</span><br><span class="line">      .<span class="title function_">catch</span>(<span class="function">(<span class="params">e</span>) =&gt;</span> <span class="title function_">handleError</span>(e));</span><br><span class="line">    <span class="keyword">return</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_">handleStream</span>(<span class="params">stream</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> video = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&quot;video&quot;</span>);</span><br><span class="line">  video.<span class="property">srcObject</span> = stream;</span><br><span class="line">  video.<span class="property">onloadedmetadata</span> = <span class="function">(<span class="params">e</span>) =&gt;</span> video.<span class="title function_">play</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_">handleError</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(e);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="五的第二种："><a href="#五的第二种：" class="headerlink" title="五的第二种："></a>五的第二种：</h4><p>之前看到<a href="https://ourcodeworld.com/articles/read/280/creating-screenshots-of-your-app-or-the-screen-in-electron-framework">有个地方</a>，用 Thumbnail 拿来做屏幕截图 :P</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; desktopCapturer &#125; = <span class="built_in">require</span>(<span class="string">&#x27;electron&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> sources = <span class="keyword">await</span> desktopCapturer.<span class="title function_">getSources</span>(&#123;</span><br><span class="line">    <span class="attr">types</span>: [<span class="string">&#x27;window&#x27;</span>, <span class="string">&#x27;screen&#x27;</span>],</span><br><span class="line">&#125;, <span class="attr">thumbnailSizeSize</span>: &#123;</span><br><span class="line">    <span class="attr">width</span>: <span class="number">300</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="number">300</span>,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> source = sources.<span class="title function_">find</span>(<span class="function"><span class="params">e</span> =&gt;</span> e.<span class="property">name</span> === <span class="string">&#x27;Electron Window Title&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> thumbnailNativeImage = source.<span class="property">thumbnail</span></span><br></pre></td></tr></table></figure><p><strong>优势</strong></p><ul><li>可以搜索特定窗口进行录屏</li><li>electron 封装了一层，使用更加简便</li></ul><p><strong>劣势</strong></p><ul><li>如同前面的 getDisplayMedia，也需要用户手动开启屏幕录像权限</li><li>如果用 desktopCapturer 的 thumbnail 来截屏，耗性能</li></ul><h3 id="第六种，使用系统原生的屏幕录制接口"><a href="#第六种，使用系统原生的屏幕录制接口" class="headerlink" title="第六种，使用系统原生的屏幕录制接口"></a>第六种，使用系统原生的屏幕录制接口</h3><p>这种就不细讲了，每端都适配复杂的原生接口，这样就已经迷失方向了。</p><h2 id="截屏的图片不是-2x-图片"><a href="#截屏的图片不是-2x-图片" class="headerlink" title="截屏的图片不是 2x 图片"></a>截屏的图片不是 2x 图片</h2><p>如果用上面的任何一种方法，在高分屏里面截图(window.devicePixelRatio &#x3D;&#x3D;&#x3D; 2)，最后得到的图片还是图片宽高放大一倍，但像素还是低一倍。</p><p>后来发现其实是图片头中有记录 meta 信息。里面就有关于 dpi 的信息。找到包 <a href="https://github.com/shutterstock/changeDPI">changedpi</a> 可以修改 canvas 和 DataUrl 拿到的图片 dpi。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个标准的 72 dpi</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">IMAGE_QUALITY</span> = <span class="number">0.92</span>;</span><br><span class="line"><span class="keyword">const</span> dataUrl = canvas.<span class="title function_">toDataURL</span>(<span class="string">&quot;image/jpeg&quot;</span>, <span class="variable constant_">IMAGE_QUALITY</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">maxDPIRatio</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> displayScale = remote.<span class="property">screen</span></span><br><span class="line">    .<span class="title function_">getAllDisplays</span>()</span><br><span class="line">    .<span class="title function_">map</span>(<span class="function">(<span class="params">display</span>) =&gt;</span> display.<span class="property">scaleFactor</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="property">max</span>.<span class="title function_">apply</span>(<span class="literal">null</span>, displayScale); <span class="comment">// 高分屏里面一般是 2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">STANDARD_DPI</span> = <span class="number">72</span>;</span><br><span class="line"><span class="keyword">const</span> daurl150dpi = changedpi.<span class="title function_">changeDpiDataUrl</span>(</span><br><span class="line">  dataUrl,</span><br><span class="line">  <span class="title function_">maxDPIRatio</span>() * <span class="variable constant_">STANDARD_DPI</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>canvas 一般情况下，最高只能获得 72 dpi 分辨率的图片。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>每个点都没有太详细的讲，但已经可以通过这些选择最终的方案了。最后当然推荐使用的是 webContents.capturePage 的方法。简单易用，在功能上能做到最好。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://stackoverflow.com/questions/16870404/whats-the-difference-between-html2canvas-and-rasterizehtml-js">https://stackoverflow.com/questions/16870404/whats-the-difference-between-html2canvas-and-rasterizehtml-js</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia">https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia</a></li><li><a href="https://ourcodeworld.com/articles/read/280/creating-screenshots-of-your-app-or-the-screen-in-electron-framework">https://ourcodeworld.com/articles/read/280/creating-screenshots-of-your-app-or-the-screen-in-electron-framework</a>)</li></ul><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/foreignObject">https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/foreignObject</a></li><li><a href="https://www.electronjs.org/docs/api/web-contents#contentscapturepagerect-callback">https://www.electronjs.org/docs/api/web-contents#contentscapturepagerect-callback</a></li><li><a href="https://github.com/electron/electron/issues/7387">https://github.com/electron/electron/issues/7387</a></li><li><a href="https://github.com/shutterstock/changeDPI">https://github.com/shutterstock/changeDPI</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;实现一个在 Electron 内截屏的功能，大概有六种实现方式，讨论一下选择哪个方案。最后再说一下高分屏的截图如何变得清晰。&lt;/p&gt;
&lt;h2 id=&quot;可选方案&quot;&gt;&lt;a href=&quot;#可选方案&quot; class=&quot;headerlink&quot; title=&quot;可选方案&quot;&gt;&lt;/a&gt;可选方案</summary>
      
    
    
    
    
    <category term="electron" scheme="https://vastiny.com/tags/electron/"/>
    
    <category term="javascript" scheme="https://vastiny.com/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>解析 Electron 的跨境成执行函数机制</title>
    <link href="https://vastiny.com/post/rrxdfu1790k1o5p2.html"/>
    <id>https://vastiny.com/post/rrxdfu1790k1o5p2.html</id>
    <published>2019-12-07T03:50:00.000Z</published>
    <updated>2026-03-23T20:16:36.431Z</updated>
    
    <content type="html"><![CDATA[<p>刚开始接触 Electron 的时候，很好奇有一个 remote 模块，能很方便地实现跨进程的操作，比如数据共享和方法调用。最近抽空研究了一下，所以成文。</p><p>实现的一个示例 demo: <a href="https://github.com/yantze/demo-electron-remote">https://github.com/yantze/demo-electron-remote</a> 。</p><p>默认的 ipc 协议是可以传送可以结构化的数据。remote 需要完成的事情是，对不能结构化的数据，用元信息(meta) 记录，然后 remote 需要被调用的时候，发起 ipc 请求到主进程或者 server 进程，远程调用方法。这里是实现一个简单的 remote 调用机制的流程，方便了解 remote 的整个调用流程。</p><p><img src="https://vastiny-com.oss-cn-hongkong.aliyuncs.com/_imgs/Fg-mj71jRNuFqwHI0cwAMOj26w0C.png"></p><h2 id="分析流程"><a href="#分析流程" class="headerlink" title="分析流程"></a>分析流程</h2><p>下面分析从一个简单的结构开始，逐渐到复杂结构体，来实现跨进程的传送。</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// server.js</span></span><br><span class="line"><span class="keyword">const</span> cp = <span class="built_in">require</span>(<span class="string">&quot;child_process&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> clientProcess = cp.<span class="title function_">fork</span>(<span class="string">&quot;./client.js&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> remoteObj = &#123;</span><br><span class="line">  <span class="attr">normal</span>: <span class="string">&quot;dddd&quot;</span>,</span><br><span class="line">  <span class="attr">num</span>: <span class="number">2</span>,</span><br><span class="line">&#125;;</span><br><span class="line">clientProcess.<span class="title function_">on</span>(<span class="string">&quot;message&quot;</span>, <span class="function">(<span class="params">commandId</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (commandId === <span class="string">&quot;GET_OBJ&quot;</span>) &#123;</span><br><span class="line">    clientProcess.<span class="title function_">send</span>(remoteObj);</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">// client.js</span></span><br><span class="line">process.<span class="title function_">send</span>(<span class="string">&quot;GET_OBJ&quot;</span>);</span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&quot;message&quot;</span>, <span class="function">(<span class="params">obj</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Receive obj:&quot;</span>, obj);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这里实现一个相当简单的进程间传送数据的例子，当 server 进程 fork 一个 child 进程后，child 发送一个请求，然后 server 接受数据后，发送给 child 一个自身的对象，然后 child 就接受到了这个数据对象。</p><p>但一旦这个 remoteObj 对象中包含了一个方法，或者 Promise 这些不可结构化的数据，那么对应的数据就会丢失。</p><h3 id="对象中包含方法时处理"><a href="#对象中包含方法时处理" class="headerlink" title="对象中包含方法时处理"></a>对象中包含方法时处理</h3><p>其实实现的方式也还好，就是判断这里面是否有 function, 如果有，就使用元信息包裹一下这个 function，记录方法名。因为当 client 进程调用对应的方法的时候，需要向 server 进程发起请求，server 接到请求后，需要找到对应的 remoteObj, 并且定位到方法名，就可以 <code>remoteObj[functionName]()</code> 实现远程方法调用了。</p><p>既然需要记录方法名等额外数据，就需要 meta 去包裹方法，这里 server 需要需要一个新的方法 valueToMeta，而 client 需要一个解释 meta 的方法， metaToValue。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// server.js</span></span><br><span class="line"><span class="keyword">const</span> valueToMeta = <span class="keyword">function</span> (<span class="params">value</span>) &#123;</span><br><span class="line">  <span class="comment">// Determine the type of value.</span></span><br><span class="line">  <span class="keyword">let</span> type = <span class="keyword">typeof</span> value;</span><br><span class="line">  <span class="keyword">if</span> (type === <span class="string">&quot;object&quot;</span> || type === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      type,</span><br><span class="line">      <span class="attr">name</span>: value.<span class="property">constructor</span> ? value.<span class="property">constructor</span>.<span class="property">name</span> : <span class="string">&quot;&quot;</span>,</span><br><span class="line">      <span class="attr">id</span>: objectsRegistry.<span class="title function_">add</span>(value),</span><br><span class="line">      <span class="attr">members</span>: <span class="title function_">parseMembers</span>(value),</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// client.js</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">metaToValue</span>(<span class="params">meta</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (meta.<span class="property">type</span> === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> remoteFunction = <span class="keyword">async</span> <span class="keyword">function</span> (<span class="params">...args</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> commandId = <span class="string">&quot;ELECTRON_BROWSER_FUNCTION_CALL&quot;</span>;</span><br><span class="line">      <span class="keyword">const</span> obj = <span class="keyword">await</span> <span class="title function_">getRemoteValue</span>(commandId, &#123; <span class="attr">id</span>: meta.<span class="property">id</span> &#125;);</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">await</span> <span class="title function_">metaToValue</span>(obj);</span><br><span class="line">    &#125;;</span><br><span class="line">    ret = remoteFunction;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    ret = &#123;&#125;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里可以看到，判断为 function 后，valueToMeta 会把将 function 解析后变成 4 个数据字段返回，id 和 members 可以可以暂时不管，因为暂时不考虑 class 类。然后 client 进程就能通过 metaToValue 解析并且还原 remoteObj 的结构。</p><h3 id="对象中包含子对象，并且还包含方法"><a href="#对象中包含子对象，并且还包含方法" class="headerlink" title="对象中包含子对象，并且还包含方法"></a>对象中包含子对象，并且还包含方法</h3><p>这个时候，就要开始复杂的嵌套分析和解析了。其实也是在上一小节里面，再加上一个嵌套，就是如果分析到一个新的对象，那就把这个子对象存起来，当子对象的方法需要被调用的时候，就需要通过 id 找到子对象，并且通过方法名调用子对象的方法。</p><p>这时需要用到上面代码中出现的 parseMembers 方法了，这个方法能解析 object 中的所有自有对象，并且把子对象再次放进 valueToMeta 中，做递归循环。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">parseMembers</span>(<span class="params">value</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> members = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyNames</span>(value);</span><br><span class="line">  <span class="keyword">return</span> members</span><br><span class="line">    .<span class="title function_">map</span>(<span class="function">(<span class="params">name</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable constant_">IGNORE_FUNCTION_MEMBERS</span>.<span class="title function_">includes</span>(name)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> meta = <span class="title function_">valueToMeta</span>(value[name]);</span><br><span class="line">      <span class="keyword">if</span> (meta.<span class="property">type</span> === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          ...meta,</span><br><span class="line">          name,</span><br><span class="line">          <span class="attr">type</span>: <span class="string">&quot;method&quot;</span>,</span><br><span class="line">        &#125;;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        ...meta,</span><br><span class="line">        name,</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">filter</span>(<span class="title class_">Boolean</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不过这里会忽略一些属性，在 <code>IGNORE_FUNCTION_MEMBERS</code> 中有声明，比如 arguments 这个 function 的自有属性。之所以把 type 改为了 method ，是因为这个一个对象的方法，需要使用 id 去寻找方法的上层对象。</p><h3 id="小节"><a href="#小节" class="headerlink" title="小节"></a>小节</h3><p>其实通过上面的介绍，已经基本清楚整个 remote 的调用流程了，如果不明白可以直接访问我新建的一个 demo 仓库，里面可以完整地运行一个简单的 remote 调用。</p><h3 id="如何使用仓库的内容"><a href="#如何使用仓库的内容" class="headerlink" title="如何使用仓库的内容"></a>如何使用仓库的内容</h3><ol><li>在启动 server.js 后，可以在 Chrome 浏览器的地址栏打开 <code>chrome://inspect</code></li><li>在里面的 Remote Target 中选择 <code>./client.js</code></li><li>跳转到 Console 面板中，执行 <code>const obj = await require(&#39;./client.js&#39;).getRemoteObj()</code></li><li>返回了 server 进程的 remoteObj</li></ol><p>试试，应该还挺好玩的 :P。</p><h2 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h2><ol><li>Electron API 说明中有说实现机制类似 Java 的 <a href="https://en.wikipedia.org/wiki/Java_remote_method_invocation">RMI</a>，然后试了一下，的确有些相似。</li><li>在 server.js 代码中有一个 ipc 参数:</li></ol><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">clientProcess = cp.<span class="title function_">spawn</span>(<span class="string">&quot;node&quot;</span>, [<span class="string">&quot;--inspect=9230&quot;</span>, <span class="string">&quot;./client.js&quot;</span>], &#123;</span><br><span class="line">  <span class="attr">stdio</span>: [<span class="string">&quot;inherit&quot;</span>, <span class="string">&quot;inherit&quot;</span>, <span class="string">&quot;inherit&quot;</span>, <span class="string">&quot;ipc&quot;</span>],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这里的 ipc 参数，能让 nodejs 在 spawn 子进程的时候，加上一个 ipc socket 隧道，默认是没有加的，那样将不能使用 process.send 方法。之所以 child_process.fork 方法可以使用 ipc，是因为 fork 其实是 spawn 的一个封装方法，里面有加上 ipc 这个参数。</p><ol start="3"><li>比如 Date 或者 Promise 等非结构化数据，暂时没有实现，这个库只是作一个简单的示例说明。</li><li>electron 也有类似的代码，里面有完整地实现，可以参看 <code>./electron/lib/browser/remote/server.ts</code> 和 <code>./electron/lib/renderer/api/remote.js</code></li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>Java 的 <a href="https://en.wikipedia.org/wiki/Java_remote_method_invocation">RMI</a></li><li><a href="https://github.com/electron/electron">https://github.com/electron/electron</a></li><li><a href="https://nodejs.org/docs/latest/api/child_process.html">https://nodejs.org/docs/latest/api/child_process.html</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;刚开始接触 Electron 的时候，很好奇有一个 remote 模块，能很方便地实现跨进程的操作，比如数据共享和方法调用。最近抽空研究了一下，所以成文。&lt;/p&gt;
&lt;p&gt;实现的一个示例 demo: &lt;a href=&quot;https://github.com/yantze/dem</summary>
      
    
    
    
    
    <category term="electron" scheme="https://vastiny.com/tags/electron/"/>
    
    <category term="nodejs" scheme="https://vastiny.com/tags/nodejs/"/>
    
    <category term="js" scheme="https://vastiny.com/tags/js/"/>
    
  </entry>
  
  <entry>
    <title>分析异步转同步库 deasync</title>
    <link href="https://vastiny.com/post/vnhivhifxpzuf7uz.html"/>
    <id>https://vastiny.com/post/vnhivhifxpzuf7uz.html</id>
    <published>2019-11-27T02:11:00.000Z</published>
    <updated>2026-03-23T20:16:32.509Z</updated>
    
    <content type="html"><![CDATA[<p>最近看到一个实现异步方法转换为同步方法的库 deasync。正好看到有些场景可以用到，所以分析一下库的源代码。</p><p>这个库的源码在这里 <a href="https://github.com/abbr/deasync">deasync</a>。最好是看了我上一篇的介绍 NodeJS debug C++ 的那篇文章，同样的内容我就不注释了。</p><h2 id="源代码"><a href="#源代码" class="headerlink" title="源代码"></a>源代码</h2><p>这其实是一个 NodeJS 的 C++ 的插件。核心代码只有下面的一些：</p><figure class="highlight cpp"><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="meta">#<span class="keyword">include</span> <span class="string">&lt;uv.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;v8.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;napi.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;uv.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> Napi;</span><br><span class="line"></span><br><span class="line"><span class="function">Napi::Value <span class="title">Run</span><span class="params">(<span class="type">const</span> Napi::CallbackInfo&amp; info)</span> </span>&#123;</span><br><span class="line">  Napi::Env env = info.<span class="built_in">Env</span>();</span><br><span class="line">  <span class="function">Napi::HandleScope <span class="title">scope</span><span class="params">(env)</span></span>;</span><br><span class="line">  <span class="built_in">uv_run</span>(<span class="built_in">uv_default_loop</span>(), UV_RUN_ONCE);</span><br><span class="line">  <span class="keyword">return</span> env.<span class="built_in">Undefined</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">static</span> Napi::Object <span class="title">init</span><span class="params">(Napi::Env env, Napi::Object exports)</span> </span>&#123;</span><br><span class="line">  exports.<span class="built_in">Set</span>(Napi::String::<span class="built_in">New</span>(env, <span class="string">&quot;run&quot;</span>), Napi::Function::<span class="built_in">New</span>(env, Run));</span><br><span class="line">  <span class="keyword">return</span> exports;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">NODE_API_MODULE</span>(deasync, init)</span><br></pre></td></tr></table></figure><p>这里面用的是 N-API 的接口，但这个库里面的代码不能正常的编译出 Debug 模块，所以把这核心代码拿出来又写了一个新的库，然后重新编译即可。</p><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>直接看代码：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;uv.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;v8.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;napi.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;node.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> Napi;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * CallbackInfo 是一个参数类，如果有调用方法有传参，可以通过 info[0]、info[1]、info.Length() 来获取</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function">Napi::Value <span class="title">Run</span><span class="params">(<span class="type">const</span> Napi::CallbackInfo &amp;info)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 每个调用方法都能通过 CallbackInfo 类获取到 env，也可以获取 Context ，通过 env 获取到值和操作</span></span><br><span class="line">    Napi::Env env = info.<span class="built_in">Env</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// HandleScope 句柄作用域，控制对象的生命周期，是 napi_open_handle_scope 的封装</span></span><br><span class="line">    <span class="comment">// 当一个句柄作用域对象被删除时，或者这个 Run 函数结束了，作用域内的对象就算被别的地方引用，也会被垃圾回收器随时释放</span></span><br><span class="line">    <span class="function">Napi::HandleScope <span class="title">scope</span><span class="params">(env)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// uv_runL 运行 Event Loop，这里加了 UV_RUN_ONCE，说明只轮询 I/O 一次</span></span><br><span class="line">    <span class="comment">// 如果是 UV_RUN_NOWAIT 标识，轮询 I/O 一次但不会阻塞</span></span><br><span class="line">    <span class="comment">// 如果 uv_run 里面没有待执行的任务回调，就会等待，算出 timeout 时间，这里就是异步转同步的关键</span></span><br><span class="line">    <span class="comment">// 然后调用 epoll_pwait 去让整个程序等待</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">// uv_default_loop: 返回初始化过的默认 event loop</span></span><br><span class="line">    <span class="comment">// 返回一个 loop 结构体，记录了初始化过的 loop 信息</span></span><br><span class="line">    <span class="built_in">uv_run</span>(<span class="built_in">uv_default_loop</span>(), UV_RUN_ONCE);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回 js value: undefined</span></span><br><span class="line">    <span class="keyword">return</span> env.<span class="built_in">Undefined</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">static</span> Napi::Object <span class="title">init</span><span class="params">(Napi::Env env, Napi::Object exports)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 给 Object 对象 exports 设置属性， exports 相当于 js 中的 object &#123;&#125;，相当于</span></span><br><span class="line">    <span class="comment">// const exports = &#123;&#125;</span></span><br><span class="line">    <span class="comment">// exports.run = function Run() &#123; ... &#125;</span></span><br><span class="line">    exports.<span class="built_in">Set</span>(Napi::String::<span class="built_in">New</span>(env, <span class="string">&quot;run&quot;</span>), Napi::Function::<span class="built_in">New</span>(env, Run));</span><br><span class="line">    <span class="keyword">return</span> exports;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">NODE_API_MODULE</span>(NODE_GYP_MODULE_NAME, init)</span><br></pre></td></tr></table></figure><p><code>uv_run</code> 显式的运行一次 EventLoop, 并且暴露 run 方法，然后用在下面的 js 代码中：</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">const</span> deasyncAddon = <span class="built_in">require</span>(<span class="string">&quot;./build/Debug/hello.node&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> loopWhile = <span class="keyword">function</span> (<span class="params">pred</span>) &#123;</span><br><span class="line">  <span class="keyword">while</span> (<span class="title function_">pred</span>()) &#123;</span><br><span class="line">    process.<span class="title function_">_tickCallback</span>();</span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">pred</span>()) deasyncAddon.<span class="title function_">run</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>先不管 <code>process._tickCallback()</code>，看起来就像是写了一个阻塞的循环代码，只有 pred() 或者 isEnd() 结束后才会中断：</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">const</span> wait = <span class="keyword">function</span> (<span class="params">time</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> start = <span class="title class_">Date</span>.<span class="title function_">now</span>();</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">isEnd</span> = (<span class="params"></span>) =&gt; <span class="title class_">Date</span>.<span class="title function_">now</span>() - start &lt; time;</span><br><span class="line">  <span class="keyword">while</span> (<span class="title function_">isEnd</span>()) &#123;&#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>但几乎完全不一样。pred 这个回调方法完成后，就会终止，而不像 wait 方法让整个线程都阻塞，干不了其它事情。</p><p>在 loopWhite 方法中， <code>process._tickCallback()</code> 的使用也很巧妙，在 NodeJS 源码中有说：</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">process.<span class="property">nextTick</span> = nextTick;</span><br><span class="line"><span class="comment">// Used to emulate a tick manually in the JS land.</span></span><br><span class="line"><span class="comment">// A better name for this function would be `runNextTicks` but</span></span><br><span class="line"><span class="comment">// it has been exposed to the process object so we keep this legacy name</span></span><br><span class="line">process.<span class="property">_tickCallback</span> = runNextTicks;</span><br></pre></td></tr></table></figure><p>在正常的代码中出现 _tickCallback()，则马上执行之前所有的 nextTicks 任务，接着执行同步任务。但我看 runNextTicks 源码的时候，发现其实是执行 <code>runMicrotasks</code> 😂。所以会把 <code>Process.nextTick、Promise.then catch finally、MutationObserver</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><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="title function_">setImmediate</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;setImmediate 1&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">process.<span class="title function_">nextTick</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;nextTick 1&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">resolve</span>().<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;promise then 1&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">process.<span class="title function_">_tickCallback</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;sync 1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// result:</span></span><br><span class="line"><span class="comment">// nextTick 1</span></span><br><span class="line"><span class="comment">// promise then 1</span></span><br><span class="line"><span class="comment">// sync 1</span></span><br><span class="line"><span class="comment">// setImmediate 1</span></span><br></pre></td></tr></table></figure><p>为什么会这样呢？因为 nextTicks 的名字有误，nodejs 网站上面有说， setImmediate 意思应该互换 nextTick。nextTick 才是在同一个阶段立即执行, 而 setImmediate 是在 EventLoop 接下来执行，或者在 tick 上触发。(文末参考链接)</p><h2 id="使用例子"><a href="#使用例子" class="headerlink" title="使用例子"></a>使用例子</h2><p>首先 deasync 库把 loopWhile 封装成一个等待方法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">deasync</span>(<span class="params">fn</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> done = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">let</span> args = <span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">slice</span>.<span class="title function_">apply</span>(<span class="variable language_">arguments</span>).<span class="title function_">concat</span>(cb);</span><br><span class="line">    <span class="keyword">let</span> err;</span><br><span class="line">    <span class="keyword">let</span> res;</span><br><span class="line"></span><br><span class="line">    fn.<span class="title function_">apply</span>(<span class="variable language_">this</span>, args);</span><br><span class="line">    <span class="title function_">loopWhile</span>(<span class="function">() =&gt;</span> !done);</span><br><span class="line">    <span class="keyword">if</span> (err) <span class="keyword">throw</span> err;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">cb</span>(<span class="params">e, r</span>) &#123;</span><br><span class="line">      err = e;</span><br><span class="line">      res = r;</span><br><span class="line">      done = <span class="literal">true</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><p>然后可以写一个简单的阻塞 sleep 方法：</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">const</span> sleep = <span class="title function_">deasync</span>(<span class="keyword">function</span> (<span class="params">timeout, done</span>) &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(done, timeout);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="title function_">sleep</span>(<span class="number">1000</span>); <span class="comment">// 阻塞 1s</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;run here!&quot;</span>);</span><br></pre></td></tr></table></figure><p>更多可以去看看 deasync 中的例子。</p><h2 id="un-promisify"><a href="#un-promisify" class="headerlink" title="un promisify"></a>un promisify</h2><p>deasync 默认只处理带 callback 的方法。如果是想要处理 promise 的方法，需要包裹一层转化一下。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">unPromisify</span> = (<span class="params">fn</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function">(<span class="params">...args</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (args.<span class="property">length</span> &lt; <span class="number">1</span>)</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;unPromisify arguments length must at least 2.&quot;</span>);</span><br><span class="line">    <span class="keyword">const</span> cb = args.<span class="title function_">pop</span>();</span><br><span class="line">    <span class="title function_">fn</span>(...args)</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> <span class="title function_">cb</span>(<span class="literal">null</span>, data))</span><br><span class="line">      .<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> <span class="title function_">cb</span>(err, <span class="literal">null</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">async</span> <span class="keyword">function</span> <span class="title function_">say</span>(<span class="params">e, e2</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;good&quot;</span>, e, e2);</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;r&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> saySync = <span class="title function_">deasync</span>(<span class="title function_">unPromisify</span>(say));</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> c = <span class="title function_">saySync</span>(<span class="string">&quot;allen&quot;</span>, <span class="string">&quot;alex&quot;</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;return:&quot;</span>, c);</span><br><span class="line">&#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(e);</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;done&quot;</span>);</span><br></pre></td></tr></table></figure><h2 id="同样类型的库"><a href="#同样类型的库" class="headerlink" title="同样类型的库"></a>同样类型的库</h2><p>还有一个类似功能的库：<a href="https://github.com/laverdet/node-fibers">node-fibers</a>，不过它的实现原理不同，并且复杂得多。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/#process-nexttick-setimmediate">process.nextTick() 对比 setImmediate() 名称问题</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;最近看到一个实现异步方法转换为同步方法的库 deasync。正好看到有些场景可以用到，所以分析一下库的源代码。&lt;/p&gt;
&lt;p&gt;这个库的源码在这里 &lt;a href=&quot;https://github.com/abbr/deasync&quot;&gt;deasync&lt;/a&gt;。最好是看了我上一篇的</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>VSCode 配置 NodeJS C/C++ Addon Debug 环境</title>
    <link href="https://vastiny.com/post/rhz0d1ortvulkhiw.html"/>
    <id>https://vastiny.com/post/rhz0d1ortvulkhiw.html</id>
    <published>2019-11-08T17:13:00.000Z</published>
    <updated>2026-03-23T20:16:34.323Z</updated>
    
    <content type="html"><![CDATA[<p>Node.js 基于 GYP（Generate Your Projects）构建 C&#x2F;C++ 插件，叫 <code>node-gyp</code>。这次介绍如何写一个简单的 hello 插件，并且能 debug C&#x2F;C++ 代码。</p><h2 id="1-创建一个可以被-node-gyp-编译的项目"><a href="#1-创建一个可以被-node-gyp-编译的项目" class="headerlink" title="1 创建一个可以被 node-gyp 编译的项目"></a>1 创建一个可以被 <code>node-gyp</code> 编译的项目</h2><h3 id="1-1-安装全局-node-gyp-命令"><a href="#1-1-安装全局-node-gyp-命令" class="headerlink" title="1.1 安装全局 node-gyp 命令"></a>1.1 安装全局 node-gyp 命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g node-gyp</span><br></pre></td></tr></table></figure><h3 id="1-2-创建-package-json-环境"><a href="#1-2-创建-package-json-环境" class="headerlink" title="1.2 创建 package.json 环境"></a>1.2 创建 package.json 环境</h3><p>新建目录 <code>hello</code>, 并 <code>npm init -y</code>。</p><h3 id="1-3-创建-binding-gyp-文件，用来配置-node-gyp"><a href="#1-3-创建-binding-gyp-文件，用来配置-node-gyp" class="headerlink" title="1.3 创建 binding.gyp 文件，用来配置 node-gyp"></a>1.3 创建 <code>binding.gyp</code> 文件，用来配置 <code>node-gyp</code></h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// binding.gyp</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;targets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;target_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hello&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;sources&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;./src/hello.cc&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="1-4-添加-C-源代码"><a href="#1-4-添加-C-源代码" class="headerlink" title="1.4 添加 C++ 源代码"></a>1.4 添加 C++ 源代码</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./src/hello.cc</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;node.h&gt;</span></span></span><br><span class="line"></span><br><span class="line">namespace demo</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">using v8::FunctionCallbackInfo;</span><br><span class="line">using v8::Isolate;</span><br><span class="line">using v8::Local;</span><br><span class="line">using v8::NewStringType;</span><br><span class="line">using v8::Object;</span><br><span class="line">using v8::String;</span><br><span class="line">using v8::Value;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">Method</span><span class="params">(<span class="type">const</span> FunctionCallbackInfo&lt;Value&gt; &amp;args)</span></span><br><span class="line">&#123;</span><br><span class="line">    Isolate *isolate = args.GetIsolate();</span><br><span class="line">    args.GetReturnValue().Set(String::NewFromUtf8(</span><br><span class="line">                                  isolate, <span class="string">&quot;world&quot;</span>, NewStringType::kNormal)</span><br><span class="line">                                  .ToLocalChecked());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">Initialize</span><span class="params">(Local&lt;Object&gt; exports)</span></span><br><span class="line">&#123;</span><br><span class="line">    NODE_SET_METHOD(exports, <span class="string">&quot;hello&quot;</span>, Method);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里的 NODE_GYP_MODULE_NAME 宏就是 前面 binding.gyp 的指定的 target_name</span></span><br><span class="line">NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)</span><br><span class="line"></span><br><span class="line">&#125; <span class="comment">// namespace demo</span></span><br></pre></td></tr></table></figure><p>例子来自： <a href="https://nodejs.org/api/addons.html#addons_hello_world">https://nodejs.org/api/addons.html#addons_hello_world</a></p><h3 id="1-5-编译-addon"><a href="#1-5-编译-addon" class="headerlink" title="1.5 编译 addon"></a>1.5 编译 addon</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node-gyp rebuild</span><br></pre></td></tr></table></figure><p>会生成 <code>build/Release/hello.node</code> 文件</p><h3 id="1-6-添加-Task，不必每次输入命令"><a href="#1-6-添加-Task，不必每次输入命令" class="headerlink" title="1.6 添加 Task，不必每次输入命令"></a>1.6 添加 Task，不必每次输入命令</h3><p>在 <code>package.json</code> 的 “scripts” 字段中，添加：</p><figure class="highlight plaintext"><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">&quot;scripts&quot;: &#123;</span><br><span class="line">    &quot;start&quot;: &quot;node index.js&quot;,</span><br><span class="line">    &quot;build:dev&quot;: &quot;node-gyp -j 16 build --debug&quot;,</span><br><span class="line">    &quot;build&quot;: &quot;node-gyp -j 16 build&quot;,</span><br><span class="line">    &quot;clean&quot;: &quot;node-gyp clean&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>之后在 Visual Studio Code 中就可以直接执行 Task 了。</p><blockquote><p>执行 Task 的方式是：通过快捷键 Command+P 在弹出的框中，输入 <code>task build:dev</code> 就可以执行对应的命令了。</p></blockquote><h3 id="1-6-创建-index-js-并在里面添加如下："><a href="#1-6-创建-index-js-并在里面添加如下：" class="headerlink" title="1.6 创建 index.js 并在里面添加如下："></a>1.6 创建 <code>index.js</code> 并在里面添加如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> helloAddon = <span class="built_in">require</span>(<span class="string">&quot;./build/Release/hello.node&quot;</span>);</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">&quot;output:&quot;</span>, helloAddon.<span class="title function_">hello</span>(<span class="string">&quot;fakeArgs&quot;</span>));</span><br></pre></td></tr></table></figure><p>这里的 helloAddon 就是编译的结果，等同于 js 代码 <code>module.exports.hello = () =&gt; &#39;world&#39;</code>。</p><p>接着执行 <code>node index.js</code> 即会输出 <code>output: world</code> 结果。</p><h2 id="2-Debug-C-C-代码"><a href="#2-Debug-C-C-代码" class="headerlink" title="2 Debug C&#x2F;C++ 代码"></a>2 Debug C&#x2F;C++ 代码</h2><h3 id="2-1-让-node-gyp-编译出-Debug-版本的-hello-node"><a href="#2-1-让-node-gyp-编译出-Debug-版本的-hello-node" class="headerlink" title="2.1 让 node-gyp 编译出 Debug 版本的 hello.node"></a>2.1 让 <code>node-gyp</code> 编译出 Debug 版本的 hello.node</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node-gyp rebuild --debug</span><br></pre></td></tr></table></figure><p>这样会重新编译 hello.cc ，并且生成 <code>./build/Debug/hello.node</code></p><h3 id="2-2-添加-vscode-配置项"><a href="#2-2-添加-vscode-配置项" class="headerlink" title="2.2 添加 vscode 配置项"></a>2.2 添加 vscode 配置项</h3><ul><li>安装 C&#x2F;C++ 的扩展 <code>ms-vscode.cpptools</code></li><li>安装 LLDB 的 Debugger 扩展 <code>vadimcn.vscode-lldb</code></li><li>项目中的 <code>&lt;node.h&gt;</code> 是红线，因为没有找到对应的头文件</li><li>在项目中 <code>Command+Shift+P</code> 弹出的下拉框输入，<code>C/C++ configuration (UI)</code></li><li>在界面中添加 include 头文件，包括 nodejs 的源代码的源文件和 v8 的源文件</li></ul><p>添加头文件路径可以参考我的，这样代码里面的头文件 node.h 就不会影响</p><figure class="highlight plaintext"><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">&quot;/Users/xxx/node-investigation/n1/node/src/**&quot;,</span><br><span class="line">&quot;/Users/xxx/node-investigation/n1/node/deps/v8/include/**&quot;</span><br></pre></td></tr></table></figure><h3 id="2-3-添加断点"><a href="#2-3-添加断点" class="headerlink" title="2.3 添加断点"></a>2.3 添加断点</h3><p>接着把先前 <code>index.js</code> 的代码 Release 改成 Debug</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="keyword">const</span> helloAddon = <span class="built_in">require</span>(<span class="string">&quot;./build/Debug/hello.node&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="2-4-配置-Debugger-的启动配置"><a href="#2-4-配置-Debugger-的启动配置" class="headerlink" title="2.4 配置 Debugger 的启动配置"></a>2.4 配置 Debugger 的启动配置</h3><p>在项目中 <code>Command+Shift+P</code> 弹出的下拉框输入 <code>open launch.json</code> ，会显示这个打开 launch.json 的选项，然后添加：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;configurations&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Debug C/C++ Addon&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lldb&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;request&quot;</span><span class="punctuation">:</span> <span class="string">&quot;launch&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;preLaunchTask&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm: build:dev&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;program&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;$&#123;workspaceFolder&#125;/index.js&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这样下次开始 <code>Start Debugging</code> 的时候，会依据 <code>launch.json</code> 执行，当给 <code>hello.cc</code> 标记断点后，程序会在断点出断点。</p><blockquote><p>注意：只有装了 lldb 插件后，才能使用 type: “lldb” 选项值。lldb 是 llvm 的一个类似 gdb 的实现。这个插件用来支持 llvm 编译器的。如果在 Windows 中，可以用 vscode 自带的 <code>cppvsdbg</code> 。</p></blockquote><h2 id="3-混合调试-C-C-和-NodeJS"><a href="#3-混合调试-C-C-和-NodeJS" class="headerlink" title="3 混合调试 C&#x2F;C++ 和 NodeJS"></a>3 混合调试 C&#x2F;C++ 和 NodeJS</h2><p>就是先启动 NodeJS 调试后，接着启动 C&#x2F;C++ 调试器，这样在深入 js 代码的过程中，还可以进入到 Addons 中去。有了上面介绍的基础，实现混合调试简单了很多。</p><h3 id="3-1-调试-JS-代码"><a href="#3-1-调试-JS-代码" class="headerlink" title="3.1 调试 JS 代码"></a>3.1 调试 JS 代码</h3><p>在 launch.json 中的 configurations 数组中，继续添加如下配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JS Debug Build&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;request&quot;</span><span class="punctuation">:</span> <span class="string">&quot;launch&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;console&quot;</span><span class="punctuation">:</span> <span class="string">&quot;integratedTerminal&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;program&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;workspaceFolder&#125;/index.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;preLaunchTask&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm: build:dev&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这样，就可以在 <code>Start Debugging</code> 的时候，选择 <code>JS Debug Build</code> 可以调试断点 JS 代码了。</p><blockquote><p>注意：调试 JS 代码，需要让 Debugger 面板切换到 <code>JS Debug Build</code> ,如下图</p></blockquote><p><img src="/./_image/2019-11-09-01-01-11.png"></p><h3 id="3-2-混合调试-Addons"><a href="#3-2-混合调试-Addons" class="headerlink" title="3.2 混合调试 Addons"></a>3.2 混合调试 Addons</h3><p>继续在 launch.json 中添加如下配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Attach C/C++ Addon&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lldb&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;request&quot;</span><span class="punctuation">:</span> <span class="string">&quot;attach&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;pid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;command:pickMyProcess&#125;&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>把 Debug 面板的 Debugger 切换到 <code>Attach C/C++ Addon</code>， 接着再启动即可。这里的 request: “attach” 是让 lldb 链接到 nodejs 进程中去 Debug。所以下面的 pid 字段就是启动后，会弹出下拉框，然后选择要 链接的进程。一旦链接成功，就可以对 C&#x2F;C++ 代码断点了。</p><h2 id="4-接下来"><a href="#4-接下来" class="headerlink" title="4 接下来"></a>4 接下来</h2><p>如果没有成功可以参考我弄的一个 demo 仓库： <a href="https://github.com/yantze/demo-nodejs-addon-debug">https://github.com/yantze/demo-nodejs-addon-debug</a>。</p><p>注意，里面的 <code>c_cpp_properties.json</code> 需要自己修改 include 路径。</p><p>如果还想弄 Node Addon API 的 NAPI 接口，配置算是类似的，就不再赘述了。不过，如果使用的是 NAPI 的代码，就不需要 node 和 v8 的头文件了，并且官方推荐的也是 N-API 实现插件。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Node.js 基于 GYP（Generate Your Projects）构建 C&amp;#x2F;C++ 插件，叫 &lt;code&gt;node-gyp&lt;/code&gt;。这次介绍如何写一个简单的 hello 插件，并且能 debug C&amp;#x2F;C++ 代码。&lt;/p&gt;
&lt;h2 id=</summary>
      
    
    
    
    
    <category term="vscode, nodejs, js, tutorial" scheme="https://vastiny.com/tags/vscode-nodejs-js-tutorial/"/>
    
  </entry>
  
  <entry>
    <title>应用内文件锁</title>
    <link href="https://vastiny.com/post/cprw9kz9b2xdxqvf.html"/>
    <id>https://vastiny.com/post/cprw9kz9b2xdxqvf.html</id>
    <published>2019-10-15T14:02:00.000Z</published>
    <updated>2026-03-23T20:16:34.656Z</updated>
    
    <content type="html"><![CDATA[<p>写了一个 NodeJS 版的 <a href="https://github.com/yantze/fs-lockfile">fs-lockfile</a> 用来管理应用层上的文件读取和写入。</p><p>这样应用内的所有磁盘 IO 都被应用程序统一接管，如果需要做比如权限管理、Bookmark、文件缓存、虚拟文件系统等，这些都可以做一个中间层，也有一定的使用场景。</p><h2 id="设计接口"><a href="#设计接口" class="headerlink" title="设计接口"></a>设计接口</h2><p>首先设计了六个方法，获取读锁、释放读锁、获取写锁、释放写锁、读文件、写文件。读文件里面封装了读锁的获取和释放，写文件里面封装了写锁的获取和释放。接口的方法如下：</p><ul><li>obtainReadLock(filePath): Object</li><li>obtainWriteLock(filePath): Object</li><li>releaseReadLock(lockMeta): undefined</li><li>releaseWriteLock(lockMeta): undefined</li><li>read(filePath): Promise</li><li>write(filePath, content): Promise</li></ul><h2 id="读文件操作的方法-read"><a href="#读文件操作的方法-read" class="headerlink" title="读文件操作的方法: read"></a>读文件操作的方法: read</h2><p>展示一下 read 简单封装了 obtainReadLock ，并且在执行读文件操作后，调用 releaseReadLock 释放读锁。</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">async</span> <span class="keyword">function</span> <span class="title function_">read</span>(<span class="params">fp</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!path.<span class="title function_">isAbsolute</span>(fp)) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;Must be an absolute path.&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> lockMeta = <span class="keyword">await</span> <span class="title function_">obtainReadLock</span>(fp);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> fsp.<span class="title function_">readFile</span>(fp);</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">releaseReadLock</span>(lockMeta);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="简要介绍-obtainReadLock-obtainWriteLock-的执行流程"><a href="#简要介绍-obtainReadLock-obtainWriteLock-的执行流程" class="headerlink" title="简要介绍 obtainReadLock, obtainWriteLock 的执行流程"></a>简要介绍 obtainReadLock, obtainWriteLock 的执行流程</h2><p><strong>obtainReadLock</strong></p><ul><li>获取 文件路径的 lockMeta ，然后要等待 lockMeta 中的写锁队列全部释放完成</li><li>之后生成一个读锁，返回 lockMeta</li></ul><p><strong>obtainWriteLock</strong></p><ul><li>获取文件路径的 lockMeta, 获取 lockMeta 中 writeQueue 最后一个写锁 prevLock。</li><li>生成一个新的写锁，并且放进 lockMeta 中的 writeQueue</li><li>等待所有读锁释放</li><li>等待 prevLock 释放</li></ul><blockquote><p>注意：要先把新生成的写锁放在 writeQueue 中，因为其它方法可能会访问这个写队列</p></blockquote><p>另外两个方法也是类似的，这样一个完整的应用层的锁就写好了。</p><h2 id="锁是怎么生成的"><a href="#锁是怎么生成的" class="headerlink" title="锁是怎么生成的"></a>锁是怎么生成的</h2><p>是一个简单的 Promise 锁。构造一个方法，生成一个 Promise 实例，然后把里面的 resolve 方法引用到一个对象中，当需要释放锁的时候，调用 resolve 方法就能继续执行。如下面的例子:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">generateLock</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> lock = &#123;</span><br><span class="line">    <span class="attr">obtain</span>: <span class="literal">null</span>,</span><br><span class="line">    <span class="attr">release</span>: <span class="literal">null</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">const</span> promise = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> (lock.<span class="property">release</span> = resolve));</span><br><span class="line">  lock.<span class="property">obtain</span> = <span class="function">() =&gt;</span> promise;</span><br><span class="line">  <span class="keyword">return</span> lock;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> lock = <span class="title function_">generateLock</span>();</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">await</span> lock.<span class="title function_">obtain</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Till release method invoked, go there.&quot;</span>);</span><br><span class="line">&#125;, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> lock.<span class="title function_">release</span>(), <span class="number">3000</span>);</span><br></pre></td></tr></table></figure><h2 id="后续问题"><a href="#后续问题" class="headerlink" title="后续问题"></a>后续问题</h2><p>这样的设计后，还是有两个扩展的问题</p><ul><li>读锁要不要直接访问之前的文件内容，这样让写锁能不与读锁互斥</li><li>文件只有一个读锁，如果有一个 readQueue 存储多个读锁,可以对读锁增加更多的控制，但相应的复杂度就会增加不少</li></ul><p>以及多线程操作的问题</p><ul><li>文件锁是基于线程的，如果是多线程或者多进程操作文件，最好是把所有的锁请求都通过 ipc 传递到一个线程中集中管理</li></ul><p>多线程进程的解决方法也好处理：前文说了，read &amp; write 封装是应对一般的需求，如果要做 ipc 通信，或者一些权限处理，或者缓存文件等，可以自行封装和完善。</p><hr><p>这里是 npm package 的地址: <a href="https://www.npmjs.com/package/fs-lockfile">fs-lockfile</a> 。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;写了一个 NodeJS 版的 &lt;a href=&quot;https://github.com/yantze/fs-lockfile&quot;&gt;fs-lockfile&lt;/a&gt; 用来管理应用层上的文件读取和写入。&lt;/p&gt;
&lt;p&gt;这样应用内的所有磁盘 IO 都被应用程序统一接管，如果需要做比如权</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>字数统计</title>
    <link href="https://vastiny.com/post/va4m9k1gcurig6s6.html"/>
    <id>https://vastiny.com/post/va4m9k1gcurig6s6.html</id>
    <published>2019-08-01T00:45:00.000Z</published>
    <updated>2026-03-23T20:16:34.545Z</updated>
    
    <content type="html"><![CDATA[<h2 id="统计字数的几种方法"><a href="#统计字数的几种方法" class="headerlink" title="统计字数的几种方法"></a>统计字数的几种方法</h2><p>一般情况下，统计的时候，Emoji 是算成一个数，但 ES6 之前的 api 是会把这种字符算成多个。</p><h3 id="1-最简单但有明显问题的-String-prototype-length"><a href="#1-最简单但有明显问题的-String-prototype-length" class="headerlink" title="1. 最简单但有明显问题的 String.prototype.length"></a>1. 最简单但有明显问题的 <code>String.prototype.length</code></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></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;中文&quot;</span>.<span class="property">length</span>; <span class="comment">// 2</span></span><br><span class="line"><span class="string">&quot;𠮷&quot;</span>.<span class="property">length</span>; <span class="comment">// 2</span></span><br><span class="line"><span class="string">&quot;💖&quot;</span>.<span class="property">length</span>; <span class="comment">// 2</span></span><br><span class="line"><span class="string">&quot;🙋‍♂️&quot;</span>.<span class="property">length</span>; <span class="comment">// 5</span></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">&quot;\u4E2D\u6587&quot;</span>); <span class="comment">// 中文</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\uD83D\uDC96&quot;</span>); <span class="comment">// 💖</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\uD842\uDFB7&quot;</span>); <span class="comment">// 𠮷</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\uD83D\uDE4B\u200D\u2642\uFE0F&quot;</span>); <span class="comment">// 🙋‍♂️</span></span><br></pre></td></tr></table></figure><p>最后一个 🙋‍♂️ 的长度有 5 个字符，中间有一个零宽字符 <code>\u200D</code>，能把两个 Emoji 合并成一个。 最后有一个 <code>\uFE0F</code> 是一个变体选择器。 可以看文末参考链接。</p><p>下面是一个黑色皮肤的举手 Emoji：</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="string">&quot;\uD83D\uDE4B\uD83C\uDFFF\u200D\u2642\uFE0F&quot;</span>); <span class="comment">// 🙋🏿‍♂️</span></span><br></pre></td></tr></table></figure><p>这里面的 <code>\uD83C\uDFFF</code> 代表的是黑色皮肤，如果是 <code>\uD83C\uDFFB</code> 代表的是白色皮肤，可以看参考文末链接。</p><h3 id="2-使用-Array-from"><a href="#2-使用-Array-from" class="headerlink" title="2. 使用 Array.from"></a>2. 使用 <code>Array.from</code></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></pre></td><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="string">&quot;中文&quot;</span>).<span class="property">length</span>; <span class="comment">// 2</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="string">&quot;𠮷&quot;</span>).<span class="property">length</span>; <span class="comment">// 2</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="string">&quot;💖&quot;</span>).<span class="property">length</span>; <span class="comment">// 1</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="string">&quot;🙋‍♂️&quot;</span>).<span class="property">length</span>; <span class="comment">// 4</span></span><br></pre></td></tr></table></figure><p>这里会把 🙋‍♂️ 分拆成四个部分 <code>[&quot;\uD83D\uDE4B&quot;, &quot;\u200D&quot;, &quot;\u2642&quot;, &quot;\uFE0F&quot;]</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="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\uD83D\uDE4B&quot;</span>); <span class="comment">// 🙋</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\u200D&quot;</span>); <span class="comment">// \u200D</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\u2642&quot;</span>); <span class="comment">// ♂</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\uFE0F&quot;</span>);</span><br></pre></td></tr></table></figure><p>所以使用 <code>Array.from</code> 也不是一个比较好的统计字数的方法。</p><h3 id="3-使用正则表达式"><a href="#3-使用正则表达式" class="headerlink" title="3. 使用正则表达式"></a>3. 使用正则表达式</h3><p>ES6 之后给 REGEX FLAGS 添加了 <code>u</code> 标识位，对 Unicode 更加友好的支持。比如：</p><figure class="highlight plaintext"><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">&#x27;𠮷&#x27;.match(/\S/g).length // 2</span><br><span class="line">&#x27;𠮷&#x27;.match(/\S/gu).length // 1</span><br></pre></td></tr></table></figure><p>但在处理组合的 Emoji 的时候，还是没有比较好的处理：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x27;🙋‍♂️&#x27;.match(/\S/gu).length // 4</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">ASTRAL_REGEX</span> =</span><br><span class="line">  <span class="regexp">/\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]?|[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|(?:\ud83c[\udde6-\uddff])&#123;2&#125;|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff])&#123;2&#125;|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/g</span>;</span><br><span class="line"></span><br><span class="line"><span class="string">&quot;𠮷&quot;</span>.<span class="title function_">match</span>(<span class="variable constant_">ASTRAL_REGEX</span>).<span class="property">length</span>; <span class="comment">// 1</span></span><br><span class="line"><span class="string">&quot;🙋‍♂️&quot;</span>.<span class="title function_">match</span>(<span class="variable constant_">ASTRAL_REGEX</span>).<span class="property">length</span>; <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><h2 id="Benchmark"><a href="#Benchmark" class="headerlink" title="Benchmark"></a>Benchmark</h2><p>写了一个脚本分别测试原生 <code>length</code>、<code>Array.from</code>、<code>/\S/gu</code>、REGEX_UNICODE_CHARACTER 测试判断的长度时间：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">UNICODE_WORD_REGEX</span> = <span class="regexp">/[\u00ff-\uffff]|\S+/g</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">UNICODE_CHARACTER_REGEX</span> = <span class="regexp">/\S/gmu</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">UNICODE_CHARACTER2_REGEX</span> =</span><br><span class="line">  <span class="regexp">/\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]?|[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|(?:\ud83c[\udde6-\uddff])&#123;2&#125;|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff])&#123;2&#125;|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/g</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">TIMES</span> = <span class="number">100000</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> str = <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10</span>)).<span class="title function_">reduce</span>(</span><br><span class="line">  <span class="function">(<span class="params">s</span>) =&gt;</span> s + <span class="string">&quot;中文𠮷？précédantお客様の💖🙋‍♂️&quot;</span>,</span><br><span class="line">  <span class="string">&quot;&quot;</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> count1 = <span class="number">0</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">time</span>(<span class="string">&quot;length&quot;</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable constant_">TIMES</span>; ++i) &#123;</span><br><span class="line">  count1 += str.<span class="property">length</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">timeEnd</span>(<span class="string">&quot;length&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;count1 length:&quot;</span>, count1);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> count2 = <span class="number">0</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">time</span>(<span class="string">&quot;array from&quot;</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable constant_">TIMES</span>; ++i) &#123;</span><br><span class="line">  count2 += <span class="title class_">Array</span>.<span class="title function_">from</span>(str).<span class="property">length</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">timeEnd</span>(<span class="string">&quot;array from&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;count2 length:&quot;</span>, count2);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> count3 = <span class="number">0</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">time</span>(<span class="string">&quot;regex unicode&quot;</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable constant_">TIMES</span>; ++i) &#123;</span><br><span class="line">  count3 += str.<span class="title function_">match</span>(<span class="variable constant_">UNICODE_CHARACTER_REGEX</span>).<span class="property">length</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">timeEnd</span>(<span class="string">&quot;regex unicode&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;count3 length:&quot;</span>, count3);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> count4 = <span class="number">0</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">time</span>(<span class="string">&quot;regex2&quot;</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable constant_">TIMES</span>; ++i) &#123;</span><br><span class="line">  count4 += str.<span class="title function_">match</span>(<span class="variable constant_">UNICODE_CHARACTER2_REGEX</span>).<span class="property">length</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">timeEnd</span>(<span class="string">&quot;regex2&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;count4 length:&quot;</span>, count4);</span><br></pre></td></tr></table></figure><p>通过测试 190 个字，循环 100000 遍，得到执行结果如下，使用 <code>UNICODE_CHARACTER2_REGEX</code> 是一个折衷方案中最好的方法。</p><figure class="highlight plaintext"><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">使用方法   |  计算长度 | 耗费时间(ms)</span><br><span class="line">String.prototype.length   |   25000000 | 3.134</span><br><span class="line">Array.from   |  22000000 | 2635.567</span><br><span class="line">UNICODE_CHARACTER_REGEX  | 22000000 | 916.429</span><br><span class="line">UNICODE_CHARACTER2_REGEX | 19000000 | 748.209</span><br></pre></td></tr></table></figure><p><code>UNICODE_CHARACTER2_REGEX</code> 统计出的 19000000 才是正确的。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>肤色编码： <a href="https://emojipedia.org/modifiers/">https://emojipedia.org/modifiers/</a></li></ul><p><img src="/_image/emoji_skin_tone.png"><br>这里的 FITZ-(1~5) 就是不同的五种肤色，从 \u1F3FB – \u1F3FF 共五个。</p><ul><li>变量选择器：<a href="https://en.wikipedia.org/wiki/Emoticons_(Unicode_block)">https://en.wikipedia.org/wiki/Emoticons_(Unicode_block)</a></li></ul><p>常见的有:</p><ul><li>“变量选择器-15”(VARIATION SELECTOR-15, 简写 VS-15): \uFE0E, 作用是让基础 Emoji 变成更接近文本样式(text-style) (e.g. ☹︎);</li><li>“变量选择器-16”(VARIATION SELECTOR-16, 简写 VS-16): \uFE0F, 作用则是让基础 Emoji 变成更接近 Emoji 样式(emoji-style) (e.g. ☹️).</li></ul><hr><ul><li>UNICODE_CHARACTER2_REGEX 来自这里： <a href="https://github.com/sallar/unicode-astral-regex">https://github.com/sallar/unicode-astral-regex</a></li><li>比较详细的介绍 Unicode 编码：<a href="https://segmentfault.com/a/1190000017782406">https://segmentfault.com/a/1190000017782406</a> 。这里面就会介绍 UNICODE_CHARACTER2_REGEX 这种这么长的正则是通过专门的库生成的。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;统计字数的几种方法&quot;&gt;&lt;a href=&quot;#统计字数的几种方法&quot; class=&quot;headerlink&quot; title=&quot;统计字数的几种方法&quot;&gt;&lt;/a&gt;统计字数的几种方法&lt;/h2&gt;&lt;p&gt;一般情况下，统计的时候，Emoji 是算成一个数，但 ES6 之前的 api 是会把</summary>
      
    
    
    
    
  </entry>
  
</feed>
