<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mの综合研究]]></title><description><![CDATA[somewhere about myself]]></description><link>https://maliut.space</link><generator>GatsbyJS</generator><lastBuildDate>Thu, 02 Jul 2026 09:39:55 GMT</lastBuildDate><item><title><![CDATA[雀乐（原落网）桌面播放器]]></title><description><![CDATA[自从落网复活以后，它就成了我日常干活时的主力 BGM。但它本身只是一个网页，如果窗口开的多了，这个时候想切歌或者看看歌名，就得在多个窗口和 tab…]]></description><link>https://maliut.space/p/luoo-player/</link><guid isPermaLink="false">https://maliut.space/p/luoo-player/</guid><pubDate>Fri, 21 Mar 2025 18:18:00 GMT</pubDate><content:encoded>&lt;p&gt;自从落网复活以后，它就成了我日常干活时的主力 BGM。但它本身只是一个网页，如果窗口开的多了，这个时候想切歌或者看看歌名，就得在多个窗口和 tab 之间来回切换，略微有点麻烦。&lt;/p&gt;
&lt;p&gt;鉴于此，我把网站包装成了一个简易的桌面客户端，修改它的样式，将它变成了一个桌面浮窗。项目已发布且开源在 &lt;a href=&quot;https://github.com/flora-studio/luoo-player&quot;&gt;flora-studio/luoo-player&lt;/a&gt; 。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9721f838ff2654af6d46a6e00774277e/d9199/luooplayer.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAADFklEQVR42g3Pa0+aBwDF8ecDLNnLJW5ps7RbDKvVsYkCWhSkQieC0Q6MRQhDLYIUuazMeKFUEK0rOi+bUCpe6q24ulbb6puqnbWuuq27xF2ybFmW7WP897z45bw7J0fIzB+Smjskc+eIzMIRaTFviZld/o651Z+4HsswPTlN7qtvWd/6lfuPj3mweczmk995svcXOwd/8/XRvzz/4T+evfwHYWh8m4RoaEI0uUNiYofBz3cZTO0R/+IZ7tZ2er1WPvGHSM3sM7N8KHrB3N1Dlta+Z/H+jyys/8zdzd9Y2/4DwdOTo7VrBW/fKv7oPVzdK3h6c4QTjwj3LXCxRs94IkzA5SCefMz47T3GMrtMzeyRmn/O1NILplbEV1++ZHbjFwSLO43Fk8LmS+O+msUlCkcWGf1sk76eFHZzE5GAj0ajibb2ON3xVSLJDa6PbBAdfUh0fIvY1DbxzFMSs/sIgXAWf3gaTzBFuDtLNLbAwOAS0YFF2n1J1BVGPm6xMNwbpLUjibtrVnyyhL9/GV8sh29gDf/wQwIjjwhNbCGEwrcIXE3j8k/QERIFx7B5BtE5+pHXBpBI5Bg0WmYH/WykR3F13sQmjju7bmPvmsHWs0hzJIe1/x6OoQcIVmcCizOG0d6HrimMxhxAWuflnRovVQYHMmkJ5YpqHA31rIx0kowGMLXdoME7Rt2VSUz+NDXBDOdDWfTdywhVRh/lpg5kxnakhjby9Q7ernZwttJKuawUacG7KGRqVHINbquW4f4AansUlf0aFc4BKtuSFLd8Sr4zidSbRpBW2SnQ2smvsvJmhYUT5z4k/5yF8x98RH2tBaVYVCgpoFiq4L0zhegu1CGzhNA4I2gvx6m8nEDpSfJWyxAnnTcRTivreUNpIk9h5HXRqTITheUNqKttWM1ODNUmFNIi1HI5RUUKJJWNlJg7WZq/w9P9Iw6O/+Sb3QNuzK/zatM1hLwiDa9JNeS9r+WETMepEh2S0gsUiyMqjZmKilrKSpUoy/QUqi4i0TRyVt+MxtKO6tIVlPYQJc1BTlt8vGLw8D/AsQ0OeHiNOQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;luooplayer&quot;
        title=&quot;&quot;
        src=&quot;/static/9721f838ff2654af6d46a6e00774277e/d7542/luooplayer.png&quot;
        srcset=&quot;/static/9721f838ff2654af6d46a6e00774277e/2efce/luooplayer.png 203w,
/static/9721f838ff2654af6d46a6e00774277e/1d180/luooplayer.png 405w,
/static/9721f838ff2654af6d46a6e00774277e/d7542/luooplayer.png 810w,
/static/9721f838ff2654af6d46a6e00774277e/d9199/luooplayer.png 960w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;制作桌面应用并非我常涉及的领域，这篇文章主要记录下与此相关的一些技术探索。&lt;/p&gt;
&lt;p&gt;说起桌面应用，第一时间就会想到 electron、nwjs、tauri 这三&lt;del&gt;巨头&lt;/del&gt;个在前端领域最主流的选择。&lt;/p&gt;
&lt;p&gt;不过在和老婆讲这个小工具的功能时，她说，听上去像是针对音乐的画中画功能。这启发了我，让我一下子想到了尚处于实验性的 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Picture-in-Picture_API&quot;&gt;Document Picture-in-Picture API&lt;/a&gt; 。如果这个 API 可用，我们就能够通过一个浏览器插件解决问题，而无需真的打包一个桌面应用出来，轻量很多。&lt;/p&gt;
&lt;p&gt;说干就干，我进行了一番测试。浏览器的视频画中画功能很简单，就是调用 &lt;code class=&quot;language-text&quot;&gt;video.requestPictureInPicture()&lt;/code&gt; 完事。我预想中，让任意元素进入画中画，无非也是一样，将这个 API 扩展到任意 dom 元素。&lt;/p&gt;
&lt;p&gt;不过它实际并没有想象中的那么开箱即用，而是更偏底层一些。一个浮窗可以视为一个独立的窗口，就像一个全新的 tab 页。而父窗口可以完全操控子浮窗的 &lt;code class=&quot;language-text&quot;&gt;window&lt;/code&gt;  和 &lt;code class=&quot;language-text&quot;&gt;document&lt;/code&gt; ，又像是一个同源的 iframe。我们需要在打开浮窗时手动把元素添加到浮窗的文档中，并把 CSS 也复制过去。在关闭浮窗时，也需要我们手动把元素还原回去。&lt;/p&gt;
&lt;p&gt;跟随 MDN 的样板代码，浮窗的展示功能很顺利地搞定了。不过涉及到交互时却遇到了麻烦：窗口会出现无规律的抖动（这一点似乎是 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;a&gt;&lt;/code&gt; 元素的问题，MDN 的 example 也存在此问题），且所有的点击事件都没有生效。其实对于这一点我并不意外。雀乐的网站是用 Next.js 即 React 实现的。正常情况下，我们基于框架编写代码，React 会通过 vdom 操作真实 dom，点击事件也会走 React 自己的合成事件机制。现在我们越过了 React 直接去操作真实 dom，要没有问题反而才是大问题。&lt;/p&gt;
&lt;p&gt;如果你是在正经 React 开发过程中使用画中画功能，你可以尝试使用 React Portal 将元素渲染到浮窗中。但我们只是一个插件，能做的事情有限。故我暂时停止了对浏览器插件功能的进一步研究，继续把目光放回打包桌面应用上。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;要在 electron、nwjs、tauri 中三选一，答案是不言而喻的。一方面，我们想做的只是一个小工具，打包一整个浏览器进去并不值得。另一方面，我们是对现有的网站再封装，网站本身就会适配各大主流浏览器，没必要再去考虑兼容性问题。因此 tauri 就是最合适的选择。&lt;/p&gt;
&lt;p&gt;tauri 的教程通篇都是开发自己的网页应用。像我这样要把一个现有第三方网站打包成应用的是小众场景，参考资料很少。&lt;/p&gt;
&lt;p&gt;tauri 为几乎每种原生能力都提供了 Rust 和 JavaScript 两种版本的 API，即使是不会 Rust 的开发者上手起来也不会太艰难。一开始我自然选择了自己熟悉的 JavaScript API，但由于我们的前端并非通常的 &lt;code class=&quot;language-text&quot;&gt;localhost&lt;/code&gt; 而是一个第三方网站，于是遇到了一些”安全问题”。&lt;/p&gt;
&lt;p&gt;因为时隔有点久，我已经忘了具体的报错是什么。我不确定是否和操作系统有关：我在 macOS 上看到了一些错误信息，而 Windows 上并没有；我也不确定是否和 dev 环境有关：查资料好像说 tauri 在 macOS 上的运行，dev 和正式打包的运行方式也有所区别。&lt;/p&gt;
&lt;p&gt;尝试了几种方法绕开后无果，我最终还是决定用 Rust 来写了。毕竟我们是打包第三方网站，把原生逻辑放在服务端也合理，而不懂 Rust 是我的问题，不是 Rust 的问题。&lt;/p&gt;
&lt;p&gt;于是不出所料地事情开始变得举步维艰了起来。我既不懂 Rust 的语法，又不熟悉 tauri 的 API，我甚至不知道怎么把一段代码抽离到一个单独的方法里去。也不是没有尝试过 AI 的帮助，但效果只能说差强人意。一会儿是幻觉出现了不存在的配置项；一会儿是生成了 tauri v1 的代码（这个问题比较严重），要么就是为了解决编译的问题反倒使得功能出现了 bug。&lt;/p&gt;
&lt;p&gt;不过总归也不是什么特别复杂的功能，对照着模版代码摸爬滚打一番，也算是实现了。真正让人挠头的还是不同操作系统之间的能力差异。比如，不同平台对窗口大小是否包含边框、标题栏的判断；macOS 禁用不了 resize；Windows 快捷键不起作用；不同平台对点击和拖动事件的处理不同等等。这只是个 side side project，我也没有兴趣再去深究，做个差不多就完事。&lt;/p&gt;
&lt;p&gt;第一次接触 Rust，虽然很仓促，但感觉其实挺有意思的。平时在写代码的时候，其实也会有直觉某些做法是好的，某些是不太好的。而 Rust 把这一部分强制了。Rust 也确实做到了只要能通过编译，那么跑起来也大概率没问题；语法错误给的提示也很贴心（大概是犯的都是初学者典型错误吧）。不过相对地，开发体验就没这么好了。有的时候你只想快速验证一些东西，但却不得不先解决所有的编译错误，非常打断心流。还有就是作为编译型语言，hot reload 速度还是慢了。哪怕只是个小项目，一次重新编译也要 10~20 秒，作为前端开发者会很不习惯。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;虽然通过 tauri 做出了成品，但我还是对画中画方案念念不忘，总觉得还是错过了什么。很快我便意识到了问题所在：我陷入了对画中画功能的思维定式中。&lt;/p&gt;
&lt;p&gt;最典型的画中画功能自然是将原有的一块区域转移到单独的浮窗中。但浏览器提供的 API 并没有限制我们只能这么做。它提供的是一个完全由你控制的窗口，就像是一块任你挥洒的画布。我们完全可以不使用原网站的 dom 元素，而是自己手搓一个播放器界面。又因为浮窗与原网页是同源的，我们可以将点击事件转发到原网页的对应按钮，并同步播放状态与原网页一致，让它看上去就像是从原网页抠下来的一样。&lt;/p&gt;
&lt;p&gt;一念天地宽，之后就是按部就班的开发时间了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;总之，我们现在有了两个版本的播放器了。其一是独立 exe，其二是一个浏览器插件。&lt;/p&gt;
&lt;p&gt;当然它们之间会有一些区别。最大的区别是通过浏览器画中画打开的浮窗，上方会带有一个无法去除的标题栏，且只有在标题栏区域才可以拖拽窗口；而 exe 版本的浮窗可以隐藏标题栏并直接拖拽窗口，更为简洁易用。其他的诸如禁止 resize、托盘图标等更加 native 的特性，也都是只是 exe 版本才可以控制的。&lt;/p&gt;
&lt;p&gt;不过虽然桌面版本的上限更高，但插件版本充分利用的浏览器的现有能力，有着更小的体积与更美好的开发体验。并且 Document Picture-in-Picture 目前仍然是实验式 API，有理由相信未来会开放更多的可定制功能。如果让我选择，仅就这个用例而言，我会更喜欢浏览器插件的版本。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[王国之泪：再而衰]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/totk/</link><guid isPermaLink="false">https://maliut.space/p/totk/</guid><pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我非常喜欢《塞尔达传说 旷野之息》，要说它是我心中的历史最佳游戏也不过分。因此当它的续作《塞尔达传说 王国之泪》发布后，我毫不犹豫就购买了。&lt;/p&gt;
&lt;p&gt;毋庸置疑，《王国之泪》也是一个好游戏，但有《旷野之息》珠玉在前，《王国之泪》的一些设计还是让我失望，也有一些槽点让我不吐不快。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;先来聊聊游戏优秀的部分。在继承了前作地图和整体框架的基础上，本作大胆创新，新能力的加入大大拓宽了游戏的玩法边界。对于像我这样看惯了现在各类同质化玩法的游戏玩家来说，《旷野之息》的能力已经让我震惊了一次：“什么，还能这样？”，而《王国之泪》让我感到的则是：“什么，还有高手？”&lt;/p&gt;
&lt;p&gt;“究极手”，是前作磁铁能力的完全上位替代。前作中我们有时会觉得“要是这里也可以吸就好了”，但我们也清楚，限制的存在是谜题设计所必需的，要是所有物体都可以随意操纵，谜题失去了难度也会失去乐趣。结果本作真的就把能力开放了出来，不仅可以操作任意物体，还可以把它们自由组合起来。虽然被吐槽谜题设计的水准有所降低，但走向了《围攻》的路线，把整个游戏变成了一个大型的游乐场。&lt;/p&gt;
&lt;p&gt;“余料建造”，不仅将前作中各种鸡肋的掉落物变废为宝，还丰富了战斗的套路和趣味性，同时体现出开发组恐怖的堆量能力。&lt;/p&gt;
&lt;p&gt;“通天术”，源自游戏开发和测试过程中的能力。和“究极手”一样，初看会让人觉得是不是过于变态了。但实际玩起来，除了能够体验到基于它衍生出的各种谜题和 Y 轴探索外，还能意识到，它组成了本作中洞穴探索玩法里不可或缺的一环。&lt;/p&gt;
&lt;p&gt;“倒转乾坤”，和前作的时停类似的时间系能力，不必多说，上限取决于玩家的想象力。&lt;/p&gt;
&lt;p&gt;此外，前作 DLC 中的特殊道具和装备，这次也直接打包塞进了游戏本体中。总的设计思路，就是尽量解除给玩家的各种限制，不怕你们把游戏玩坏，只怕你们玩的不够爽。&lt;/p&gt;
&lt;p&gt;关于这些优点，前人之述备矣，这里就不展开了，接下来我想谈谈那些让我觉得失望的点。&lt;/p&gt;
&lt;h2&gt;“退而求其次”的 POI 设计&lt;/h2&gt;
&lt;p&gt;POI (Point of interest) 此处主要指的是神庙和塔（鸟望台）的位置。《旷野之息》的开发者访谈提过，游戏的地图基于京都的真实地图设计，并利用三角法则等摆放各个重要地点的位置，以确保玩家在游玩过程中，时刻都能找到吸引人前去探索的点。&lt;/p&gt;
&lt;p&gt;简单来说，就是神庙和塔的位置都不是乱放的，而是经过精心设计的。《王国之泪》继承了前作的地图，但设定上左那乌科技取代了希卡科技，那么这些位置就必然需要发生变更，也意味着它们必然要远离前作中精心设计过的、理想的点位。&lt;/p&gt;
&lt;p&gt;在实践中，开发组把很多神庙藏进了洞穴中。一方面是为了让玩家有动力探索本作重点开发的洞穴地形，另一方面，也是因为地面上没有更多能放神庙的好位置，而不得不做出的无奈之举。但这样相当于减少了地面上 POI 的数量，使三角法则失效，也连带着降低了玩家（尤其是新玩家）在地面的探索乐趣。&lt;/p&gt;
&lt;p&gt;还有一部分神庙，主要是作为村庄、驿站的传送点存在。它们绝大多数的位置都比前作更不方便了。甚至为此大精灵之泉也被迫移了位置。而前作那些一看就浑然天成，很适合摆放神庙的位置，在本作中只能另作它用，甚至直接空在那里，也让人感到一丝尴尬。&lt;/p&gt;
&lt;p&gt;虽然本作继承了前作的地图，但为了提升老玩家的新鲜感，势必要对原有的地图做一些改动。除了本作新增的空岛、地底和洞穴系统外，对地面的改动中，我最喜欢的是经格鲁德峡谷前往沙漠的道路，由于天地异变导致原有道路积水难行，人们从岩壁处新开辟出了一条曲径通幽的道路~~，充分体现了劳动人民的智慧~~。其他就没有什么印象深刻的了。地面绝大多数的变化，还是新增了很多从天而降的落石碎片，以及瘴气。虽说符合剧情设定，但也让原本充满美感的旷野丑陋了不少。&lt;/p&gt;
&lt;h2&gt;好奇心不再得到奖励&lt;/h2&gt;
&lt;p&gt;不同于其他开放世界游戏，用一堆任务和 checklist 牵着玩家走，《旷野之息》的优点之一，就是在剧情上使用弱引导，而通过精巧的地图设计去激发玩家的好奇心和主观能动性。机制上也给予玩家充分的自由度，不会因为游玩的先后顺序导致卡关，还会针对不同的剧情进度设置各种不同的 NPC 对话，增强了世界的真实感。可以说，在《旷野之息》中，每个玩家的通关经历都构成了一部独一无二的冒险故事。&lt;/p&gt;
&lt;p&gt;《王国之泪》虽然大体继承了前作的这套设计，但在强化了剧情内容的同时，也难以避免对探索的自由度产生了影响。更令人失望的是设计者似乎没有对玩家远离主线的行动做出完整的应对。&lt;/p&gt;
&lt;p&gt;从初始空岛下来以后，几乎所有的 NPC 都在引导你赶紧前往利特村，好像下一秒村子就要完蛋了一样。由于引导实在太过于赤裸裸，不禁让人害怕，要是不按引导走，是不是游戏就进行不下去了。&lt;/p&gt;
&lt;p&gt;不幸的是，我担心的事情真的发生了。虽然我不爱循规蹈矩地走大路，但也没有逆反到非得对着引导干的地步。因此我没有错过龙之泪的剧情，也没有错过和伯库林的第一次见面。但是当我来到利特村所在地区的鸟望台时 —— 来到一个新的地区，首先就想去开塔，我想这是一件很多玩家都会做的事 —— 一下子就被不远处的一堆乱石吸引了目光。看似只是一堆杂乱无章的装饰，但倘若将它们看作落脚点，则似乎隐隐约约能看到一条螺旋上升的通道。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/163636295419d607d2f659f07c81818f/21b4d/1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACA0lEQVR42i2PW3KbMBSG2U130IemzXTauCEYbMzVIG62AWNAEkiIa5q0e+jaup4ePJ35Rw9H8/0X6cNfI8WKfdwqxmZnK06oB6kd5cesirIqznGSVUmceVaoWYFmB5rlqwdfMfytiVQJ/fFIpOhI1axn3dnawc4OV3mJEademLrH+GAG6t6VDbT+mt4WSAupbqxLzVSKuYSrE+5MpJm+Cgkgw1OPsQFyIx30on97Uj5r5gZgcyX36GxJzVJXbRqkrhse/sPwIlV3ZRtpbqjrjqLq32X1cfPy6Vl9dKO9Fx3Ci3u+BdL4S7RDndYJujgw2PKhmAoLVfNpZ2505/nr949P8sOPlwfdlh20Dy7O6YpyfCrbTJp/9+NbR0SZ1ac499DJhgqwGVaZ/hZ6KtsvhiMfbPkY6GkZFfRc8ysVNyIKCUhQtzRNX5csz+vkUgTgHWVHlDp+bKDwcM79rAhLcmmHik+Yz4SNpBlqaXwXw/sKs4nQoaZ9RfsSRMQN3xMwu5ImY13ZL03/yvrXlo0YxGe6JoufbIVn2oz4DlfNRNhExdKOb7ybKO8xF7gbCV8oFWVSlEGWNz0GWIhXBmR7T8YQ1RV0qMCI36P4SCgrYBSb8I1mYYzspHBPVzoQSSxdN7cAA0mgbV9hUQIMXnwh672vMSvAseLXUx7eSI67BnPCZ/oPcer/ABvkNCEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;1&quot;
        title=&quot;&quot;
        src=&quot;/static/163636295419d607d2f659f07c81818f/d7542/1.png&quot;
        srcset=&quot;/static/163636295419d607d2f659f07c81818f/2efce/1.png 203w,
/static/163636295419d607d2f659f07c81818f/1d180/1.png 405w,
/static/163636295419d607d2f659f07c81818f/d7542/1.png 810w,
/static/163636295419d607d2f659f07c81818f/d56b5/1.png 1215w,
/static/163636295419d607d2f659f07c81818f/21b4d/1.png 1280w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;通往天空的连绵岛屿&lt;/p&gt;
&lt;p&gt;不得不说这段场景的设计实在是太出色了。利用看似杂乱无章，实则经过了精心设计的路线拾级而上，仅用自己的双脚就来到了万米高空，随后从风暴眼一跃而下，一艘巨大的天鸟船映入眼帘。初次见到这个场景，相信无人不会为之震撼。&lt;/p&gt;
&lt;p&gt;然而就当我准备大展拳脚之时，走上正中的交互点，迎接我的却是一个大大的红叉。我心中顿感不妙。不过我并不信邪，“毕竟这可是任天堂啊”，不可能就这么让我空手而归，一定是我还有什么地方没注意到吧。怀揣着这样的心思，我开始了在神殿的解谜。但无论我对着任何看上去可能可以交互的物体上蹿下跳，都始终找不到任何能够推进进度的契机。直到我忍不住查了攻略才知道，这些机关需要贤者的技能才能够交互。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/34b46086f5ff88bc896b00c6f3b521ee/21b4d/2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACj0lEQVR42gXBWU/aAAAA4P6j6dANiUYFy1HLXdpKgUI5bCkFORxYjnIJApYiIoiOUY85dZ4z28OSJfNpPuw3mCxZspe97Q/s+wDi6IQ8v6Ku76mbB/Ljref8ipCPnYMd77she9hL7pQSYp6u83iFR7sSfrCPHw7JszP67i7w4RRQp9YNBQFuidjbsffiynd54zo6dkubhZ5wsJ89243JnehAjGSrLFVN44NdtN9zyePA5UVAHgJTOKIgsGkPMRMK6nJZfHjgGctZqXDR4W7FVbnFnErhB4l631ktNqJE5Y2zs7Uqd9190TvYBuAkrQ27tSypjtDgWhRpiz6pNa7Tn6v417Lpe8Mut+ijuu+6Qey1mEQh5G9mmZGENssesQo4N3m7ELMJCWyrjJQzaLuZqPOPNfSpZvlZnP1VnXqugXsNJl9hdktuXvAHK3F2uO1tV71iGbCmGXsuYs1FjXzUkI6YimlyI9zniV7KuhkDH2vQ37piVKdIIb4vECXeTRe5jCwlRiKz1wCWaacxRukjHh1LQpxPy1EI5x7lcN4/vxFUPtcVf8qTX8rYejGcjaEcY/MlvfFBnelWvM0CoCYdaj8x78O0lEPnRZSoUeOCT1LaEqVkKdUTP/EptSglrFIUiobMSABxhHFfMeHiOXydAWZXzK8R0wxqmnNZVCvwtE3/ankuiM/laE2YBTm/DichJqAXGXXIv2z1GGEKsUYpG+s2BTFAiZqVmFVhMU4ZlwxuO06hGwG9zKpzYc1aEt7moHZwoR/XCzETGYC1KKiyLb00gZPw4oRhFlBaQPUKvGTVoE5dhkXv8s7fkuNfF/pR0Z1k9N/yy/dpYy0Ms5QBxfSgWaeCFpRmzYTZ8MIC/Qfmrh6fCqAeDwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2&quot;
        title=&quot;&quot;
        src=&quot;/static/34b46086f5ff88bc896b00c6f3b521ee/d7542/2.png&quot;
        srcset=&quot;/static/34b46086f5ff88bc896b00c6f3b521ee/2efce/2.png 203w,
/static/34b46086f5ff88bc896b00c6f3b521ee/1d180/2.png 405w,
/static/34b46086f5ff88bc896b00c6f3b521ee/d7542/2.png 810w,
/static/34b46086f5ff88bc896b00c6f3b521ee/d56b5/2.png 1215w,
/static/34b46086f5ff88bc896b00c6f3b521ee/21b4d/2.png 1280w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;除了一个代表拒绝的红叉，没有任何交互和提示&lt;/p&gt;
&lt;p&gt;我只能悻悻地回去解锁完前置任务，再带着贤者前来，但已经失去了初见的震撼。即使有了贤者之力的加持，也还失误了几次。这还是那个奖励玩家好奇心的任天堂吗？如果不查攻略，我恐怕还在神殿内部鬼打墙呢。你用精妙的引导和优秀的地图设计让玩家来到这里，却不愿意给一段剧情，甚至一个提示。甚至连个传送点都不给。&lt;/p&gt;
&lt;h2&gt;剧情的不用心&lt;/h2&gt;
&lt;p&gt;我们尚可以为主线的强引导辩解几句，因为确实有许多玩家在弱引导下会丧失目标感，控制玩家早期的游玩动线，是为了尽早把所有的主线和机制展现出来；而参与一次解锁神殿，也可以为后续的游玩流程打个样。但为什么开发者选择了风神殿作为流程中的首个呢？&lt;/p&gt;
&lt;p&gt;我们当然可以为此找到理由，例如去风神殿的路上怪物强度相对较低，并且风神殿的 boss 是机制杀，不需要玩家有较多的资源。但这难免有倒果为因之嫌。考虑到大多数玩家都觉得风神殿是印象最为深刻、综合表现最为出色的神殿，我们可以阴谋论地认为，开发组就是希望把最好的内容最早地呈现在玩家眼前。&lt;/p&gt;
&lt;p&gt;当玩家完成了风神殿，看着古代贤者讲述以前的故事时，恐怕没人能想到，一模一样的故事，他们还要再看三遍。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/15c6d3718aab0bd44786420e5b4630b5/eea4a/3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQCAwX/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAgP/2gAMAwEAAhADEAAAAdaqaecviQf/xAAbEAACAgMBAAAAAAAAAAAAAAAAAQMRAiEjMf/aAAgBAQABBQJuiPKjmS6hF5//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAYEAACAwAAAAAAAAAAAAAAAAABEAAREv/aAAgBAQAGPwKHRtF//8QAGhAAAwADAQAAAAAAAAAAAAAAAAERITFBUf/aAAgBAQABPyG25g1G9ZNmlk0AujOD/9oADAMBAAIAAwAAABDEP//EABYRAAMAAAAAAAAAAAAAAAAAABARQf/aAAgBAwEBPxBUf//EABYRAQEBAAAAAAAAAAAAAAAAABEQIf/aAAgBAgEBPxB0n//EAB0QAQACAgIDAAAAAAAAAAAAAAEAIRExQVFhgcH/2gAIAQEAAT8QKigZZd7LprxMgANrZcVQAfcAGPfyKReJ/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;3&quot;
        title=&quot;&quot;
        src=&quot;/static/15c6d3718aab0bd44786420e5b4630b5/627f4/3.jpg&quot;
        srcset=&quot;/static/15c6d3718aab0bd44786420e5b4630b5/efcb5/3.jpg 203w,
/static/15c6d3718aab0bd44786420e5b4630b5/02e66/3.jpg 405w,
/static/15c6d3718aab0bd44786420e5b4630b5/627f4/3.jpg 810w,
/static/15c6d3718aab0bd44786420e5b4630b5/78959/3.jpg 1215w,
/static/15c6d3718aab0bd44786420e5b4630b5/eea4a/3.jpg 1280w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;一模一样的剧情重复四次，古代贤者毫无前作四英杰的鲜明个性&lt;/p&gt;
&lt;p&gt;无论是主观上不用心也好，还是客观上产能不足也罢，目前的神殿支撑不起前作那四段有血有肉的剧情了。&lt;/p&gt;
&lt;p&gt;要说剧情，本作中重点刻画的是龙之泪剧情。龙之泪剧情本身没得说，但它过于独立，与其他剧情较为割裂的问题也受到较多诟病。玩家在游戏早期就可以在忘却神殿看到全地图所有的龙之泪位置，龙之泪的地面花纹也足够显眼，玩家往往在前中期就能大致猜测到公主的去向。然而龙之泪剧情线中的林克和其他主线中的林克就好像精神分裂了一样。在其他主线中，林克看到所有 NPC 为了找公主而被耍的团团转，却一言不发。直到游戏后期快打 boss 了，两条剧情线才交汇到一起。&lt;/p&gt;
&lt;p&gt;和前面风神殿的问题一样。塞尔达系列所具有的“根据不同的剧情进度有不同的对话”这一优点，常被大家褒奖为“老任好细”，甚至将其用作评判开放世界真实性的标准。但至少在这几个问题上，开发组做的是不合格的。&lt;/p&gt;
&lt;p&gt;还有一个被广为诟病的问题是对希卡科技的交待。从游戏设计的角度，我完全可以理解为了给新内容让位，必须要让希卡科技和建筑退出。但《王国之泪》在剧情上承接了《旷野之息》的时间线，希卡科技曾经遍布整个世界，在物理上的消失可以从设定上解释，但在 NPC 们口中、心中的消失，难道希卡科技还有大记忆消失术的功能吗？&lt;/p&gt;
&lt;h2&gt;敷衍的地底世界&lt;/h2&gt;
&lt;p&gt;四贤者体现了剧情的敷衍与同质化。然而同样同质化的不止是剧情。&lt;/p&gt;
&lt;p&gt;神庙是游戏里注水比较严重的。这个问题其实在《旷野之息》中就已经初见端倪。当你好不容易发现一个神庙，兴致勃勃准备迎接挑战时，却发现是个奖励神庙，就好像一盆冷水当头浇灭了你的热情。&lt;/p&gt;
&lt;p&gt;《旷野之息》中这样的神庙还是少数，考虑到有 120 个神庙需要设计谜题，偶尔塞几个也无可厚非。但在《王国之泪》中，这样的奖励神庙数量却大大增加了。或许有人会说，之所以是奖励神庙，是因为发现神庙的过程本身就是一种挑战。有些神庙确实是这样，但更多的所谓挑战，拜托，只是藏的比平时深了一点罢了。哪怕你不塞神庙挑战，我逛街也会逛到这儿。&lt;/p&gt;
&lt;p&gt;从《旷野之息》的 DLC 来看，开发组并不缺乏设计出高质量神庙的能力 —— 从发现神庙、到达神庙，再到解开神庙，都是谜题的一部分。《王国之泪》的神庙现状，只能说是出于工作量或其他因素考虑，被有意缩水了。&lt;/p&gt;
&lt;p&gt;还有很多人提到空岛的同质化。初始空岛是游戏里最大，也是内容最丰富的空岛。其余的空岛数量和体积都比较小，并且还有很多【神庙+扭蛋机】的同类型、中转站性质的空岛。不过，在空岛的问题上，我还是想稍微为开发组辩护一下。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1ce07d7432c031b2816290aa91813723/eea4a/4.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHHSFGEFF//xAAZEAEAAgMAAAAAAAAAAAAAAAABADECEBH/2gAIAQEAAQUCK5MrHTP/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwGH/8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQAGPwJf/8QAFxABAQEBAAAAAAAAAAAAAAAAAQARUf/aAAgBAQABPyEkR3bgSYWsnb//2gAMAwEAAgADAAAAENzP/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAR/9oACAEDAQE/ENCf/8QAFxEBAAMAAAAAAAAAAAAAAAAAAAERMf/aAAgBAgEBPxC0sf/EABkQAQADAQEAAAAAAAAAAAAAAAEAESExQf/aAAgBAQABPxDtm3TcPweMIEyoly4Xs7bHPZ//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;4&quot;
        title=&quot;&quot;
        src=&quot;/static/1ce07d7432c031b2816290aa91813723/627f4/4.jpg&quot;
        srcset=&quot;/static/1ce07d7432c031b2816290aa91813723/efcb5/4.jpg 203w,
/static/1ce07d7432c031b2816290aa91813723/02e66/4.jpg 405w,
/static/1ce07d7432c031b2816290aa91813723/627f4/4.jpg 810w,
/static/1ce07d7432c031b2816290aa91813723/78959/4.jpg 1215w,
/static/1ce07d7432c031b2816290aa91813723/eea4a/4.jpg 1280w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;除初始空岛外，其余的大型空岛数量寥寥&lt;/p&gt;
&lt;p&gt;探索天空世界是本作开发时一个重要的想法和前提，设计师并不缺乏空岛上玩法的各种点子。反倒是随着空岛数量的不断变多，被嫌弃天空看起来很脏。因此空岛的设计实际上是开发人员的创作热情被设计要求所抑制的结果&lt;sup&gt;[1]&lt;/sup&gt;。另外，以现在的地图设计，倘若不依赖于 MK2 等通用飞行器，而是利用场景中提供的机关和道具去到达各个空岛，也不乏挑战性和乐趣。换句话说，空岛地图是经过精心设计，有其设计意图和合理性的。&lt;/p&gt;
&lt;p&gt;反观地底世界，才是整个游戏敷衍的代表，最大的败笔。&lt;/p&gt;
&lt;p&gt;诚然，讨厌地底世界，有我主观的成分。地底世界阴暗、恐怖、充满危险。《旷野之息》以其清新、淡淡的油画般的画风吸引了像我一样的玩家群体。即使早已通关，我也愿意回到游戏世界，哪怕什么都不做，只是闲逛，欣赏风景。甚至有人专门研究怎么通过卡 bug 等方式去除 HUD，只为了给《旷野之息》做旅游宣传片。而《王国之泪》的地底世界天然就在这些特点的对立面，让人一开始就消灭了探索的欲望。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e0b0afa19f32dc9e0135da615959e1ee/21b4d/5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACM0lEQVR42hWP6ZKaUBBGKTd2Lvdy2UHZRHFBWQZQIjOCMVpmnCw1lbz/k4T0r66u71R/h5inq9/X6lZFt5dFvvUD3/Idg1MVUlFoTaVkeYIRiTFtGKjIBXcmWCZwpshz9EVAJF1+/O7NNp69sJGtMzKiJZHEaIIx2ZMIjgR+LAo9L3YtHwa0ovC2hbwZdGxi22anjwPwTejqvKlRWBqJwgSKEySORdCTQ5Yd8Vx/gXkGyoI3DTQ1OUMnFZnYteXmkGBPh1MZ2gYrYxKKFOr/I87AY8ANOXbAMrRpgiJnq4q2bbLPSL2LRPhVjAJT8nRxqnCaTCE44Pkhz48hkAMbqHDAMAOKInWdzzKc7STPGsnyGAq9C7E8ZfrKVcIp9kxelydAGDPMhGP7hZLgkGUGE3JM031zLo7h8cDObJKhR0DojYioyfRlb69iV1P9maBgFUITIQ6iAUUDDBRD5QQBqIrwpWYOBVpFPMsi8H8Ir4zlwDTWruZb09BzN1F6ft3W1f5UW+HU9LVFmszjxFhH3PsDvTXS3EOmJctY7Gvb+7keubN06WwCdWbmXVM/34+Px+F+C7PYif3NS77Lsp9tC66d9e2yzF/w7pUPS8m2ifC4Ty9Nce++fNzq5z2/duX9lpzPSfvmp7G7CeIiT4q0vTR2XRTd67qs+e2F23baYkmUj677+9n9+Wx+/Tg+n0n3df92XhbFuj6ujtWizPfNaXc6+XkaJFsnCrW54xd11Fy3dfUP1DJVBcp3YK0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;5&quot;
        title=&quot;&quot;
        src=&quot;/static/e0b0afa19f32dc9e0135da615959e1ee/d7542/5.png&quot;
        srcset=&quot;/static/e0b0afa19f32dc9e0135da615959e1ee/2efce/5.png 203w,
/static/e0b0afa19f32dc9e0135da615959e1ee/1d180/5.png 405w,
/static/e0b0afa19f32dc9e0135da615959e1ee/d7542/5.png 810w,
/static/e0b0afa19f32dc9e0135da615959e1ee/d56b5/5.png 1215w,
/static/e0b0afa19f32dc9e0135da615959e1ee/21b4d/5.png 1280w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;地底的 90% 都是这么一个样子&lt;/p&gt;
&lt;p&gt;除了个人偏好的因素，地底世界从游戏设计的角度也令人诟病。地底世界主打的特色是反转、对称，想必大家在实际游玩后不久就可以发现。例如地上地势高对应地下地势低，地上的河流对应地下的墙壁，地上的神庙对应地下的树根等等。&lt;/p&gt;
&lt;p&gt;或许在我们刚开始发现这样的对应关系时，会有一种惊喜的感觉。但随着游戏的进行，再仔细想想，这样的对应关系有必要吗？有助于提升游戏的乐趣吗？&lt;/p&gt;
&lt;p&gt;不得不承认有些对应关系还是非常合适且合理的。例如河流对应墙壁、岩浆对应温泉，既符合常理，又丰富了地底的环境。但整个地底世界的地势完完全全是地面地势的反转，根本就不是一件科学的事。&lt;/p&gt;
&lt;p&gt;同样是“位于地表下方的空洞”这一概念，洞穴的设计和地底形成了鲜明的对比。本作中洞穴也几乎遍布了整个游戏地图，但在不同的区域，洞穴也有不同的特点，例如有的干燥，有的湿润，有的明亮，有的阴暗，有的还可以作为连接两个看似不相关区域的秘密通道。~~除了有些为了硬塞魔犹伊导致的套路化洞口，~~游戏的各处洞穴可谓是丰富多彩，在洞口间的穿梭让人意料之外，看到地图后却又感到情理之中，并不给人以同质化的观感。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ddfbbecad0c3926ead60ec0dcdb116f3/eea4a/6.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAMBAv/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAABhSBxyxP/xAAbEAEAAgIDAAAAAAAAAAAAAAABAhEAAxITIf/aAAgBAQABBQLr8NOSKbeMJNqr/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGBABAQEBAQAAAAAAAAAAAAAAAAEhETH/2gAIAQEABj8CdeJqatr/xAAYEAEBAQEBAAAAAAAAAAAAAAABEQAhMf/aAAgBAQABPyGpU93lFLzUTFrLFY6ysVu//9oADAMBAAIAAwAAABC3H//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABoQAQEBAQEBAQAAAAAAAAAAAAERACFRMUH/2gAIAQEAAT8QHnRwZ+zMoXIiCzzO9Denmvg57mcQ8fuQ2jq7/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;6&quot;
        title=&quot;&quot;
        src=&quot;/static/ddfbbecad0c3926ead60ec0dcdb116f3/627f4/6.jpg&quot;
        srcset=&quot;/static/ddfbbecad0c3926ead60ec0dcdb116f3/efcb5/6.jpg 203w,
/static/ddfbbecad0c3926ead60ec0dcdb116f3/02e66/6.jpg 405w,
/static/ddfbbecad0c3926ead60ec0dcdb116f3/627f4/6.jpg 810w,
/static/ddfbbecad0c3926ead60ec0dcdb116f3/78959/6.jpg 1215w,
/static/ddfbbecad0c3926ead60ec0dcdb116f3/eea4a/6.jpg 1280w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;美丽的水晶蘑菇洞。不同区域的气候环境也孕育着不同风格的洞穴&lt;/p&gt;
&lt;p&gt;而地底世界，最大的问题就是其照搬地面的地形本身。无脑的反转地形导致地底充满了各种突兀的、完全不可能出现在现实中的奇怪地势转折，毫无设计感可言。又由于地形是反转而来，玩家在熟悉了地面的地形后，不用探索就能想象的到对应地底的样子，进一步减少了探索的欲望。又由于只反转了地形，地面上的树林、草原、雪山、海洋……各种丰富的地貌，在地底都变成了只有光秃秃的惨绿色的岩石、诡异的蕨类植物、和瘴气，更进一步减少了探索的欲望。&lt;/p&gt;
&lt;p&gt;还有一些对应关系，或许从剧情设定上合理，但在游戏设计上并不合适。说的就是神庙和树根的对应关系。同样作为神圣能量的来源，两者相互对应确实合理。但神庙作为地面上数量有限的探索资源，是非常宝贵的。游戏鼓励玩家探索，并设置了很多宝箱、Yahaha 等作为探索的奖励。而神庙无疑是奖励中最高的那一档。在游戏后期遇到神庙，我是既开心又失落，开心于我的探索得到了回报，失落于游戏的剩余未知内容又少了一些，离游戏结束又近了一分。&lt;/p&gt;
&lt;p&gt;而树根的加入毁了这一切。不同于很多神庙藏在深山老林，发现时非常有成就感，寻找树根简直太容易了。在一片漆黑的地底，一个树根就是天空中最亮的那颗星。不仅如此，树根还承担了地面上鸟望台的功能，用来开启地图的迷雾。而树根点亮的范围是固定的圆形。再考虑到地下的特殊区域，例如独立的小岛等，也必须被点亮，也就是说，即使肉眼没有看到树根，我们也可以很轻易地推测出下一个树根的位置。&lt;/p&gt;
&lt;p&gt;这样的设计就导致，如果你是地面地下同步游玩，往往点亮树根的进度会比解锁神庙的进度提前很多。但树根的位置又和神庙一一对应，当你回到地面，哪怕是还没探索过的区域，神庙的位置也已是明牌。这样一来，发现神庙所带来的不再是令人激动的体验，而只是完成任务罢了 —— 就像我们不屑的其他 checklist 式开放世界游戏一样。&lt;/p&gt;
&lt;p&gt;我们都知道，在游戏开发中，点子是最廉价的。可能刚开始觉得很有意思，但实际做进游戏，玩起来就又是另一回事了。所谓地面与地底的对应关系就是如此。开发组不可能不知道这个道理。即便如此仍然要这样做，并不是真的觉得这样的设计很精妙，而是另有原因。&lt;/p&gt;
&lt;p&gt;在开发者访谈中提到，与空岛不同，地底世界并不是一开始就决定制作的。并且在决定采用与地面反转地形的设计后，地底的制作可以通过程序自动化的方式生成，无需花费大量时间和精力，这也帮助它避免了被叫停开发，最终得以面世&lt;sup&gt;[2]&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;换句话说，选择反转地形，并没有多少是出于游戏设计上的考量，而主要是为了节省工作量。&lt;/p&gt;
&lt;p&gt;但代价是什么呢？程序生成，通常会用在沙盒 / roguelike 等允许重复游玩的游戏类型中，为了让每一次游玩都有独一无二的体验。而本作与这些游戏类型并不沾边。在&lt;a href=&quot;/p/how-to-find-yahaha&quot;&gt;前一篇《旷野之息》的文章&lt;/a&gt;中我们就已经提到过，它之所以优秀，就是因为它的整个世界都是经过精心设计的，和其他套路化的游戏不一样。而本作的地底世界，既然选择了偷懒，那质量的下降也是咎由自取。&lt;/p&gt;
&lt;p&gt;那么，这样一个偷懒的地底世界，又该如何吸引玩家呢？本来，倘若下笨功夫，往地底堆大量的内容，靠量大管饱征服玩家，也不失为一种诚意。但既然已经决定地底以节省工作量为宗旨了，开发组又怎么可能愿意这么做呢？&lt;/p&gt;
&lt;p&gt;从明面上讲，开发组设计了一套天空-地面-地底的循环机制，在天空获取向阳草和藏宝图，在地面获取光亮花，有助于探索地底；而地底可以获取电池和强力武器装备，又有助于探索地面和天空；地底战斗较为硬核，也鼓励了玩家去解锁地面神庙提高自身精力和血上限。由此，玩家不会长久呆在同一个地方，而是在三层地图中不停切换，同步探索。&lt;/p&gt;
&lt;p&gt;但仔细思考这个循环，就会发现循环中真正值得探索的只有地面和天空。地底虽然也有像火神殿、第五贤者、魔人像之类的少数大型任务，但绝大多数的内容还是打怪、收集武器装备、采矿、采烟雾蘑菇和炸弹花。在这套循环中，地底扮演的是资源提供者的角色。但这些资源就非得放在地底吗？只是开发组决定将它们放在地底。只怕不放在地底，地底就显得可有可无了。&lt;/p&gt;
&lt;p&gt;地底是如此的无聊与同质，以至于只能塞资源来解决玩家没有动力探索的问题。&lt;/p&gt;
&lt;p&gt;地底中最重要的资源是矿石，矿石服务于游戏的核心玩法。游戏鼓励玩家使用“究极手”能力拼装各种左那乌装置，但生成装置和装置运行的电量消耗，都需要矿石补充。越高级、越复杂的装置，需要的矿石就越多，但持续时间往往很有限。因此，为了玩得爽，就需要玩家源源不断地采集矿石。&lt;/p&gt;
&lt;p&gt;有很多玩家吐槽过这种采集几小时，爽玩几分钟的游戏体验，甚至在爽完过后选择读档。《王国之泪》的几种新能力，给了玩家更高的自由度，能看出开发组也是希望玩家可以充分发挥创造力，爽玩的，但偏偏在矿石和电量这种地方卡着玩家。明明只要调整一下数值，就可以改善玩家的体验。&lt;/p&gt;
&lt;p&gt;比起堂堂正正地在地底堆量，开发组选择了将地底变成一个无趣、但足够消磨玩家时间的刷刷刷游戏。&lt;/p&gt;
&lt;p&gt;也不是说刷刷刷不好。喜欢玩刷刷刷游戏的也大有人在。可就像我玩《Cookie Clickers》也可以很上头，但我不会拿它去评选年度游戏一样，这可是《塞尔达》系列！可是要成为游戏设计教科书的存在！要么不做，要么做好，何至于用这种手段来应付玩家，强行拉长游戏时间呢？&lt;/p&gt;
&lt;p&gt;联想到地底世界在游戏的前期宣传中几乎没有露面，官方的口径是希望保守秘密待玩家自己去发现。但在这个酒香也怕巷子深的年代，假如地底世界真的像官方所说，“设计了相当丰富的玩法”；假如地底真的和它的面积一样，拥有和地表世界同样的内容体量……作为一个“大型 DLC” 却比前作还卖的贵一个档次的作品，官方难道不会将其作为一个重要的卖点大肆宣传吗？不妨小小阴谋论一下，恐怕官方对此守口如瓶，也算是有点自知之明吧。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;虽然这篇文章充满了大量对《王国之泪》的抱怨，但这些只是我的集中吐槽。总的来说，《王国之泪》仍然是一部非常优秀的作品。无论你之前是否玩过《旷野之息》，你都不能错过《王国之泪》。&lt;/p&gt;
&lt;p&gt;只是，如果说《旷野之息》是艺术品，《王国之泪》就是一部优点和缺点同样突出的作品。由于继承了《旷野之息》的世界观和场景，很多问题其实难以避免，很难有完美的做法。或许本作如果是一个完全新作会变得更好，我也依然相信任天堂在未来能够拿出更加出色的作品。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;[1] &lt;a href=&quot;https://www.nintendo.com/hk/interview/totk/03.html&quot;&gt;開發人員訪談: 薩爾達傳說 王國之淚｜任天堂&lt;/a&gt;&lt;br&gt;
[2] &lt;a href=&quot;https://zhuanlan.zhihu.com/p/655839219&quot;&gt;《塞尔达传说 王国之泪》Fami 通万字专访&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[我为什么不再爱好摄影]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/why-i-give-up-photography/</link><guid isPermaLink="false">https://maliut.space/p/why-i-give-up-photography/</guid><pubDate>Sat, 17 Aug 2024 15:57:00 GMT</pubDate><content:encoded>&lt;p&gt;熟悉我的朋友会知道我在初中到大学前期喜欢摄影，那时还买过自己的单反相机，也参加过一些校内外的摄影活动。然而这份热情在之后却快速冷却下来，不仅相机长期吃灰，现在即使出去玩，也懒得拍一张游客照。&lt;/p&gt;
&lt;p&gt;反倒是从微信朋友圈出现之后，我观察到朋友圈里出现了越来越多的摄影爱好者。当然这很容易理解，只是不禁让我自我怀疑：难道我的特性是唱反调吗？&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;要讨论为什么我不再爱好摄影，首先要回想我为什么会爱好摄影。小时候我早早就接触到了相机，还记得第一个接触到的是一台 Olympus 的银色卡片机。后来我常看的报刊杂志中也会介绍很多摄影相关的技巧，一来二去也无师自通了许多理论知识。而在校园里，虽然没有什么使用电子产品的机会，可遇上各种活动，摄影往往是被允许且被鼓励的。凭借我自学到的技巧，出片确实优秀了不少，也收到了不少来自外界的认可。渐渐地正反馈就形成了。&lt;/p&gt;
&lt;p&gt;那么失去兴趣也很简单，直接原因就是正反馈循环的缺失。感觉缺乏了摄影技术进一步提升的能力和动力。&lt;/p&gt;
&lt;p&gt;拍出更好的摄影作品需要什么？当然需要个人摄影技巧的精进，但各类外在因素也不容忽视。&lt;/p&gt;
&lt;p&gt;首先是器材。虽然现在摄影的门槛已经降到很低，但一方面当时的智能手机还远没有如今这么强大，另一方面，要想真正登堂入室，你终究需要去了解专业的可变镜头相机，而这就是个无底洞了。就算机身可以将就，也总不能一直只拿着 18-55mm 打天下吧。更不用说还有各种七七八八的配件。然而无论是以当时的财力还是性格，都不会支持我在这方面持续投入。&lt;/p&gt;
&lt;p&gt;其次是拍摄的内容。如果你去参加学校运动会的拍摄活动，你会发现大家最后的作品虽然良莠不齐，但拍摄题材来来回回就那几个。再好的照片，看多了也觉得千篇一律，审美疲劳。而朋友圈的旅行爱好者们，面对风景名胜，随手拍都能咔咔出片。摄影终究是一项取对象的活动，再优秀的摄影技巧，没有拍摄对象也终究是无根之木、无源之水。这也是为什么摄影博主和旅行博主的身份高度重合。当然同样，无论是以我的财力还是性格，都不会选择在这条赛道上深耕。&lt;/p&gt;
&lt;p&gt;也有人会说，不拍风景，可以去拍人像啊。确实有很多（尤其是二次元领域）想学摄影是为了去拍 coser 的。如果是之后想走职业摄影道路，选择人像摄影也是相对需求较大的方向。且不说我最早是从风景入坑（看国家地理看的），如果转向人像摄影，又会面对新的挑战。&lt;/p&gt;
&lt;p&gt;一是人像摄影需要大量的人际沟通，在摄影过程中需要协调、指挥模特摆出各种姿势表情，调动模特的情感。摄影过程之外也免不了涉及各方面的沟通交流。想到这里我的社恐已经犯了。&lt;/p&gt;
&lt;p&gt;二是修图。人像摄影就逃不过修图。当然这是所有摄影到了后期必需的一个技能点，即使是拍拍风景，不用仔细 PS，也免不了后期拉拉曲线啥的。只是我个人对修图这件事实在没这么感兴趣，审美能力也有限吧。&lt;/p&gt;
&lt;p&gt;三其实还是人的问题。有人的地方就容易有各种节外生枝，导致“功夫在摄影外”。一个印象较为深刻的示例是，当时微单还刚刚兴起，如果你拿一个顶级微单和一个入门单反出去拍摄，反而是拿着入门单反会受到更多的尊敬，而微单会被当成卡片机而受到鄙视。虽然也不是不能理解这样的心理，但遇到这种看人下菜碟的情况，拍人这事也就更让人意兴阑珊了。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;摄影作为我的第二爱好，与第一爱好编程相比，正好形成了一个有趣的对照：一个好动，一个喜静；一个外显，一个内倾；一个吃硬件，一个吃软件。两者并不十分兼容。而人的时间精力有限，把其中的一个爱好变为主业+副业，另一个爱好的淡化也是必然。&lt;/p&gt;
&lt;p&gt;说了这么多，以上都是我自己的问题。但倘若再往下思考，我觉得摄影作为一种艺术形式，也存在着一些深层次的矛盾。&lt;/p&gt;
&lt;p&gt;摄影与绘画或许有着异曲同工之妙。在摄影技术出现之前，绘画是唯一可以记录现实场景的手段。因此当时绘画的主流是以真实为佳。但随着摄影技术的出现和不断发展，无论是速度还是精确度都超越了绘画，绘画技术一时失去了自身存在的意义。直到以印象派等各类新的绘画流派兴起，才算重塑了自己的定位，不仅仅一味追求真实，而是百花齐放、将画面作为个人思想与情感的表达，而这也让绘画真正从一门技术升格为了一门艺术。&lt;/p&gt;
&lt;p&gt;摄影的发展又何尝不是在走绘画的老路。刚开始摄影还是一门技术活，要是不懂点相机操作和成像原理，光是拍出一张合格的照片，画面不糊，对焦准确，不过曝欠曝，已是实属不易。然而随着科技的进步，普通人用手机也能拍出不逊色于专业相机的大片了。这时候，“会摄影”就不是一个值得称道的技能了，得像“朋友圈摄影大赛”那样，比一比谁的作品更好了。&lt;/p&gt;
&lt;p&gt;那么，如何区分更好的照片呢？技术不再成为门槛，就只能在内容上开卷了。卷构图，普通的构图还不够，得来点标新立异；卷画面，再美的自然风景，也要加入一些“人文因素”才算完美；拍摄事件，即使是与事件本身并不相关的犄角旮旯，也要处理得尽善尽美，不能留下任何可被挑刺的地方。&lt;/p&gt;
&lt;p&gt;可真实的世界，哪有这么多完美的场景在等着你呢？即使有，就那么一瞬间，怎么就刚好被你捕捉到了。借用网上打假新闻时常说的一句话：你的脑袋上长了摄像头吗？对完美的极致追求，让不少摄影师们忘记了初心。即使是在各种摄影大赛中，使用摆拍、PS 等事件也是屡见不鲜。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2caa508af3fa92c8741ccb3a85faf4be/18c13/1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.591133004926114%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACs0lEQVR42m2T3YvUZhTG8395U+iNlF7bm8r2QrxYVy+UUpBuRSulgoIg1paWwlJaLUUKKiL2Umxd3V3cnd3pjjtmJsnk+zuZzEeSmZ9nIqKIB07Oycmb55zz5Ikyq0uSUMUY6JTVlNmsklhSlhMqeVbVFZOqkvqMNzafzxt/197cKfV0TLT9lP6+Tr/roe47uFaMbfgc7Flo/+u4e21GSUY1GDB/B/hDpsyku7u9S69t0uv4Ahww0AJM8YM9n15Lx9newTdszPWnBH5AEEZEccJwWIgPSdKULB/KFnMBlNGToiAbZuTiw1FGnCak2SLPyYqcSA4neU46GglQTJwkr0GyvAHPBbSq6oYGRa5MphN0WScMw6a44Gs2qxv+6lqi5LXU3uftgyvX9ZzYCgkGBvYgwHdjPCfCtSN8L0ZXHayuSuK6FMOSPJlQZCXjomKUl+I14+FbXpVpOaf1z39YzzfY3dIxuiEDNUE78NC7DgetiKzVobJtXHMsXMdo+9KoEwjfIe0Ng531l6RJ3GymRGHA6ZNH+fLUZywvfcKDh3/x6Mnv3L1/jTv3fsT1TGzPwxRAz3OIkpAo8IhCt5noj7UrnFr+lDMrR7BNDSWNI66tnuDGt+dYXf6cX3++xG8XP+bG1WPcFtB2u8X6k3W2Njd5/PhfNjY22d58hqn3mQldP13/hpXjh7jw9WF0rSMTBgFnlg7z/VfHuXj2C364usovl85y88/vuPX3ZVT1BbphEEYRpmnKD2DiOjaWaeAHMTfXzrN2/SNu31qSDXooZVmy23pO7+UOfbUtL2n0+l1MS0PTVVzfJ7QdptJ4LFIqophE8lg4S9MMvd/BsXalSV8UUaEspFCMJqR50cTxZCr6GzWiTdNcJhPdyXSpeCz5QthhEBImKZFlkdmenJ82ol585VcekWgJNKL5RwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;1&quot;
        title=&quot;&quot;
        src=&quot;/static/2caa508af3fa92c8741ccb3a85faf4be/d7542/1.png&quot;
        srcset=&quot;/static/2caa508af3fa92c8741ccb3a85faf4be/2efce/1.png 203w,
/static/2caa508af3fa92c8741ccb3a85faf4be/1d180/1.png 405w,
/static/2caa508af3fa92c8741ccb3a85faf4be/d7542/1.png 810w,
/static/2caa508af3fa92c8741ccb3a85faf4be/18c13/1.png 1005w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;可以说，如今的摄影，比起记录真实，更像是先在脑内预设了画面，然后在现实中拍摄出来。但既然这样，为什么不自己创造世界呢？&lt;/p&gt;
&lt;p&gt;另一方面，即使你的摄影再卷生卷死，最终成品也不过是方寸之间，能传达的信息量终究是有限的。这也是为什么摄影作品在参赛时都要配上说明文字。如果没有说明文字，观众看到它们，只会觉得：啊，很美的构图和画面，但这样的图片我也见得多了。随后很快被淡忘。而有了说明文字，观众才会意识到，啊，原来这张照片看似普普通通，背后原来有着一段这么有趣的故事。而那些能够被广为流传的照片，往往是因为它反映的事件已足够为人熟知，已经在观众的心中植入了先验知识，才无需额外说明，就拥有了极大的传播力。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/80a3fb7604f24dc547d0dffa441c225e/15ec7/2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAADAv/aAAwDAQACEAMQAAABVsmysSaih//EAB0QAAAFBQAAAAAAAAAAAAAAAAABAhEiAwQhMTL/2gAIAQEAAQUCVENJhV7LFqrf/8QAGBEAAgMAAAAAAAAAAAAAAAAAAAECEUH/2gAIAQMBAT8BitKP/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAIRIf/aAAgBAgEBPwFm0p//xAAYEAADAQEAAAAAAAAAAAAAAAAAARARgv/aAAgBAQAGPwI3YzpT/8QAGxABAAICAwAAAAAAAAAAAAAAAQARITEQkbH/2gAIAQEAAT8hsBAW9TULFa4vuiprGL2KsJ//2gAMAwEAAgADAAAAEKc//8QAGBEBAQADAAAAAAAAAAAAAAAAAQARIYH/2gAIAQMBAT8QMDqyd3//xAAWEQEBAQAAAAAAAAAAAAAAAAABACH/2gAIAQIBAT8QRRYv/8QAHRABAQACAQUAAAAAAAAAAAAAAREAIXExUaGx8P/aAAgBAQABPxBQEUW7vIGNRppfvWccZOsI8YlOlidZVLzjS0J2z//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2&quot;
        title=&quot;&quot;
        src=&quot;/static/80a3fb7604f24dc547d0dffa441c225e/15ec7/2.jpg&quot;
        srcset=&quot;/static/80a3fb7604f24dc547d0dffa441c225e/efcb5/2.jpg 203w,
/static/80a3fb7604f24dc547d0dffa441c225e/02e66/2.jpg 405w,
/static/80a3fb7604f24dc547d0dffa441c225e/15ec7/2.jpg 690w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;如特朗普遇刺照，本身没得说。但是如果让一个不认识特朗普，不了解他的政见，不了解事件来龙去脉的人观看，也只不过是一张普通的好照片罢了。&lt;/p&gt;
&lt;p&gt;我能够理解把摄影作为职业的人，他们必须卷。同样的事件、同样的景，全世界的摄影师都在拍摄，当然要挑出一个各方面都完美无缺的。但是对我自己，以及大多数人来说，摄影只是为了记录生活。如果为了画面的好看去刻意凹角度、去扭曲时空，费时费力不说，甚至要以减损真实性为代价，岂不是本末倒置。&lt;/p&gt;
&lt;p&gt;以我个人的经历来说，当我打开相册回忆过去时，那些我当时精心设计、构图的照片看起来固然令人赏心悦目，但更让我的内心泛起波澜的，反而是那些随手抓拍的照片。它们看上去构图稀烂，对焦不准，但拍摄当时发生的事却好像还是历历在目，忍不住让人嘴角勾起微笑。而这些事情，又往往不足为外人道也。&lt;/p&gt;
&lt;p&gt;摄影的价值并不在于各种拍摄和 P 图技巧，而在于记录当下那份独一无二的经历、场景和情感。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;我痴迷摄影时，去哪里玩都想着带着相机，看到什么都想拿相机比划比划，生怕错过任何一个出片的机会。即使没有举着相机，眼睛也跟个取景器似的，想着怎么构图、怎么拍摄。一趟下来，要是没拍到满意的照片，感觉就像亏了一样。然而熟悉相机的人都知道，相机的 FOV 远小于人眼。总是透过相机看世界，长此以往视野就会被局限。后来当我放下相机，真正用眼、用心去看世界，我才意识到，真实世界的风景远比镜头中的、远比我想象的更加波澜壮阔。&lt;/p&gt;
&lt;p&gt;摄影应该服务于生活。如果像我之前一样，为了摄影反而损失了自己的生活体验，那就不好了。说到这又想起来，现在很多人在吃饭时“让手机/相机先吃”，也引发过不小的争议。我也期待着，或许在未来，随着 AR 技术的发展，摄影能够更加融入于生活。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;2024.12.14 补充：&lt;/p&gt;
&lt;p&gt;最近在看新出的《离线·滤镜》，看到一段话还是挺有感触的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;史蒂芬·肖尔在拍摄那张著名的的洛杉矶路口的照片时，一开始非常果断地摆放相机的位置，组织所有的视觉元素，为他的拍摄对象赋予秩序，由此产生画面的“结构”。……&lt;/p&gt;
&lt;p&gt;然而，肖尔突然意识到，&lt;strong&gt;自己是在把一种真正经典的图片化的结构处理强加在拍摄对象上&lt;/strong&gt;。……&lt;/p&gt;
&lt;p&gt;于是，他又回到那个路口重新拍摄，把“结构”隐藏再隐藏，这次拍摄的照片看上去完全没有构图，就像眼前所看到的一样。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“他做这整套的思考实践仅仅是为了还原他当时的亲眼所见”。因此，虽然“他们的作品在最初被判定为‘平庸’、‘无聊’”，但“几十年过去后再看，纷纷产生共鸣”。&lt;/p&gt;
&lt;p&gt;我觉得我的想法与他们有着不少相通之处。正如我最近常常看到的名句“凡此种种，表象取代存在，皆为景观”。如今许多人摄影，拍的并不是真实的生活，而是他们想要给别人看到的生活。这没有错，只是我个人不喜欢。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[基于 Vue Composition API ，实现面向对象的状态管理]]></title><description><![CDATA[在大部分时候前端都使用简单的对象字面量（Plain Object）来定义状态。这一方面是因为很多前端业务比较简单。例如纯数据展示的场景，可以直接使用后端返回的 JSON 结构，或只需要做一些基础的转换。另一方面，在经典的 React + Redux…]]></description><link>https://maliut.space/p/vue-composition-api-oop/</link><guid isPermaLink="false">https://maliut.space/p/vue-composition-api-oop/</guid><pubDate>Sun, 21 Jan 2024 22:08:00 GMT</pubDate><content:encoded>&lt;p&gt;在大部分时候前端都使用简单的对象字面量（Plain Object）来定义状态。这一方面是因为很多前端业务比较简单。例如纯数据展示的场景，可以直接使用后端返回的 JSON 结构，或只需要做一些基础的转换。另一方面，在经典的 React + Redux 架构中，我们需要保持数据的不可变性以触发视图更新，而这依赖大量的对象解构，也促使大家优先选择对象字面量进行操作。&lt;/p&gt;
&lt;p&gt;然而前端并不是只有视图层，还有复杂的业务场景，像一些具有复杂结构和联动关系的表单、可视化编辑器之类的需求。在这些需求中，前后端的攻守之势易矣。后端只需要提供基础的增删改查接口，而所有的业务逻辑和状态流转都需要放到前端进行。&lt;/p&gt;
&lt;p&gt;在这样的需求中，简单的组件级别状态显然不够用了，必须引入全局或模块级别的状态管理。关于全局状态管理的必要性，我们已在&lt;a href=&quot;/p/global-state-management/&quot;&gt;上一篇文章&lt;/a&gt;中探讨过了。而本文主要聊聊状态的组织，以及笔者在实践中总结出的使用 Composition API 组织状态的技巧。&lt;/p&gt;
&lt;h2&gt;引入派生状态与操作&lt;/h2&gt;
&lt;p&gt;相比于简单的纯展示需求，在复杂业务中，我们除了需要保存状态数据本身，还需要额外处理派生状态，以及对状态的操作。可以使用最直接的，也是最函数式的做法 —— 把这些逻辑放到单独的函数中。举个最简单的例子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;John&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Smith&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;person&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;person&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hi, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;person&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这种做法类似于“贫血模型”。它的状态对象被保持为 Plain Object，这是我们最熟悉的做法，可以与任何前端框架结合起来使用。&lt;/p&gt;
&lt;p&gt;不过这种做法也有一定的问题：&lt;/p&gt;
&lt;p&gt;首先是这样做导致了逻辑的分散。它的状态、派生状态和操作是三个独立的东西，在实践中往往会放在不同的地方（例如 Vuex 的 &lt;code class=&quot;language-text&quot;&gt;store&lt;/code&gt; 、&lt;code class=&quot;language-text&quot;&gt;getters&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;action&lt;/code&gt;）。但如今无论是 React Hooks 还是 Vue Composition API，动机之一都是重新组织代码的结构，使得同一逻辑关注点的代码能够被聚集在一起，并抽离到函数中，以方便逻辑复用。将状态、逻辑分散的做法并不符合最佳实践，也与当今趋势背道而驰。&lt;/p&gt;
&lt;p&gt;其次，这种做法放大了状态和派生状态的区别。尤其是在 React 中，当你有一个复杂的派生状态时，你很难保证正确地使用它。你需要小心翼翼地编写代码，甚至利用 reselect 等第三方库，才能保证界面不被无谓地重新渲染。&lt;/p&gt;
&lt;p&gt;我们可以利用面向对象的做法改写这个例子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  firstName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;John&apos;&lt;/span&gt;
  lastName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Smith&apos;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;sayHi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hi, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fullName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过定义一个 class，我们把所有 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt; 相关的状态和方法都放到了一起，更加内聚。在使用上，面向对象的做法也更符合人们的思维习惯，更加直观。&lt;/p&gt;
&lt;p&gt;此外，这样做还相当于构建了一个防腐层，可以防止后端改了一个字段名，前端就要改一百个地方。对于纯 JavaScript 项目来说，它还能一定程度上起到 TypeScript 的作用，能让开发者清晰地看到一个业务对象定义了哪些字段，并获得 IDE 的自动补全。&lt;/p&gt;
&lt;p&gt;更深层次的好处是，通过面向对象的分析与设计，搭建具有高度复杂度的系统，在历史上已经有了很多成熟的方法论，可以为我们所用。&lt;/p&gt;
&lt;h2&gt;与视图层结合&lt;/h2&gt;
&lt;p&gt;我们把状态、派生状态和操作一起封装成了对象。接下来我们要把它放到视图层中使用。&lt;/p&gt;
&lt;p&gt;在 Vue 中，组件的 &lt;code class=&quot;language-text&quot;&gt;data&lt;/code&gt; 可以不是 Plain Object。我们直接把一个类的实例放到 &lt;code class=&quot;language-text&quot;&gt;data&lt;/code&gt; 中，它的响应式也毫无问题。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ person.fullName }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@click&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;person.sayHi()&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Say Hi!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看上去这样已经足够了。不过又有了个新的问题：class 中的 getter 只是一个普通函数，并不能享受到 Vue 的自动收集依赖与记忆功能：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ person.fullName }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ fullName }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@click&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;counter++&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;render count: {{ counter }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;fullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;recalculate&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;person&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;person&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可通过点击按钮触发组件的重新渲染进行测试。由于 &lt;code class=&quot;language-text&quot;&gt;person&lt;/code&gt; 的属性均没有变化，在重新渲染组件时，并不会重新触发 &lt;code class=&quot;language-text&quot;&gt;computed&lt;/code&gt; 打印 log。但如果把打印语句放到 class 的 getter 中，可以看到每次重新渲染都会打印 log。&lt;/p&gt;
&lt;h2&gt;使用 Composition API 改写对象&lt;/h2&gt;
&lt;p&gt;使用 class 语法会造成功能缺失和性能损失，这显然是不可接受的。而使用 computed 又得修改 SFC ，导致数据与 UI 的耦合。有没有两全其美的做法呢？所幸 Vue Composition API 的推出，使得 Vue 的响应式核心可以脱离视图层单独使用，我们可以使用 Composition API 改写 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt; 对象：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;John&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lastName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Smith&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fullName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;firstName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;lastName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;sayHi&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hi, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;fullName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reactive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    firstName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lastName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fullName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    sayHi
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ person.fullName }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@click&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;counter++&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;render count: {{ counter }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们可以将这种写法与使用 class 的写法进行对比：&lt;/p&gt;
&lt;div class=&quot;col-2&quot;&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* 实例变量 */&lt;/span&gt;
  firstName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;John&apos;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/* 构造函数 */&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* getter */&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* 方法 */&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;sayHi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* 构造函数 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* 实例变量 */&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;John&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* getter */&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fullName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* 方法 */&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;sayHi&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reactive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    firstName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fullName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    sayHi
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;可以看到，虽然我们用的是 Composition API，和 class 的形式颇为不同，但两者的结构和能力是大体相同的，可以互相转换。这也容易理解，毕竟闭包和对象本就是一体两面。&lt;/p&gt;
&lt;p&gt;值得注意的是，在返回时我们包裹了一层 &lt;code class=&quot;language-text&quot;&gt;reactive&lt;/code&gt; ，而这在普通的组合式函数中并不常见：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;diff&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-diff line-numbers&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;return { ... }
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;return reactive({ ... })&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通常，组合式函数是为了抽取一段公共的逻辑。当一个组合式函数返回一个对象时，本质上只是为了同时返回多个值。外部只用解构出自己需要的成员使用。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stop &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useResizeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在我们的例子中，技术上当然也可以这么使用，不添加 &lt;code class=&quot;language-text&quot;&gt;reactive&lt;/code&gt; ：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 不使用 reactive&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fullName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sayHi &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fullName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sayHi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 使用 reactive&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;person&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fullName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
person&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sayHi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但 &lt;code class=&quot;language-text&quot;&gt;person&lt;/code&gt; 对象并不只是简单的响应式变量的集合，它是一个真切的拥有业务意义的对象，是“面向对象”这一概念的“对象”。后者的写法显然更为直观易懂。&lt;/p&gt;
&lt;p&gt;还有一个更为现实的原因。一个复杂业务对象的层级往往较多，意味着会经常有组合式函数嵌套的情况，而这是常规使用 Composition API 时非常少见的场景。例如：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useCompany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; boss &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reactive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; boss &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 若不使用 reactive 包裹：&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// return { boss }&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; company &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useCompany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;company&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;boss&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fullName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;token comment&quot;&gt;// 若 useCompany 不使用 reactive 包裹：&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// company.boss.value.fullName&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 若 useCompany 和 usePerson 都不使用 reactive 包裹：&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// company.boss.value.fullName.value&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果不使用 &lt;code class=&quot;language-text&quot;&gt;reactive&lt;/code&gt; ，在对象变得复杂以后，你很快就会困惑于什么时候需要加 &lt;code class=&quot;language-text&quot;&gt;.value&lt;/code&gt; ，而什么时候又不需要。别说你了，TypeScript 都分不清楚。因此我们建议任何时候都加上 &lt;code class=&quot;language-text&quot;&gt;reactive&lt;/code&gt; ，以保持调用方式的一致性。&lt;/p&gt;
&lt;p&gt;虽然同样是使用 Composition API，但一个是抽离重复且功能内聚的代码片段，一个是利用面向对象思维进行业务建模。思考的出发点不同，导致了编写代码时的细节差异。&lt;/p&gt;
&lt;h2&gt;与 class 比较&lt;/h2&gt;
&lt;p&gt;让我们再更多探讨一些使用 Composition API 构建对象，与使用 class 语法的不同点。&lt;/p&gt;
&lt;p&gt;最重要的区别，也是我们最开始这么做的出发点，就是 class 的 getter 无法达到与 computed 同等的能力。&lt;/p&gt;
&lt;p&gt;不过 JavaScript 是一门灵活的语言。硬要说的话，也可以用一些高阶函数等手段，将 class 的字段包装为响应式属性（类似于使用 @vue/reactivity 造了个 mobx 轮子）。但一方面你要自己造轮子，还要教会你的团队成员使用轮子。另一方面，&lt;a href=&quot;https://www.zhihu.com/question/516551830&quot;&gt;也不是所有人都喜欢 class 语法&lt;/a&gt;，而使用 Composition API 对于 Vue 使用者来说更为简单易懂，且无可指摘。&lt;/p&gt;
&lt;p&gt;在不喜欢 class 的原因之中，很多是对 JavaScript 中 &lt;code class=&quot;language-text&quot;&gt;this&lt;/code&gt; 的设计颇有微词。而使用 Composition API 规避了 &lt;code class=&quot;language-text&quot;&gt;this&lt;/code&gt;，也需要你更加清晰地理顺各个对象之间的依赖关系。&lt;/p&gt;
&lt;p&gt;class 的一个好处是可以使用 &lt;code class=&quot;language-text&quot;&gt;instanceof&lt;/code&gt; 判断类型，而这是闭包不具备的。如有需要我们可以自己额外定义个 &lt;code class=&quot;language-text&quot;&gt;type&lt;/code&gt;  属性，使用字符串或 Symbol 去判断。&lt;/p&gt;
&lt;p&gt;另一个 class 独有的关键字是 &lt;code class=&quot;language-text&quot;&gt;extends&lt;/code&gt; 即继承能力。但继承能力并不是必需的，组合优于继承已经成为了大家的共识。而在组合这一点上，借助解构语法，Composition API 甚至能实现得更加简洁。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 利用 class 实现组合&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Child&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parent
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstName
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastName
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fullName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fullName
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;sayHi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sayHi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;This is from child!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 利用 function 实现组合&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;sayHi&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; parentSayHi&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;restParentProperties &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toRefs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;sayHi&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    parentSayHi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;This is from child!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reactive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sayHi&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;restParentProperties &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在使用 class 实现组合时，为了封装被组合对象的属性，还需要写一些模板代码去做转发。而后者的实现中我们只需要关注 &lt;code class=&quot;language-text&quot;&gt;child&lt;/code&gt; 与 &lt;code class=&quot;language-text&quot;&gt;parent&lt;/code&gt; 的不同点，剩下的透传即可。&lt;/p&gt;
&lt;h2&gt;对 watch 的使用&lt;/h2&gt;
&lt;p&gt;在 Composition API 中，&lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 是一个常用的方法。不过当我们改变了对 Composition API 的使用思路后，对 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 的使用也有了新的注意事项。&lt;/p&gt;
&lt;p&gt;在 Vue 官网中提到：“侦听器必须用&lt;strong&gt;同步&lt;/strong&gt;语句创建：如果用异步回调创建一个侦听器，那么它不会绑定到当前组件上，你必须手动停止它，以防内存泄漏。”&lt;/p&gt;
&lt;p&gt;如果我们只是普通地使用 Composition API，那么我们基本上就是这么做的，大多数时候并不用关心 watcher 的停止问题。但如果我们把 Composition API 当对象来用，脱离组件的范畴，那就会出现诸如网络请求回来，异步初始化对象的情况。这就需要我们仔细审视对 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 的使用。&lt;/p&gt;
&lt;p&gt;最笨的情况当然是我们在对象结束使用的时候手动去调用停止 watcher 的方法。相当于人肉垃圾回收。有更优雅些的做法吗？&lt;/p&gt;
&lt;p&gt;其实很多时候我们并不是真的需要使用 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 。假如我们现在需要添加一个联动关系：在 &lt;code class=&quot;language-text&quot;&gt;firstName&lt;/code&gt; 变化时清空 &lt;code class=&quot;language-text&quot;&gt;lastName&lt;/code&gt; 。第一反应是用 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 实现：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;John&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lastName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Smith&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;firstName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lastName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但既然我们已经知道是 &lt;code class=&quot;language-text&quot;&gt;firstName&lt;/code&gt; 的变化引发 &lt;code class=&quot;language-text&quot;&gt;lastName&lt;/code&gt; 的变化，我们可以为 &lt;code class=&quot;language-text&quot;&gt;firstName&lt;/code&gt; 做一层封装，在它的 setter 中去实现联动的逻辑，这样就可以规避对 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 的使用：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _firstName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;John&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _firstName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;newValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    _firstName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; newValue
    lastName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;还有些时候，我们可以确定某段逻辑是由视图层触发的。可以把这段逻辑作为一个方法定义在对象中，而在触发事件的源头 —— 即视图层中，再真正调用它：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;clearLastName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lastName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reactive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clearLastName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;v-model&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;person.firstName&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@change&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;person.clearLastName()&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;还有些时候，我们可以通过拆分组件，实现异步变同步。例如原本的逻辑：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;v-if&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;person&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;...&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;onMounted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;...&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  person&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;defaultValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以拆分成：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- DataProvider.vue --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;v-if&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;defaultValue&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;PersonView&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;:default-value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;defaultValue&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;onMounted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  defaultValue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;...&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- PersonView.vue --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;...&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; props &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defineProps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;defaultValue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; person &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;usePerson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;defaultValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后的最后，让我们重新看待 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 的例子。确实 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 使用不当容易产生内存泄漏，但它&lt;strong&gt;一定&lt;/strong&gt;会内存泄漏吗？&lt;/p&gt;
&lt;p&gt;我们知道内存泄露往往是由于长生命周期的对象引用了短生命周期的对象，导致后者虽然不需要了，但不会被 GC。而在上文的示例中，watch 建立的是 &lt;code class=&quot;language-text&quot;&gt;firstName&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;lastName&lt;/code&gt; 之间的联动，而这两个变量的生命周期是一致的。当 &lt;code class=&quot;language-text&quot;&gt;person&lt;/code&gt; 对象被 GC 时，两者会一起被回收，从而不会导致内存泄露。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 具体是如何建立引用链的，需要查看 Vue 的源码。我们让 Claude 整理了一下。只要在写代码时遵循这些原则，我们并不需要谈“内存泄漏”色变。&lt;/p&gt;
&lt;blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;source&lt;/th&gt;
&lt;th&gt;callback 引用&lt;/th&gt;
&lt;th&gt;自动 GC？&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;局部&lt;/td&gt;
&lt;td&gt;局部&lt;/td&gt;
&lt;td&gt;✅ 会&lt;/td&gt;
&lt;td&gt;整条引用链生命周期一致，source 回收时全部回收&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;局部&lt;/td&gt;
&lt;td&gt;全局&lt;/td&gt;
&lt;td&gt;✅ 会&lt;/td&gt;
&lt;td&gt;callback 引用全局对象不影响；GC 方向是 source → effect → callback，source 回收后 effect 和 callback 就不可达了&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;全局&lt;/td&gt;
&lt;td&gt;局部&lt;/td&gt;
&lt;td&gt;❌ 不会&lt;/td&gt;
&lt;td&gt;全局 source 的 dep set 一直持有 effect 引用，watcher 永远存活&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;全局&lt;/td&gt;
&lt;td&gt;全局&lt;/td&gt;
&lt;td&gt;❌ 不会&lt;/td&gt;
&lt;td&gt;同上，source 永远存活导致 watcher 永远存活&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;关键结论&lt;/strong&gt;：决定因素是 &lt;strong&gt;source 的生命周期&lt;/strong&gt;，不是 callback。因为引用链方向是 &lt;code class=&quot;language-text&quot;&gt;source → dep → effect → callback&lt;/code&gt;，只要 source 活着，整条链就不会被 GC。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;最后的碎碎念&lt;/h2&gt;
&lt;p&gt;得益于 Vue Composition API，组合式函数可以脱离视图使用，为状态管理提供了极大的灵活性。它的背后由 &lt;a href=&quot;https://www.npmjs.com/package/@vue/reactivity&quot;&gt;@vue/reactivity&lt;/a&gt; 库支持。这个库本身是独立的，并不依赖 Vue 生态，可惜的是它目前还依附于 Vue 的代码仓库，没有独立的文档，社区对它的讨论也少之又少。在我看来，它有很大的潜力。期待未来能够与其他的框架、语法（例如装饰器等）结合，擦出更多的火花。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[再见，2023]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/goodbye-2023/</link><guid isPermaLink="false">https://maliut.space/p/goodbye-2023/</guid><pubDate>Sat, 30 Dec 2023 13:25:00 GMT</pubDate><content:encoded>&lt;p&gt;临近年末，大家又纷纷在各个社交平台上写起了年终总结。通常来说，我对写年终总结这件事并不感兴趣。以我的习惯，即使是写些随笔，通常也会确定一个主题。而所谓的跨年、纪念日，无非是人类人为定义的时间点罢了，并没有什么值得特别一提的价值。&lt;/p&gt;
&lt;p&gt;但为什么又会有这篇文章呢？说来好笑，写它的动机无非是已经好久没有在博客发布新的文章了，不想让今年的文章数量挂零罢了。哈哈，我也终究不能免俗。&lt;/p&gt;
&lt;p&gt;不过你以为文章挂零是因为我这一年碌碌无为，所以年底了来这里抒发悔恨吗？错啦。恰恰相反，今年可以说是我最&lt;del&gt;赛博&lt;/del&gt;充实的一年了，以至于写文章成为了低优先级事项。有几篇文章一直没有结尾，还有不少新的文章的构思，但只能南望发布又一年了。&lt;/p&gt;
&lt;p&gt;其实这也和我的博客文章发布策略有关。早期我写文章都是通过 &lt;code class=&quot;language-text&quot;&gt;hexo new&lt;/code&gt;  然后编辑 &lt;code class=&quot;language-text&quot;&gt;md&lt;/code&gt; 文件，所以在网页上看到的时间其实是我开始写一篇文章的时间。而我写的又慢，所以有可能在 2024 年发布一篇 2023 年的文章。但现在改为了在笔记软件中写好，再导出 &lt;code class=&quot;language-text&quot;&gt;md&lt;/code&gt; 并添加元信息。这样文章的发布时间就是我写完它的时间了。不过 whatever，这不重要。明年要把写文章的优先级提高一些，不然还没写出来就过时了 orz&lt;/p&gt;
&lt;p&gt;步入正题。今年的主基调就是一个字“忙”。公司的事情肉眼可见多了起来，摸鱼时间也随之变少，烦躁成了我在公司情绪的主流。最搞笑的是年初还搞什么降本增效裁员，后来实在忙不过来，又把同样岗位的招了回来。也不知道有没有降到本。不过至少能看到业务是往好的方向走的，在这大环境如此寒冷的现在，也算得上是一件好事。&lt;/p&gt;
&lt;p&gt;而在工作繁忙导致业余时间被进一步压缩的情况下，我的 GitHub 提交记录还是创下了历史新高。虽然比不上那些全绿的大佬，但对自己还是比较满意的。&lt;/p&gt;
&lt;p&gt;项目方面抽卡模拟器稳定更新。跑团机器人及其周边项目也在稳定迭代，随着功能复杂度的提升和工作忙碌，开发进度有所放缓，但总体仍然有了长足的进步，且也已经计划了后续一长串的功能更新，还能作为新技术的试验田。除此之外，年内还完成了一个完整的小游戏，以及几个能带来一点收入的项目，以及几个小工具项目，以及给开源项目贡献代码……翻了翻 GitHub 才发现比我之前想的还要更多。&lt;/p&gt;
&lt;p&gt;如果这篇文章真的有人看的话，你或许会疑惑，这些项目都是什么呢？是的，因为太忙的缘故，我都没有机会在博客中介绍这几个项目。后面也会把一些项目放到新的工作室网站中，敬请期待吧。&lt;/p&gt;
&lt;p&gt;工作和个人项目双双忙碌，那么，代价是什么呢？电子阳痿。不是我不想玩《王国之泪》，也不是不想玩《博德之门3》，而是我实在担心一旦打开就再也停不下来，然后一边困得要死一边懊悔自己要做的事进度不如预期。年底一看，玩的最多的还是欧卡 2、马车 8、nintendo sports —— 虽然玩的也不算久就是了。而那些原来吃灰的游戏还是继续吃灰。氪金手游全部戒了。都不好意思说自己是游戏爱好者了。&lt;/p&gt;
&lt;p&gt;有时候会回忆前几年在租房的生活。那段时间偶尔有一些项目的灵感，就会用极高的热情唰唰地做完。当然这些项目也不出圈，且也不全是面向大众的，因此也没有什么长期维护的工作。这样的时期总是一波一波的，更多的时候下班回家也只是休息放松。那时的我一定会羡慕现在的我吧，有能被大家看见的项目，有一丢丢的粉丝。虽然少了休息时间，但总的来说，还是朝我希望成为的样子又前进了一点。&lt;/p&gt;
&lt;p&gt;但无论如何，all work and no play，心情也总是不由自主地变得烦躁易怒。俗话（？）说得好，「间歇性踌躇满志，持续性混吃等死」。我也在渐渐与自己和解。&lt;/p&gt;
&lt;p&gt;以前的我总是会想，如果我再努力一点，在工作之余抓紧点时间做事，而不是总抱怨又烦又累没有时间，这样我才能有所成就。但多次的尝试之后我已经发现了规律：一旦在一段时间内持续地&lt;del&gt;逼迫&lt;/del&gt;驱动自己高强度爆肝，那么一定会以感冒、或是其他不舒服，啥也做不了，休息个好几天告终。论综合效率，和细水长流地做事也差不多。后者还健康。所以如今当我察觉到自己的负面情绪时，我会心安理得地让自己休息下来。是的，确实有那种非全职 GitHub 还全绿的大佬，确实有人一天只睡五六个小时还精力充沛。但我做不到，我也没办法。&lt;/p&gt;
&lt;p&gt;说到不舒服，感觉今年的亚健康时段确实比前两年多了不少。或许有疫情放开的影响，不过这不重要。年末鼓起了莫大的勇气去看牙。正好最近第一阶段告一段落。看牙就像自杀，结果未必不好，但过程一定痛苦。希望一切都会好起来吧。&lt;/p&gt;
&lt;p&gt;又一年即将过去，在父母的嘴里，我又大了一岁吧。在电话里谈论到年龄的话题时，我总是半开玩笑地说自己老了。我没有在开玩笑。&lt;/p&gt;
&lt;p&gt;和同龄人相比，我的工作、结婚、安家置业，似乎都快不少。这并不是因为我想当人上人，相反我个性谨慎，并不总是想当第一个吃螃蟹的，只是机缘巧合，顺其自然罢了。不过客观上这也使我在周围的圈层中向来被视为年轻人看待。我也抱着自己是一个年轻人的心态，似乎未来还有无限的可能。但年复一年地过去，这些事情在我们这个年纪也并不稀奇了。互联网上，同龄人们也不再是主流，不少的年轻人比我们在他们那个年纪时更加耀眼。&lt;/p&gt;
&lt;p&gt;社会时钟正在渐渐赶上我。&lt;/p&gt;
&lt;p&gt;我充分投入我的业余时间，我完成一个又一个项目，我努力追赶着理想中的自己。但现实的引力似乎愈发沉重，我的身上已经有了不小的牵绊。在肉眼可见的未来，我势必要将更多的精力投入到工作和家庭中。而我的目标似乎还很遥远。&lt;/p&gt;
&lt;p&gt;高中做班刊的时候，大家写过一期「写给十年后的自己」，正好是写给现在这个年纪的。其实我早就忘了这回事，也是刷到同学的微博才想起来。当时我写了一串代码回避了这个问题。不过现在想想，这是一个多么有趣的问题啊。那是一个有着无限可能性的岁月，在接下来的十年，会经历高考、大学、工作……正是这种种的变数让这个题目有了很大的发挥余地。而若是想象从现在开始的十年后呢？我似乎已经一眼望到了头。要说有什么悬念，可能只剩下换了几份工作以及什么时候失业吧。&lt;/p&gt;
&lt;p&gt;「老」是可能性的收束。&lt;/p&gt;
&lt;p&gt;当然，这也只能怪我自己。最早我心想，能做一个像《Minecraft》一样的游戏，人生就圆满了。后来我心想，能做一个像《Journey》一样的游戏，人生就圆满了。再后来我又想，能做出像《To the Moon》一样的游戏，人生就圆满了。结果我想着想着，别人的游戏已经做出来了。或许是我没有天赋，或许是我不够努力，但就像我努力过了头就会倒下一样，能持续努力又何尝不是一种天赋呢？用现时时髦的话说，我只能接受自己的平庸。&lt;/p&gt;
&lt;p&gt;扯远了。回到 2023。现实生活一如既往地乏善可陈。公司楼下每天都有一群现充聚集在网红街上。我只嫌他们吵闹。&lt;/p&gt;
&lt;p&gt;纪念日去了迪士尼乐园，很奇妙的体验。但该去的应该都已经去过了，也轮不到我来安利。&lt;/p&gt;
&lt;p&gt;今年的冬天很冷。春天可能会来，也可能不会。但不管春天来不来，生活还得继续下去。&lt;/p&gt;
&lt;p&gt;再见，2023。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[笔记形态进化史]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/evolution-of-the-note-form/</link><guid isPermaLink="false">https://maliut.space/p/evolution-of-the-note-form/</guid><pubDate>Mon, 01 May 2023 12:23:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;这里是知识管理系列的第二篇，来聊一聊笔记软件的形态变迁。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;自大学始，由于专业习惯，我便基本抛弃了手写笔记，改用电脑记笔记。选择趁手的笔记软件，就尤为重要。在此记录下自己使用过的一些笔记软件，从中也可一窥近年来笔记软件的发展脉络。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;纯文本与富文本&lt;/h2&gt;
&lt;p&gt;记笔记最简单朴素的选择就是系统自带的记事本软件，方便快捷，无需任何使用门槛。&lt;/p&gt;
&lt;p&gt;或许大家会觉得记事本很 low，但事实上在上课等需要速记的场景中，我们需要软件快速打开、响应，但对排版没有这么高的要求，完全可以事后再进行整理。此时记事本就成为了你的不二之选。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dfbcdf5955290d4f5e6064a24fc9c7e9/af590/image.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.724137931034484%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABsElEQVR42p2Sy46bMBSGect5qr5Bt912213TaDaVqmpUiQmBcCmXEC4BDCRcDObvscmMUNVVLX06x7/tI5+LFpctLNdHVZYQiwDnHNM0YSLLNyjtH8izeZY+RxRF0KSwrgX/u5ZlfVvSp7T2PsDKO/R8RnyO4LguwjCAYRh4PRg4vOrwPB+24yCOY2RpCpfuOLYDy7Kg6zqKokBKepKk0Mq2g550GCggK6/IL2c4pgHTNGHbNtkjDhS46zqMwwA+jhjI9n2P2+2GOyGzFEKowNow9Jgp/0XQD9sJRsbhVxzjLDBMMyHtAi5ALBiVXSEZnBhJnynl7EoBGWNwPQ+syPHxR42nT2d8eC5URe90u5sebP2t9qZTxDDJoY18VAWVX/670FvEG2LjP/bz461KWXamaVqiUbQEqxuUrEZNurTKr1fatnn3132rNFlP2UxNRpWB5CGTF8jXf6d4MT18/vIV338d8dOK4PohLGrQbrdTHZcz51Gp9vs9ad+UfzqdoOV5DlnHqqoUjFVUiyucMMHRDWAHF7jnDElKXC4IgoDGI0GWZWpUwiCE7/tqL8fpD2CF+HQaChP/AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image&quot;
        title=&quot;&quot;
        src=&quot;/static/dfbcdf5955290d4f5e6064a24fc9c7e9/af590/image.png&quot;
        srcset=&quot;/static/dfbcdf5955290d4f5e6064a24fc9c7e9/2efce/image.png 203w,
/static/dfbcdf5955290d4f5e6064a24fc9c7e9/1d180/image.png 405w,
/static/dfbcdf5955290d4f5e6064a24fc9c7e9/af590/image.png 626w&quot;
        sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;我通常使用 win+r 记忆的 notepad 回车就可以快速打开一个记事本窗口&lt;/p&gt;
&lt;p&gt;但毕竟我们的需求并不仅仅是临时记录，记事本终究难堪大任，因此我们大部分人随即都会把目光放到富文本编辑器上面，其中最强大的也是我们最熟悉的就是 Word 了。也会有不少人选择更加“专业对口”的 OneNote。&lt;/p&gt;
&lt;p&gt;作为大而全的富文本编辑器，Word 能够满足几乎所有的排版需求。但过大的自由度有时候也并不是一件好事。一方面，我们容易纠结于排版的细节踟蹰不定。另一方面，操作的繁琐也使得我们不得不键鼠并用，容易打断笔记编辑时的心流体验。&lt;/p&gt;
&lt;p&gt;如果说上述的问题，只是我们在还没用习惯强大软件之前的“自己的问题”，但“将 Word 作为笔记软件”这种使用方式本身也并非没有缺点。Word 将文件存储为私有格式，非常不利于检索和版本管理。如果你有多个 TXT 文件，你可以将它们放在同一个文件夹中，很方便地进行全文搜索。而如果你的笔记在多个 Word 文件中，就很难进行跨文件的搜索了。这对于构建自己的知识库是一个致命的问题。&lt;/p&gt;
&lt;p&gt;大部分人用到的 Word 功能不到 10%。为了一些绝大多数我们用不到的功能，而需要忍受如此不便，这性价比未免太低。有没有一种折中的方法，既能保留纯文本的便捷，又能覆盖到做笔记的常用需求呢？&lt;/p&gt;
&lt;h2&gt;Markdown&lt;/h2&gt;
&lt;p&gt;没错，Markdown 就是这个问题的答案。&lt;/p&gt;
&lt;p&gt;Markdown 本质上也是和 TXT 一样的纯文本文件。也就是说，你可以同样使用记事本编写。但和纯文本不同的是，你可以使用一些特殊的格式来标识文档的成分。例如：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# 这是一级标题
## 这是二级标题

这是**加粗文字**

这是一个列表：
1. apple
2. banana
3. orange&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后，在支持 Markdown 格式的软件、网站中，你的文档就会像富文本一样被美观地渲染出来。比如将标题放大加粗、列表添加统一的缩进等等。可以理解为它们自动帮你进行了类似 Word 的排版操作。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b5788583dbbdf45d2104396ae426791b/1d499/image_y.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.768472906403936%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABDElEQVR42o2S3Y6CMBSEef/Xc69INEYoiKWILS2lf7MH2GyyCrpNJr06X2dOJ1PKwhgDNWhoM8JO7o9GkvcB/z1ZUSmwW4v6esNDSkzOv0CdtQhFgdA0cHmOUNfrdEqvwIv0yO8BvQ3wBBufYAuQFPsecRgQuw5J632HlVCohISQGtMGbIlMDlPLkThfnKawv4KsK89w7IRJSdiNuLPmhzgXKMsKRpvftM9agKfzBYz2Z13YhC0uyeG14ShZjeEHuOvwcPgCYxV8iDQ4bQNHCykVweYmfADyVkB0983fXd1NCJEeo1rV1+ZzbRx1bNZ+3BVoHhLHI+3bufdASwN7UVfgWuw0u6Toke53wG8PnQ4HQ/oICwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image y&quot;
        title=&quot;&quot;
        src=&quot;/static/b5788583dbbdf45d2104396ae426791b/d7542/image_y.png&quot;
        srcset=&quot;/static/b5788583dbbdf45d2104396ae426791b/2efce/image_y.png 203w,
/static/b5788583dbbdf45d2104396ae426791b/1d180/image_y.png 405w,
/static/b5788583dbbdf45d2104396ae426791b/d7542/image_y.png 810w,
/static/b5788583dbbdf45d2104396ae426791b/d56b5/image_y.png 1215w,
/static/b5788583dbbdf45d2104396ae426791b/0d292/image_y.png 1620w,
/static/b5788583dbbdf45d2104396ae426791b/1d499/image_y.png 1632w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;左侧是 Markdown 原文，右侧是它渲染出的样式，效果视不同的主题而定&lt;/p&gt;
&lt;p&gt;Markdown 最早被广泛使用于程序员写技术文档的场景，近年来渐有出圈之势。许多笔记软件，以及知乎、简书等主流网站，都默认支持这一格式。即使像公众号等原生不支持的平台，也有人自发为其制作了 Markdown 转换工具。&lt;/p&gt;
&lt;p&gt;不妨让我们分析一下 Markdown 的优势何在，它缘何得到了众多用户的青睐呢？&lt;/p&gt;
&lt;p&gt;首先，Markdown 的本质是纯文本，这意味着它拥有上文提到的纯文本的所有优点 —— 简单、轻量易编辑、易于版本管理和搜索、不会被单一软件绑架等等。&lt;/p&gt;
&lt;p&gt;其次，Markdown 的语法格式设计非常讨巧。它使用常见的符号涵盖了一篇文章的常用元素，例如标题、粗体、斜体等，甚至有序列表的符号就是非常自然的 &lt;code class=&quot;language-text&quot;&gt;1.&lt;/code&gt;  &lt;code class=&quot;language-text&quot;&gt;2.&lt;/code&gt;  &lt;code class=&quot;language-text&quot;&gt;3.&lt;/code&gt;  。~~当然你是极端最佳实践派觉得应该全部使用 &lt;code class=&quot;language-text&quot;&gt;1.&lt;/code&gt; 就不在本文的讨论范畴之内了。~~这一方面使得 Markdown 上手非常简单，另一方面也意味着 Markdown 的语法噪音极低。虽然一般我们看的是 Markdown 渲染过后的结果（如上图右侧），但即便光看原文（如上图左侧），我们也很容易理解文章的内容，自动脑补出右侧的样子。&lt;/p&gt;
&lt;p&gt;Word 中我们往往需要使用鼠标手动调整排版、修改样式。而 Markdown 使用符号标识文章元素，记录笔记内容和排版都是通过打字完成，无需脱离键盘，你得到的是即为顺滑的书写体验。&lt;/p&gt;
&lt;p&gt;这里我想特别指出 Word 和 Markdown 中“排版”的不同点。在 Word 中排版时，我们关注的往往是标题或正文的字体、字号、颜色等。但在 Markdown 中，我们在写作时只需标注出哪里是标题，哪里是正文。至于它们最终具体长什么样，则由渲染 Markdown 的软件/网站决定。通常它们也会提供各种主题供你挑选。&lt;/p&gt;
&lt;p&gt;这体现的是 Markdown 的“内容与样式分离”原则。在写作时，我们只需专注内容，无需为样式分心。而在调整样式时，假如我们调整了标题的样式，文章中的所有标题都会自动更改，无需担心遗漏。&lt;/p&gt;
&lt;p&gt;其实 Word 中也能使用内容与样式分离的做法，只是大多数时候我们并不习惯于这么做&lt;/p&gt;
&lt;p&gt;最后我们也不能忽视 Markdown 功能强大的特点。虽然大多数讲解 Markdown 的资料都着重于介绍它的简单易用，但也不要忘了 Markdown 依托的是 HTML，即网页，这一互联网的基础设施。这代表着只要网页能做的事情，Markdown 也都能做。硬要说的话，没有什么是 Word 能做而它不能做的。&lt;/p&gt;
&lt;p&gt;当然，到这个份上，就需要一些超纲的知识了。以 Obsidian 为代表的不少软件通过插件等形式扩展了 Markdown 语法以支持这些复杂的功能。不过这些知识并非本文重点，就不详细展开了。&lt;/p&gt;
&lt;p&gt;Markdown 也并非没有缺点。就我的观察，非计算机专业的用户入门时往往会困惑于如何居中、设置文字颜色以及调整图片的大小。虽然严格来说这并不是缺点，而是 Markdown 技术方案下的设计如此，但对小白用户来说却是实打实令人感到费解的点。&lt;/p&gt;
&lt;h2&gt;Typora&lt;/h2&gt;
&lt;p&gt;提到 Markdown 就不得不提到 Typora 这一款软件。Markdown 的出圈，Typora 功不可没。&lt;/p&gt;
&lt;p&gt;传统的 Markdown 笔记软件通常采用两栏式布局。左侧放置 Markdown 的源文件，供你写作编辑。而右侧展示这个文件最终渲染出来的效果。&lt;/p&gt;
&lt;p&gt;或许它们最早的设计师和用户都是程序员，这布局像极了代码对比工具的样子，但将其作为一个笔记软件的界面着实过于丑陋。左右两侧的内容大部分时候相差不大，浪费空间不说，还吸引你的视线不时在两侧之间切换，反倒容易打断你的写作思路。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c12238b0996c9785d84edebb42deb7b8/a8417/image_t.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.42857142857143%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAACQUlEQVR42i1S13LbMBDk/39WHjIptuMZR82SLBEE0YheWCRRzorODefmAHCxe4urvr1sn19Xr9v6edf+WJFf2/b3jj2985ejfNoLFI/lXjwfu7/E/iVuRdy2Mfv9+c/3n9X6UL/t682J7mp6JJTUdUPqtm1JfRasScHl6Lqu41L1OQwl9Tml4Pu+HCmvOFfJ+mB9diFaF3yMucRUyjCM07TdbtfrzenjtNls1pvNarVGxJju9zsBuGkoI7XT+joOY98PufQ5j8MwLdFSyhjTnQa5UqrrtPc+p3y73ZS2VdvKHmvnojHR2oSsdd/3OB7HUQgBmJRS6+6rvizxeZ+ZNNXh3LSs1QbSg7UO2SGsmy4XgKUQQC6cYNfG6FJKzvkyjZR3VSt0yQmK5/uMTr4S4vPzE7IDLgsBBYRcr9d5nrH/dQpgdab8eDw0qmGRhwSzchlLngruAsZaS2rCWoailB6C5yUAZspUlCklhQ0ajruiba/zGKZLP10GyI4xSoGGtYMdKQ3DcLve/jNLXdFGDCFlH7P1yZSEf8Bhy7UM8BzdwoBHvwqGSfDDEGyO4/AA7z4kaTyhVmrPhePCkqbj3BjoKEVJKbiAX0xqaGScc9Y2lOLpjnUL5rZ3Jjk75Yynnufb0tQjQySstgZ8FhPwcBMHD+tukN1wVZ1r6o1RQmgpUWAGlnGQIfiFGW+rwIOeo/ePHGPw/na9QEv19n76qJuaMt4ZtzSuXcBnQ7YhYU9pp21QxsnOCGWkRoExTgfC/wFpMgqctxxV4wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image t&quot;
        title=&quot;&quot;
        src=&quot;/static/c12238b0996c9785d84edebb42deb7b8/d7542/image_t.png&quot;
        srcset=&quot;/static/c12238b0996c9785d84edebb42deb7b8/2efce/image_t.png 203w,
/static/c12238b0996c9785d84edebb42deb7b8/1d180/image_t.png 405w,
/static/c12238b0996c9785d84edebb42deb7b8/d7542/image_t.png 810w,
/static/c12238b0996c9785d84edebb42deb7b8/a8417/image_t.png 941w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;传统 Markdown 编辑器的布局样式&lt;/p&gt;
&lt;p&gt;而 Typora 打造了一个任何人都可以轻松上手并喜欢上的软件。它最大的卖点就是在当年独一无二的实时渲染功能，在输入 Markdown 符号后就会自动渲染为富文本的样式。这种做法既保留了 Markdown 书写流畅、顺滑的优点，又与 Word 等所见即所得的富文本编辑器在表现上别无二致。它洗脱了 Markdown 的极客标签，即使是完全没听说过 Markdown 的人也有兴趣去尝试使用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/d9fff02ebc53cf852cb42f138efa3fd2/9ea8f-ezgif-com-video-to-gif.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;Typora 所见即所得的编辑体验&lt;/p&gt;
&lt;h2&gt;块笔记&lt;/h2&gt;
&lt;p&gt;Typora 陪伴了我的整个大学生活。2021 年底，Typora 宣布推出正式版并转为付费，顿时引来了一大批用户的哀叹。&lt;/p&gt;
&lt;p&gt;我觉得为一款优秀的软件付费是理所当然，值得鼓励的。我只是感到可惜，可惜 Typora 收费策略的生不逢时。如果它早几年开始收费，我一定会坚定地买买买。但我们不得不又提到 Notion 的横空出世。&lt;/p&gt;
&lt;p&gt;虽说 Notion 的定位和 Typora 并不相同，Typora 只是一个文本编辑器，并不是大而全的笔记软件。但出于对它编辑体验的喜爱，我一直将它作为笔记的主力工具。而 Notion 出现后，其足以承包绝大多数笔记需求，剩余极少数的临时记录或编辑需求也足以使用记事本或代码编辑器的自带 Markdown 支持完成。Typora 在我的笔记体系中终究沦为鸡肋。&lt;/p&gt;
&lt;p&gt;在前一篇文中我们已经介绍了 Notion 的数据库功能。但数据库之于 Notion 也不过是舶来品，Notion 真正革命性的功能是基于“块”的笔记构建形式。&lt;/p&gt;
&lt;p&gt;初次使用 Notion 时，你会发现它兼容了 Markdown 的大多数语法，可以让你和 Typora 一样所见即所得地进行写作。但在底层，Notion 并不把文本存储为 Markdown，而是作为一个个“块”。&lt;/p&gt;
&lt;p&gt;一个标题元素、一个段落、一张图片等等，都是一个独立的“块”，每个块都可以对其单独操作。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5a70a9954f1152b6b87ca03868380966/6ee58/image_b.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.03448275862069%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAwUlEQVR42p2PzWrCUBCF82bZ5l3yltkUKkLsSlpJF0mjRIUYr/f/3s9EERpc6YEzDMNw5puEUTFGFp8ffK1KdrstTVPzmL+qZCo+RNabX7btH33fo5TCOYv37p3AiHGRuhOch4H+NCDEmWHspbzMKJ1zaK1x1hJDePK4fCecQlEX9ifJUej5yX+Bx66j+vmma1uslGghZg7ek1RVRVmWLJZLDoc91pqRQt1IJkJjzGsv53lOlmWkaUpRFLdhCJ53dQXPdc8Ynw6kyAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image b&quot;
        title=&quot;&quot;
        src=&quot;/static/5a70a9954f1152b6b87ca03868380966/d7542/image_b.png&quot;
        srcset=&quot;/static/5a70a9954f1152b6b87ca03868380966/2efce/image_b.png 203w,
/static/5a70a9954f1152b6b87ca03868380966/1d180/image_b.png 405w,
/static/5a70a9954f1152b6b87ca03868380966/d7542/image_b.png 810w,
/static/5a70a9954f1152b6b87ca03868380966/d56b5/image_b.png 1215w,
/static/5a70a9954f1152b6b87ca03868380966/0d292/image_b.png 1620w,
/static/5a70a9954f1152b6b87ca03868380966/6ee58/image_b.png 1640w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;Notion 中的块&lt;/p&gt;
&lt;p&gt;块笔记的形式，相比于 Markdown 又有不少的优点。&lt;/p&gt;
&lt;p&gt;块相对于 Markdown 的样式有更高的可定制性。在 Markdown 中原生支持不佳的文字颜色、居中等特性，在块笔记中无非就是给块增加几个属性罢了。&lt;/p&gt;
&lt;p&gt;将笔记的每个元素抽象为块，也极大增强了笔记内容的可扩展性。&lt;/p&gt;
&lt;p&gt;原生 Markdown 中包含了常用的标题、段落、列表等元素，但对于更为复杂的需求，如嵌入视频、音乐、pdf 等，即使能用超纲的方法实现，但编辑和管理起来也并不友好。&lt;/p&gt;
&lt;p&gt;而块笔记中，文本内容和其他奇奇怪怪的内容，只是不同类型的块罢了。它们都是笔记软件的“一等公民”。如果未来需要支持更多类型的内容，也只需要增加不同类型的块即可，非常方便扩展。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 758px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/21c27ce97950fe3c2aa7b9d3db7468bc/c8e86/image_z.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 94.58128078817734%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAACA0lEQVR42p1U7W7aQBDk/V+kT1BVVaSqf/szKSGlSjABGwz+/rbvbE93LjGRq4KtrrQcB3tzM7tjLyChtUbXdWjb9r+T5xkLfrSthlIaTcNs0Chl9rxIazULkLVcDWBV1ajKHHkSIE0zBIF/uXEq6rqWM6lZq6rCou9Fcl1gH2jcrRU2z2tY2y0OhwN8e4XQfrwJSJCyLMeATVXATTV+OB2Ozh6OgJ3PZ0TH3wgPT5MMhzSAEMRC+tXaO7TfvuLXywZ5lmJusDVFURiwC2BZV6ijFPnOxfF0EsDMFPd9Z3IKUMkQR5JrkRxkGiunxdNqCcc5vAP2sxgO7D4YcpNkSFwfQRjKlANjgTlgHIixmuSFIb+kWY5AQD3PMxOeA0gFBGQPR5KV2OYYKHy/L+Eebex2e1iWhTzPr0onoziOkUm/CcpaAr/3sISfNFi+CtMkQiiyoygyt14LKhik8inhGRrcALbC8NnX+PRTIfZd7G3HMHRd9yog2ZAVGVLqhw8hPiwrqCKFjl0kcgv7ODUY/jeAkOXY2JXQTk/o3EfxoQd7/9bDW4D0HhlS5j8Z6iJBn5zwsrHw8HBvpMzxIvs3Zsh+ULK8bboixtnzYdu2mdwc2xDwryfl7Udr+4rPX+6w3VpYLpdYr9emcCp4MdWMGHJDT/ENw2FwHWzA4lvJmqGO0v8ADeDHpgtx0VMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image z&quot;
        title=&quot;&quot;
        src=&quot;/static/21c27ce97950fe3c2aa7b9d3db7468bc/c8e86/image_z.png&quot;
        srcset=&quot;/static/21c27ce97950fe3c2aa7b9d3db7468bc/2efce/image_z.png 203w,
/static/21c27ce97950fe3c2aa7b9d3db7468bc/1d180/image_z.png 405w,
/static/21c27ce97950fe3c2aa7b9d3db7468bc/c8e86/image_z.png 758w&quot;
        sizes=&quot;(max-width: 758px) 100vw, 758px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;这些乱七八糟的东西也可以作为块插入到笔记中&lt;/p&gt;
&lt;p&gt;更进一步地说，块笔记相比于传统笔记，体现的是它在底层逻辑上的根本不同。&lt;/p&gt;
&lt;p&gt;传统的笔记软件，无论是类似 Word 的富文本编辑器，还是 Markdown 编辑器，它们都以文章作为最小单元。而块笔记以块作为最小单元。&lt;/p&gt;
&lt;p&gt;更小的抽象单元意味着更灵活的组合和使用方式。块不局限于图文，块与块可以自由地排列组合，组合而成的不一定是文章，也可以是看板、目录等其他视图。&lt;/p&gt;
&lt;p&gt;块与块之间还可以互相引用、链接，这不正对应着现实世界中，知识与知识之间也是互相连接着的吗？这也使得块笔记顺理成章地进化到了知识管理软件的形态之上。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/40712ea92928a9df05ae3b42404cf0e0/7e21b/image_2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.99507389162561%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB3klEQVR42q1T2Y7bMAzM/39Z3wpsA3SxcRos1vcVx7YsW5Y0HSpOYrRPBWpAISMNh+RQOnRdh+PxCO89/sd3cM5hnmcsywKlFC6XC0ZarTXO5zP6vg9nURTher1iXVd8nE4oyzL4ghFfMMIVCGWjKIqwaUhmt8OpYNA0wTHzVL78RTDGwBKz0jprA166DITStqyJAbppMLMqQ6BUIFYSrQwy2/+ZlQrpIhjuyxLMk3AcRwzDcCe83aBpRQbVttAMnAkemUgTJ74i4cOfWEjwiX+2XNc1sjy/EyUJLK2MaGUSxyqCz6CHb9iBk4ro6zTFyiLEDxXKjwjfshrJ4t1LD8clVsALz62cb4HhnDjRz2/4QCgkdV09N/bfYy8E/xHod8n2sYe+H3A6RRwAhTUrdTEU/y6yJck/30PvLbo2R1snGPsKw62AolVjD9XU0GyV0wIvKTg9isY76w3yrsJNjVCGQzIzhmWCoVwHu85IPt/QFh+Arah+SdULGQNU/InsxxscB+a5XJYCzRW9G/Et+o5faYz4WuGrKZD2bSAOQ2maFnlR/FW+mjRiTlFe0Cbq3RqLoWqQ8kZkSRru5rNl0SnLMsTxV3gxwGswcrEnkondf/KSapL9fH8PT2+v4W/H4/NXeAT0TgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image 2&quot;
        title=&quot;&quot;
        src=&quot;/static/40712ea92928a9df05ae3b42404cf0e0/d7542/image_2.png&quot;
        srcset=&quot;/static/40712ea92928a9df05ae3b42404cf0e0/2efce/image_2.png 203w,
/static/40712ea92928a9df05ae3b42404cf0e0/1d180/image_2.png 405w,
/static/40712ea92928a9df05ae3b42404cf0e0/d7542/image_2.png 810w,
/static/40712ea92928a9df05ae3b42404cf0e0/d56b5/image_2.png 1215w,
/static/40712ea92928a9df05ae3b42404cf0e0/0d292/image_2.png 1620w,
/static/40712ea92928a9df05ae3b42404cf0e0/7e21b/image_2.png 1626w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;你甚至可以利用块的组合和关联，给自己做一个管理系统&lt;/p&gt;
&lt;p&gt;块笔记相对于 Markdown 等基于页面的笔记逻辑无疑是降维打击。自 Notion 起，许许多多的笔记后起之秀们都使用了块笔记作为它们的底层逻辑。&lt;/p&gt;
&lt;p&gt;当然，块笔记也并非没有不足之处。&lt;/p&gt;
&lt;p&gt;Markdown 及其背后的 HTML 有着统一的标准，这使得它们有良好的互操作性，软件的迁移成本很低，且方便与其他外部工具集成。（虽然有些 Markdown 软件为了增强自己的功能，也对 Markdown 做了不少标准之外的魔改，从而失去了这一优点。）&lt;/p&gt;
&lt;p&gt;而块笔记由于本身的功能、块关联等能力较为复杂，难以使用一个简单的标准进行定义。各家也都有自己的标准和实现方式，并不能简单地互联互通。这也是为什么我们看到的块笔记软件都倾向于做一个 all in one 的大软件，而没有单独轻量的编辑器。&lt;/p&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;还有一些值得一提的笔记形态，例如以 OneNote 和 AFFiNE 为代表的白板笔记。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/70a5898dcf489c8ecc5af42cfcfac2ea/302a4/image_n.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABkElEQVR42lWRXW4UMQzH92ycinsgVHEQKk4AD0DVBx4qxAuraqezM/lw4nw6cfDsdneL5ZEcZ36O/feOefAYrffeNmM5j8G9i1NruZ7OF6OTi3UW590pbtEflfUWnAGnDbBWg+qVOfMPNn7aL3v4WvM3wSVzhod1QpvVOPF5WbU2xoKSD1Gaqq1JhZ9Y72b/tP+g1MdXmDcboJdlmvThwa5/DOTVWmXErDFuKxCTy8VQOxZK+bm1w/nJHUllZsCy+LhEa8Ic8ZByTKWmXHKsiCVTm2o9lmprzcS9jTdwZx8zhKg8EstNuYw53o4d+NTrFvMFFpmJ5wm1BQveeQQfotSqWVEEn+6/69nUSmX1HjAk6afSK7xp3cZ0CCKSXRQAuhADxpeMz+Qa1S8/1GRq0kG9mJizlOmt32ARzDpc9aa2UXsAJeInmbq0x9/K4vaON2FdjJLeMBC1G7zdhShZmwjd3xRBuyoLOlp69/7X3edZfsBcMEaUgVIWlf6DzwrI0kQ8kVDjaQd8TTPzTcNr8A/dTHnjPhpvNgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image n&quot;
        title=&quot;&quot;
        src=&quot;/static/70a5898dcf489c8ecc5af42cfcfac2ea/d7542/image_n.png&quot;
        srcset=&quot;/static/70a5898dcf489c8ecc5af42cfcfac2ea/2efce/image_n.png 203w,
/static/70a5898dcf489c8ecc5af42cfcfac2ea/1d180/image_n.png 405w,
/static/70a5898dcf489c8ecc5af42cfcfac2ea/d7542/image_n.png 810w,
/static/70a5898dcf489c8ecc5af42cfcfac2ea/302a4/image_n.png 1080w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;可以自由涂涂画画的白板&lt;/p&gt;
&lt;p&gt;白板笔记能够将整个页面变成一个大白板，打破了页面布局的条条框框，非常适用于进行批注和创造性工作的场合。但白板这一形态本身并不适合作为笔记的主力形态，往往与其他功能配合出现（例如 OneNote 的富文本 + 白板，AFFiNE 的块 + 白板），只能算是一个锦上添花的 feature，并不能算做笔记形态进化的主干脉络之一。加之本人也并不常用白板笔记，因此就不赘述了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;了解了形形色色的笔记形态之后，在系列的下一篇，我们将讨论如何把笔记关联起来，从而将一篇篇独立的笔记转化为完整的知识体系。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[宇宙的尽头是数据库？]]></title><description><![CDATA[记录一些最近在知识管理上的思考和总结，可能是系列文，欢迎大家多多讨论留言 最近发现自己关注的几个有意思的产品形态都在向数据库靠拢，大有殊途同归之势。 Strapi —— 从 CMS 到数据库 Strapi 自称是一个开源的 headless（在本文中你无需知道它的含义） CMS…]]></description><link>https://maliut.space/p/all-goes-to-database/</link><guid isPermaLink="false">https://maliut.space/p/all-goes-to-database/</guid><pubDate>Tue, 23 Aug 2022 11:23:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;记录一些最近在知识管理上的思考和总结，可能是系列文，欢迎大家多多讨论留言&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近发现自己关注的几个有意思的产品形态都在向数据库靠拢，大有殊途同归之势。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;Strapi —— 从 CMS 到数据库&lt;/h2&gt;
&lt;p&gt;Strapi 自称是一个开源的 headless（在本文中你无需知道它的含义） CMS（Content Management System，内容管理系统）。&lt;/p&gt;
&lt;p&gt;我们都很熟悉各种新闻、官网类的网站。这些网站往往有好几个栏目，每个栏目下面又有好几篇文章。（虽然现在大家都习惯看公众号了，公众号的逻辑也是一样的。&lt;/p&gt;
&lt;p&gt;这些网站的背后都有一个管理后台，用来给运营人员编辑并发布文章。这个管理后台就叫做“内容管理系统”。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a7ecf75e58b81dc03e64a9492b2c9cb0/c83ae/image.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.33497536945813%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsTAAALEwEAmpwYAAABaUlEQVR42o1Qy07CQBTt3g9wYxQSBQPRKPhI0KAkbHxsWBhXfoMLVxhRQBEQQdTEL3Dj3xh/gIUQKJSZ6bSUzrRMvS3GlSScnMXc3HvPOXOl9a1YMLSSOjktPz7dlyulSi1fKOUKRWD+PxbKlYdavVp/qT6/SpHEgW9tO50v2raFMDIMw7ZtZwJGHv5KafEsM3N8fvH2oRHUaLYbLbnR6jR7uNPHlFJN06gHzQMmpKf0GOdgAKWUPEyFVjezt0VVVeVuFxGCMEz0gQghRVEIIdBCGMty15Ohqm6MhLBsW4onjwLhSO6uJIQYy0MfxsEEiBDWdZ0x07Ks4XAIAVSNgqrBLDd2dGd/dt5/eZ2FAmPMOTcZ5OKcwcPkFmeMja8gHEcjpN9uU4Jlor9/fksbuwlfMJy+uhnfw5kMN5qbijDTJNrgq9WXIrH48lJg2uXBgFAKX/i9djS2N7fgT2emWjbBE5wZc4eF+AFsqr16wHSNvgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image&quot;
        title=&quot;&quot;
        src=&quot;/static/a7ecf75e58b81dc03e64a9492b2c9cb0/d7542/image.png&quot;
        srcset=&quot;/static/a7ecf75e58b81dc03e64a9492b2c9cb0/2efce/image.png 203w,
/static/a7ecf75e58b81dc03e64a9492b2c9cb0/1d180/image.png 405w,
/static/a7ecf75e58b81dc03e64a9492b2c9cb0/d7542/image.png 810w,
/static/a7ecf75e58b81dc03e64a9492b2c9cb0/c83ae/image.png 1180w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;最著名 CMS：WordPress 的后台文章编辑器&lt;/p&gt;
&lt;p&gt;传统的 CMS 都把文章编辑作为核心功能。但假如你的网站不是一个新闻类网站，而是一个展示商品的网站，每个商品有自己的图片、类目、价格等信息。这时编辑文章的功能就不适合这个网站了。&lt;/p&gt;
&lt;p&gt;虽然你也可以把每个商品当成一篇文章，把商品的信息作为正文输入。但一方面它的界面很不优雅美观，另一方面也失去了类目、价格等的“元信息”，后续无法实现诸如根据价格筛选等功能。&lt;/p&gt;
&lt;p&gt;而 Strapi 则更加灵活，它允许你自己创建不同的类型。例如，我们可以创建一个“商品”类，在里面增加标题、图片、类目、价格等各个字段。随后你就可以随意地新增、编辑、删除这些商品了。&lt;/p&gt;
&lt;p&gt;相比于主要为新闻、博客网站服务的传统 CMS，Strapi 可以藉此胜任各行各业的需要。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/08b62a22fcf1ad060ac90a8f59064bf6/d9ed5/image_t.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.08374384236453%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAABS0lEQVR42o1Sy27CMBD0//9M1U+pqvZIJTgQEhzi167fdJyUyLSXWiPk2DuzM2vE+1u9XpVSRBTUQl+H4XRqcM7j5C+Yg9bh9SWdjlEw12UxSllcWMvn83S5zPOsvY8hJJRi0wMnRHEYstZReB+miS8DW5uda9cx5pQKmNiEEEsp9bGw1drhMKVmQcSYpDS3m3OuSYKjNQ3DTUptDOdc7t0CmciDDDBHgW6ok3LBB0DEkAQHovhFUc+v9Y7OCFjKHR6F1mUcNTJvZJhETdeqeX7unJkhXRoZV/vAVj5Cpz0kYvdk7I1xbSq5NtsQWwe4PUPLDJ8bE9UYZ865H5i1BPUYYaF1rt0bIrMHGUVbq9w+ftmGu+R92cil/wNAFZGIGr/8rLoDUvNcjsc6jqvtvvMu8QjiVzzd4kWtbVAqCDLTnvmfwCCkDJ8f8RuKwrk6PAaRkgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image t&quot;
        title=&quot;&quot;
        src=&quot;/static/08b62a22fcf1ad060ac90a8f59064bf6/d7542/image_t.png&quot;
        srcset=&quot;/static/08b62a22fcf1ad060ac90a8f59064bf6/2efce/image_t.png 203w,
/static/08b62a22fcf1ad060ac90a8f59064bf6/1d180/image_t.png 405w,
/static/08b62a22fcf1ad060ac90a8f59064bf6/d7542/image_t.png 810w,
/static/08b62a22fcf1ad060ac90a8f59064bf6/d56b5/image_t.png 1215w,
/static/08b62a22fcf1ad060ac90a8f59064bf6/0d292/image_t.png 1620w,
/static/08b62a22fcf1ad060ac90a8f59064bf6/d9ed5/image_t.png 2880w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;Strapi 允许我们创建任何类型的数据结构，而不仅仅是编辑文章&lt;/p&gt;
&lt;p&gt;相信大家也都能看得出来，Strapi 之所以能做到这么灵活，是因为它向我们开放了自定义数据库表结构的能力。事实上它的实现也是简单的一个类型对应一张表，增加或修改类型还需要重启服务器。&lt;/p&gt;
&lt;h2&gt;Airtable  —— 从 Excel 到数据库&lt;/h2&gt;
&lt;p&gt;Airtable 是近几年风头正劲的低代码平台代表产品。用一句话来概括，就是高级版的 Excel。&lt;/p&gt;
&lt;p&gt;Excel 是很多人在工作中都会用到的软件，我们可以利用它存储和分析数据。对于复杂的功能，也可以利用公式和宏来实现。&lt;/p&gt;
&lt;p&gt;不过 Excel 毕竟只是个电子表格，还是有很多功能力不从心。Excel 的单元格本质只是文本，你无法在里面管理图片、视频等多媒体数据；Excel 使用简单的序号来定位行和列，这使得你在 Excel 中建立的公式和 Sheet 之间的联系关系是十分脆弱的，一旦修改了 Excel 的结构，这些精心设计的公式就会崩坏掉。&lt;/p&gt;
&lt;p&gt;在网上也有人一针见血地指出，这样的需求其实已经超过了电子表格的范畴，应该用数据库实现。Office 中确实也已有了 Access 数据库。但传统的数据库主要还是藏在软件的后端，对单独使用并不友好，更不用说给零基础的普通用户使用了。&lt;/p&gt;
&lt;p&gt;这时就轮到 Airtable 登场了，它把数据库披上了一层 Excel 的外衣，让普通用户也能像操作 Excel 一样简单地操作数据库。用户同样可以通过直接双击单元格录入内容，但表格的每一列不再只是一个 A、B、C 这样的序号，而是有了一个确定的名称和类型。而类型也不再只是单纯的文字，而可以是图片、标签、多选以及与其他表的关联等等。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b20639045a185513c2f290d7a6345576/e7dd1/image_3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.21674876847291%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAACCElEQVR42h2QuW4UQRCG55VIyREJEocQAYQ8AQEpERGEJJCAOGRZXoxXYi2QQUgsWMJGa6/t9e5Yy97jObun7+7pnqNpu6Kq6vrq/7u8Jz/AZj999CW+s766uxHf+BDfWk9ubiS3W+mDNrjfBvc+gYed/PHXtHWMOr7+7BcfR6I9YtunwEOMJpQ0RmiOGUxljhWlJc0rho1gjZF1wUuBIqCZIT/5zklxaC+jriovYMXuOYrTTBWFKat/8yFNlj1iv0clyDBAVEiZwSwEzTb6dv342uG0axvLpZKq8IzRKE1cmLK0tjk42Ismox1g1wKbRrlQWisFYZakzev43dX9K/PB36oqpRLGGE8bE6YJ46y5NDNdzIZHva0Ff7+ySQgzxDAiDk6h3UKdN/vPdn+trZYDN9k0jacI41EmGNfa7TE5hIKQoLK+qjnlELlnziidA1E3tpDqouTckY731DITu2ciJ9rosiwXs+ngbIgPx3Z/gjDnQjrbjNFZRNxNHFMaY+vaXcslnnTSLCiUU3a/Nsv5zB/76fHMnMQ5ZowLJaXgfAVVGMXDo36sTGxt73f35fOnXkHOyaJLYFg4Ya2F4KqoDPV1vANAjqlrXPgOsM4A7Pd72TysAzI8GWy0Nj1CGSKCMiaVcoMI5a6jSKzyKcGUOmlxwS+ghDmO4nA8WRJc7/3pvn314j9g5BjVx/VZfQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image 3&quot;
        title=&quot;&quot;
        src=&quot;/static/b20639045a185513c2f290d7a6345576/d7542/image_3.png&quot;
        srcset=&quot;/static/b20639045a185513c2f290d7a6345576/2efce/image_3.png 203w,
/static/b20639045a185513c2f290d7a6345576/1d180/image_3.png 405w,
/static/b20639045a185513c2f290d7a6345576/d7542/image_3.png 810w,
/static/b20639045a185513c2f290d7a6345576/d56b5/image_3.png 1215w,
/static/b20639045a185513c2f290d7a6345576/0d292/image_3.png 1620w,
/static/b20639045a185513c2f290d7a6345576/e7dd1/image_3.png 3354w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;Airtable 的聚类和统计功能&lt;/p&gt;
&lt;p&gt;得到了这些类型信息之后，我们可以做到更多事情。和 Excel 类似的筛选、数据聚类、统计图等功能自不必说，我们还可以把数据表本身以各种不同形式进行展示。例如，你可以把你的照片管理起来，提取其中的图片字段，得到一个画廊；可以输入你的日程安排，得到一本日历；根据你的任务未开始、进行中、已完成等状态，得到一个项目看板。甚至，根据数据表的字段，生成一个表单发给别人填写，代替问卷星的职责。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/22f92496f4286878444624ad274d3e8e/5f6dd/image_h.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.69458128078818%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABnElEQVR42l1Qy07cMBS1QPxBN/wei1JVAgS7tmhaCQl2LBD/wAJpUIv4jUqwQIImms6wgDxsx3HiV+ykJ8kAox5dW9fX95xzbfLhdLZ5Nts4iTaO4/WjmBxG5Osd+RGRCeIP+YZj/B5f4rVJtPY9Xp88kI9XJHqmv/+yrYv77enD9vTx08Xj/mW09zP6fBXvXs93fv0fKI71g5sZcUZZZRqlnKms1VaIIFXrXfC2C6/RrkQwrxVHXopSytIH33WdlJJyVsiyVjqEthBlmuWIJM3GYCxlnFPGs5y+JClZ0AIwxoJc13VZQksiCSFgpwNQqaq6wqprrbVSCgmOxDqHvm6Ac45zLgcg7wcZ0DQNhgzet23brYAIqbzvZ8YFmkYytKGYZRkSYwwa7BJmbF6SyyRXWiNDN9yEEJh8HBtktCPBqIunRZImAKRXnUvr7Jsz3o/HgA+hPM9hi2IIvvFNj2GHnB9AaMFFkfvGjW+uBsAQd3AGGUUQXI9x749LZ6l1JYvGGXwFeuDJKBX4cMb47a2x1q1g1JrP5+cD/gFOrEo88wPRSgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image h&quot;
        title=&quot;&quot;
        src=&quot;/static/22f92496f4286878444624ad274d3e8e/d7542/image_h.png&quot;
        srcset=&quot;/static/22f92496f4286878444624ad274d3e8e/2efce/image_h.png 203w,
/static/22f92496f4286878444624ad274d3e8e/1d180/image_h.png 405w,
/static/22f92496f4286878444624ad274d3e8e/d7542/image_h.png 810w,
/static/22f92496f4286878444624ad274d3e8e/5f6dd/image_h.png 1139w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;使用 Airtable 的表结构创建数据收集表&lt;/p&gt;
&lt;p&gt;当然这并不是 Airtable 的全部，基于数据库，他们正在探索更多工作流相关的可能性，不过这和本文就没有太大关系了。也许未来某一天这些功能会变得家喻户晓。&lt;/p&gt;
&lt;h2&gt;NocoDB —— 从 CRUD 到数据库&lt;/h2&gt;
&lt;p&gt;NocoDB 在它的 Github 介绍中，第一句话写着 “ Open Source Airtable Alternative ”，意即 Airtable 的开源替代品。由于 Airtable 完全运行在云端，会有不少人考虑到数据安全、隐私、免费、定制化等因素，选择一个开源且可以纯本地运行的替代品使用。&lt;/p&gt;
&lt;p&gt;NocoDB 也贴心地提供了预编译的二进制包，供普通用户下载后直接使用，不用了解技术细节。它的功能也和 Airtable（的核心数据库功能）大同小异，此处就不赘述了。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e3679a222c13ae24176bb38385d79dbb/4c041/image_m.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.679802955665025%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACCElEQVR42i2Ry2oUQRSG+1VceVtINq7EF1Fw6SP4GBFxayBeQHHhJgRcCQHBmIzoIIxmcCZz656urq6u+/3imei/qFV953x/VfV8FJ6O8v55eXCcrh3mR8f+yadw+7DsvSz335V7b8veq3LjIN98ka8flFuH5eFRevwx3X2T77wuVfS65FhK8SFQpY0zkuHZYrnGYtGJJVbrXq0xx1zjAV/27ZT0swYb7qh0FafCGhdijiH6EFksP8fjyY+zlvD5qlk2qKOSCCO1d5LadorbU0LqYkN2roKBjsviQjBWactsbjeXzihC+mazQe2WMaqkFLCCCd7MFs3ZFq+ttSnnSkjpnAPtFCMY8FDQ/MJIgXuyWq0QQhhjzrimOvgo0Zigb5Qz71zOqRJcgDDAOSXrowDt2S8m2ECGrus454wxpSRnMvjkulZtJ0KwcpUKBGLcwWkHB53L+fgzgHSgG9BGqO97KSUBQWV1s6DteYPnX6dHk8sPVc7535grOIL2bDZndBeA4eRcwALusu47vr5Y4tG0Hr0/2T85fVZprf93zinEBA6busa7bQraEkJAG2ATk+PDUF+s2i9StjGUAA9mrgJw8F4Z20sHHw5BHa7rBrWIkAGaa0TMQOrt5Hf/nelBa5VSrOCe9x5qw36plJAaTqvVn+3QIEwHgnfpJRdKiKBMcMYHDy4557+3yVs9Mznj9gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image m&quot;
        title=&quot;&quot;
        src=&quot;/static/e3679a222c13ae24176bb38385d79dbb/d7542/image_m.png&quot;
        srcset=&quot;/static/e3679a222c13ae24176bb38385d79dbb/2efce/image_m.png 203w,
/static/e3679a222c13ae24176bb38385d79dbb/1d180/image_m.png 405w,
/static/e3679a222c13ae24176bb38385d79dbb/d7542/image_m.png 810w,
/static/e3679a222c13ae24176bb38385d79dbb/d56b5/image_m.png 1215w,
/static/e3679a222c13ae24176bb38385d79dbb/0d292/image_m.png 1620w,
/static/e3679a222c13ae24176bb38385d79dbb/4c041/image_m.png 3328w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;NocoDB 的界面，与 Airtable 大同小异&lt;/p&gt;
&lt;p&gt;不过如果仅仅是这样，就没必要单独介绍它了。NocoDB 自我介绍的第二句话才真正图穷匕见：“ turns any MySQL, Postgres, SQLite into a Spreadsheet with REST APIs ”。给我任何一个数据库，我可以把它变成一套 REST API，省去了程序员每次都要开发增删改查接口的重复劳动。&lt;/p&gt;
&lt;p&gt;事实上，如果你留心一下 NocoDB 的历史，就会发现它最早叫做 Xmysql, which means &quot;One command to generate REST APIs for any MySql database&quot;。也就是说，它最早就是一个单纯为了程序员服务的工具，只是发现了 Airtable 和它们产品形态的相似之处，才更改了名字和描述，以扩展用户群体。&lt;/p&gt;
&lt;p&gt;在如今的 NocoDB 中你依然可以找到很多属于程序员的极客部分。例如对 API 的重视：虽然 Airtable 也有完善的 API 功能，不过只是针对少部分用户，并不是重点。而 NocoDB 虽然在其他功能上和 Airtable 还有不小差距，但在 API 上，不仅有 REST API，他们甚至做了 JSSDK，甚至提供了Swagger 生成文档和 Mock 数据。&lt;/p&gt;
&lt;p&gt;又比如 NocoDB 允许你在新建列时操控数据库的细节：&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a2ba1e57fa7a18d88504b3a823403ced/e3729/image_s.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.11330049261084%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAACE0lEQVR42o2STWsTURSGZ1OLirhUcKMboX8iVsSN6KYLwZWIRF25EFRwWSQtgisLbvwZ/ZKKWUh1Yas00pCCLWljm49pMsnM/Z479/XMhNCqsXrh4Zy58573njtnvLFbRVy8WcTZ60sYyc3jGDGam8Px8VmcvDxH+SzOXFvEhYl3OHdjCaeuLOLE+AJOX32L8xPvab+Y5SO5BYxemof3dGYdk28qePa6jPzUGu5Nf0O+sIKHLz7i0ctPeFBYxofVGrpBgM/lNp68KuHxTBn3p7/ibmENtydXcOf5lyzmp0rwGAsRsQiHl40NIaEVz7CxhuAMsZEIewE41SjJkFgFFgaUR5AiRJLE8MSeD77TgIsEXChguwyacm0MDMG5AIt6sNbQUQmci6lwkNssxtSAlBKC8EgJxIQ9wMYxCR0VAiHT2K0L1PY06k2L5j76+I6iQ8MHOoGlw3Vm6mHIsmTqHBkSTGhs/zBY32CofBfY2sEvVDYNanVNXf7D0GhNp1p0upKe+x27Q9cc4BJN7zW0PsIwpitzxqGUIqGioQlEXEEqQ3uGvq+l/RiK0NrSt1OZ2ZGGQohMkA6l2QpRrbbgdzVagYJPDGKzTbHDScf+bph2dmDIwRjLJp2kUxqyUk1KWjfUMP1dBoaChO0gwn6gwWU6pOQ3HHph3/C/riwFx24jQGmjha3tzh9sVjto+V3S9fU/AbxHbhQzV1ZiAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image s&quot;
        title=&quot;&quot;
        src=&quot;/static/a2ba1e57fa7a18d88504b3a823403ced/d7542/image_s.png&quot;
        srcset=&quot;/static/a2ba1e57fa7a18d88504b3a823403ced/2efce/image_s.png 203w,
/static/a2ba1e57fa7a18d88504b3a823403ced/1d180/image_s.png 405w,
/static/a2ba1e57fa7a18d88504b3a823403ced/d7542/image_s.png 810w,
/static/a2ba1e57fa7a18d88504b3a823403ced/d56b5/image_s.png 1215w,
/static/a2ba1e57fa7a18d88504b3a823403ced/0d292/image_s.png 1620w,
/static/a2ba1e57fa7a18d88504b3a823403ced/e3729/image_s.png 1794w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;谁能说出这几个缩写代表的具体含义，欢迎在评论区留言←_←&lt;/p&gt;
&lt;p&gt;不过最绝的还是 NocoDB 允许你在新建项目时连接一个现有的数据库。无论是 Airtable 及其同类产品，还是 Strapi 这些同样的开源产品，都把数据库放在内部维护。而传统的 RoR、Django 等全栈框架又得把数据库和具体的后端技术栈绑定。但 NocoDB 更加灵活，不会侵入你现有的架构之中。&lt;/p&gt;
&lt;p&gt;这就使得 NocoDB 非常适合初创公司和个人项目。接入它，你就近乎零成本地获得了一套现成的 API 和管理后台。项目做大之后想要切换技术栈或去掉它，也不会有太大的代价。&lt;/p&gt;
&lt;h2&gt;Notion —— 从文档到数据库&lt;/h2&gt;
&lt;p&gt;我应该不用介绍 Notion 了，因为它在这几年实在是太出圈了。&lt;/p&gt;
&lt;p&gt;Notion 最早是一个基于块的笔记软件，后续加入了数据库功能（一说是借鉴了 Airtable），从单纯的笔记升级为了知识库，也使它破圈了一波。以至于很多人甚至把数据库当做了 Notion 的核心功能。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/46b27ee29b1c7d49347d25ce33ea61e1/c7ddf/image_1m.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.78325123152709%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA50lEQVR42s2SyW6EMBBE/f//lc/IJVISwMAYb+CNmu4GpGhuM8khJVle1JRfNVbOWeScUGsFy3mPaRrxqtSy3LCuEa1V7PuOvu+htUZKCdu20rzRvMk+5yw1Mujj1hpqyQi54K2LCKVBDUNHdEUIuXAcR7pgPYprgzEG8zzBE/myGKEPIcLMnwhuEqq+n/H+wecJqvv+EkM2kMjWCg1OAjZnM06xydrJmSGzuHqpmwy16WbRCEDx7RyrEPqjLuqneqj1gBiD/JhntOPo47GmcW6U9/aMXPEXUlfc69n82pDNfr7Df2d4B+q/FIVW44OFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files image 1m&quot;
        title=&quot;&quot;
        src=&quot;/static/46b27ee29b1c7d49347d25ce33ea61e1/d7542/image_1m.png&quot;
        srcset=&quot;/static/46b27ee29b1c7d49347d25ce33ea61e1/2efce/image_1m.png 203w,
/static/46b27ee29b1c7d49347d25ce33ea61e1/1d180/image_1m.png 405w,
/static/46b27ee29b1c7d49347d25ce33ea61e1/d7542/image_1m.png 810w,
/static/46b27ee29b1c7d49347d25ce33ea61e1/d56b5/image_1m.png 1215w,
/static/46b27ee29b1c7d49347d25ce33ea61e1/0d292/image_1m.png 1620w,
/static/46b27ee29b1c7d49347d25ce33ea61e1/c7ddf/image_1m.png 2810w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;Notion 的数据库功能&lt;/p&gt;
&lt;p&gt;Notion 的数据库功能和前面的同类产品大同小异，就不具体介绍了。而 Notion 与其他产品的不同之处在于，它是先有文档，再有数据库的，因此需要考虑如何把数据库和它的文档功能有机地结合在一起。&lt;/p&gt;
&lt;p&gt;不像别的数据库应用中，每条记录都只是一条单纯的记录。在 Notion 中数据库的一条记录也是一个页面。你可以把记录展开，然后像普通的页面一样编辑它，插入各种类型的块，与其他页面进行链接。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1f7260acee45d875c7ec375cf58eb519/b0bb3/8931944a-0e10-4c9a-b51e-9f6044d5823f.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.66502463054187%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABK0lEQVR42oWR227CMAyGef/n2YtMjOMmQUvbFAY5Nk3txO1M2QVMhTlOZCX+nOT3bBiGAFgJYa0Z7qzruqI4VGXR9/3wxGY8rXNVVTpnKUUH8W1nNVBCqOvaSgkRhDpFomm4DaHxnusnIkyUX4y2jlICAD4FjLuyPmuTUpqApbEHUUPq2RnWxnxfJCI2TRPagIBKSqWk934C7ke7BczcYiKKMfLldPfgP/+f/e4+luQkJpFHjP8INmldCNUhr2shykKw7Nm+2u9EnpVZVmWZPB5fwd65zfv7dr3cLubrj/l2NQbLxddm/blalnn2Co4I5nK2Whkp9eloFQcXzbJpRSzEqPw0fBWQ24ZdwsAti7GLGK7r6AgtdJ4oPYVTitZpZ1XrLac+OsMtUfoBxzd/JOn76UgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;files 8931944a 0e10 4c9a b51e 9f6044d5823f&quot;
        title=&quot;&quot;
        src=&quot;/static/1f7260acee45d875c7ec375cf58eb519/d7542/8931944a-0e10-4c9a-b51e-9f6044d5823f.png&quot;
        srcset=&quot;/static/1f7260acee45d875c7ec375cf58eb519/2efce/8931944a-0e10-4c9a-b51e-9f6044d5823f.png 203w,
/static/1f7260acee45d875c7ec375cf58eb519/1d180/8931944a-0e10-4c9a-b51e-9f6044d5823f.png 405w,
/static/1f7260acee45d875c7ec375cf58eb519/d7542/8931944a-0e10-4c9a-b51e-9f6044d5823f.png 810w,
/static/1f7260acee45d875c7ec375cf58eb519/d56b5/8931944a-0e10-4c9a-b51e-9f6044d5823f.png 1215w,
/static/1f7260acee45d875c7ec375cf58eb519/0d292/8931944a-0e10-4c9a-b51e-9f6044d5823f.png 1620w,
/static/1f7260acee45d875c7ec375cf58eb519/b0bb3/8931944a-0e10-4c9a-b51e-9f6044d5823f.png 2344w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;一个数据库条目也是一个页面&lt;/p&gt;
&lt;p&gt;自 Notion 起，数据库功能几乎成为了所有新一代笔记软件的 must have。为什么会出现这种趋势，这是一个值得思考的问题。传统形式的文档，承载的是我们线性的思考流程。但很多时候我们还需要管理&lt;strong&gt;结构化的数据&lt;/strong&gt;，例如通讯录、日程安排、书单等。数据库才更符合这些场景。有点像是 Strapi 想要解决的问题了。&lt;/p&gt;
&lt;p&gt;即使是编辑文档，我们有时仍然希望给文档加上一些额外的元信息。传统文档往往只有一个分类（笔记软件的树形层级可视为多级分类）和多个地位平等的简单标签，无法很好地描述这些额外信息。而解决方法只能是像 Notion 这样把文档也作为数据库的记录来实现。&lt;/p&gt;
&lt;h2&gt;为什么是数据库？&lt;/h2&gt;
&lt;p&gt;其实数据库一直都在我们身边，只是从软件开发的幕后走到了普通用户的台前。&lt;/p&gt;
&lt;p&gt;各种软件“数据库化”的这一趋势也能体现出软件开发领域的一些思路变化。&lt;/p&gt;
&lt;p&gt;在传统的开发流程中，我们需要先确定好一个软件有哪些功能，考虑这些功能如何实现，而去设计一个特定的数据库表结构来满足功能。但用户的需求众口难调，现有的结构和功能往往满足不了所有的需求，开发人员也不得不不断迭代。用户也时常纠结于几个功能大差不差的同类产品之中。&lt;/p&gt;
&lt;p&gt;但在一个数据库应用中，我们不再以功能为中心，而是以数据为中心。我们把数据结构的主导权交给了用户，而是去思考从数据出发能做些什么。&lt;strong&gt;我们不再单纯服务用户，而是赋能用户。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;后者对前者可以说是降维打击。上面这些数据库的应用，可以直接取代掉简单的日程管理、项目管理、问卷等等软件。当然，这也对用户的能力提出了更高的要求。对工具类的开发者来说，也不一定再需要从头构建一个项目，而可以在这类平台的 API 基础上开发更加定制化的功能。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;无论是一项技术，还是一门编程语言，它们并非凭空产生，而是反映了它们看待世界的方式。&lt;/p&gt;
&lt;p&gt;我们使用数据库，也是对这个现实世界建模。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[2022年，我们还需要全局状态管理吗？]]></title><description><![CDATA[这是一篇从去年开始就在构思的文章论鸽子如何把标题的 2021 改成 202…]]></description><link>https://maliut.space/p/global-state-management/</link><guid isPermaLink="false">https://maliut.space/p/global-state-management/</guid><pubDate>Tue, 03 May 2022 15:30:00 GMT</pubDate><content:encoded>&lt;p&gt;这是一篇从去年开始就在构思的文章&lt;del&gt;论鸽子如何把标题的 2021 改成 2022&lt;/del&gt;。因为总觉得难以体系化地把我的想法表达出来，但又不吐不快。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;要说现代前端有哪些最混沌的领域，那状态管理必然占据了一席之地。似乎每个人都能插上两脚，知乎大佬们人均一个轮子。我今天也难以免俗地趟一趟浑水，不过我不会谈论那些具体的状态管理轮子，而是聚焦于【全局（集中式）状态 vs 本地（组件级）状态 】这一个话题上。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;在我大学第一次接触 React 时，正是 Redux 如日中天的时候，那时使用 React 基本上等同于使用 Redux 全家桶。紧接着社区里出现了反思的声音，认为“&lt;a href=&quot;https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367&quot;&gt;你或许不需要 Redux&lt;/a&gt;”，不要无脑地把状态都交给 Redux 来管理。&lt;/p&gt;
&lt;p&gt;随后，Hooks 的出现大大简化了状态和组件逻辑的复用。鉴于此，大家认为使用 &lt;code class=&quot;language-text&quot;&gt;useState&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;useReducer&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;context&lt;/code&gt; 足以完成状态管理的职责，或仅对它们做一个简单封装后使用。&lt;/p&gt;
&lt;p&gt;从使用哲学上，也认为状态应尽量贴近于使用它的组件，以实现组件功能上的自治。支持组件级别的 local state 成为了状态管理库的标配功能，而 Redux 已然成为明日黄花，人人路过都能批评两句。&lt;/p&gt;
&lt;p&gt;即使 &lt;a href=&quot;https://react.docschina.org/docs/thinking-in-react.html&quot;&gt;React 的官方文档&lt;/a&gt;中也推荐将状态按上述的原则，自下而上定义。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于应用中的每一个 state：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;找到根据这个 state 进行渲染的所有组件。&lt;/li&gt;
&lt;li&gt;找到他们的共同所有者（common owner）组件（在组件层级上高于所有需要该 state 的组件）。&lt;/li&gt;
&lt;li&gt;该共同所有者组件或者比它层级更高的组件应该拥有该 state。&lt;/li&gt;
&lt;li&gt;如果你找不到一个合适的位置来存放该 state，就可以直接创建一个新的组件来存放该 state，并将这一新组件置于高于共同所有者组件层级的位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;~~那么接下来是我的暴论：~~似乎状态的原子化、组件化管理已经成为了一种趋势。但很遗憾这并不是一种好的做法。我们对全局状态的抵触有些矫枉过正了，恰恰相反，&lt;strong&gt;我们应当更多考虑把大多数的应用状态纳入全局状态管理之中&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;状态与 UI 的分层优于状态的组件化&lt;/h2&gt;
&lt;p&gt;我在&lt;a href=&quot;/p/out-of-date-css/&quot;&gt;之前的一篇文章&lt;/a&gt;中反对了将 HTML、CSS 与（操作 UI 的）JavaScript 以传统的三分离形式看待，而应将它们纵切，通过组件化方式使用。但状态是否也应该被组件化呢？正相反，状态与 UI 之间应严格地保持分层。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9824ffd239b76bc6b1d6d13cec417d1e/8fdfc/layer.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.128078817733986%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACXUlEQVR42m2T3UtTYRzHT3XRVTfVVWig0JsiRgS+ZG9iJToLmjbTJAkyJAgq1FKi7hJ1my+bold10x+QXiSZRbkiwczKaTLdm69bqWdrujnYp+dsWk7l4cP3+f1+n+c551wcqc3di8Hdg9H9ZhNKX0Hv6kbnekWjyK08hWbXa6ZWFpAODFeSaH5IwgYSzdXkWrRkWxrIGdeinmhBNa7b5CkkmWuI/X6XXo8ZKdeiQ2XRb0mJtZ1rtnbKnc+5ZX8m6o4tvYuWRrLG6hjwWZEC+FkWK7COldX0hXzhHLSP8ss/L8ylqPla+sPpJySW9HksxMeRECZziA/DIfpW8/1wpK/UHS+tvB0KYBohPFvvKflpNIJ3CaS0RzInH8tc1nopbPKSr/OiafRyVexznnrC/WLDMpfqI7NCZdbsDfeLRBbovZx6InOsapG+0SBSSvUCaTUL5NXKFDd7UDfI3Gj1UNQko6pd5LrBQ1nHH7GXxQUyN9s9aPSyeLhMqdFDvlYmXZxPrpjH9HMF6cckDDngqx2+2CIMWAW2SH/ICZ39v+m3BMP1mrPmDYpz35wRzxcQnzznnmbGNcmsayrM3CrTsw6c0w6RTrp6e5iwW5iZm2R6xhHlza4jGBRvuOv4BfakqdidmhtF3DkN8ec1xGVdIeaMmpizavZn5hMr2OjuTc9jZ3ImXe9MSFLsUbYfOYF0KC2Kg9kaTpfcJqO4nIScIlI1ZWSW3iE+q0D8DSlsO/zf3ZGQgbQviRed3UhV2jYqGlqp3MD9OsM/KuqNomfkgXCVeqOr3HGvtoUxm4O/77a+hBlKhw4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;分层架构&quot;
        title=&quot;&quot;
        src=&quot;/static/9824ffd239b76bc6b1d6d13cec417d1e/d7542/layer.png&quot;
        srcset=&quot;/static/9824ffd239b76bc6b1d6d13cec417d1e/2efce/layer.png 203w,
/static/9824ffd239b76bc6b1d6d13cec417d1e/1d180/layer.png 405w,
/static/9824ffd239b76bc6b1d6d13cec417d1e/d7542/layer.png 810w,
/static/9824ffd239b76bc6b1d6d13cec417d1e/d56b5/layer.png 1215w,
/static/9824ffd239b76bc6b1d6d13cec417d1e/0d292/layer.png 1620w,
/static/9824ffd239b76bc6b1d6d13cec417d1e/8fdfc/layer.png 1725w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;分层架构的一个经典原则是单向依赖，而前端应用中的 UI 和状态正符合单向依赖的关系。UI 负责展示状态，提供修改状态的用户界面。&lt;/p&gt;
&lt;p&gt;同一坨状态可能展示为不同的 UI，如 PC、移动端，甚至用户切换主题或分辨率时也会切换不同的 UI 展示。若将状态组件化，则意味着状态会与具体的 UI 框架挂钩，破坏了单向依赖原则，也会为 UI 在多平台、多分辨率下的适配带来不便。&lt;/p&gt;
&lt;h3&gt;组件化的状态难以应对频繁的需求变更&lt;/h3&gt;
&lt;p&gt;UI 是易变的，尤其是每个需求的最初版本 UI 往往并不是最理想的，会根据用户反馈不断修改。而很多时候在产品经理和设计师看来一个很小的改动，其实会对组件和状态的层次结构产生很大的变化。&lt;/p&gt;
&lt;p&gt;如果我们根据初版的 UI 来划分组件，并把状态分散在组件中，那么在 UI 修改时，状态的组织形式、放置位置也不得不跟着重构。于是，要么我们花费了很多额外精力处理状态的重构和测试，要么在 DDL 的催促下打上 &lt;code class=&quot;language-text&quot;&gt;ref&lt;/code&gt; 等丑陋的命令式补丁，违反最佳实践来跨组件地操作内部状态，并逐渐陷入维护的噩梦。&lt;/p&gt;
&lt;h3&gt;UI 与状态的分层，可类比于前后端分离&lt;/h3&gt;
&lt;p&gt;早年前端还是整个后端工程的一个文件夹时，HTML 由服务端渲染而来，也没什么复杂的交互和逻辑。前端与其说是状态，不如说是后端数据在某一时刻的快照。那时我们讲 UI 和内容分离，CSS 与 HTML 分离。&lt;/p&gt;
&lt;p&gt;前后端分离后，采用 API 交互，但如何设计合理的 API 又成了问题。如果完全按照页面结构来设计，API 就得跟着页面改动，也无法一套 API 适配多端。于是我们有了 RESTful API 规范。而前端在需要管理自身的状态之后，也面对着如何组织状态结构的问题。而答案就在&lt;a href=&quot;https://cn.redux.js.org/tutorials/essentials/part-6-performance-normalization&quot;&gt;被你们嫌又臭又长的 Redux 文档&lt;/a&gt;之中。&lt;/p&gt;
&lt;p&gt;RESTful 虽好，但当页面复杂，需要多个数据源时，就得请求多次进行组合，甚至还有依赖关系。于是我们进一步发展了 GraphQL、BFF 等。同样，UI 层需要考虑如何从范式化的全局状态中组合出自己需要的数据，也就是很多状态管理库中所谓的 selectors、getters 。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c1781e3b4f5da046d4e0235f095f02e5/4b190/selector.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 67.98029556650246%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAMBAgQF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAepZ0JkGi//EABoQAAEFAQAAAAAAAAAAAAAAAAABAgMREyH/2gAIAQEAAQUCyiMWDoo7sRR69//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABgQAAMBAQAAAAAAAAAAAAAAAAACMiAz/9oACAEBAAY/AuakKQuP/8QAGxABAAMAAwEAAAAAAAAAAAAAAQARIUFRcZH/2gAIAQEAAT8hSafjFdXfIYZZwStVsA7h18n/2gAMAwEAAgADAAAAEBwv/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHBABAAIDAAMAAAAAAAAAAAAAAQARITFRkaHR/9oACAEBAAE/EHAXO/UL0yF3x4hoWrYa2y9I2dQZM77mYINfTP/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;selectors&quot;
        title=&quot;&quot;
        src=&quot;/static/c1781e3b4f5da046d4e0235f095f02e5/4b190/selector.jpg&quot;
        srcset=&quot;/static/c1781e3b4f5da046d4e0235f095f02e5/efcb5/selector.jpg 203w,
/static/c1781e3b4f5da046d4e0235f095f02e5/02e66/selector.jpg 405w,
/static/c1781e3b4f5da046d4e0235f095f02e5/4b190/selector.jpg 800w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;我们按照代码运行的位置，划分出了前端和后端。但前端的状态管理，相对于 UI 层，甚至还与后端更接近些（这里再往深处想，还能得出更多有趣的结论，以后有时间可以写得更多）。毕竟在前后端不分离的时代，数据库不就是最大的 single source of truth 吗？我们不应该把在写 UI 上的经验，套用到状态管理上来。&lt;/p&gt;
&lt;p&gt;将 UI 与状态分离后，我们可以抛开 UI，以整体的视角掌握一个前端应用所处的状态。这将帮助我们高屋建瓴地了解整个项目的业务逻辑，也有助于代码重构和单元测试。&lt;/p&gt;
&lt;h2&gt;你或许还是需要全局状态管理&lt;/h2&gt;
&lt;p&gt;在上文中我们从理论上阐述了集中式状态管理的合理性。此外，在实际业务中，我们会发现集中式的状态管理能够很方便地解决一些组件化状态难以解决的问题。下面就试举两例。&lt;/p&gt;
&lt;h3&gt;全局状态管理与回放系统&lt;/h3&gt;
&lt;p&gt;在大前端项目中，捕捉和定位线上问题一直是一个痛点。由于代码是在用户的设备上执行，我们无法还原问题现场。即使在用户的配合下拿到了日志，也会因为事前埋点不够完善等因素，收集不到定位问题所需的必要信息。&lt;/p&gt;
&lt;p&gt;前几年，闲鱼团队公布了被称为“&lt;a href=&quot;https://mp.weixin.qq.com/s/xDhR9iZwM8PQehkTIlsOhw&quot;&gt;千人千面线上问题回放技术&lt;/a&gt;”的解决方案，希望能一劳永逸地解决这一痛点，把用户的操作和当时的数据完整地复现出来。&lt;/p&gt;
&lt;p&gt;为了能够记录下用户的操作和操作后引发的状态变更，该方案在底层做了大量的工作，例如模拟触摸，hook 所有业务代码中的方法和闭包等等，甚至需要通过一些反汇编的手段来破译出系统底层的实现逻辑。&lt;/p&gt;
&lt;p&gt;当时我所在的团队也深受线上问题困扰，故调研了一番。但该方案并不开源，且只支持单平台，加之本人能力有限，最终也不了了之。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;时隔多年以后回顾这个案例，我们可以思考一下有没有更好的解决方案。难以复现的问题可大致分成两类。其一是老生常谈的兼容性问题，如有相同的设备大体还是好定位出来的。另一类，也是占大头的问题，根源还是在于应用状态的不确定性，例如用户特定的操作路径导致了状态异常，或是多线程等导致状态的流转顺序有问题。本质上，这是一个状态管理范畴的问题。&lt;/p&gt;
&lt;p&gt;确定了问题的本质，再来看看我们的武器库中有哪些武器。假如我们在应用中采用了集中式的状态管理……相信大家已经反应过来了，这不就是“时间旅行”功能吗？只要记录下初始状态和用户操作、系统事件引发的一系列 action，就能推出之后任意时刻的应用状态。又因为 &lt;code class=&quot;language-text&quot;&gt;UI = f(state)&lt;/code&gt;，我们就可以通过状态驱动 UI 变化，实现回放的目的。&lt;/p&gt;
&lt;p&gt;而从状态管理的角度看待闲鱼的原方案，一般移动端老项目并没有现代前端这些成熟的状态管理方案，可以理解为组件化状态（状态分散在各个页面和组件中）、可变数据。由于状态分散，对状态的修改也十分随意，没有 action 等概念，所以才不得不使用 hook、反汇编等奇技淫巧，才能把散落的状态和状态变化收集起来，实现回放。这种解决方案不仅繁琐，且与平台强相关，想移植到不同的平台上就得重头再来。&lt;/p&gt;
&lt;p&gt;当然，对闲鱼这种体量的老项目来说其实没得选，不可能为了一个锦上添花的功能去重构整个项目的状态管理。但如果你要起一个新项目，又有这方面的需求，那无疑选择全局状态管理更为方便。&lt;/p&gt;
&lt;h3&gt;全局状态管理与游戏存档&lt;/h3&gt;
&lt;p&gt;我们都知道游戏有存档功能，能够记录并恢复任一时刻的状态。但游戏类型不同，实现存档功能的做法和难度也不同。&lt;/p&gt;
&lt;p&gt;之前我好奇有没有一种通用的游戏存读档机制，便向 Unity 社区寻求灵感。Unity 默认的范式不同于前端的声明式，采用的是&lt;a href=&quot;http://gameprogrammingpatterns.com/component.html&quot;&gt;实体-组件模式&lt;/a&gt;（Entity-Component）。场景中的物体称为 Entity，本质只是一个容纳 Component 的容器。而 Component 承载了各式各样不同的业务逻辑，是复用的基本单元。不同的 Component 组合形成了各种不同功能的 Entities。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 696px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c183344093af1748a602d137fa378f37/82158/ec.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.137931034482758%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA0ElEQVR42l2QOw9EQBSF/f/fIB4hQUNEo1Ko6SlWPEIQ4pHx3Bh7MrLF7lfcnLlzzp3c4e77DsPQNE3P87qum+d5HMdlWZIkcRzHdV3btsuyhO26rvsXjlLa932e503TvL/ARwhBMwiCoigw6wlTxpOE4LZtwx3qcRzTNJEv67riiOS+7zBAn+f5//KLUdd1lmVxHKOWjDRNoyiCKBjoY5Gqqtq2xV6YNQwDJ8uyJEmGYfA8Dy2KoqIomqZBSAxd11FVVUVfEATLsjAXv+P7/ge/HgXxksUBjgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;游戏开发中的实体-组件模式&quot;
        title=&quot;&quot;
        src=&quot;/static/c183344093af1748a602d137fa378f37/82158/ec.png&quot;
        srcset=&quot;/static/c183344093af1748a602d137fa378f37/2efce/ec.png 203w,
/static/c183344093af1748a602d137fa378f37/1d180/ec.png 405w,
/static/c183344093af1748a602d137fa378f37/82158/ec.png 696w&quot;
        sizes=&quot;(max-width: 696px) 100vw, 696px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;这一模式拥有很大的灵活性，使引擎得以支持各种类型游戏的开发，但逻辑的分散也使得引擎无法自带一套统一且开箱即用的存档方案。&lt;/p&gt;
&lt;p&gt;我买了一套 Unity 上的第三方游戏框架学习。它的做法是将存档功能也作为一个组件，挂载到所有需要保存状态的实体上。存档时，该组件遍历实体上的其他组件，反射收集它们的内部状态并保存起来。而读档时只需反向操作一下就可以了。&lt;/p&gt;
&lt;p&gt;这种做法侵入性低，开发者无需改变自己熟悉的开发习惯，只需在最后加入存档逻辑即可，使用方式简单易懂。&lt;/p&gt;
&lt;p&gt;但如果考虑到游戏更新呢？组件的状态可能有增删，甚至组件乃至实体本身都有可能已不复存在。假如这时我们去载入老版本的存档……没人愿意游戏每次更新都不兼容旧版本的存档吧？&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;那么能否考虑借鉴全局状态管理的思想，来实现存档功能呢？我们需要将有存档需求的状态都定义在全局，而非使用它的组件中。变量的定义处和使用处相距甚远，这乍看是非常“反最佳实践”的，且与大多数开发者的习惯相冲突。恐怕大多数人已经开始摇头了。&lt;/p&gt;
&lt;p&gt;但是换个角度看问题，将状态定义在全局上，实际上是倒逼我们在开发的早期就想清楚整个应用的状态结构。而有了清晰的全局状态数据结构之后，存档和读档不过就是一个数据结构的序列化和反序列化而已，没有任何魔法。&lt;/p&gt;
&lt;p&gt;版本更新的场景也是同理，只需将一个数据结构转化为另一个数据结构，就完成了存档版本的升级。而这一步只是一个纯函数，不依赖任何游戏逻辑，与任何游戏引擎和框架无关。我们优雅地做到了在状态分散管理时难以做到的事。&lt;/p&gt;
&lt;h2&gt;关于全局状态管理的一些「迷思」&lt;/h2&gt;
&lt;p&gt;在网上关于全局状态管理的评论中，经常能看到一些典型的观点。让我们对它们做一些分析。&lt;/p&gt;
&lt;h3&gt;“全局变量”是邪恶的&lt;/h3&gt;
&lt;p&gt;全局状态管理的方式往往容易让人联想到全局变量，而全局变量的坏处早已在大家心里根深蒂固了，因此对全局状态管理也会下意识产生抵触心理。&lt;/p&gt;
&lt;p&gt;但请注意，虽然它们看上去很像，但全局状态管理并不等于全局变量，而区别正在于“管理”两字。全局变量的坏处很大程度上是因为它全局的可见性，导致我们可以在任意处定义一个全局变量，并在任意处修改它，以至于难以追踪它的变化。&lt;/p&gt;
&lt;p&gt;而当我们使用一个全局状态管理库时，我们需要以合适的方式组织状态的结构，而非随意地定义一个全局变量。同时这些状态管理库都推荐我们不要直接修改全局状态，而是采用 action 等形式间接修改。这就使得修改状态的行为能够被追踪，也意味着为状态上了一层封装，隔离了状态和 UI，迫使我们思考如何设计一套合理的 API 向 UI 层暴露修改状态的入口。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;倘若我们把全局状态的范畴再扩大些，虚拟机的堆、后端的数据库等，不也是全局状态和数据吗？但我们通过抽象、封装，在它们的基础上合理组织变量的数据结构和访问控制，而并不会去质疑它们本身是否邪恶。&lt;/p&gt;
&lt;h3&gt;领域（业务）状态由全局管理，UI 状态由组件内部管理&lt;/h3&gt;
&lt;p&gt;老实说这一说法并没有什么问题，而且比较中庸，能够得到大多数人的支持。但如果用它来指导开发，会发现有时我们很难界定一个状态究竟属于业务状态还是 UI 状态。有些东西，我们传统意义上认为是 UI 状态，但在交互、逻辑复杂的情况下，只放到组件内部管理也是远远满足不了需求的。&lt;/p&gt;
&lt;p&gt;如果只是一个简单的 loading 状态，那么放到组件内部管理无可厚非。但很多时候伴随着某一处组件 loading 的，还有其他的组件状态改变，例如禁用个按钮，修改个文案之类。而这些组件在组件树上的位置可能相距甚远。&lt;/p&gt;
&lt;p&gt;在类似这种复杂的情况下，loading 不能被视为一个单纯的 UI 状态了，&lt;strong&gt;它事实上被赋予了业务上的意义&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;尽管前端正在逐渐接触领域驱动的概念，但并不能简单地把后端的领域模型照搬到前端。目前后端接口主流仍然采用无状态的模式，但前端则更像是一个状态机，在范式上存在着本质的区别。而有些复杂度是不能被消除的。我们不能眼睛一闭，假装几百毫秒的 loading 不存在，而应该把加载中状态和加载完成、加载失败等状态同等严肃地看待。&lt;/p&gt;
&lt;h3&gt;全局状态的生命周期管理很麻烦&lt;/h3&gt;
&lt;p&gt;如果把状态放在一个组件中，在组件创建时它会自动生成，组件销毁时也会被自动销毁。但如果将它放到全局状态管理中，我们就得手动管理它的生命周期，显得较为繁琐。如果没有及时清理，也容易产生内存泄漏的隐患。&lt;/p&gt;
&lt;p&gt;但这有时也是一个优点。**它给予了我们对状态更精细的控制能力。**对于那些本来就需要跨组件共享的状态自不必说，即使是一般来说仅在单个组件中使用的状态，若加以合理的结构组织，也可以发掘出其他的用途。&lt;/p&gt;
&lt;p&gt;一种用法是，我们可以把状态保留下来，在另一个需要相同数据源的页面中充当缓存。例如，当用户从列表页点击进入详情页，而详情页的数据还未获取到时，先展示一部分已有的字段，提升加载过程中的用户体验。&lt;/p&gt;
&lt;p&gt;或是单纯用作已销毁组件的状态缓存，用于在 React 中实现类似 Vue 中 &lt;code class=&quot;language-text&quot;&gt;keep-alive&lt;/code&gt; 的效果 —— 虽然并不发生在 VDOM 层，但更具有普适性。&lt;/p&gt;
&lt;h3&gt;全局状态难以横向扩展， 实现“分形状态”&lt;/h3&gt;
&lt;p&gt;当状态保存在组件中时，每当组件被复用时，就会生成一份状态的副本。随着组件的复用，状态也自然而然地随着组件树的结构，形成了一棵状态树。而每一个组件也只需关注自身节点的状态，无需关心它被复用了几次，无需把它和在别处被复用的组件区分开来。&lt;/p&gt;
&lt;p&gt;相反，如果使用了全局状态，那么每次一个组件被复用时，我们就需要在全局状态中为这个组件开辟一块新的地盘，并确保组件连接到正确的状态上。这似乎使全局状态依赖了 UI 的变更，不利于组件的复用和横向扩展。&lt;/p&gt;
&lt;p&gt;这里的问题是，首先把状态按照组件树的层次结构进行组织本身就是不合理的。其次，虽然使用了组件化状态后，每个组件的状态自治了，我们在技术上无需关心每个组件被复用了几次，每次复用的区别是什么，&lt;strong&gt;但它们的区别在业务上是客观存在的&lt;/strong&gt;。一旦业务需求中需要感知这些信息，组件化的状态就要抓瞎了。&lt;/p&gt;
&lt;p&gt;举例来说，项目中有很多表格，希望能让用户自定义字段的排序、显隐等状态。第一想法是把这些状态放到表格组件中，这样所有使用该表格组件的地方都自然拥有了这些功能，调用方无需感知。&lt;/p&gt;
&lt;p&gt;但有一天，新的需求希望能把用户自定义后的配置保存到 localstorage 或后端，以便记忆用户的自定义设置。业务上这很合理，但技术上你就不得不想办法把每个技术上相同的表格组件区分开来，接受它变得不那么组件化这个事实。你需要为每个表格分配一个业务唯一的 key，把配置存储到一个中心化的、扁平的 Map 结构中。&lt;/p&gt;
&lt;h3&gt;你说的这么多用途，我都用不上&lt;/h3&gt;
&lt;p&gt;可能有人说，我平时的需求就是进入一个页面，请求一次数据，展示出来而已。你上文举的例子都不怎么典型，不是我们常见的前端功能。纵使你说的千般合理，千般有用，对我来说却只是徒增麻烦罢了。&lt;/p&gt;
&lt;p&gt;这确实是一个很现实的问题，它反映的正是我想在下文中提及的，目前前端领域所面临的一个“结构性矛盾”。&lt;/p&gt;
&lt;h2&gt;状态管理之争，实为前端应用化之争&lt;/h2&gt;
&lt;p&gt;从前端文档到前端应用，是现代前端发展的主要脉络之一。理解了这一脉络，就能理解现在前端发生的很多事情。&lt;/p&gt;
&lt;p&gt;在脉络的一端，是我们熟知的传统博客、展示型官网等。这些前端只有数据，几乎没有状态一说，自然也不存在什么所谓的状态管理。&lt;/p&gt;
&lt;p&gt;而脉络的另一端，则是放在十几年前只可能出现在桌面端的在线文档、视频编辑器、页游等应用。这些应用的开发者，他们的关注点早已超越了什么组件化与全局状态之争。他们早已更进一步，研究起了状态具体的组织形式、数据结构等，以适应其特殊的需求。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你需要处理复杂的动画或 AI 逻辑，你可能需要&lt;a href=&quot;https://juejin.cn/post/7045090471852376101&quot;&gt;使用 xstate 把状态抽象为状态机&lt;/a&gt;；如果你需要多人协同，你可能需要 &lt;a href=&quot;https://zhuanlan.zhihu.com/p/452980520&quot;&gt;Yjs 处理状态的同步与冲突&lt;/a&gt;；如果是业务逻辑重前端的应用，&lt;a href=&quot;https://zhuanlan.zhihu.com/p/421300074&quot;&gt;你可能需要借助 MobX&lt;/a&gt;、&lt;a href=&quot;https://github.com/remesh-js/remesh&quot;&gt;Remesh&lt;/a&gt; 等实践前端 DDD：在这些场合中，我们熟悉的基于 Plain Object 的传统状态管理早已不够用了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5da357f092b67eb707ac57901a792126/eea4a/webappvsdocument.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBP/EABYBAQEBAAAAAAAAAAAAAAAAAAEAA//aAAwDAQACEAMQAAABpZnuSA3/xAAaEAACAwEBAAAAAAAAAAAAAAABAgADEhMx/9oACAEBAAEFAq6iypX0li4cMZoiez//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAQIf/aAAgBAgEBPwGn/8QAGxAAAQQDAAAAAAAAAAAAAAAAAAEQETESIUH/2gAIAQEABj8Cy0LHCCy2/8QAGxABAAIDAQEAAAAAAAAAAAAAAQARITFRQWH/2gAIAQEAAT8hF2y7H0D6iI9nIUoVTwR05Z//2gAMAwEAAgADAAAAEMfv/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAGBEAAgMAAAAAAAAAAAAAAAAAACEBETH/2gAIAQIBAT8QpmRjP//EABwQAAICAgMAAAAAAAAAAAAAAAERACExUWGBkf/aAAgBAQABPxAP89AC67l3qBh7evIVcFF4XFuNHAWzAxmzyG5//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;前端应用与前端文档&quot;
        title=&quot;&quot;
        src=&quot;/static/5da357f092b67eb707ac57901a792126/627f4/webappvsdocument.jpg&quot;
        srcset=&quot;/static/5da357f092b67eb707ac57901a792126/efcb5/webappvsdocument.jpg 203w,
/static/5da357f092b67eb707ac57901a792126/02e66/webappvsdocument.jpg 405w,
/static/5da357f092b67eb707ac57901a792126/627f4/webappvsdocument.jpg 810w,
/static/5da357f092b67eb707ac57901a792126/78959/webappvsdocument.jpg 1215w,
/static/5da357f092b67eb707ac57901a792126/eea4a/webappvsdocument.jpg 1280w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;然而，纯粹前端应用的开发在如今的前端市场上毕竟是少数。而我们平时更为常见的官网、管理后台等需求，它们的形态更接近于前端被发明之初的用途——前端文档上。它们真的需要前后端分离，需要 SPA 吗？&lt;/p&gt;
&lt;p&gt;尽管现代前端应用对这些需求早已有了成熟的解决方案，例如 SSR、SSG 等渲染方案，一堆基于 SPA 的 admin-template 等等。但在我们看不到的角落，在很多传统行业中，使用的还是古老的 JSP、ASP，甚至传统 C/S 架构。而它们的用户，关注更多的还是系统的功能是否全面，并不过于在意用户体验&lt;del&gt;能用就行&lt;/del&gt;。若非特意指出，甚至感知不到它们与 SPA 的差别。这些老系统会继续运行很久很久，直到它们的维护团队跑路为止。&lt;/p&gt;
&lt;p&gt;虽然很遗憾，但我们不得不承认，对于这种类型的网站来说，&lt;strong&gt;前后端分离与 SPA 更多提升的是开发体验，而不是用户体验。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这也是为什么我们老是争论状态管理的方式：并不是哪一方错了，只是我们虽然都名为前端，但解决的是不同的问题。&lt;/p&gt;
&lt;p&gt;不过，前端终究还是会朝着应用化的方向发展。毕竟前端应用是前端文档的超集，后者能做的前者也能做。而即使是传统的前端文档也在逐渐融入应用的要素，例如在技术文档、博客中引入 MDX 等，以增强页面的交互性，以及在后台管理系统中各种复杂联动、交互的表单等等。如果还是按照传统的思维开发这些功能，迟早会走进死胡同。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;回到主题，我并不鼓吹你们把所有的状态都放到全局管理，毕竟现实的项目中总是存在着权衡，没有银弹。但我希望能放下旧有的观念，重新思考状态的组织形式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;状态并不理所当然是本地优先的&lt;/strong&gt;，实在满足不了需求才要被放到全局中。相反，我们应当规划好状态的数据结构与层次，优先考虑把它们放到组件以外的状态管理中，做好分层与隔离。&lt;/p&gt;
&lt;p&gt;即使我们最终仍然选择将某个状态放到组件内部，我们也应当清楚地知道这么做的理由。是因为它纯用作一次性展示？还是与业务逻辑无关的简单交互？或是仅仅为了实现简单？都可以，但并不理所应当这样做。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[【Vue】当你 watch 一个 computed value…]]></title><description><![CDATA[各位五一快乐，最近工作比较忙，来拿点工作中遇到的问题水一水。在 Vue…]]></description><link>https://maliut.space/p/vue-watch-computed-value/</link><guid isPermaLink="false">https://maliut.space/p/vue-watch-computed-value/</guid><pubDate>Thu, 31 Mar 2022 22:58:00 GMT</pubDate><content:encoded>&lt;p&gt;各位五一快乐，最近工作比较忙，来拿点工作中遇到的问题水一水。在 Vue 中我们经常使用 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 来监听响应式变量的变化。有时，我们需要监听多个变量，但执行同样的逻辑，这时写多个 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 就很浪费。不难想到，可以使用 &lt;code class=&quot;language-text&quot;&gt;computed&lt;/code&gt; 把这几个变量组合起来，这样只需监听这个单独的计算属性就可以了。&lt;/p&gt;
&lt;p&gt;下面的例子展示了这一用法：当 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 和 Post 表单的 &lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt; 属性变化时，打印一行日志。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vue &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hello world&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Lorem ipsum&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;mutableParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;mutableParams&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;triggered&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input v&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Post Title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input v&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;post.title&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Post Desc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input v&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;post.desc&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但现实往往并没有那么简单。假设我们需要把 Post 表单封装成一个单独的组件：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt; PostForm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vue &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;modelValue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;update:modelValue&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modelValue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; value &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Post Title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;modelValue.title&quot;&lt;/span&gt; @input&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;emit(&apos;title&apos;, $event.target.value)&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Post Desc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;modelValue.desc&quot;&lt;/span&gt; @input&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;emit(&apos;desc&apos;, $event.target.value)&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后用这个组件替换掉上文的例子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;diff&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-diff line-numbers&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-arrow deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt;!-- App.vue --&gt;
&lt;/span&gt;...
&lt;span class=&quot;token deleted-arrow deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt;template&gt;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &amp;lt;div&gt;ID: &amp;lt;input v-model=&quot;id&quot; /&gt;&amp;lt;/div&gt;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   &amp;lt;div&gt;Post Title: &amp;lt;input v-model=&quot;post.title&quot; /&gt;&amp;lt;/div&gt;
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   &amp;lt;div&gt;Post Desc: &amp;lt;input v-model=&quot;post.desc&quot; /&gt;&amp;lt;/div&gt;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   &amp;lt;post-form v-model=&quot;post&quot; /&gt;
&lt;/span&gt;&lt;span class=&quot;token deleted-arrow deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt;/template&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看上去一切天衣无缝。但倘若你编辑了 Post 表单的 &lt;code class=&quot;language-text&quot;&gt;desc&lt;/code&gt; 属性，会发现日志依旧被打出了，即使我们并没有监听 &lt;code class=&quot;language-text&quot;&gt;desc&lt;/code&gt; 的变化。这显然是与我们的预期不符的。&lt;br&gt;
……&lt;br&gt;
……&lt;br&gt;
相信各位身经百战的读者们很快就能发现问题所在。当 &lt;code class=&quot;language-text&quot;&gt;PostForm&lt;/code&gt; 封装为组件之后，无论是修改 &lt;code class=&quot;language-text&quot;&gt;title&lt;/code&gt; 还是 &lt;code class=&quot;language-text&quot;&gt;desc&lt;/code&gt; ，都会为 &lt;code class=&quot;language-text&quot;&gt;post&lt;/code&gt; 对象赋一个新值。而 Vue 的 &lt;code class=&quot;language-text&quot;&gt;computed&lt;/code&gt; 通过依赖收集机制，将 &lt;code class=&quot;language-text&quot;&gt;this.post&lt;/code&gt; 作为了依赖。一旦 &lt;code class=&quot;language-text&quot;&gt;post&lt;/code&gt; 对象改变，就会重新触发 &lt;code class=&quot;language-text&quot;&gt;computed&lt;/code&gt; 生成新的 &lt;code class=&quot;language-text&quot;&gt;mutableParams&lt;/code&gt; 对象，进而触发 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 的逻辑。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;那么我们该如何解决这个问题呢？&lt;/p&gt;
&lt;p&gt;一个显而易见的想法是，既然问题出在每次编辑都会产生一个新的 &lt;code class=&quot;language-text&quot;&gt;post&lt;/code&gt; 对象，那么我们只要让编辑前后的 &lt;code class=&quot;language-text&quot;&gt;post&lt;/code&gt; 对象保持不变就可以了。基于这种思路，可做出如下改动：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;diff&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-diff line-numbers&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-arrow deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt;!-- PostForm.vue --&gt;
&lt;/span&gt;...
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; methods: {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   emit(key, value) {
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;     this.$emit(&apos;update:modelValue&apos;, { ...this.modelValue, [key]: value })
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     this.$emit(&apos;update:modelValue&apos;, Object.assign(this.modelValue, { [key]: value }))
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   }
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; }
&lt;/span&gt;...&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;虽然这样解决了问题，但这并不是种好做法。该方法本质上是直接修改了组件 &lt;code class=&quot;language-text&quot;&gt;prop&lt;/code&gt; 的值，而这是&lt;a href=&quot;https://v3.cn.vuejs.org/guide/component-props.html#:~:text=%E6%B3%A8%E6%84%8F%E5%9C%A8%20JavaScript%20%E4%B8%AD%E5%AF%B9%E8%B1%A1%E5%92%8C%E6%95%B0%E7%BB%84%E6%98%AF%E9%80%9A%E8%BF%87%E5%BC%95%E7%94%A8%E4%BC%A0%E5%85%A5%E7%9A%84%EF%BC%8C%E6%89%80%E4%BB%A5%E5%AF%B9%E4%BA%8E%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84%E6%88%96%E5%AF%B9%E8%B1%A1%E7%B1%BB%E5%9E%8B%E7%9A%84%20prop%20%E6%9D%A5%E8%AF%B4%EF%BC%8C%E5%9C%A8%E5%AD%90%E7%BB%84%E4%BB%B6%E4%B8%AD%E6%94%B9%E5%8F%98%E8%BF%99%E4%B8%AA%E5%AF%B9%E8%B1%A1%E6%88%96%E6%95%B0%E7%BB%84%E6%9C%AC%E8%BA%AB%E5%B0%86%E4%BC%9A%E5%BD%B1%E5%93%8D%E5%88%B0%E7%88%B6%E7%BB%84%E4%BB%B6%E7%9A%84%E7%8A%B6%E6%80%81%EF%BC%8C%E4%B8%94%20Vue%20%E6%97%A0%E6%B3%95%E4%B8%BA%E6%AD%A4%E5%90%91%E4%BD%A0%E5%8F%91%E5%87%BA%E8%AD%A6%E5%91%8A%E3%80%82%E4%BD%9C%E4%B8%BA%E4%B8%80%E4%B8%AA%E9%80%9A%E7%94%A8%E8%A7%84%E5%88%99%EF%BC%8C%E5%BA%94%E8%AF%A5%E9%81%BF%E5%85%8D%E4%BF%AE%E6%94%B9%E4%BB%BB%E4%BD%95%20prop%EF%BC%8C%E5%8C%85%E6%8B%AC%E5%AF%B9%E8%B1%A1%E5%92%8C%E6%95%B0%E7%BB%84%EF%BC%8C%E5%9B%A0%E4%B8%BA%E8%BF%99%E7%A7%8D%E5%81%9A%E6%B3%95%E6%97%A0%E8%A7%86%E4%BA%86%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%EF%BC%8C%E4%B8%94%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%AF%BC%E8%87%B4%E6%84%8F%E6%96%99%E4%B9%8B%E5%A4%96%E7%9A%84%E7%BB%93%E6%9E%9C%E3%80%82&quot;&gt;官方文档中明确不推荐的&lt;/a&gt;，会让数据流变得混乱。这种场景下我们把对象作为值类型，还是使用不可变数据结构更好一些。&lt;/p&gt;
&lt;p&gt;另一个问题是，一个合理的组件设计，调用者只需关注它对外暴露的接口，而无需关心内部实现。但这种情况下不同的内部实现却会影响到外部逻辑的正确性，这显然是不合理的。更何况很多时候我们根本无法控制某个第三方组件的内部实现。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;还有个比较简单粗暴的想法是，既然是 watch computed value 引发的问题，那干脆回归到最原始的模式，只 watch primitive value，这样就不用考虑引用变化的问题。在这种做法上 Vue3 有着绝对的优势，因为 Vue3 的 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; API 支持传入多个值：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vue &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script setup&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; PostForm &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./PostForm.vue&apos;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;watch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ref&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;vue&apos;&lt;/span&gt;
  
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hello world&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Lorem ipsum&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  
&lt;span class=&quot;token function&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;triggered&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在老项目中，如果要一个一个写 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; option 也太蠢了。可以利用 &lt;code class=&quot;language-text&quot;&gt;computed&lt;/code&gt; + 循环 + &lt;code class=&quot;language-text&quot;&gt;$watch&lt;/code&gt; 来模拟类似的效果：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;  &lt;span class=&quot;token function&quot;&gt;mounted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mutableParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mutableParams&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;triggered&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;姑且不提这种做法很不优雅，即使它可以解决示例的这一个问题，但也难以成为一种通用的解决方案。如果我们的 &lt;code class=&quot;language-text&quot;&gt;mutableParams&lt;/code&gt; 需可动态配置、会增减字段，或是存在更深的嵌套层次等，就很难继续按照这个思路写下去了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;我们还是需要循其本。当我们使用 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 时，我们期望的效果是当 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 的目标值变化时触发监听。但 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 的实现原理是，当目标的依赖变化时触发监听。虽然在绝大多数的情况下两者的效果是相等的，但它们并不是彼此的充要条件。&lt;/p&gt;
&lt;p&gt;如果我们监听的目标值发生了变化，那么一定是它的依赖发生了变化，但反过来，当它的依赖发生变化时，并不能推出目标值最终也一定变化了。而这个例外，最容易出现的情况就是在 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 一个 &lt;code class=&quot;language-text&quot;&gt;computed&lt;/code&gt; value 时。&lt;/p&gt;
&lt;p&gt;严格来说，问题的根源是我们期望的“变化”与代码层面的“变化”定义不同。因此，如果我们能够自定义判断是否“变化”的方法（类似重写 JavaBean 的 &lt;code class=&quot;language-text&quot;&gt;equals&lt;/code&gt; 方法），这个问题就迎刃而解了。&lt;/p&gt;
&lt;p&gt;与 Vue 响应式原理类似的状态管理库 MobX 就内置了类似的方法。在 MobX 中，可以在定义 &lt;code class=&quot;language-text&quot;&gt;computed&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 时提供一个&lt;a href=&quot;https://mobx.js.org/computeds.html#equals&quot;&gt;自定义的比较函数&lt;/a&gt;，如下例所示：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;observable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hello world&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Lorem ipsum&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mutableParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 相当于 Vue 中的 watch&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;reaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; mutableParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;triggered&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 对前后的值做一次浅比较，结果不同时才触发监听器逻辑&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 也可以换成深比较，或任何自定义的比较逻辑&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; comparer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shallow &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;runInAction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 不会触发 console.log&lt;/span&gt;
  data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hello world&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;something else&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Vue 并没有提供这种开箱即用的内置选项，不过在有了思路之后也很容易自己实现。考虑到无论哪种比较方式的必要条件都是依赖发生变化，我们只需在 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 内部额外做一层判断即可。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vue &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;mutableParams&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;newValue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; oldValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// equals 是任意的判断相等性的方法&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newValue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; oldValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;triggered&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;总的来说不是个很难的问题，不过在复杂项目中偶然遇到一次还是挺搞人心态的。不知为何网上也都找不到相关的讨论，故在此抛砖引玉一下。如果大家有更好的想法，欢迎一起交流~&lt;/p&gt;</content:encoded></item><item><title><![CDATA[【偶像梦幻祭2】抽卡模拟器]]></title><description><![CDATA[朴实无华而又简单易懂的项目名称。如果你已经明白这是什么，并想要立即体验的话，请点击这里。  而下面，只是一些无聊的心路历程。 众所周知，每一个有一定人气的手游，都能带火一堆关于它的工具项目。当我在 Github 上浏览了一个原神抽卡模拟器后，Github…]]></description><link>https://maliut.space/p/es2-gacha/</link><guid isPermaLink="false">https://maliut.space/p/es2-gacha/</guid><pubDate>Sun, 20 Feb 2022 22:37:00 GMT</pubDate><content:encoded>&lt;p&gt;朴实无华而又简单易懂的项目名称。如果你已经明白这是什么，并想要立即体验的话，请&lt;a href=&quot;https://es2.florastudio.app/&quot;&gt;点击这里&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0dcc5b42f80da8883cd05868d25039a6/e7dd2/es2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.75369458128079%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAwX/xAAWAQEBAQAAAAAAAAAAAAAAAAACAQP/2gAMAwEAAhADEAAAAdJln0nWECP/xAAbEAACAgMBAAAAAAAAAAAAAAAAAwECBBESIf/aAAgBAQABBQK6pFLtYhHmaItPW5P/xAAWEQEBAQAAAAAAAAAAAAAAAAABEEH/2gAIAQMBAT8BHZ//xAAXEQADAQAAAAAAAAAAAAAAAAAAARET/9oACAECAQE/Aa4as//EABsQAAICAwEAAAAAAAAAAAAAAAABAjESIUFx/9oACAEBAAY/AllJL040IgPbos//xAAdEAEAAwABBQAAAAAAAAAAAAABABExIUFRcZGx/9oACAEBAAE/IVcF7wA6uLuDkLW1EpZ6P2FALyjdr3P/2gAMAwEAAgADAAAAEFgv/8QAGBEAAwEBAAAAAAAAAAAAAAAAAAERQVH/2gAIAQMBAT8QheipYf/EABcRAQADAAAAAAAAAAAAAAAAAAEQETH/2gAIAQIBAT8QaouR/wD/xAAdEAEBAAEEAwAAAAAAAAAAAAABEQAhUWHBMUGx/9oACAEBAAE/EIwQsWc9YQi6x8Jde8gEAWmrvhAie14YPI1wRtlr6s//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;抽卡模拟器&quot;
        title=&quot;&quot;
        src=&quot;/static/0dcc5b42f80da8883cd05868d25039a6/627f4/es2.jpg&quot;
        srcset=&quot;/static/0dcc5b42f80da8883cd05868d25039a6/efcb5/es2.jpg 203w,
/static/0dcc5b42f80da8883cd05868d25039a6/02e66/es2.jpg 405w,
/static/0dcc5b42f80da8883cd05868d25039a6/627f4/es2.jpg 810w,
/static/0dcc5b42f80da8883cd05868d25039a6/78959/es2.jpg 1215w,
/static/0dcc5b42f80da8883cd05868d25039a6/e7dd2/es2.jpg 1594w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;而下面，只是一些无聊的心路历程。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;hr&gt;
&lt;p&gt;众所周知，每一个有一定人气的手游，都能带火一堆关于它的工具项目。当我在 Github 上浏览了一个原神抽卡模拟器后，Github 给我连着推了一星期不重样的各种原神工具。而即使是之前我已经烂尾的&lt;a href=&quot;/p/arknights/&quot;&gt;明日方舟作业帮&lt;/a&gt;，也时不时会收到一些自然流量。&lt;/p&gt;
&lt;p&gt;也正因此，当我们发现《偶像梦幻祭2》没有与之人气相称的抽卡模拟器时，「自己做一个」就成为了自然而然萌生的 idea 。&lt;/p&gt;
&lt;p&gt;当然，最初是怎么想到要去找这个抽卡模拟器的，就又是一个悲伤的故事了。&lt;/p&gt;
&lt;h2&gt;技术&lt;/h2&gt;
&lt;p&gt;这么个小工具，单纯实现它的逻辑并不难。但首先要面对的无疑是数据的问题。本来以为肯定有现成的 wiki，结果国内只找得到 b 站的，还好久没更新了。还好找到了国外的 wiki，经过一番查找后确定能在上面拿到所有我们需要的数据。&lt;/p&gt;
&lt;p&gt;有了数据源以后没什么可说的，爬就是了。虽说数据的复杂程度，不能让我直接用控制台大法就完事，不过偶尔在公司的耳濡目染（？）也让我知道了爬数据的正确打开方式。再加上这只是个普通的国外 WordPress 网站，可以说是新手难度了。&lt;/p&gt;
&lt;p&gt;然后来到最喜欢的创造环节。这次没怎么犹豫就选择了一直想正经用一次的 Vue3（特指 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;script setup&gt;&lt;/code&gt;）然后当然是要搭配 Vite + Typescript 。UI 框架则选用了最近流行的 naive UI（Element Plus 害人不浅……不知道现在出了稳定版本好点没）。&lt;/p&gt;
&lt;p&gt;其实最先做的是一个配置后台，这是因为当初以为有很大一块数据不能从 wiki 获取到，需要手工录入。不过后来实际上只起到了一些简单的增删改作用，目前已经废弃了。&lt;/p&gt;
&lt;p&gt;而真正的主工程，考虑到我其实并没有真正写过手机端 H5 项目，一上来还是有点无所适从的。这回算是真正搞懂了网上一直说的移动端适配是怎么个事，并且实现了一个总体还比较满意的横屏适配。当然若是说到细节就要另起一篇文章了。不满意的地方也是有的，主要是默认的脚手架工程没带 babel 之类的适配，手工处理了一些兼容性的问题。考虑到目前浏览器兼容性总体已经好了很多，而且整个工程规模也小，所以问题不大。&lt;/p&gt;
&lt;p&gt;不过实际发布后在用户手机上发现的各种奇葩表现还是让我大跌眼镜。&lt;/p&gt;
&lt;h2&gt;发布之后&lt;/h2&gt;
&lt;p&gt;这样一款工具的宣发并不顺利。在微博遭到了严重的限流，至今不知为何。在 NGA 遇到了错版 + 权限不够无法发帖。在公众号遇到了字数不够无法开原创。Lofter 链接严重被吞。即使是顺利发出去的平台，观者也寥寥。这无疑给我们浇了一盆大大的冷水。当天我们郁郁寡欢地睡去，默认这个项目已经凉了。&lt;/p&gt;
&lt;p&gt;谁知第二天好戏才开始上演。早上来到公司，打开数据看板，赫然显示近半小时有 8 个自然用户正在使用。没过多久，出现了第一条 b 站评论。11 点，用户为我们找到了一个 bug，紧急修复了一版。随后贴吧也开始出现评论。同时后台显示的用户数量也开始逐渐增长。&lt;/p&gt;
&lt;p&gt;我们看着近半小时用户数从个位数突破了 100，随后在下午两点突破了 200。从 200 到 300 只花了半个小时，而又在仅仅 15 分钟以后来到了 400。人数在 400 多稳定了一段时间，我们心想这就是最高峰了吧。看着地图上一个个代表使用者的点密密麻麻地亮起，内心的高兴与激动之情溢于言表，毕竟有如此多的真实用户正在使用我亲手写出的代码，作为个人的 side project 还是头一遭。&lt;/p&gt;
&lt;p&gt;然而我们以为的最高峰，其实只是半山腰上的一个小台阶。在短暂的休息过后，数据的爬坡变得更有力了。不知不觉在下午五点半时，这个数字突破了 1000。同时 b 站视频的数据也在疯涨。而我们在兴奋之余，感到更多的，却是深深的，恐 惧。&lt;/p&gt;
&lt;p&gt;说「恐惧」其实是玩梗，其实担忧更贴切些。这就要说到项目的部署方式。一开始的打算是 Github Pages + Gitee Pages。前者的网速难以接受，故以后者为主，但偏偏 Gitee Pages 现在需要开启实名认证，审核说是一个工作日，实际至少三个工作日。当时等不及，就又找到了一个类似的国外平台，但在国内访问飞速的地方进行部署（实际应该是部署在了香港阿里云）。&lt;/p&gt;
&lt;p&gt;该平台的免费额度和 Github Pages 类似，每月有 100G 的流量额度。老实说我以前从来不关心这个额度，毕竟部署的博客啥的基本上也没什么人看，就跟无限流量差不多。但在如此大量的用户访问之下，即使文件的大小极小，总共花费的流量累计起来也会成为一个 G 级别的数字。&lt;/p&gt;
&lt;p&gt;当时整个应用的大小在几百 K，得益于占流量大头的图片都走了 wiki 的外链（感谢 wiki 不杀之恩）。而用户呼声最大的功能是抽卡动画的加入。加入动画，技术难度倒还在其次，但带来的带宽成本却会成倍增加，100G 的免费额度就远远不够用了。而若是用户规模照着目前的增长速度继续下去，即使是维持目前的版本，也会轻易让免费额度见底（事实上当天最终流量花费达 11G 之多）。&lt;/p&gt;
&lt;p&gt;虽说后面不可能天天维持第一天的热度，但还是得做好服务不可用导致宕机的准备。一方面我们把 Github Pages 和 Gitee Pages 的地址也公布了出来作为备用地址，希望能缓解一些主域名的压力，并且准备一旦挂了就把 DNS 切到 Gitee Pages 上去（还好买了个自己的域名而不是用平台给的默认域名）。另一方面我也赶紧去调研了一番性价比最高的云服务部署方案 —— 自然是要钱的，有点心痛，但谁又能拒绝玩家们如此高的热情呢？&lt;/p&gt;
&lt;p&gt;与此同时数据还在继续上涨。晚上六点，近半小时用户数在 1400 停留了一阵。当我们以为这次终于来到了最高点时，谁知晚高峰才刚刚开始。晚八点数据突破了 2000，此时我下班回家。本以为回家这段时间顶多也就再涨个一千吧，结果到家后却惊讶地发现数据一下子翻了两番，来到了 6000。也就是说，每时每刻都有上千人正在使用我们的产品。&lt;/p&gt;
&lt;p&gt;真正的高峰最终出现在接近午夜，6700+，为这疯狂的一天划下了句号。而在这一天来访过的总用户数已有近 6w 人。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 456px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6c4c9f0a625c87d557032a321700eaea/7f664/1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 132.01970443349754%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAEG0lEQVR42oVVS2tcZRie3yLoqiCiFncK6sa9G3cKdmMRRK0xxZKa1pVoLSi0LnSho0wyrShCkSKkGBNM4y5NZkwy15yZc+Zy7vfL4/t+35wzZ2LAE5581/eZ9/5VojhGnCRI0wz8ZVkmkBDSNEVGSOic7+WISyjvh1GECl92PQ+6bsC0bNiOi6lhQNNG0EZjjMYTGKaFlH6Af5gRRVKYwfN8n1GJ4wQOkbCgMlRJ2MRkMsWIyDRtDGUwFGfEh9waHoMgFGD5BUI2j0k0TcNU17G1tY1m8xCdTgeKogg3JMmiUEJuOL3OUWGBvb091Ot1tIlkZ+chNjYeoFZbo/mOIAyEadHczHjRTEEqQITMGoah0M4kTdkEi3zJvvGDoDAx/B/C/EyYrFMQ+uSr/skA3Z6CoTrCZKoL7UJBRgIxE0vwuiCb7RU+ZA09irLvmYgCi0YLnmvTnoOM/7K0BE4tOSYppVqWFGcFYUoHbpjim00PtzZc3LjvYOVnGxerJt6tGfj8Nwvfbjr4+oGNL3+3aW2L9aU1A5fvmPjoJxP3H/lESprKKCfQ3RgvfTrCuSsqnvlYE+NjS0Ocv6bhqasqnlxR8TTtP7Gs4vFlOX92VRPn565oeOXGmCoiNzmJ4QQJrv9q4/01C0t11szCW9+b+GCd1zOsm3i7quPidzourRt4r0Yj4Z0fdKz+YoItlYlN/ziavmdL+A5cx4Rj6bCMKUxjApvmQeBxYS76FPN5npsiKKZliYroKwOoVHI8b3d7aHW6aBy2xNyk8gviVEQ9LkV8DrlfyaOTZovNgZtFMmsYYZJR4BKh4en8y2s7oFwWebiY7dxdMJvHsL0EN+/5+OKei1c/O8JSlTT1YtGFojOICx8WbWnWxqSyGS7c7uHDHy2oZoLDgYc3vmrhdQLnopA7o/xkUHyfEtmfEafCzO2mgxdXG8LZ8svIfylevtbEVtOareOzNeRaZh/wPIgSIV7fnuC1m8dC0HIj+KEkvnC7g9qfUzGPyEXs+3I3qoShLGomFE2AwCR7PRfnl/cxNALkn2rEeO7yAZqKL9vazD0igGWT/9M5YhnR63cUvHC1iermGNU/xnh+pYFP7g4WyHLkmlbmTXKxUXJuccKubY3x5q22wN2/DEFWuLX0SU3TWZQJXhDB4+AQcvN5n2sdaUgS0hWpSKuMKodcFdId4bK40LSSm9jRXBy3OthvNKkvKkRIgYgydFUHh8ctOuuKHsneMNwE7XYHR0ctnCj05owmhZYVaW5Gmnmi/R80Gni4u4tevy8uOa6DR/sH2N39Gycn0n8xadRqtent+Yeej3353JY1ZHAe8iWVHqtur4fBcCiEfXrZ+BVUVU1APloper2+eHoV0tD3g7mG5acxr+Oyo0vLhaieDkiu4b9RtKIXx5KhWQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;1&quot;
        title=&quot;&quot;
        src=&quot;/static/6c4c9f0a625c87d557032a321700eaea/7f664/1.png&quot;
        srcset=&quot;/static/6c4c9f0a625c87d557032a321700eaea/2efce/1.png 203w,
/static/6c4c9f0a625c87d557032a321700eaea/1d180/1.png 405w,
/static/6c4c9f0a625c87d557032a321700eaea/7f664/1.png 456w&quot;
        sizes=&quot;(max-width: 456px) 100vw, 456px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;本文断断续续写了一周。如今的数据稳定在每天 1000+ UV，和巅峰时期已然不能相提并论，但对于以前的我来说已经是一个想都不敢想的成绩了。&lt;/p&gt;
&lt;h2&gt;数据&lt;/h2&gt;
&lt;p&gt;可以看到上文中我对各种数据信手拈来。事实上在近几年的工作当中，一直被不断强调数据的重要性，而这也是我曾经并不太重视的地方。&lt;/p&gt;
&lt;p&gt;当然，重视是一回事，实际去做又是另一回事。在之前的项目中我也接过一些国内的数据平台，但都没用起来，一是因为没那么好用，二是本身也没那么火，看数据也没啥意思。这回倒是一开始就接了 Google Analytics，虽然打开要翻墙，但胜在好用与更加熟悉（公司也用）。&lt;/p&gt;
&lt;p&gt;不过也有些可以改进的地方。接入 Google Analytics 后，只会收集一些通用的数据，比如用户数、地域设备分布等数据。但后来我突发奇想想知道用户一共点了多少次抽卡，就赶紧加上了点击次数的埋点。&lt;/p&gt;
&lt;p&gt;总的来说我现在加入的数据统计还是以满足自己的好奇心&lt;del&gt;和方便吹比&lt;/del&gt;为主，并没有涉及什么数据分析的工作。更好的做法是把数据分析放到和新功能开发同等的地位上，在最开始就谨慎设计好所有需要埋点的事件和参数。&lt;/p&gt;
&lt;p&gt;那么就来分享一下其他有意思的数据。在所有的用户中，移动端占比达到了 85% ~ 90%。这倒也是意料之中的事情，所以虽然移动端适配相对繁琐，但也要做好了以后再发布，不能抱有侥幸心理。不过有趣的是，在热度降下来之后，桌面端的占比反而涨了不少。我怀疑是桌面端大家都有收藏的习惯，因此很方便就能再次打开，而移动端大家都不常用收藏或添加到桌面，因此爽过了就不常来了。&lt;/p&gt;
&lt;p&gt;从用户的地域分布来看，北上广深杭贡献了较多的用户量，这很符合我们的认知。但榜单上除了这几个城市之外，还有一个南宁强势加入。原来南宁这么二次元的吗……&lt;/p&gt;
&lt;p&gt;此外若不是没有数据统计，我也无法发现玩家间产生的自发传播。从用户的来源，可以看到有来自豆瓣、QQ 空间、抖音等我们并没有去宣传过的平台。甚至还有来自 Facebook 和 Instagram 的国外用户。我们的用户一共来自 50 余个国家和地区。&lt;/p&gt;
&lt;p&gt;还得感谢最终部署的平台，虽然当初选择它只是为了替代 Gitee Pages，但它的后台也提供了一些数据分析功能。除了基础的 PV、UV 等，它还提供了宝贵的流量分析功能。也正是这一功能才能让我直观地感受到免费流量的消耗情况，从而做出一些决策。&lt;/p&gt;
&lt;h2&gt;后记&lt;/h2&gt;
&lt;p&gt;人生本是无意义的，但自诩为创作者的我，最大的愿望就是自己的作品能被人们用到，能被人们记住。抽卡模拟器上线一周有余，很快就要突破 10w 的用户量了。有时会想，我这也算是死而无憾了吧（笑）。&lt;/p&gt;
&lt;p&gt;不过这次的局限性也很明显。直白地说，这个项目本身，既没有过人的技术力，也没有精巧的玩法设计，无非是蹭了游戏本身的热度，才得到了如此的成就。比起它在作品本身上的「成功」，它给我带来更多的是一种激励，是告诉我，即使是一个素人，也有一些事情是我可以做到的。&lt;/p&gt;
&lt;p&gt;而我也会怀揣着这份信心，继续走下去。（虽说没有这一次我也不会放弃就是啦）&lt;/p&gt;</content:encoded></item><item><title><![CDATA[React Leaflet + React Pixi：双倍的快乐，我全都要]]></title><description><![CDATA[一篇实用性的文章，记录一下最近在自娱自乐使用 Leaflet 和 PixiJS 的过程中整的一个有意思的活，帮助我们使用 React 声明式的语法在 Leaflet 的图层上使用 PixiJS…]]></description><link>https://maliut.space/p/react-leaflet-react-pixi/</link><guid isPermaLink="false">https://maliut.space/p/react-leaflet-react-pixi/</guid><pubDate>Mon, 22 Nov 2021 00:18:00 GMT</pubDate><content:encoded>&lt;p&gt;一篇实用性的文章，记录一下最近在自娱自乐使用 Leaflet 和 PixiJS 的过程中整的一个有意思的活，帮助我们使用 React 声明式的语法在 Leaflet 的图层上使用 PixiJS 绘图。&lt;/p&gt;
&lt;p&gt;如果你对这些库和它们的用途都已有所了解，只想直接看代码的话，可以直接访问 &lt;a href=&quot;https://github.com/maliut/react-leaflet-react-pixi&quot;&gt;react-leaflet-react-pixi&lt;/a&gt;（然后点个 star 再走，谢谢👀）。&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;项目动机&lt;/h2&gt;
&lt;p&gt;项目整体框架使用了 React。由于需要大地图展示的功能，一番调研以后决定采用 &lt;a href=&quot;https://leafletjs.com/index.html&quot;&gt;Leaflet&lt;/a&gt; 来实现。Leaflet 是一个用于快速构建可交互地图的 JS 库，开箱即用地为我们提供了地图中各种常见的交互能力，被广泛地用在 WebGIS 领域当中。&lt;/p&gt;
&lt;p&gt;不过作为一个纯 JavaScript 的库，leaflet 依然采用的是命令式的 API，在 React 中使用起来显得也是格格不入，颇为蛋疼。所幸的是，已经有了 &lt;a href=&quot;https://react-leaflet.js.org/&quot;&gt;React Leaflet&lt;/a&gt; 库能帮助我们在 React 中通过声明式的语法来使用 Leaflet。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// pure leaflet 语法&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; map &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;map&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.505&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.09&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tileLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;attribution&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&amp;amp;copy; &amp;lt;a href=&quot;https://www.openstreetmap.org/copyright&quot;&gt;OpenStreetMap&amp;lt;/a&gt; contributors&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.09&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindPopup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;A pretty CSS3 popup.&amp;lt;br&gt; Easily customizable.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
&lt;span class=&quot;token comment&quot;&gt;// react-leaflet 语法&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;MapContainer center&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.505&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.09&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; zoom&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;TileLayer
    attribution&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&amp;amp;copy; &amp;lt;a href=&quot;http://osm.org/copyright&quot;&gt;OpenStreetMap&amp;lt;/a&gt; contributors&apos;&lt;/span&gt;
    url&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&quot;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Marker position&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.09&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Popup&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;A&lt;/span&gt; pretty &lt;span class=&quot;token constant&quot;&gt;CSS3&lt;/span&gt; popup&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;br &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Easily customizable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Popup&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Marker&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;MapContainer&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们做一个地图类的应用，往往需要在地图上绘制各种标记、图形等。Leaflet 本身已经提供了这些功能，你可以直接使用 marker 在地图上显示一个标记（就像上面的代码那样），也可以使用 Path、Polygon 等功能画一些简单的图形。&lt;/p&gt;
&lt;p&gt;但这些绘图功能都比较简单，往往满足不了我们的需要。更重要的问题是，绘图功能的默认原理是向网页中插入新的 DOM、SVG 等元素，并在地图发生拖动、缩放时，动态改变它们的 CSS 样式。当这些元素变得越来越多时，网页容易变得卡顿。&lt;/p&gt;
&lt;p&gt;因此，我们需要使用更有效率的方式来绘图。不难想到可以使用 Canvas 实现。当然市面上也早已有了类似的解决方案，例如 &lt;a href=&quot;https://github.com/eJuke/Leaflet.Canvas-Markers&quot;&gt;Leaflet.Canvas-Markers&lt;/a&gt; ，&lt;a href=&quot;https://github.com/CartoDB/Leaflet.CanvasLayer&quot;&gt;Leaflet.CanvasLayer&lt;/a&gt; 等等。&lt;/p&gt;
&lt;p&gt;不过提到 2D Canvas 绘图，显然有个更好的方案就是 &lt;a href=&quot;https://pixijs.com/&quot;&gt;PixiJS&lt;/a&gt; 。它不仅为我们封装了繁琐的 Canvas API，更能通过 WebGL 的能力进一步提升性能，还自带了交互和动画的能力。&lt;/p&gt;
&lt;p&gt;当然，和 Leaflet 一样，PixiJS 采用的也是命令式 API，在 React 中显得不甚优雅。不过也同样，我们可以通过现成的 &lt;a href=&quot;https://reactpixi.org/&quot;&gt;React Pixi&lt;/a&gt; 库来帮助我们使用声明式语法操作 PixiJS 。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// pure pixijs 语法&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PIXI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;640&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;360&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;view&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; basicText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PIXI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Basic text in pixi&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
basicText&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
basicText&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;basicText&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// react-pixi 语法&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Stage width&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;640&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; height&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;360&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Text x&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; y&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Basic text in pixi&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Stage&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最终，我们需要将它们结合在一起，让 Leaflet 能使用 PixiJS 来渲染地图上的各种图形。又一次我们有了现成的轮子：&lt;a href=&quot;https://github.com/manubb/Leaflet.PixiOverlay&quot;&gt;Leaflet.PixiOverlay&lt;/a&gt;，它搭建起了 Leaflet 到 PixiJS 之间的桥梁。&lt;/p&gt;
&lt;p&gt;但这时问题就出现了。Leaflet.PixiOverlay 以命令式的语法附加到 Leaflet 本尊之上，也要求我们以命令式的语法在它的 callback 中调用 PixiJS 执行绘图逻辑。在纯 JavaScript 的世界中这无可厚非，但对于早已在采用了声明式语法的现代前端框架中浸淫了多时的我们来说，简直不能忍。&lt;/p&gt;
&lt;p&gt;明明已经有了 React Leaflet，又有了 React Pixi，两个现成的轮子重合在一起，得到的，本该是无限丝滑的开发体验。但是，为什么会变成这样呢？（被拖走&lt;/p&gt;
&lt;p&gt;不管了，我全都要！&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/341cd1fe5e23448af65eaae007c6aa45/03073/cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5615763546798%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIFAwT/xAAVAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAABTBe4qWUAr//EABkQAQEBAQEBAAAAAAAAAAAAAAIAAQMTIf/aAAgBAQABBQLObyWXy92bm1M41//EABcRAAMBAAAAAAAAAAAAAAAAAAABAhL/2gAIAQMBAT8BVs0f/8QAFhEBAQEAAAAAAAAAAAAAAAAAABES/9oACAECAQE/Aco//8QAGhAAAwADAQAAAAAAAAAAAAAAAAEREBIhQf/aAAgBAQAGPwK6tY7TjPKys//EABsQAQADAQEBAQAAAAAAAAAAAAEAETEhQVGB/9oACAEBAAE/IbRVjdi9fTRirR/Ia/v3s6rsirU9xz//2gAMAwEAAgADAAAAEHff/8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8QC3//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAgEBPxBBH//EABoQAQEBAQEBAQAAAAAAAAAAAAERIQBBcVH/2gAIAQEAAT8QU2GoK+Ozmwx8AUlpy1Q8GM4++tHE/OBTVrv526Fnf//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;cover&quot;
        title=&quot;&quot;
        src=&quot;/static/341cd1fe5e23448af65eaae007c6aa45/627f4/cover.jpg&quot;
        srcset=&quot;/static/341cd1fe5e23448af65eaae007c6aa45/efcb5/cover.jpg 203w,
/static/341cd1fe5e23448af65eaae007c6aa45/02e66/cover.jpg 405w,
/static/341cd1fe5e23448af65eaae007c6aa45/627f4/cover.jpg 810w,
/static/341cd1fe5e23448af65eaae007c6aa45/03073/cover.jpg 976w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;问题的核心就在于，我们如何能将 Leaflet.PixiOverlay 与 React Leaflet 和 React Pixi 结合起来，在开发时对这些丑陋的胶水代码无感知。我们理想的代码长这个样子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* React Leaflet 封装的地图容器 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;MapContainer &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* 地图的图层 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;TileLayer &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* Pixi 的图层，内部的东西都使用 PixiJS 渲染 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PixiRoot&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* Pixi 图层的内部，可以用任何 React Pixi 的组件编写 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Text text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;marker&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Sprite &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;PixiRoot&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;MapContainer&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;下面就让我们一步一步来实现它。&lt;/p&gt;
&lt;h2&gt;结合 React Leaflet&lt;/h2&gt;
&lt;p&gt;Leaflet.PixiOverlay 与 React Leaflet 的结合相对来说比较简单，就像 React 结合任何 JavaScript 库一样。我们可以在 React 组件的生命周期/Hook 中调用这些库的相应 API 。&lt;/p&gt;
&lt;p&gt;首先搭建 React Leaflet 环境，本文使用的 dependencies 是：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-json line-numbers&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;leaflet&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^1.7.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;react-leaflet&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^3.2.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 文章简洁起见，下文中用到的依赖也在这里一并列出&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;pixi.js&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^6.2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;leaflet-pixi-overlay&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^1.8.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;@inlet/react-pixi&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^6.6.5&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随后写一段最简单的 Demo 代码：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; MapContainer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TileLayer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Marker &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react-leaflet&apos;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;leaflet&apos;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.505&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.09&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;MapContainer center&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; zoom&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; style&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;100vw&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;100vh&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;TileLayer
        attribution&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&amp;amp;copy; &amp;lt;a href=&quot;http://osm.org/copyright&quot;&gt;OpenStreetMap&amp;lt;/a&gt; contributors&apos;&lt;/span&gt;
        url&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&quot;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Marker position&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; icon&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divIcon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;我是 div icon&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;MapContainer&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;配置过程中的注意点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不要忘记引入 Leaflet 的 css：&lt;code class=&quot;language-text&quot;&gt;@import &apos;~leaflet/dist/leaflet.css&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;React Leaflet 源码中使用到了 &lt;code class=&quot;language-text&quot;&gt;??&lt;/code&gt; 语法，需要额外为 babel 配置 &lt;code class=&quot;language-text&quot;&gt;@babel/plugin-proposal-nullish-coalescing-operator&lt;/code&gt; 插件&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dd8c6ad1bed12bb589d6f35bbc7f104f/5bf79/base.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.26108374384237%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB/UlEQVR42gXByXLTMAAAUP//H3CCC8x0BpjQ0iyNt8TxLlm2bC2WZMdrnJCQlnY6hVN5T6uFNXXwUIGWrFtijXXEWSCp2zCIHX2PN1RhmG5FbP4qvCMPhz3Le+jnVpjutJPwzlyvuKmE3xXWqOCeGk2+Zf6ChMajgiPaqnRT81TVPCFe1eOQBRCt+7HUDszo9+Gxiy49uDZ4YKAX+Z6LJo9OCg3APmP/3JCpzCaJj73g1Pdss2GR6mqtk15FPZ6abgZDz9oEUCp+ai5l+SblS9u+ivqV8hclX/69/X1/f2+b3tF1tl0dwEarM9227pfullY89nQA/QyFgV9kaFDN0zhdr7+fT8fr+XQ5Xp8f/zzVh2GBmp0fUc/Qhi7cALAyf8LtzHeWzvIri25/fPg0v/mSBKYbEz1TPiUPQfrZRpAA0cOEJTOfoSzWJmlDDBzrHrrzA3djd30UlvHxBscusr5L5JBaeVS6hBCOc8JiFsomu4MlZ0gTyg/KNMFzCmZVPs/sbyXfEd/ugTES0w+teLeUJAH4QdK4LIQUaVFVd4lUMtGqPYiiVRCtVR3yigTLWQk3ZxF2ntGWRdfmMLQdc+mVMBZ6U+yKViDR3EJVUKgxulsYa6nKQ51OaEdCk+VBWatesgtD04DHUdWS6IvbBDwotGo46EeyKqoY5/8BxZ7sAC76GkUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;base&quot;
        title=&quot;&quot;
        src=&quot;/static/dd8c6ad1bed12bb589d6f35bbc7f104f/d7542/base.png&quot;
        srcset=&quot;/static/dd8c6ad1bed12bb589d6f35bbc7f104f/2efce/base.png 203w,
/static/dd8c6ad1bed12bb589d6f35bbc7f104f/1d180/base.png 405w,
/static/dd8c6ad1bed12bb589d6f35bbc7f104f/d7542/base.png 810w,
/static/dd8c6ad1bed12bb589d6f35bbc7f104f/5bf79/base.png 966w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;可以看到地图和标记已经成功显示了出来（我为标记增加了一些 CSS 样式，让它变得更明显）。通过开发者工具可以看到，标记就是一个普通的 div 元素。&lt;/p&gt;
&lt;p&gt;接下来，我们引入 Leaflet.PixiOverlay 。它的文档中展示了命令式的用法：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; map &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; pixiOverlay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pixiOverlay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;utils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// your drawing code here&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PIXI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Container&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

pixiOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不难看出 PixiOverlay 与 Leaflet 通过 &lt;code class=&quot;language-text&quot;&gt;map&lt;/code&gt; 对象连接在一起。而 React Leaflet 提供了 &lt;code class=&quot;language-text&quot;&gt;useMap()&lt;/code&gt; hook 供我们获取 &lt;code class=&quot;language-text&quot;&gt;map&lt;/code&gt; 对象。因此一切就顺理成章了：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PixiRoot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; map &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 获取 map 实例，来自 react-leaflet 的 MapContainer&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    pixiOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 不需要渲染任何东西，把它放在 MapContainer 组件内部即可&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们可以在 PixiOverlay 初始化的回调函数中调用 PixiJS 的 API 画图。当地图移动和缩放时，PixiOverlay 会调用此回调函数重新绘图。此外我们也可以调用 &lt;code class=&quot;language-text&quot;&gt;redraw()&lt;/code&gt; 方法手动触发重绘。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;leaflet-pixi-overlay&apos;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Must be called before the &apos;leaflet&apos; import&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;leaflet&apos;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PIXI&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;pixi.js&apos;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 构建 Pixi 对象树的层级&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PIXI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Container&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PIXI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;我是 Pixi Text&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xff1010&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pixiOverlay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pixiOverlay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;utils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; renderer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRenderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; project &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latLngToLayerPoint
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; coords &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.505&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.08&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 需要把经纬度转换为 canvas 上的坐标&lt;/span&gt;
  text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; coords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x
  text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; coords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y
  text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;我是 Pixi Text &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  renderer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在上面的例子中，我们绘制了一个 PIXI.Text，并在每次触发重绘时随机改变它的内容。我们可以通过开发者工具看到它确实是通过 PixiJS 使用 WebGL 渲染到 Canvas 上的。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5dfa75b2436764d9b627e2a1ca5cfa5f/47218/pixi.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.21674876847291%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAABYlAAAWJQFJUiTwAAACEUlEQVR42hWQS2+bQACE+dW9V6rURmpz6D23qlKVW+qqqZwa1zg2JmBwwEAggLGBBZaF3eWNk7quNDP6DnOZYRxt5j1xjjbXl6yjzhWFs2UusNa2q0AKT6cTyWmNYJ2ROiV1RqsEVylpcdNVLRPb4rnqbpaByqHHWbRdeLpkG5KqTg++2/c9Rij2QQ6SAsACxDjOcJLiGCEAmYfVQtUNx3r0DHk7GxnyH02caKu7xWSy8/Zt1+Ywz0BKCkwLQvL/SQktMS3LkuG524fxzfTqajmbKyynriVhIYwXomDrIYSnv68NrYe2fun6Y9sNbXdsujN3Tdc2HSOL42DyNXn7Rv11bdx+lyRFYFmV5zVbCCOnQKhAKUYZjEEBU5TEZyVRkEQAQcRo/E9LustmI9/k1zK3FqayvbRMWTO3XhhiTOu6adq267qz+2Go6jpN0+x8RBIzJs+qyr33tI01OTxoW1VkF2Ntc7uz1/vQhxAdovS8l9CqH46UlhBmOS7rps3zgnGfNFNb+QdnY6xcfYn2pr5RfoxuFOl3nuwS4IKDAePnFNglhYTAqsxBBv0YQkIZd++Y+tzwV3Kk73wlM8TYfuZFYTqfRFFAoIWhXuVWQ3ZDi14G/HrEVQUxSbu+ZHxLVUVu5xsgsP3DJghtQxbsxwdVvi8hYL+NLt69v7z4ePnh05fPV/0ua4qyLSrqZUNe/QOc5gk/TeObYQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;pixi&quot;
        title=&quot;&quot;
        src=&quot;/static/5dfa75b2436764d9b627e2a1ca5cfa5f/d7542/pixi.png&quot;
        srcset=&quot;/static/5dfa75b2436764d9b627e2a1ca5cfa5f/2efce/pixi.png 203w,
/static/5dfa75b2436764d9b627e2a1ca5cfa5f/1d180/pixi.png 405w,
/static/5dfa75b2436764d9b627e2a1ca5cfa5f/d7542/pixi.png 810w,
/static/5dfa75b2436764d9b627e2a1ca5cfa5f/d56b5/pixi.png 1215w,
/static/5dfa75b2436764d9b627e2a1ca5cfa5f/47218/pixi.png 1344w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PixiJS 也提供了 Devtools 用于查看 PixiJS 对象树。但有个坑点是，它是通过检测 &lt;code class=&quot;language-text&quot;&gt;window.PIXI&lt;/code&gt; 对象来判断页面是否使用 PixiJS 的，因此在现代前端项目中，你需要在代码中手工定义 &lt;code class=&quot;language-text&quot;&gt;window.PIXI = PIXI&lt;/code&gt; 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在这个例子中可以看到，我们刚开始就把 PixiJS 的对象树给建立好了，而每次重绘只是修改了 Text 的属性。但在真实项目中，对象树的层级、以及涉及子元素的增删改会复杂许多。如果每次的变化都需要我们手动来管理，还是很麻烦的。这其实和我们手动操作 DOM 树遇到的问题是一样的，也就是为什么我们需要用声明式的语法来写 PixiJS 。&lt;/p&gt;
&lt;p&gt;那么下面就来继续探索如何结合 React Pixi 。&lt;/p&gt;
&lt;h2&gt;结合 React Pixi&lt;/h2&gt;
&lt;p&gt;结合 React Pixi 就没有前面那么容易了。或许我们会想能不能直接把 React Pixi 的内容渲染到 &lt;code class=&quot;language-text&quot;&gt;MapContainer&lt;/code&gt; 内部：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;MapContainer &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;TileLayer &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* Stage 是 React Pixi 的根节点 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Stage width&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; height&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Sprite
        image&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/693612/coin.png&quot;&lt;/span&gt;
        scale&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        anchor&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        x&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        y&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Stage&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;MapContainer&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但运行后会发现 React Pixi 的内容被 Leaflet 地图遮挡，且并不会随着地图的平移和缩放来改变自己。这是因为，Leaflet 的图层体系帮助我们做了这些事情，而这并不是我们简单把东西放在 Leaflet 的层级内部就能享受到的。&lt;/p&gt;
&lt;p&gt;通过审查元素，可以发现用这种方式创建的 Canvas，完全游离在 Leaflet 的体系之外：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;纯文本&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-纯文本 line-numbers&quot;&gt;&lt;code class=&quot;language-纯文本&quot;&gt;leaflet-container
├─ leaflet-map-pane
│  ├─ leaflet-tile-pane         &amp;lt;-- 地图图块所在层级
│  ├─ ... 省略其他层级
│  └─ leaflet-overlay-pane
│     └─ leaflet-pixi-overlay   &amp;lt;-- Leaflet.PixiOverlay 所在层级
│        └─ canvas              &amp;lt;-- Leaflet.PixiOverlay 创建的 Canvas
├─ leaflet-control-container
└─ canvas                       &amp;lt;-- React Pixi 创建的 Canvas&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;实践中还要注意的一个类似问题：React Pixi &amp;#x3C;Stage&gt; 之下的组件是渲染在另一个 Root 下面的。因此假如配合全局 Provider 使用（例如使用 Redux、MobX 等全局状态管理工具），需要在 &amp;#x3C;Stage&gt; 内部再套一层 &amp;#x3C;Provider&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;既然如此，我们不难想到两种解决途径。第一种方法是尽量融入 Leaflet 的体系，想办法把 React Pixi 创建的内容渲染到 Leaflet 内部的 Canvas 中。第二种方法则是维持 React Pixi 的用法不变，但把 Leaflet 层级交互的相关逻辑抽取出来，手动控制 Canvas 位置和缩放与 Leaflet 地图同步。&lt;/p&gt;
&lt;p&gt;理论上两种方法都是可行的，那就得看哪种方法做起来更方便。通过一番查找文档后，我看到 React Pixi 提供了一个&lt;a href=&quot;https://reactpixi.org/render&quot;&gt;独立的 render 方法&lt;/a&gt;，可以绕过整个体系，直接把 JSX 格式的代码渲染到一个 PIXI.Container 中。这使得实现上述第一种方法的路径一下子清晰了起来。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Text text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello World&quot;&lt;/span&gt; x&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; y&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;那么这个 &lt;code class=&quot;language-text&quot;&gt;render&lt;/code&gt; 方法做了什么呢？一开始我以为它的作用是渲染当前 JSX 对应的画面，但把它放到代码中，调用之后却一点反应都没有。是我的姿势不对吗？文档里已经没有更多信息了，只好打开源码一看究竟。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;render&lt;/code&gt; 方法的源码十分简单，核心逻辑就是一行代码：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;callback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  PixiFiber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; root&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;继续查看 &lt;code class=&quot;language-text&quot;&gt;PixiFiber&lt;/code&gt; 的实现，这次则更为简单：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; PixiFiber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Reconciler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hostconfig&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;虽然似乎什么有用的信息都没有，但当看到 &lt;code class=&quot;language-text&quot;&gt;Reconciler&lt;/code&gt; 这个词时想必心里已经明白了七七八八。对 React 架构稍有了解的人都会知道 Reconciler 是 React 的一个重要部分，负责 Fiber 树的生成和更新。因此我们可以合理推测 React Pixi 的 &lt;code class=&quot;language-text&quot;&gt;render&lt;/code&gt; 方法做的也是类似的事情，即将 JSX 转换为 PixiJS 的对象树（官方称为 &lt;a href=&quot;https://pixijs.io/guides/basics/scene-graph.html&quot;&gt;Scene Graph&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;既然已经生成了 Scene Graph，接下来直接调用真正的渲染器将其渲染为画面就完事了。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PIXI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Container&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pixiOverlay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pixiOverlay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;utils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; renderer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRenderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; project &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latLngToLayerPoint
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; coords &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.505&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.08&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 将 JSX 渲染为以 container 为根节点的 PixiJS 对象树&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Text x&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;coords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; y&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;coords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;我是 Pixi Text&apos;&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 将 PixiJS 对象树真正渲染为画面&lt;/span&gt;
  renderer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;成功了！&lt;/p&gt;
&lt;p&gt;（图和上面一样，就不放了）&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;不过能渲染 JSX 只是成功的第一步。对比于理想情况，我们还希望彻底把 &lt;code class=&quot;language-text&quot;&gt;pixiOverlay&lt;/code&gt; 封装起来，不被外界感知；以及让 React Pixi 的 JSX 和正常的 React 组件一样，自由地使用各种各样的 Hooks。&lt;/p&gt;
&lt;p&gt;我们依然把复杂度封装在上节中已经创建的 &lt;code class=&quot;language-text&quot;&gt;PixiRoot&lt;/code&gt; 组件中：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PIXI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Container&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PixiRoot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; children &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  
  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;现在这个组件感知它内部的 children 即 React Pixi 的 JSX。当 children 发生改变时，我们通过副作用将其转变成对应的 PixiJS 对象树。&lt;/p&gt;
&lt;p&gt;回到 React Pixi 的源码，我们可以看到，在生成新的对象树的 Diff 算法中，如果检测到更新，就会抛出名为 &lt;code class=&quot;language-text&quot;&gt;REACT_PIXI_REQUEST_RENDER&lt;/code&gt; 的自定义事件，明显是为了请求触发重新渲染：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;appendInitialChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dispatchEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;__REACT_PIXI_REQUEST_RENDER__&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;appendInitialChild&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因此我们可以监听这个事件来触发画面的重新渲染。之所以不在上一步调用 &lt;code class=&quot;language-text&quot;&gt;render&lt;/code&gt; 方法之后直接调用重新渲染，是因为 children 内部子元素的变化并不会触发父元素的更新，但自定义事件是始终会触发的。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; requestUpdate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  pixiOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redraw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;__REACT_PIXI_REQUEST_RENDER__&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestUpdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;__REACT_PIXI_REQUEST_RENDER__&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestUpdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;requestUpdate&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上面的代码在监听到自定义事件后立即调用了 &lt;code class=&quot;language-text&quot;&gt;redraw&lt;/code&gt; 方法，进而触发 &lt;code class=&quot;language-text&quot;&gt;pixiOverlay&lt;/code&gt; 的回调函数。在回调函数中我们只需保留 &lt;code class=&quot;language-text&quot;&gt;renderer.render(container)&lt;/code&gt; 这一句，将其真正渲染为画面就可以了。&lt;/p&gt;
&lt;p&gt;这样做可行，但存在着重复渲染的问题。如果 Diff 过程中 &lt;code class=&quot;language-text&quot;&gt;REACT_PIXI_REQUEST_RENDER&lt;/code&gt; 事件被多次触发，那么就会调用多次 &lt;code class=&quot;language-text&quot;&gt;redraw&lt;/code&gt;，造成资源浪费。一般在这种重绘图和动画的应用中，我们会使用 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 来实现渲染逻辑，以使得渲染逻辑与屏幕刷新保持步调一致。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;想用好 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 并不是一件简单的事。无论新手还是老手，我都推荐阅读&lt;a href=&quot;/p/javascript-game-loop/&quot;&gt;我之前翻译的一篇文章&lt;/a&gt;，它非常详细地讲解了你可能会遇到的问题和解决方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PixiJS 把原生的 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 封装为了 Ticker 模块。我们可以维护一个是否需要重绘的 flag，在每次 tick 时通过判断 flag 再真正触发重绘。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 判断是否需要重绘的 flag&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;needsRenderUpdate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setNeedsRenderUpdate&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 当触发 __REACT_PIXI_REQUEST_RENDER__ 事件时，不立即重绘，而是点亮 flag&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; requestUpdate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setNeedsRenderUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 在每次 tick 时判断 flag 来决定是否真正触发重绘&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; renderStage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;needsRenderUpdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setNeedsRenderUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    pixiOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redraw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;needsRenderUpdate&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ticker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PIXI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared
  ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;autoStart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;renderStage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;__REACT_PIXI_REQUEST_RENDER__&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestUpdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;renderStage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;__REACT_PIXI_REQUEST_RENDER__&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestUpdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;renderStage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestUpdate&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样一个相对完善的渲染逻辑就完成了。让我们写个简单的 Demo 来验证下效果是否与我们的预期一致：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MyContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;flag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setFlag&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Container x&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;65506&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; y&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;43586&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; interactive&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; click&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setFlag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;val&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Text text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;flag &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pixi Text&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Text Changed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;flag &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Sprite y&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; image&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/693612/IaUrttj.png&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Container&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;MapContainer &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;TileLayer &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PixiRoot&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;MyContent &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;PixiRoot&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;MapContainer&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/2ab8f9c9ac98ac7dc9018b01b92d18b3/3.gif&quot; alt=&quot;Demo&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们终于把复杂度隐藏了起来，使得 React Leaflet 与 React Pixi 的结合就像写原生 React 那样自然，并且可以自由地使用 Hooks 等能力。我们成功了！但还有最后一点遗憾，在上例中为了演示方便，组件的坐标和缩放的值都是写死的奇怪的值。这是因为，在 &lt;code class=&quot;language-text&quot;&gt;pixiOverlay&lt;/code&gt; 被封装起来之后，我们没有办法调用它的投影函数动态地把经纬度转换为 Canvas 坐标了。这在真实项目中可不能接受。下一节我们就来解决这个问题。&lt;/p&gt;
&lt;h2&gt;注入上下文&lt;/h2&gt;
&lt;p&gt;我们需要向子组件中注入一些诸如坐标转换、缩放等必要的信息与工具函数。不难想到可以用 React 的 Context 机制配合自定义 Hook 来完成。下面就分别来实现它们。&lt;/p&gt;
&lt;h3&gt;坐标转换&lt;/h3&gt;
&lt;p&gt;对于地图上的物体，我们主要关心它的经纬度，但 PixiJS 需要的是它们在 Canvas 上的坐标。Leaflet.PixiOverlay 提供了 &lt;code class=&quot;language-text&quot;&gt;utils.latLngToLayerPoint&lt;/code&gt; 函数帮助我们完成这一坐标转换。另外要注意的是，PixiJS 元素的坐标是相对于它们的父元素的，这就要求我们自顶向下地逐级计算每个元素的坐标，再减去父元素的坐标。&lt;/p&gt;
&lt;p&gt;基于以上原则，可以设计出坐标转换的 Hook：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useProject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;latlng&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parentPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// project 就是 utils.latLngToLayerPoint&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; project &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; myPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;latlng&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 返回值就是应该被填入 PixiJS 元素的 xy 值&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;myPosition&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; parentPosition&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; myPosition&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; parentPosition&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;缩放感知&lt;/h3&gt;
&lt;p&gt;默认情况下，Leaflet.PixiOverlay 已经为我们做好了缩放的逻辑，当地图缩放时，上面绘制的物体也会随之缩放。但有时我们希望地图上的标记在地图缩放时保持大小不变，这就需要能感知当前的缩放并作反向的响应。&lt;/p&gt;
&lt;p&gt;我们可以在组件中维护当前的缩放值。当地图发生缩放时，必定会触发 &lt;code class=&quot;language-text&quot;&gt;pixiOverlay&lt;/code&gt; 的回调，那么正好在回调中更新最新的缩放值。为了做到这一点，我们把 &lt;code class=&quot;language-text&quot;&gt;pixiOverlay&lt;/code&gt; 也放到 &lt;code class=&quot;language-text&quot;&gt;PixiRoot&lt;/code&gt; 组件中：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// in PixiRoot Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setScale&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pixiOverlay&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PIXIOverlay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;utils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; renderer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRenderer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 更新当前的 scale&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setScale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 本职工作：渲染画面&lt;/span&gt;
  renderer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后提供一个 Hook 获取最新缩放值：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useScale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; scale &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; scale
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;useTick&lt;/h3&gt;
&lt;p&gt;我们往往需要做一些动画效果。上文已经提到，可以使用 PixiJS 的 Ticker 模块。在 React 中，为了更加方便地使用，可以将 Ticker 的命令式 API 封装为 &lt;code class=&quot;language-text&quot;&gt;useTick&lt;/code&gt; Hook。&lt;/p&gt;
&lt;p&gt;React Pixi 中已经提供了这个 Hook，但是它使用的 ticker 来源于它内部自己管理的 PIXI.Application 实例。我们并没有采用 React Pixi 的全套体系，也就没有这个 Application 实例，也就不能拿它的 Hook 直接用。&lt;/p&gt;
&lt;p&gt;但不用担心，这个所谓的 Application 并没有什么魔法，&lt;a href=&quot;https://pixijs.io/guides/basics/architecture-overview.html&quot;&gt;只是对 PixiJS 的 Loader、Ticker 和 Render 模块的简易封装&lt;/a&gt;。我们依旧可以单独使用它的 Ticker 模块：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useTick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ticker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PIXI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared
    ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;callback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;callback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;callback&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不过，在实际使用 &lt;code class=&quot;language-text&quot;&gt;useTick&lt;/code&gt; 时，我们往往会传入内联函数，即 &lt;code class=&quot;language-text&quot;&gt;useTick(() =&gt; {...})&lt;/code&gt;，进而导致 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 被频繁触发。第一种解决方案是通过 &lt;code class=&quot;language-text&quot;&gt;useRef&lt;/code&gt; 维护对最新 callback 的引用。这是一种&lt;a href=&quot;https://zh-hans.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback&quot;&gt;写在 React 官方文档上，但并不被推荐&lt;/a&gt;的做法，不过可以减轻调用者的心智负担。React Pixi 源码中采用的就是这种做法。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useTick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; savedRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    savedRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; callback
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;callback&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  
  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ticker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PIXI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;tick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;delta&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; savedRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ticker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;delta&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ticker&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tick&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      ticker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tick&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;第二种方案则是保持我们先前的实现不变，但调用者必须小心地采用 &lt;code class=&quot;language-text&quot;&gt;useCallback&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;useReducer&lt;/code&gt;（当 &lt;code class=&quot;language-text&quot;&gt;useCallback&lt;/code&gt; 的依赖经常发生变化时）保持传入 &lt;code class=&quot;language-text&quot;&gt;useTick&lt;/code&gt; 的 callback 不频繁改变。&lt;/p&gt;
&lt;p&gt;这是一个 Hooks 使用中的经典问题了。社区中已经有不少文章（如&lt;a href=&quot;https://zhuanlan.zhihu.com/p/98554943&quot;&gt;这篇&lt;/a&gt;或&lt;a href=&quot;https://zhuanlan.zhihu.com/p/56975681&quot;&gt;这篇&lt;/a&gt;）探讨这一话题，本文不作展开。为了保持与 React Pixi 行为的一致性，目前采取了第一种方案。&lt;/p&gt;
&lt;h3&gt;把它们放在一起&lt;/h3&gt;
&lt;p&gt;由于坐标转换和缩放的 Hook 都用到了 Context，我们需要在调用 React Pixi 的 &lt;code class=&quot;language-text&quot;&gt;render&lt;/code&gt; 方法时为 children 套上一层 Provider：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; project &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pixiOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latLngToLayerPoint
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; provider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PixiOverlayProvider value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;project&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;PixiOverlayProvider&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;provider&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pixiOverlay&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后用一个综合性的例子收尾，利用各个 Hook 创建一个不断平移的小图标，并在缩放中保持图标大小不变：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MyContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;containerX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; containerY&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useProject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.09&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;markerOffset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setMarkerOffset&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;markerX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; markerY&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useProject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;51.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.09&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; markerOffset&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;containerX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; containerY&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useScale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;direction&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setDirection&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;useTick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;delta&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setMarkerOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; val &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; delta &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; direction &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markerOffset &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setDirection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markerOffset &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setDirection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Container x&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;containerX&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; y&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;containerY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Sprite x&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;markerX&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; y&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;markerY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; anchor&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; image&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/693612/IaUrttj.png&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Container&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/97eca8cc96ea84417b629d88fb7c2387/demo.gif&quot; alt=&quot;Demo&quot;&gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;虽然看上去我们沿着很顺畅的思路完成了这一切，最后的代码量也不多，回头看去也不难，但当中经历的方向不明时的探索和纠结，也只有自己才知道。在这过程中也对一些 React 的机制有了更深的理解。正所谓纸上得来终觉浅，绝知此事要躬行。&lt;/p&gt;
&lt;p&gt;Leaflet 这块在整个大前端中应该算是比较小众的领域，如果正好有做这方面开发的朋友们，希望这篇文章能为你们起到帮助。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[存在主义咖啡馆：自由、存在和杏子鸡尾酒]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/at-the-existentialist-cafe/</link><guid isPermaLink="false">https://maliut.space/p/at-the-existentialist-cafe/</guid><pubDate>Sat, 09 Oct 2021 13:16:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/23b1349fdfeffbb6f9b58400361170f1/47311/p1100522.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 137.93103448275863%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAcABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQCAQX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABs7PRBgVZrVC9Af/EABsQAAICAwEAAAAAAAAAAAAAAAECABEDEBIU/9oACAEBAAEFAmbkXFuHD0WJAGVCOIUueZJUrX//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAcEAADAAEFAAAAAAAAAAAAAAAAARExAhAgIVH/2gAIAQEABj8CsonIdjb1HpmbQxw//8QAHhAAAwACAQUAAAAAAAAAAAAAAAERMWEhQXGBofH/2gAIAQEAAT8hzpHSiZ0bhggpTZRGvGhDi2LLwMe/oibRp6Zb6JEqwLuf/9oADAMBAAIAAwAAABAs9wD/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAdEAEBAQACAgMAAAAAAAAAAAABEQAhMUFRYXGR/9oACAEBAAE/EKQ8gOQe5jqVSmz7wSoHtfjIvG6HTIqsTghYfOuBVjwuitP63CCojRwSx+4qrPE6N6A+yYQ7P3v/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;p1100522&quot;
        title=&quot;&quot;
        src=&quot;/static/23b1349fdfeffbb6f9b58400361170f1/627f4/p1100522.jpg&quot;
        srcset=&quot;/static/23b1349fdfeffbb6f9b58400361170f1/efcb5/p1100522.jpg 203w,
/static/23b1349fdfeffbb6f9b58400361170f1/02e66/p1100522.jpg 405w,
/static/23b1349fdfeffbb6f9b58400361170f1/627f4/p1100522.jpg 810w,
/static/23b1349fdfeffbb6f9b58400361170f1/47311/p1100522.jpg 1080w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;趁着这次十一期间的空闲时间看完了本书。好几年前通过未读图书偶然知道了这本书，但它一直被我放在购物车里吃灰。直到这次在微信读书上看到，才意识到距它的出版已经过去了这么多年，以及它的流行程度已经远远超出了我的预料。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;作为一个“民间哲学爱好者”，我一向不愿意在公开场合发表一些观点，就像谁愿意认真看民科的文章呢？它更有可能带来的是误读而非真正有价值的启发。&lt;/p&gt;
&lt;p&gt;我也很久没有看过一些“哲普”书籍，因为就我之前的观感，很多这样的读物带入了太多作者自身的“私货”，参考性并不强。还有些读物带着哲学的名字，实则贩卖着心灵鸡汤，属实是对哲学的矮化。&lt;/p&gt;
&lt;p&gt;正如学习一门新技术的最高效方法，是直接去阅读官方文档，同样在学习哲学时也有很多人推荐学习原著。但对于像我这样一个几乎没有受过科班训练的单纯爱好者，在希望进一步登堂入室时，往往会遇到一些困难。&lt;/p&gt;
&lt;p&gt;首先，哲学不同于数学、物理学等学科。后者们往往拥有一套自洽的理论体系，从古至今的所有学科发展都是对这一套理论体系的发展和去伪存真，由此逐渐构建起整个学科的理论大厦。而我们学习的过程便是在这座大厦中拾级而上，每一层的知识都对更上层的学习有所助益。&lt;/p&gt;
&lt;p&gt;而哲学的体系——当然不同时期不同人的哲学之间也有脉络和传承，但——至少没有那么强。当一个哲学家面对概念上的困难，与学界前辈思想的微小不同，他往往倾向于定义出一套属于自己的、新的名词及话语体系。因此比起其他理科的同一套理论体系，哲学看上去就是由好多套相互交融但又排斥的体系所组成。&lt;/p&gt;
&lt;p&gt;又因为哲学不像其他理科有着明确的对错之分，我们很难决定如何去扬弃历史上的各个理论。而理论与理论们不断地堆叠，注定使得后来者想要学会“用哲学的语言说话”将会越来越难。&lt;/p&gt;
&lt;p&gt;其次，也是因为哲学中对名词和概念的敏感性，导致对这个领域知之甚少的读者很难顺利地把哲学书读下去。即使硬着头皮读了下去，也难以知道自己的理解究竟是不是原作者的本意。何况原作者把自己的思想用文字表达出来已经经过了一重损失，而翻译又经过了第二甚至第三重损失。&lt;/p&gt;
&lt;p&gt;如果说讨论呢？在电子书的笔记区常常能看到一些明显的理解谬误，可悖论也在于此：你怎么知道它一定是错误的，或是其实它是对的，只是我没能理解这位读者写下的话呢？&lt;/p&gt;
&lt;p&gt;另外，哲学家们自己的思想，往往也是在不断地变化之中。我们倘若仅仅阅读某一哲学家的代表作，也只能代表作者在某一时间的某一思想的侧面。某种意义上也是对这位哲学家的误读与偏见。&lt;/p&gt;
&lt;p&gt;再次，正如“哲学就是哲学史”（虽然大佬考据出来似乎这句话与黑格尔的原意相距甚远）所示，一个人的思想，不可能不被当时的整个历史环境所影响，尤其是对于存在主义这一相对而言更加形而下、更加入世的理论（更不必说它的发展处在二战这一极端动荡的时期）。如果只是离散地去读各个哲学家，而不去了解他们各自所处的历史环境与思想传承的脉络，也会对理解他们的思想平添困难。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a74cc1c13addc4b996a863bb6462c433/47311/p1100531.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.8768472906404%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAEEAgP/xAAXAQADAQAAAAAAAAAAAAAAAAAAAQID/9oADAMBAAIQAxAAAAHrK58ryIH/AP/EABoQAAIDAQEAAAAAAAAAAAAAAAABAgMTERL/2gAIAQEAAQUC8cg6YuOA6x1szZ//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwGn/8QAGxAAAgIDAQAAAAAAAAAAAAAAAAERIRASMkH/2gAIAQEABj8CjYtqTpHhSx//xAAdEAADAAEFAQAAAAAAAAAAAAAAARFBITFRcaGx/9oACAEBAAE/IYgxyzUSb6SElKHdjunIvT//2gAMAwEAAgADAAAAEEsv/8QAFhEBAQEAAAAAAAAAAAAAAAAAEQBh/9oACAEDAQE/EAjL/8QAFxEBAAMAAAAAAAAAAAAAAAAAABEhYf/aAAgBAgEBPxCydf/EABkQAQEBAQEBAAAAAAAAAAAAAAERACExUf/aAAgBAQABPxBNWIp1xAEKpXIKHt8cFCwvBrjmStRODvveW9//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;p1100531&quot;
        title=&quot;&quot;
        src=&quot;/static/a74cc1c13addc4b996a863bb6462c433/627f4/p1100531.jpg&quot;
        srcset=&quot;/static/a74cc1c13addc4b996a863bb6462c433/efcb5/p1100531.jpg 203w,
/static/a74cc1c13addc4b996a863bb6462c433/02e66/p1100531.jpg 405w,
/static/a74cc1c13addc4b996a863bb6462c433/627f4/p1100531.jpg 810w,
/static/a74cc1c13addc4b996a863bb6462c433/47311/p1100531.jpg 1080w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;面对上文的这些问题，《存在主义咖啡馆》一书则交出了较好的答卷。如果用一句话来概括，这本书可以被称为存在主义哲学家们的群像剧。胡塞尔、海德格尔、萨特、波伏娃、加缪等等，一系列耳熟能详的人名，虽然在之前我已经很熟悉他们的名字和主要著作，但他们在我的脑海里也只是一个个孤立的点，也说不出他们的观点究竟代表了什么。但在这本书中，作者把他们真正串联了起来，使我对他们的思想脉络有了更清晰的认识。同时，通过对他们的日常生活以及所处的历史环境的描写，也让这些哲学家们不再是一座座似乎遥不可及的偶像，而是一个个鲜活的人。&lt;/p&gt;
&lt;p&gt;作者对书中内容的安排也较为合理。书中讲述哲学概念的部分与哲学史的部分大概五五开，对于非专业的读者来说既不会显得过于晦涩，也没有让这本书变成完全没有营养的八卦。也得益于对历史的讲述，作者能够将不同哲学家之间的观点作比较阐释，使读者更好地把握概念本身以及它们的沿革。&lt;/p&gt;
&lt;p&gt;很多“哲普”书的作者喜欢自以为是地在书中加入很多奇妙的比喻和修辞，以为能帮助读者理解，实则给信息多添加了一层噪音。更不用说这些比喻是否正确，会不会反而让人误入歧途。而本书作者的写作语言平和，存在感适度，能够较为忠实地反映这些哲学观点和历史的本来面貌。即使在有些篇目中亲自现身称“我”如何如何，也能让读者轻松将事实与作者的观点区分出来。作者的话也并非强加观点给读者，而是为了引起读者进一步的思考。&lt;/p&gt;
&lt;p&gt;也许有人认为这本书讲的太浅薄，“不够哲学”。不过在我看来这本书已经很好地完成了知识普及与导读的作用。如果你有更进一步学习的诉求，完全可以通过书中提到的人名和著作名去按图索骥。而在看完这本书后再去学习那些经典名著，也能因为这本书带来的前置知识而让你更好地理解。&lt;/p&gt;
&lt;p&gt;推荐给所有感兴趣的读者。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[星露谷物语：我们无处逃离的“现代生活”]]></title><description><![CDATA[最早知道《星露谷》这款游戏的时候，它还在开发之中，而我正拿着手机模拟器沉迷《矿石镇》。后来也没有特别去关注它的发展，但当它的名字越来越频繁地在我耳边出现时，我知道它火出了预期，不过也没有去玩（虽然喜 +1 了）。直到最近想要重温此类游戏的乐趣时，考虑到 PC…]]></description><link>https://maliut.space/p/stardew-valley/</link><guid isPermaLink="false">https://maliut.space/p/stardew-valley/</guid><pubDate>Fri, 20 Aug 2021 12:19:00 GMT</pubDate><content:encoded>&lt;p&gt;最早知道《星露谷》这款游戏的时候，它还在开发之中，而我正拿着手机模拟器沉迷《矿石镇》。后来也没有特别去关注它的发展，但当它的名字越来越频繁地在我耳边出现时，我知道它火出了预期，不过也没有去玩（虽然喜 +1 了）。直到最近想要重温此类游戏的乐趣时，考虑到 PC 端、联机的因素，才真正打开它。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;我们选择一款种田游戏，往往是为了将自己从枯燥乏味的现实中抽离出来，进入一种田园牧歌式的生活。在这个世界中，我们才是自己的主人，我们用双手创造自己的生活，而游戏贴心地为我们省去了真正种田所需要的所有繁琐与劳累之处，并通过快速的反馈赋予了我们极大的成就感。&lt;/p&gt;
&lt;p&gt;在《星露谷》的开头，作者给予我们的便是这熟悉的动机。“我”作为一个 996 打工人，日复一日地重复着格子间的工作。而“我”所供职的 Joja 公司，是这个世界的垄断巨头，几乎控制了人们生活的一切。在工作中，“我”的时间被严格安排，“我”的一举一动都被监控，这使得员工更像是流水线上的机器。终于有一天，“我”决心不再忍受这样的生活，在得知祖父为“我”留下了一个庄园之后，便辞职回归农村，走上种田之路。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8d4e28d27bdab660c86f188dbf3fe363/07a9c/1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACRElEQVR42k1TX0sicRSdr+eDpVSGSWGZUJuZa6OmpZZlNs7UjoKvKhZE2EMLrmlihrlpYY9bi1/m7JwbxT5cLnPu+Z37d5RarYZOp4Ner4f7+3s8Pj6i3+/j+fn5ywaDgeC04XAo1mq1cHt7i3q9juvra1xeXiIWi0Eh8PDwgHa7jWaziW63KyIvLy8Yj8d4fX0V//vpSYRHo5H4m5sbNBoNVCoVlMtlXFxcYHd3F8r8/Dz8fj/i8TgikQiWlpawt7eHVColfj+9D9MwcXKiIXlwgIT1iJXs7OxgbW0Nc3NzUFUVoVAIDocDytTUlIgxQ6lUgsvlwtbWljyKqBGEo2GEcmGkU3H82FahhsMiFotGpZDFxUVwbLTV1VUodrsduq6jWq1iY2MDy8vLyOfzghm6Af1UR0j/juD2NxhqFKd6HnkrRk42mxV+2Epyfn4uhShOpxM+nw8ej0fKTyaTMAzDavFEvKblUdBNFMwCtLMzwZnsM04+u2Kl9ApF2Lvb7ZahknR4eCgPjo+Pxbc7bfx9f8dPaxHEcrmc4JlMRviJRAILCwuw2WxQZmZmwMVwsCSm0+kvwQNrCZqm4e6ujclkgsavpnAoxDh5XB6xqDXT6elpKCyVGY6OjmQuJFDk/wp5pxTk3XFu5BKnEFvmO3K5aSUYDApomqacCbMzyEf0NB7929sf61ZbluBHy59xVslRFYvFj6Vw1by9QCAgy+Es19fXwUTEeJv8E3jQV1dX8s345uamxL1eL1ZWVqS62dlZ/AMS8Tx2I/FQqwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;1&quot;
        title=&quot;&quot;
        src=&quot;/static/8d4e28d27bdab660c86f188dbf3fe363/d7542/1.png&quot;
        srcset=&quot;/static/8d4e28d27bdab660c86f188dbf3fe363/2efce/1.png 203w,
/static/8d4e28d27bdab660c86f188dbf3fe363/1d180/1.png 405w,
/static/8d4e28d27bdab660c86f188dbf3fe363/d7542/1.png 810w,
/static/8d4e28d27bdab660c86f188dbf3fe363/d56b5/1.png 1215w,
/static/8d4e28d27bdab660c86f188dbf3fe363/07a9c/1.png 1440w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;相信大家对这个主题都不陌生。但在很多同类游戏中，玩家选择了回归乡村，从此便开始了世外桃源般的生活。在这个新世界中，玩家与旧世界毫无瓜葛，一切都可以凭自己的意愿从头再来。但《星露谷》的作者显然不想放弃这个矛盾。虽然玩家抛弃了以 Joja 公司为代表的现代都市生活，但资本却并不是你想甩开就能甩开的。Joja 公司就把自己的触角也伸向了玩家用来避世的小镇，在镇子的一角建立起了 Joja 超市。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b945bbd0812b3a398667ce3bc5f86099/c1b63/2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAADJUlEQVR42hWRW0ybBQCFfxfr1I0glDpHoS0t3XoBW9Y7sJYitDR0UjRQCqVbC7ZLKmMMnG6lg2UZNLtEkg22BsdMvARXtqGwuOBYZcki0zhj4uXBdx+mD8ZozGKyz/rw5Tyc5Dx8RxBbH1Jmekix/hsGXRaSkW7Sc484PvMzH2/8y/IDWN58UsgnXC+wtAmzy49pj83QFJ4ieihNNh3ki2kv5wb2IugteQz2dYp199nuMaLr7+Cd7F9kPvmDs9ceMb/xJwvf/sP7X//N7MpvnMv9ztHLv5I8+jlzuce8t/oL1y4Mkz/t5VL0FYQd5hAiWwNiWw9iv4ES5x7KZIcpqb1ImXEGfd8ipiO3MCeX2d2aRWK5xIuKw4R7p1nIQzZ7j8XRAHOdZlIeK4KkLEHpCwmkkjjthgEiJg8N2jDG4BpN0RvsSy5h783S/EaOxtgK5sht6o1RetyDuJM/4gzeob5lCYfzA5y6KEJRqYMiiYNisQOl0Y3BXIdFH8bQdRNr8CNcb63iHJml/UAG/+CH1IVyuGwHaanpxJX6CXssT7nmKqXyeUpK3kZ4alc1wv+8rEZwqhA51Vg1LbS7Y9T5MwRGskyNJpgIRnh3aJxOz37ajG2Yq4PI2u5SabvJTtkZxNILiLfHEWTmauqMahptatp8u3AH1IS8zRzrDTO2P8JEoo+zQ68zfSjEZLyXkwMh4v4uvPYQTuc4dusEu3eGqSjqxlDejXCiXsVkgalGFVc6VCxFZcxF9jAa2MepUAuZA61kol7OxDyc7vdyosfH2Ks+Eo4Gghoz3ioTjeV2HDusuCrNCMqmKpR7FVS0ygkGVFyPV/HpsJwbbypYGjNxO6Xny3Ql+bSc9eMKbh2R81lSzshrSqR2PWKJhfJnTUhERoZ9hoLD5oqCt2qeLgwK3kpCXUpWh7TcOaZlPW3i3mQtD6a1bE5p+eqUlvzJGu6mdIzHdGypMbBVrWWrVcEWjYJUzI7wfKuUbWYV2xS1PFMoin1ycplu7l/uY23Sz3fnO/lhoZ/vL4ZYO9/HykKCjfmDLKY7Cq/aEGk1POeXInLLSERf4j+jF8qobHV/FgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2&quot;
        title=&quot;&quot;
        src=&quot;/static/b945bbd0812b3a398667ce3bc5f86099/d7542/2.png&quot;
        srcset=&quot;/static/b945bbd0812b3a398667ce3bc5f86099/2efce/2.png 203w,
/static/b945bbd0812b3a398667ce3bc5f86099/1d180/2.png 405w,
/static/b945bbd0812b3a398667ce3bc5f86099/d7542/2.png 810w,
/static/b945bbd0812b3a398667ce3bc5f86099/c1b63/2.png 1200w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;明显在小镇中他们也没有改变压榨员工的传统，以至于为了超市打工的 NPC 谢恩老是一副精疲力尽的样子，对话时也没什么好气。~~可他的工作时间好像也只是朝九晚五诶。~~不过 Joja 公司并不打算止步于此，他们打算进一步收购小镇的社区中心，将其改造为超市的仓库。&lt;/p&gt;
&lt;p&gt;社区中心是小镇上历史悠久的建筑，用镇长刘易斯的话说，“这里曾经是这个镇子的骄傲”，经常举办各种活动。但由于现在人们的懒惰，人与人关系的疏离，几乎都不出门参加活动，导致社区中心无人问津，年久失修，几乎沦为了一座废墟。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/487a0e8ff2768f6515570af6a07648a9/29114/3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAClklEQVR42iWSy0/aAACHq1nmC8UZjbyk0JZCC60UqAoMrDwLLa8iioiCgEVeogg+pnNbps5tmnnZ47C4G4ftsJ22ZH/GzvtjRrLbL1/y5bv8AM4ok0se6KeG7KDUh0yWF+Azwb3rRAt2Q4Zjtl3GugM5FdxNF5rAZeKcJobLVaMDBwx+toQCrElFyIbyFpWAa6781Pdc8O/P7o8Sd8vTB+nQh6T9c8H753f3VzV2wVrPgtT9Ku2FJxoxQyNiBBjduHF6sEgjnEk7b1U+j9u7zey75GLHYWxY4NacvumhXm4kLoob+ewq6mK9VtKpGqmFdHkfBKwQihwFvoqb6zECJuW1GNGtOL+1mCOWLDn0TQZrB7HzMHm3nz8UU/5I1Ldg5vDJPYf6PGgAfPKxnH6q5oSSVlAwgMuzYCeAfSzNNSK6gg/qxPD3RfqTuHC+TJS8UHFJXQ1hu9viCWfdXksDfkhSdshfCKZnMeJlGH/KGt+uWw4TWJmFKgHTTdb5teV6vUnVOLjOI1UeqQehuhNtL5IlGwKETeMJWtaO6q6y1N0WfZ2lOgImsnDJD12uUycFSy1lut2ypR0zjHGqx8UA9ITXvUmRp7wBmKctKkl/hYVyHlAMQllGnbQrd0Jwbx8K2GXGnH2sPYrjEfO0UzmWJBQVn5ZBpYRyhFRKgJV4mFSO1UPw0aq1zmMW+fDsjKQcRnbCSK+zFzXsRw3tmGGH1/NLoODWXqyYNI8GAADo6wOAjBWkFMPHLPplP3BfdTVYXd6rrbJQ1Q9WOKTiB8WAZt2uTts1EY8m40du1mY1E4M9ub8fAGidi5INF11a3q3upIjrTaoRQVPzskVo9JhDPDqpYJtephWp3r2ccM6mbYUQpfTh//I/ZdKvtFnAQxMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;3&quot;
        title=&quot;&quot;
        src=&quot;/static/487a0e8ff2768f6515570af6a07648a9/d7542/3.png&quot;
        srcset=&quot;/static/487a0e8ff2768f6515570af6a07648a9/2efce/3.png 203w,
/static/487a0e8ff2768f6515570af6a07648a9/1d180/3.png 405w,
/static/487a0e8ff2768f6515570af6a07648a9/d7542/3.png 810w,
/static/487a0e8ff2768f6515570af6a07648a9/d56b5/3.png 1215w,
/static/487a0e8ff2768f6515570af6a07648a9/0d292/3.png 1620w,
/static/487a0e8ff2768f6515570af6a07648a9/29114/3.png 1920w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;这里就需要玩家进行抉择。玩家可以选择帮助社区中心重焕生机。在这条路线中，玩家需要生产、收集游戏中各种各样不同的物品，献祭给废墟中的小精灵“祝尼魔”。每当献祭完成度达到一定程度后，就会解锁游戏中的新区域、新功能作为奖励。最终，在所有收集选项都完成后，社区中心会焕然一新，像是一座大别墅。大家齐聚一堂，其乐融融。而 Joja 超市则会受到大家的一致抵制，黯然离开小镇。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e3a31187fb01d5f11b61e766bbb3d4ef/01a9b/4.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.70935960591133%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECA//EABUBAQEAAAAAAAAAAAAAAAAAAAIE/9oADAMBAAIQAxAAAAHOLc9WYhH/xAAZEAEBAQADAAAAAAAAAAAAAAABAhEAISL/2gAIAQEAAQUCeiHeVOs+ipJaXf/EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAwEBPwFkf//EABURAQEAAAAAAAAAAAAAAAAAABBB/9oACAECAQE/Aaf/xAAYEAEAAwEAAAAAAAAAAAAAAAABABARIf/aAAgBAQAGPwLa7EYJX//EABsQAQEBAAIDAAAAAAAAAAAAAAERACExQWGB/9oACAEBAAE/IbWg8bkF+YbQPsx7CUwOxXSOXrf/2gAMAwEAAgADAAAAENjP/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARIf/aAAgBAwEBPxBBLDl//8QAGREBAAIDAAAAAAAAAAAAAAAAAQARITFB/9oACAECAQE/EB07LXM//8QAHRABAQEAAQUBAAAAAAAAAAAAAREAUSExQXGh8P/aAAgBAQABPxBMgQE96gW08CsnbSfBJd+GDhgX9xunDha4UCoWM3//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;4&quot;
        title=&quot;&quot;
        src=&quot;/static/e3a31187fb01d5f11b61e766bbb3d4ef/627f4/4.jpg&quot;
        srcset=&quot;/static/e3a31187fb01d5f11b61e766bbb3d4ef/efcb5/4.jpg 203w,
/static/e3a31187fb01d5f11b61e766bbb3d4ef/02e66/4.jpg 405w,
/static/e3a31187fb01d5f11b61e766bbb3d4ef/627f4/4.jpg 810w,
/static/e3a31187fb01d5f11b61e766bbb3d4ef/78959/4.jpg 1215w,
/static/e3a31187fb01d5f11b61e766bbb3d4ef/0047d/4.jpg 1620w,
/static/e3a31187fb01d5f11b61e766bbb3d4ef/01a9b/4.jpg 1710w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;但玩家也可以选择加入 Joja 超市的会员计划，支持将社区中心改造成 Joja 仓库。在这一路线中，翻新后的建筑外观也会变成和 Joja 超市一样，在小镇中显得刺眼的蓝色。而建筑内部依然维持了破旧的样子，里面仅有的设施都被拆除，取而代之的是成堆的箱子。游戏中的解锁奖励，在你走这条路线时依旧可以取得。只是获得的途径不再是生产、收集各种各样的物品，而是简单粗暴地 —— 通过金钱购买。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1aac54c221ad10bc4b047373c21bc81d/c1b63/5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.20197044334976%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACqklEQVR42gGfAmD9ADYgEVpdcFB/mkeZukeQtFeCnjw3L0cgD0cmHm42I6ZlTr2Xio5DLXdTQXtYRo5ELbiRhaxuWno7JXY/KwA3IA9OQ0tQVFZqkZZumZ9WWlk5JxhMMjc1Q4poR1uVXE6dgo1qXo92PzNyNCGrj5q/o6CZYExoSFVHM0YATisaUCsXUR0GXV9bX1xXTx0JWi0VVlB7Q27UTnPQXHbDinuEW3S8J0aZTT5GrKq/yK2ZoX1TNU2FITdyAD8ZE0YeGEEUDk5HRE1GQ0EUEFEgEpNlSF5rtFFlv0tgwn95j1FYkiA0eodpKV9gfYFwbaB9O29eNW1bMwBlSB5mSBxoRx10ViNzVSVpSB5zTR+fdDupf1Sid1WHXE2VdUR0WjOHZCbFjjCufkuZeEqNcC+CazCDbDIAgV0ll3QobEokXD8eYUMgXUAcSTATiWMirYAmtYMpnHUnlnIklXEenHQjxI455aFB0pU1imwzjGsmm3MhAFVPQ3dTJjtNXThMXDtQXz5MWSIVEWlRI35wRnNqUn9pLoNrLY9uJJNvIap9KrKEOGpgXjlIdoxyV51/TwBeOCA9Gw1eT0tbUlJVTE5jWlpBKBhFPDmBfotUarVaV01jbol+bUSRbyKScCV9aDcgOYBOXIVobpJkbYwAZF1BfWEkQktbE0KKJUZyLFGDOyIWT0tWjpXMiZDUeHBqR1d/Rzk+hl8bhWIiUzwnLEGAUWGHV2ecX2qPADVNbIhvMW5bMyZBbRRRlyFamywhG1dUc4aO0YqEpoZtQFZjjCwjJ2YzFWQzGzgmGDRAeERPdUlWh0lVegA2O1FhWEGIbi5aWUwKOocfRIMzJSJJLR1hQDVdOypWNhtQNy85JRaBPSd5MyYdEwoiFxYnGhcmGhglGRcISeYRRepjuAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;5&quot;
        title=&quot;&quot;
        src=&quot;/static/1aac54c221ad10bc4b047373c21bc81d/d7542/5.png&quot;
        srcset=&quot;/static/1aac54c221ad10bc4b047373c21bc81d/2efce/5.png 203w,
/static/1aac54c221ad10bc4b047373c21bc81d/1d180/5.png 405w,
/static/1aac54c221ad10bc4b047373c21bc81d/d7542/5.png 810w,
/static/1aac54c221ad10bc4b047373c21bc81d/c1b63/5.png 1200w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;如果是你，你会作何选择呢？如果是一个（经济学概念中的）理性人，那么不难得出结论：加入 Joja 会员计划是更好的选择。&lt;/p&gt;
&lt;p&gt;如果你选择自己去收集各种物品，你的游戏体验将会十分繁琐。你需要来自种田、放牧、挖矿、打怪等各种来源渠道的物品，有些物品还只能在特定的季节、天气下获取，有些甚至在游戏的中后期才能获得。你将不得不为了收集而疲于奔命，投入巨大的前期成本只为了一点点用于献祭的产出。&lt;/p&gt;
&lt;p&gt;而如果你选择通过金钱购买解锁奖励，那么你的目标就被简化成了如何赚钱。至于如何赚钱，你完全可以选择自己喜欢的方式，忽略不喜欢的玩法。专注于某项技能也会使你在该技能上的熟练度快速提高，让你更有效率地赚钱。这就是我们熟悉的现代生活，通过社会分工，提高生产的效率。&lt;/p&gt;
&lt;p&gt;但从感性的角度，我相信大多数人还是会选择为了修复社区中心而收集的玩法。一方面，各种收集的物品也是游戏的一部分，从体验游戏内容的角度，我们很乐意去尝试。另一方面，游戏中的“我”本就是为了逃离以 Joja 公司为代表的大资本对打工人的压迫与异化，而来到了这个小镇，为何最终又要卖身依附于它，与它们同流合污呢？&lt;/p&gt;
&lt;p&gt;可倘若再仔细想想，我们会意识到，做出这样的选择某种程度上也是出于开发者对我们的仁慈。在游戏中，Joja 超市的存在感实在有限。它坐落在小镇的角落，与我们的农场隔得几乎最远。超市中贩卖的东西，大多数也比镇子中镇民自家开的杂货店要来的贵些。这使得我们几乎没有去超市购买东西的理由，因此即使剧情最后 Joja 超市被逐出小镇，对我们也没有什么影响。&lt;/p&gt;
&lt;p&gt;但假如这一切反过来呢？如果超市开在交通便捷的镇中心，甚至就开在你家的旁边，如果超市可以 24 小时营业，如果超市卖的商品种类远多于个体户杂货店，并且价格愈发低廉，而质量更佳……你还能轻易地做出将超市逐出小镇的决定吗？而这，才是真实世界中的超市，真正拥有的样子。&lt;/p&gt;
&lt;p&gt;《星露谷》的作者给了我们一个浪漫的结局，可现实生活中远没有如此浪漫可言。我们想要逃离资本社会对我们的压迫与异化，但又享受着它为我们带来的种种方便；我们想要回到乡村，过一种田园牧歌般的生活，但又四体不勤，五谷不分；我们可以在游戏中逃避现实，可回到现实中却又不得不被裹挟着前进；我们看似自由，却又别无选择。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;《星露谷物语》是作者 Eric Barone 从 2012 到 2016 年，历时四年制作的独立游戏。游戏中的角色设计、文案、音乐与像素素材等，都完全由他一人独立完成。在他开始开发的 2012 年，独立游戏还是一个略显新颖的概念，国人登陆 Steam 的作品一只手就能数得过来。在游戏发布的 2016 年，独立游戏已成百花齐放，百家争鸣之势。而如今，独立游戏早已成为了一个烂大街的概念，成为了一个时髦的营销名词。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/84df782d8f097c8e23dfe975f6c073df/c58a3/6.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.497536945812804%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAHABQDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAT/xAAWAQEBAQAAAAAAAAAAAAAAAAAFAQP/2gAMAwEAAhADEAAAAaYDCVhA3//EABYQAQEBAAAAAAAAAAAAAAAAAAAhQ//aAAgBAQABBQKtY//EABgRAAIDAAAAAAAAAAAAAAAAAAIDECFx/9oACAEDAQE/ARth5H//xAAWEQADAAAAAAAAAAAAAAAAAAAEECH/2gAIAQIBAT8BEq//xAAYEAACAwAAAAAAAAAAAAAAAAABEBETcf/aAAgBAQAGPwIWmcf/xAAaEAACAgMAAAAAAAAAAAAAAAAAAREhcYGx/9oACAEBAAE/IYVQrPQfQ8j/2gAMAwEAAgADAAAAEIQ//8QAGBEAAgMAAAAAAAAAAAAAAAAAABEBMfD/2gAIAQMBAT8QmhlDP//EABgRAAIDAAAAAAAAAAAAAAAAAAEQEUHw/9oACAECAQE/ECkda//EABoQAAIDAQEAAAAAAAAAAAAAAAERADFBIdH/2gAIAQEAAT8QLcRUiAQKt9jY4uwiIQpP/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;6&quot;
        title=&quot;&quot;
        src=&quot;/static/84df782d8f097c8e23dfe975f6c073df/627f4/6.jpg&quot;
        srcset=&quot;/static/84df782d8f097c8e23dfe975f6c073df/efcb5/6.jpg 203w,
/static/84df782d8f097c8e23dfe975f6c073df/02e66/6.jpg 405w,
/static/84df782d8f097c8e23dfe975f6c073df/627f4/6.jpg 810w,
/static/84df782d8f097c8e23dfe975f6c073df/78959/6.jpg 1215w,
/static/84df782d8f097c8e23dfe975f6c073df/c58a3/6.jpg 1500w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;&lt;i&gt;Indie Game the Movie&lt;/i&gt; 中提及的具有代表性的独立游戏（从左至右）：&lt;i&gt;Super Meat Boy&lt;/i&gt;(2010), &lt;i&gt;Fez&lt;/i&gt;(2012), &lt;i&gt;Braid&lt;/i&gt;(2008)&lt;/p&gt;
&lt;p&gt;关于“独立游戏”的准确定义，一直以来都是一个容易引起争议的话题。但是在以前，鉴定一个游戏是否是独立游戏，并不真正成为一个问题。有人说，看到一款游戏就知道是不是独立作品了。因为这些作品都有着共同的特征：它们并不完美，也许存在着许多明显的缺陷，但有着浓郁的个人特色，而这往往使人欲罢不能。可现在，随着各个大厂、资本的入局，越来越多行业老兵们的加入，独立游戏这一领域下也逐渐“内卷”了起来。面对各方面都打磨得臻于完善的作品，我们很难再分辨它是不是一个“独立游戏”了。&lt;/p&gt;
&lt;p&gt;《星露谷》的作者，就好像《星露谷》中的“我”，也许并不在每个方面都擅长，但独自一人打造着自己向往的作品。而如今的巨头和资本，就好像《星露谷》中的 Joja 公司。它们嗅到了独立游戏市场的蓝海，蜂拥而至，在各方面资源的加持下，无论在数量和质量上都可以实现对个人的完全碾压。&lt;/p&gt;
&lt;p&gt;同样的故事曾经发生在工业界，如今它不仅发生在独立游戏界，还发生在微博、微信、b 站、抖音等等的内容领域。你以为你发现了一个宝藏素人，也许背后的是一整个 MCN 机构。你以为是你的兴趣选择了内容，实际上你看到的只是资本想让你看到的内容。&lt;/p&gt;
&lt;p&gt;如果《星露谷》放在现在才开始制作，那么又一个四年之后，它还能复制如今的热度吗……？&lt;/p&gt;
&lt;p&gt;至少是内容行业，作品与作品之间并不能严格地相互替代，一切皆有可能。所以等待，并心怀希望吧。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[不要再拿组合式 API 与 React Hooks 来对比了！]]></title><description><![CDATA[在网上能看到很多人在谈论 Vue 的组合式 API（Composition API）与 React Hooks 时，将两者一起拿来比较，并认为随着这两者的推出，React 和 Vue 变得越来越像，甚至还引发过一些不大不小的撕逼。 诚然，Vue 的组合式 API…]]></description><link>https://maliut.space/p/composition-api-not-hooks/</link><guid isPermaLink="false">https://maliut.space/p/composition-api-not-hooks/</guid><pubDate>Sat, 14 Aug 2021 23:30:00 GMT</pubDate><content:encoded>&lt;p&gt;在网上能看到很多人在谈论 Vue 的组合式 API（Composition API）与 React Hooks 时，将两者一起拿来比较，并认为随着这两者的推出，React 和 Vue 变得越来越像，甚至还引发过一些不大不小的撕逼。&lt;/p&gt;
&lt;p&gt;诚然，Vue 的组合式 API 在设计之初受到过 React Hooks 的启发，且这两者都可以把逻辑抽象成函数的组合，来实现逻辑的内聚与复用，在视觉上有相似之处。但或许这就是它们所剩无几的相同点了。&lt;/p&gt;
&lt;p&gt;本质上组合式 API 与 React Hooks 的心智模型还是大不相同的，如果将它们混为一谈，不但容易引发对两者的误解，更可能（基于这份误解）造成写出来的代码中含有不易察觉的 bug。&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;组合式 API 的动机与实现&lt;/h2&gt;
&lt;p&gt;组合式 API 的效果用下面这张图片就可以清楚地表示出来：&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c265669334b87d3acf6a783bebdd6fbb/c1b63/1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAACIUlEQVR42qWUDW/aMBCG+/9/0aR1FRRKISFAoE1boK1KIB8kzgcJ/ozvRssGXWVNmmZZZ8unx/feK8sX+B/j4rRLsyIlRZbvil2dkPx0XpZ1GKXkI7s7pNK83NVf4aqmtNrTei85p5TB73PGRFHTmvEd5VRIyjjn4iusGx0HUZYSkiSMsrM4gCaIYBPgNsY4RqUMsrVUpeex5YLNH1VGjthHQvNbS3VuoW/BwEZKDXDTqMifp9EziZe0zk8waHgaM8/mTyMxn0hxbugTzEFbqhoitZGF+K4NfqnGsSXtnnYdnDrImcltLvVsyR5X6v6tSUt9Un2Iri2cXjO1YeaAGZYCnK6c9GF8A9sNfoGHh8o2TP8CWzeN00e7h1HwBzy1+bCnXFvPHG2GldRLN3m9y5+nWZGwT2bjxH6/dGThxEJOTTBqQV9a/LUFmzZU/tltAP8hWN0H4Tw+TCWUGeZ+d7+61mEX6/XR6WMU6x59uYSwg3EPG2aSrfTjC1n4xdIvi0qcZB/WZj1gi0tY32DYR2WChcCBo0dTGE4wTs6GHV5Jlnhp4uZkVmb3uhFmw/yHPFrW4aLaF/JsGEKL5d9UeQVVC6s9alPPTMMVwXaJrQrX8tQyIHRS90fmdCq3W88omCo3sllNnjfeW+C91WR39Pm9Muh2OviedVtl/7ocUG3qWYK0i7vx/mHEvK3KjjWPMRFZrMhWkkSRBhqT7H//hn4C7Ah8Ap4b41kAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;组合式 API 与选项式 API 对比&quot;
        title=&quot;&quot;
        src=&quot;/static/c265669334b87d3acf6a783bebdd6fbb/d7542/1.png&quot;
        srcset=&quot;/static/c265669334b87d3acf6a783bebdd6fbb/2efce/1.png 203w,
/static/c265669334b87d3acf6a783bebdd6fbb/1d180/1.png 405w,
/static/c265669334b87d3acf6a783bebdd6fbb/d7542/1.png 810w,
/static/c265669334b87d3acf6a783bebdd6fbb/c1b63/1.png 1200w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;在传统的选项式 API（Options API）中，组件为我们提供了一组 Object 形式的配置对象。我们可以将响应式变量定义在 &lt;code class=&quot;language-text&quot;&gt;data&lt;/code&gt; 字段，监听逻辑定义在 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 字段，各个生命周期想做的事情定义在诸如 &lt;code class=&quot;language-text&quot;&gt;mounted&lt;/code&gt; 等生命周期字段中。&lt;/p&gt;
&lt;p&gt;但真实世界中的一个组件往往由复杂的逻辑交织而成，在组合式 API 中，我们只能把同一逻辑关注点的代码分散到各个字段中。例如我们需要一个监听器，但开始监听和取消监听就得分别放在 &lt;code class=&quot;language-text&quot;&gt;mounted&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;beforeDestroy&lt;/code&gt; 中。随着组件体积的膨胀，这些代码逻辑所在的位置愈发割裂，使得开发者在阅读理解代码时也愈发困难。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，组合式 API 就应运而生了。在组合式 API 中，我们不再要求业务代码把自己的逻辑分门别类地放置在配置对象中，而是利用依赖倒置的思想，将配置对象的各个字段变为钩子函数（例如 &lt;code class=&quot;language-text&quot;&gt;mounted: someFunc&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;onMounted(someFunc)&lt;/code&gt; ），提供给业务代码，使业务代码能够将自己&lt;em&gt;钩入&lt;/em&gt; 组件的生命周期等逻辑中。这样，相同逻辑关注点的代码就可以被放在一起，从而提高了代码的内聚性。&lt;/p&gt;
&lt;p&gt;一段典型的组合式 API 代码如下所示（来自官网的例子）：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; repositories &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getUserRepositories&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    repositories&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// fetch repositories&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;onMounted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;getUserRepositories&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 在 user prop 的响应式引用上设置一个侦听器&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getUserRepositories&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    repositories&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    getUserRepositories
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;与它等价的选项式 API 则是：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;repositories&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;getUserRepositories&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;getUserRepositories&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;repositories &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// fetch repositories&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;mounted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUserRepositories&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;虽然这个简单的例子里两种写法看上去差不多，但如果这个组件的功能再复杂些，选项式 API 写出来的代码很快就会变成一团乱麻。而组合式 API 中，我们的这段代码逻辑可以天然地放到一个函数当中，隐藏掉内部实现的细节，使组件整体的代码保持清爽。&lt;/p&gt;
&lt;p&gt;不仅如此，以函数来表示逻辑，还可以实现逻辑的细粒度复用。这也是组合式 API 带来的另一个好处。在选项式 API 中，虽然可以通过 mixin 的方式实现一定的复用，但存在着变量名冲突、难以处理逻辑之间的依赖关系等种种问题。而通过函数的方式进行逻辑复用，使这些问题迎刃而解，并且没有任何 magic，可以说是十分自然优雅。&lt;/p&gt;
&lt;h2&gt;Android Lifecycle API&lt;/h2&gt;
&lt;p&gt;与其说 Vue 的组合式 API 像 React Hooks，我觉得不如说它更像 Android 的 &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/lifecycle?hl=zh-cn&quot;&gt;Lifecycle API&lt;/a&gt; 。我挺惊讶居然没有人把这两者作对比。&lt;del&gt;说好的大前端呢&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;虽说 Android 原生并没有 Vue 核心的响应式数据概念（当然，已经有官方支持的 LiveData 与 DataBinding 这些了&lt;del&gt;更不用说 Jetpack Compose&lt;/del&gt;，但这里不展开，不然就没完没了了），但 Android Lifecycle API 与 Vue 组合式 API 所要解决的问题 —— 即逻辑关注点分散、混乱的问题 —— 与解决手段，却是有着异曲同工之妙。&lt;/p&gt;
&lt;p&gt;在传统的 Android 应用开发中，不少逻辑都依赖于生命周期实现。Android 中拥有生命周期的单元是 Activity 和 Fragment ，其他的业务逻辑若是需要感知生命周期，就需要把它们的代码放到 Activity 和 Fragment 的各个生命周期中，这就导致了代码膨胀与逻辑关注点分离的问题。&lt;/p&gt;
&lt;p&gt;例如我们需要一个感知当前地理位置的功能，一般以如下形式实现：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyActivity&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppCompatActivity&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyLocationListener&lt;/span&gt; myLocationListener&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 可以类比为 Vue 的 created&lt;/span&gt;
        myLocationListener &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyLocationListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 可以类比为 Vue 的 mounted&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        myLocationListener&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onStop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 可以类比为 Vue 的 beforeDestroy&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onStop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        myLocationListener&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;很明显，这样的做法与 Vue 的选项式 API 存在同样的逻辑割裂、组件膨胀等问题。Android 官方也逐渐意识到了这个问题。因此，在它们提供的新的架构组件中，推出了 Lifecycle API。它提供的解决方案我们也不难想到。我们把 Lifecycle 作为一个单独的概念抽象出来，使之不局限于 Activity 和 Fragment 中。而需要感知生命周期的业务逻辑，可以以回调函数的形式把这些逻辑&lt;em&gt;勾入&lt;/em&gt; 相应的生命周期中。&lt;/p&gt;
&lt;p&gt;这样一来，和这个业务单元的所有相关代码都可以被放在一起，提高了内聚性，且保持了 Activity/Fragment 文件本身逻辑的清晰。另一方面，我们在使用这业务逻辑时也无需关心它究竟在 Activity 还是 Fragment 中，只要存在生命周期就好。这也提升了逻辑的可复用性。&lt;/p&gt;
&lt;p&gt;使用 Lifecycle API 实现的例子如下所示：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyObserver&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LifecycleObserver&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 可以类比为 Vue 的 onMounted()&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OnLifecycleEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Lifecycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ON_RESUME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connectListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 可以类比为 Vue 的 onBeforeUnmount()&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@OnLifecycleEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Lifecycle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ON_PAUSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;disconnectListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 类比为在 setup 函数中调用你自己的逻辑&lt;/span&gt;
myLifecycleOwner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLifecycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对比 Android 与 Vue，可以看到它们所面临的问题，以及选择的解决方式，都是极为相似的。&lt;/p&gt;
&lt;h2&gt;那么 React Hooks 呢？&lt;/h2&gt;
&lt;p&gt;在 React Hooks 出现之前，我们编写有状态组件，只能使用类组件（Class Component）的形式，并在类组件的生命周期方法中编写相关的逻辑。明显，这和 Vue 与 Android 中我们面临的问题是一样的。我们可以轻易地将上文展现的解决方式迁移到 React 中，为它设计一套新的 API：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyComponent&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Component&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 相当于 Vue 的 setup()&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;contructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 把业务逻辑注册到生命周期中，以实现逻辑的内聚和可复用&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;didMount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;didUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;很容易理解，不是么？可是 React 团队却不满意于此，而是交出了一份不一样的答卷。他们完全抛弃了类组件的写法，连带着抛弃了生命周期的概念。&lt;/p&gt;
&lt;p&gt;我们学习一项新技术时，会想怎么把旧的思维方式迁移过去。这不就有人会问：class 的生命周期方法是怎么对应到新的 hooks 上面的呢？于是搜索了一下发现 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 可以通过各种写法的组合来模拟原来的 &lt;code class=&quot;language-text&quot;&gt;componentDidMount&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;componentDidUpdate&lt;/code&gt; 与 &lt;code class=&quot;language-text&quot;&gt;componentWillUnmount&lt;/code&gt;，然后一边抱怨怎么起了个这么一个和原来的生命周期完全不像的名字，一边死记硬背各种组合的用法。&lt;/p&gt;
&lt;p&gt;那么为什么 React 团队放着前面简单的方式不做，而是采用这种看似如此绕的方式呢？其实 React Hooks 为我们带来的是一种新的思维方式，只是我们还拿旧的思路去理解它，所以觉得它比较绕。&lt;/p&gt;
&lt;p&gt;为了说明这个问题，我们还得从类组件说起。众所周知，React 采用了函数式编程的理念，通过不可变性来感知状态变更，从而触发 UI 更新。无状态的函数组件是纯函数，自然可以无限次数地调用。可对于有状态的类组件，每次渲染仅仅是调用它的 &lt;code class=&quot;language-text&quot;&gt;render&lt;/code&gt; 函数，这个组件类的实例在多次渲染之间仍然是同一个，因此在它身上定义的实例变量&lt;strong&gt;事实上成为了可变数据&lt;/strong&gt;。这就使得类组件的工作方式在 React 理念中显得格格不入，甚至带来 bug 的风险。&lt;/p&gt;
&lt;p&gt;可变数据的典型便是 &lt;code class=&quot;language-text&quot;&gt;this.state&lt;/code&gt;。虽然理论上我们必须通过 &lt;code class=&quot;language-text&quot;&gt;setState&lt;/code&gt; 函数来修改 &lt;code class=&quot;language-text&quot;&gt;this.state&lt;/code&gt;，但这只是为了让 React 能够感知到变化并触发重新渲染。你也完全可以直接对 &lt;code class=&quot;language-text&quot;&gt;this.state&lt;/code&gt; 赋值，然后手动触发渲染，或是让别处触发的重新渲染使你之前的修改体现在 UI 上。&lt;/p&gt;
&lt;p&gt;这样的行为符合我们长久以来（习惯于面向对象与可变数据）的认知，但有时会给我们带来麻烦，尤其是在有异步行为的场景中。React 核心开发者 Dan Abramov 在&lt;a href=&quot;https://overreacted.io/zh-hans/how-are-function-components-different-from-classes/&quot;&gt;他的文章&lt;/a&gt;中举了一个十分有代表性的例子。例如在社交网站中，我们可以点击关注一个人的社交帐号，向服务器请求回包后弹窗告知用户关注成功。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProfilePage&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Component&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;showMessage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Followed &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;showMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handleClick&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Follow&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这段代码存在着 bug，而 bug 的根源就在 &lt;code class=&quot;language-text&quot;&gt;this.props&lt;/code&gt; 的可变性上。假如我们在 A 的主页点击了关注，但在 3 秒中内切换到 B 的主页，由于 &lt;code class=&quot;language-text&quot;&gt;this.props.user&lt;/code&gt; 变成了 B，弹窗的内容就成了“Followed B”。但如果我们使用函数组件，就可以天然规避这个问题（注意这个例子并不涉及 Hooks）：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ProfilePage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;showMessage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Followed &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;showMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleClick&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Follow&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;没有 bug 是因为，作为函数式组件，&lt;code class=&quot;language-text&quot;&gt;ProfilePage&lt;/code&gt; 本质只是一个普通的函数。当 &lt;code class=&quot;language-text&quot;&gt;user&lt;/code&gt; 改变时，React 传入新的 &lt;code class=&quot;language-text&quot;&gt;props&lt;/code&gt; 重新执行了这个函数。但在上次函数执行过程中声明的 &lt;code class=&quot;language-text&quot;&gt;showMessage&lt;/code&gt; 函数，它绑定的 &lt;code class=&quot;language-text&quot;&gt;props&lt;/code&gt; 的值仍然是上次执行时的值，而不是像类组件那样，改变了 &lt;code class=&quot;language-text&quot;&gt;this.props&lt;/code&gt; 的内容。以这个函数的视角来看，&lt;code class=&quot;language-text&quot;&gt;props&lt;/code&gt; 只是一个普通的对象值罢了，而不是某个实例变量的引用，因此在当次函数执行过程中，所有地方读到的 &lt;code class=&quot;language-text&quot;&gt;props&lt;/code&gt; 永远都是同一个值，即当次渲染传入的值。&lt;/p&gt;
&lt;p&gt;理解了函数组件如何规避可变状态带来的问题，就可以理解为何要用 Hooks 取代类组件。通过 Hooks，我们可以让 &lt;code class=&quot;language-text&quot;&gt;state&lt;/code&gt; 也和 &lt;code class=&quot;language-text&quot;&gt;props&lt;/code&gt; 一样，获得在组件单次渲染中的不可变性。这也更加贯彻了 React 的函数式、不可变的理念。我们可以把上面的例子用 &lt;code class=&quot;language-text&quot;&gt;useState&lt;/code&gt; 来改写，一样是符合我们预期，没有 bug 的。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ProfilePage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setUser&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;A&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;showMessage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Followed &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;showMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleClick&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Follow&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;没有 bug 的原因是，&lt;code class=&quot;language-text&quot;&gt;user&lt;/code&gt; 变量在单次渲染时实际上就是一个普通的字符串（可以想象把所有的 &lt;code class=&quot;language-text&quot;&gt;user&lt;/code&gt; 都替换成 &lt;code class=&quot;language-text&quot;&gt;&apos;A&apos;&lt;/code&gt; ）。而倘若通过 &lt;code class=&quot;language-text&quot;&gt;setUser&lt;/code&gt; 改变了 &lt;code class=&quot;language-text&quot;&gt;user&lt;/code&gt; 值，也只是触发下一次对函数的调用，对当次渲染毫无影响。&lt;/p&gt;
&lt;p&gt;我们可以以 Vue 的视角审视下这个例子。如果你认为 Vue 的组合式 API 与 React Hooks 相当的话，我们可以用组合式 API 来实现这个例子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;select&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;v-model&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;option&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;A&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;option&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;option&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;B&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;option&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@click&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;handleClick&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Follow&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ref &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;vue&apos;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;A&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;showMessage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Followed &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;showMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不难看出这段代码和 React 的类组件实现一样，是存在 bug 的版本（当然，用选项式 API 也一样）。但是对于 Vue 来说这样的结果是符合预期的。因为不像 React 遵循的不可变性理念，Vue 采用的是响应式数据的理念，数据是可变的，这必然要求我们持有响应式数据的引用。&lt;/p&gt;
&lt;p&gt;事实上想要使用 React Hooks 完美还原这段 Vue 代码的能力，我们也要采取类似的做法，通过 &lt;code class=&quot;language-text&quot;&gt;useRef&lt;/code&gt; Hook 为 &lt;code class=&quot;language-text&quot;&gt;user&lt;/code&gt; 变量创建一个可以横跨多次渲染的引用。而想要在 Vue 中规避这个 bug，并没有完全对应 React 函数组件的做法，只能另想办法规避了。&lt;/p&gt;
&lt;h2&gt;useEffect 与 watch&lt;/h2&gt;
&lt;p&gt;React 的 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; Hook 与 Vue 组合式 API 的 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 方法是一组经常被拿出来比较的概念。它们做的事情类似，都是在各自的依赖发生改变时，执行对应的逻辑。但光看它们的名字却是大不相同。倘若我们了解了 React Hooks 与 Vue 组合式 API 各自的理念和动机，就不难理解它们为什么不同。&lt;/p&gt;
&lt;p&gt;Vue 的 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; API 很容易理解，它对应于原来选项式 API 中的 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 选项。顾名思义，就是监听数据的变更。由于 Vue 基于响应式数据的理念，能够跟踪所有响应式变量的变更，因此能做到这些并不费吹灰之力。&lt;/p&gt;
&lt;p&gt;而对于 React 的 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; Hook，上文已经提到，我们不要再用生命周期的思想去类比它，而是从函数式的角度去理解。&lt;/p&gt;
&lt;p&gt;在理想情况下，所有渲染函数都是纯函数，这样我们可以放心地无限次调用它们。但是现实显然没有这么美好，除了渲染之外往往还要做些别的事情。React 统一把这些事情认为是副作用（Side Effect），这也就是函数名 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 的来历。&lt;/p&gt;
&lt;p&gt;说句题外话，从源码的角度，React 核心只是维护内部的组件树。至于把树的最新状态同步到 DOM 上这一工作（即 react-dom 的职责），也被认为是一种副作用。&lt;/p&gt;
&lt;p&gt;既然是渲染的副作用，那它自然是在每次渲染之后都会调用一次。而它的依赖数组，也只是用来规避重复调用副作用时可能带来的性能、死循环等问题。在 React 看来，依赖数组只是一个普通的数组，并不非得是真正的依赖。这也是为什么 React 不会为我们自动收集依赖。如果你愿意，你完全可以通过自己编排依赖数组来达到目的。只是大多数时候人脑应付不过来过于复杂的逻辑，因此官方推荐我们诚实地把依赖数组的内容与副作用函数的真正依赖保持一致。&lt;/p&gt;
&lt;p&gt;所以 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt; 这两者在设计意图上就完全不同，只是最终殊途同归而已。网上很多文章在对比组合式 API 与 React Hooks 时只会轻描淡写地提到一句“前者只执行一次，后者会执行多次”，却没有意识到这一区别的背后隐藏着它们设计理念上的巨大差别。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;在对比了 Vue 组合式 API 与 React Hooks 之后，我们发现它们并不是像看上去那样变得逐渐相似，恰恰相反，它们进一步把自己的特点推向了极致。Vue 通过组合式 API 进一步暴露了它的响应式数据能力，使之不再局限于 Vue 实例以内，更便于逻辑组合与复用。而 React 通过 Hooks 弥补了 class 组件中可变数据的隐含问题，进一步贯彻了函数式编程、不可变数据的设计理念。&lt;strong&gt;React 和 Vue 实际上变得越来越不同了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而对于我们使用者来说，在使用一项技术前需要让自己充分理解这一技术的设计理念，顺着它的思路来写代码。当然，这需要花点时间去学习和适应新的概念，尤其是对于 React Hooks。顺着正确的心智模型，才能事半功倍，而不是想当然地把旧的经验套用在新技术上，等出了问题再高呼真坑。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[装修随笔]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/renovation/</link><guid isPermaLink="false">https://maliut.space/p/renovation/</guid><pubDate>Sat, 07 Aug 2021 23:34:00 GMT</pubDate><content:encoded>&lt;p&gt;如题，前段时间完成了人生的第一次装修。在经历了一系列兵荒马乱之后，休息了一段时间，回过头来再看这段经历，希望能尽量客观条理地记录下自己的见闻。如果有幸能帮助大家在面对装修时少一点迷茫，那就更好了。&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;期待与担忧&lt;/h2&gt;
&lt;p&gt;一开始面对装修时我们总是怀着两种矛盾的心态。&lt;/p&gt;
&lt;p&gt;一方面，面对第一次能够完全设计自己新家的机会，我们无疑是兴奋的。我们会在网上寻找各式各样的装修效果图，并以此来想象新家未来的样子。&lt;/p&gt;
&lt;p&gt;另一方面，装修本身就是一个早已臭名昭著的行业，很容易就能看到别人在装修时遇到的各种烦心事。没有一家能够安安稳稳，完全顺利地完成装修的过程。这不禁也让人对即将开始的装修过程充满恐慌。&lt;/p&gt;
&lt;p&gt;而更令人感到恐慌的是，装修知识跨越了很多专业领域。这使得当你看到别人提到的一些装修中的坑，你甚至都看不懂他们在说什么。这就显得你像一只待宰的羔羊，随时会被敲骨吸髓。&lt;/p&gt;
&lt;p&gt;好在后来偶然来到了一个上海本地生活论坛（篱笆网）。在这个论坛上有很多人以日记的形式图文并茂地发了自己家的装修经历。贵在真实，他们的记录中并不全是效果图展示，也不都是投诉坑爹的。看的多了以后，就对整个装修过程有了全局的认识，也渐渐把一些之前只停留在文字上的术语与实际的工程步骤联系在了一起，给了我开始装修的底气。&lt;/p&gt;
&lt;p&gt;这也是我学到的一个经验，即本地生活论坛往往拥有最真实的一手信息。&lt;/p&gt;
&lt;h2&gt;公司选择&lt;/h2&gt;
&lt;p&gt;一般来说有装修公司与施工队可选。网上很多都说装修公司下面主要也都是外包工人，还要收额外的管理费，不如直接找靠谱包工头 + 第三方监理。&lt;/p&gt;
&lt;p&gt;老实说这并没有错，不过有公司这个实体在，终究更靠谱一点。毕竟我们也没有装修经验，且工作忙不能时刻盯着工地。（当然，找了装修公司也不意味着你就可以当甩手掌柜了。）另外找公司你能看到各路评价，但是找到靠谱包工头的难度就大多了。因此最终还是决定选择装修公司。&lt;/p&gt;
&lt;p&gt;刚开始肯定是想着通过我们熟悉的互联网渠道去寻找。可惜的是装修行业与互联网的整合并不是很深，不像其他业已成熟的 O2O 行业，能够为我们提供省心与标准化的服务。另外在互联网上营销厉害的几家公司，在网友中的风评反倒都很差。（网友：你把钱都拿去打广告了还指望管好装修质量？）&lt;/p&gt;
&lt;p&gt;后来发现篱笆网以后，就自然打算在这个平台上进行选择了。虽然这里面的装修公司也不是十全十美的，但贵在真实。（虽然后面发现装修公司还是会做一些控评，以及对于在网上发帖的客户可能会有一些区别对待）&lt;/p&gt;
&lt;h2&gt;设计师&lt;/h2&gt;
&lt;p&gt;装修公司一般都会有自己的设计师，如果你对设计有更高的要求，也可以花更多的钱选择独立设计师。我们房子又不大，也只想做中规中矩的装修。反倒是独立设计师对我们来说又平添了外部依赖与沟通成本，故一开始就不考虑。因此下文也都是讨论装修公司的设计师。&lt;/p&gt;
&lt;p&gt;设计师一般是装修公司中第一个与你打交道的角色。他们为你的量房并了解你的需求，然后出设计图，并只收取一笔极少的设计费或干脆由装修公司免费提供。而这一阶段往往会选择多个装修公司进行比稿，在他们之中选择一家正式签约。这也是为什么很多人把装修公司的设计师称为销售/绘图员。&lt;/p&gt;
&lt;p&gt;能感受到设计师的两种风格。有一类设计师比较专注在设计上面，能够给出一些比较有特色的设计；而另一类在设计上看上去中规中矩，但是对于装修的工艺，可能遇到的坑等信手拈来。&lt;/p&gt;
&lt;p&gt;我们最终选择了后者，现在看来还是明智的。因为本身房子限制，设计空间有限，对于功能分区的设想我们自己也已经想的八九不离十了。而对装修过程的了解，使得设计师能够顺畅地与装修工人沟通，在设计落地到施工上时避免了不少的坑。&lt;/p&gt;
&lt;p&gt;当然，这也取决于设计师的负责任程度。也有设计师在出完图收到钱以后就做了甩手掌柜。不过这个就得看运气了。&lt;/p&gt;
&lt;h2&gt;签约方式&lt;/h2&gt;
&lt;p&gt;稍微在网上了解了装修的人，都会接触到全包、半包、清包的概念。&lt;/p&gt;
&lt;p&gt;清包就是工人只出人力，装修用到的水泥黄沙等一切材料都要你自己负责采购。很明显这种是最省钱的，但是你自己也最累。装修公司都要保证自己的利润空间，肯定不会接受这种方式，一般是有闲且对装修过程十分了解的人，直接找工头才会这样做。&lt;/p&gt;
&lt;p&gt;而全包和半包就是谁来购买主材的区别。主材可以简单理解为那些看得见摸得着，但只有装修时才会特意去关注的建材，比如瓷砖、地板、橱柜之类的。全包就是全部通过装修公司选购，你只能从几个特定的款式中挑选，如果有定制化需求就得另加钱。而半包的话，这些就得自己去购买。&lt;/p&gt;
&lt;p&gt;现在网上主流都是推荐使用半包&lt;del&gt;中庸之道&lt;/del&gt;。不过个人觉得可以商榷。全包选择少，价格不透明，难以货比三家。不过也有好处，就是省去了与很多商家单独对接的精力，有什么问题也会有装修公司居中调节。（前提是装修公司要比较靠谱，在网上看到的不靠谱的例子也不少）。&lt;/p&gt;
&lt;p&gt;需要澄清的一点是，全包也并不意味着你可以对工地撒手不管了。它只是帮你解决了材料供应商的问题，但在实际施工中随时都可能会有情况发生，需要你去处理。如果真想要拎包入住，恐怕只能买精装修了。&lt;del&gt;可精装修被曝出质量问题的还少吗？&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;我们最终名义上是半包，实际上介于半包和全包之间。在合同中约定了我们在装修公司合作厂家中挑选一定价值的主材，并承诺价格不高于市场价。在签约时也听店长唠嗑了几句，人工和材料费用的上涨是大趋势，为了维持装修公司的利润，未来全包会逐渐趋于主流。&lt;/p&gt;
&lt;h2&gt;材料采购&lt;/h2&gt;
&lt;p&gt;装修过程中的采购，频次低，金额大，且会服务你很多年，因此值得谨慎对待。&lt;/p&gt;
&lt;p&gt;随着网购的发展，我们往往会面临的抉择是在网上还是在实体店购买。网购挤占了不少实体店的空间，也滋生出了不少套路。&lt;/p&gt;
&lt;p&gt;最常见的套路就是款式区分线上与线下款，为同样配置的商品打上不同的型号以混淆视听。更有甚者会在不同的电商渠道上也区分不同的型号。&lt;/p&gt;
&lt;p&gt;型号被打乱，一方面杜绝了顾客在线下店看好型号，再去线上搜索比价的情况。另一方面，如果你拿着网上的型号和配置去线下咨询，他们就会以没有这个型号为借口，向你推荐配置和价格更高，但不一定符合你需求的产品。然后通过销售的口才让你冲动下单。&lt;/p&gt;
&lt;p&gt;有些大品牌，可以在网上搜到线上线下型号的对照表。如果搜不到，就只能靠自己多多对比配置信息进行判断了。还有一种说法是线上款之所以便宜，是因为品质有缩水，偷工减料了。我个人对此说法持怀疑态度。&lt;/p&gt;
&lt;p&gt;并非所有商品都是线上比较便宜，线下店的价格水分还是很大的。当然这也视品类而定。在线下逛时，光看标价是不够的，很多时候都是虚高，装作要买的样子和销售聊聊，才能打听到真实的价格。如果你能说会道，善于砍价，就更好了。&lt;/p&gt;
&lt;p&gt;印象比较深刻的是卫浴产品，上来就是对折起步，实在是让人大开眼界。还有看上去非常高科技的软水机，实体店报价就比线上便宜好几千，还送赠品。当然也有像方太这种线上线下统一型号统一价格的，点名表扬。&lt;del&gt;虽然线下购买还是送了不少赠品&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;大多数情况线上价格会更优惠。如果正好赶上大促，还可能再享受一波优惠。如果赶不上大促，许多商家还能允许一段延迟发货期，多问问客服或许可以给你晚点发货。实在赶不上优惠也不用强求，否则一堆东西堆在工地就难以干活了。装修会使你意识到，能用钱解决的问题都是小问题，要是造成工程延期、配合不上之类的问题会更加头疼。&lt;/p&gt;
&lt;p&gt;还需要注意的一点是装修中的大件商品很多都不能立即发货，因此要提前做做功课，避免因为等待到货导致进度延期。&lt;/p&gt;
&lt;p&gt;在选购过程中，对于一些大件我们是在设计师的陪同下选购的。有人会很反感这种现象，认为设计师会吃回扣。这种情况确实存在，但还是像之前所说的那样，看设计师的靠谱程度。而设计师的存在还是很有必要的，因为商家默认你懂装修上的各种事情，会问你一些比较专业的问题，你甚至都听不懂。如果没有设计师的陪同，我们就只能和商家面面相觑了。&lt;/p&gt;
&lt;h2&gt;联系&lt;/h2&gt;
&lt;p&gt;装修并不是搭积木。在你可见之处的各种功能，往往都需要很多不可见之处的支持。因此在开始装修时，就一定要计划好未来家中的各种设施，越细节越好。否则一旦隐蔽工程成型，就再也没有补救的机会了。（但这其实蛮悖论的，因为第一次装修之前你根本就没有这么多知识，而随着装修过程你慢慢意识到了这些却又来不及了，只能说尽量吧）&lt;/p&gt;
&lt;p&gt;大家最耳熟能详的就是插座和开关的问题，每篇文章都会提要尽早计划好插座的位置，留够数量等等。设计师在出图时也会考虑这些，所以翻车的比较少。但还有很多东西是很难想到要在前期做好计划的。&lt;/p&gt;
&lt;p&gt;一个例子是零冷水热水器。如果被破旧出租房的热水器摧残过，一定都知道一开水龙头就放出热水是多么幸福。现在有很多热水器都拥有零冷水功能，但其原理决定了在铺设水管时必须事先增加一条回水管。而目前装修的标准化流程默认并没有这一项。如果你之前没提出来，后续安装热水器时就得犯难了。&lt;/p&gt;
&lt;p&gt;安装软水机也是类似的原理。不过软水机本身也是专业的机器，反倒会更上心一点。软水机的工作人员也会在水电定位时予以协助。&lt;/p&gt;
&lt;p&gt;现在还有很多人希望在家中实现智能家居，首要任务就是把开关都换成智能开关。但智能开关安装时比普通安装需要多一根零线，这也需要在电线布线时额外处理。&lt;/p&gt;
&lt;p&gt;其实像热水器、还有智能开关这两个，即使前期没做准备，后面也有补救的方案，但并不完美，或多或少还是存在些隐患。既然都重头装修了，为什么不一次性做好呢？&lt;/p&gt;
&lt;p&gt;还有一个比较大的问题就是家里的网络。如今无线网已经是媲美水电的基础设施了，但装修工人对此的了解还远远不足，只会把网线拉到指定位置完事。电信的工程师倒是非常专业，但只在装修结束后安装宽带时登场&lt;del&gt;然后帮我们穿了水晶头以及发现工人接法不对，把网口一一撬开重新接线&lt;/del&gt;。因此对于光猫、路由器的摆放、网口位置，家里是不是比较大以至于需要 AP/mesh 组网等，自己也得做好功课。&lt;/p&gt;
&lt;p&gt;关于网络这一块，网上有很多专业的文章了。我本身也是一知半解，而且没什么极客精神，就不在这里误人子弟了。如果你对这块比较有追求，一定要在一开始就做好规划，并一开始就紧盯住装修公司，包括网线、水晶头的用料。&lt;/p&gt;
&lt;h2&gt;监理&lt;/h2&gt;
&lt;p&gt;面对装修行业鱼龙混杂，以及客户缺乏装修知识的现状，一群曾经的装修行业从业者站了出来，催生了装修监理这一职业。如今监理已普遍被客户和装修公司所接受。&lt;/p&gt;
&lt;p&gt;现在的装修公司，提供监理已经成为了标配。不过同样归属于装修公司，难免让人觉得是既当运动员又当裁判员。此外，也有独立于任何装修公司的第三方监理。&lt;/p&gt;
&lt;p&gt;虽然装修公司的监理让人怀疑是不是会包庇自己人，但默认在他们讲职业道德的前提下，还是很有帮助的。这是因为他们对工人是有监督的强制力的。另外，比起工人，他们往往沟通能力较强，能成为客户与工人之间沟通的桥梁。&lt;/p&gt;
&lt;p&gt;第三方监理则在专业能力上更胜一筹，检查的项目也更加细致。但他们只能提出建议，对工人则没有强制力，工人有时并不服他们。这时有装修公司的监理在中间说和就比较重要。&lt;/p&gt;
&lt;p&gt;另外在第三方监理中还有一类陪签服务，即在与装修公司签约时负责审核报价单。&lt;/p&gt;
&lt;p&gt;在刚签约阶段，我们对装修往往还是比较懵逼的状态，对着装修公司给出的事无巨细的报价单也看不出个所以然（而在签约前，装修公司一般是不允许你带走报价单的）。这时候一个靠谱的陪签能帮你审核报价单是否合理，以避免潜在的增项，也可以为后续装修过程中可能会遇到的问题给你打个预防针。&lt;/p&gt;
&lt;h2&gt;接受不完美&lt;/h2&gt;
&lt;p&gt;虽然很遗憾，但不得不说装修就是一个不断妥协的过程。&lt;/p&gt;
&lt;p&gt;在游戏《Minecraft》中，整个世界被抽象成了一个个规整的小方块，这为我们布置场景提供了极大的便利。但现实中显然没有如此理想的环境。假如你有一格的空间，那么肯定能放下一格的工作台。但假如你有一米宽的空间，那么一定是放不了一米宽的柜子的。&lt;/p&gt;
&lt;p&gt;一个比较违背直觉的事实是，你家的墙与墙，墙与地之间的夹角并不是完美的横平竖直，呈 90° 的。如果你想要做的完美些，好的，请加钱（而且相当于变相缩小了你的室内空间）。有兴趣可以去搜索“顺平”和“垂平”。&lt;/p&gt;
&lt;p&gt;还要考虑的是踢脚线和石膏线的影响。这决定了你的家具不能完美地顶天立地（除非做吊顶）或者完美地贴合墙面。家里的管道也可能凭空占用你布局的一个角落。两种不同材料之间的交界面也不像游戏里那样可以自然贴合在一起。比如瓷砖和地板、和乳胶漆之间，必须考虑如何收边的问题。这又使你的家的角角落落显得参差不齐起来。&lt;/p&gt;
&lt;p&gt;最后，你买的家具和电器，也不能为你保证尺寸的精确性，误差是常有的事。如果你卡着尺寸买东西，结果刚好放不进去就尴尬了。装修与做事一样，都要留点余地。&lt;/p&gt;
&lt;p&gt;现在有万能的互联网，面对一些工艺上的问题，除了传统的做法以外，我们往往能在网上找到一些没那么主流的方法，但看似能完美解决你的问题。但这样的做法并非十全十美，它们可能需要花费不少的金钱，或是需要比较苛刻的前提条件，或是施工工艺比较复杂等等。即使理论上可行，实际上也要面对工人执行层的不完美。&lt;/p&gt;
&lt;p&gt;总之，如果选择了非主流的做法，就要面对可能翻车的风险。选择常规的做法，也许最后的效果并不完美，但至少也不会太离谱。&lt;/p&gt;
&lt;h2&gt;邻居&lt;/h2&gt;
&lt;p&gt;你买的房子可以选择，你的装修公司可以选择，你买的材料、家具可以选择，但你未来的邻居和物业的好坏，却像盲盒一样，当你能够认清楚他们的面目时，却已经没有选择的余地了。&lt;/p&gt;
&lt;p&gt;物业：干啥啥不行，收钱第一名。结合知乎上看到的各种物业事迹，说他们是当地黑社会地头蛇我也信了。本来想是不是个例，不过和监理聊了聊感觉大开发商的物业也不怎么样，更不用说也许是连物业费都收不上来的老破小了。也许这就是双输吧。&lt;/p&gt;
&lt;p&gt;邻居：由于是老小区，邻居也以老年人为主。虽然装修规定只能在工作日上班时间进行，但对一堆老年人来说并没有什么区别。因此他们对装修持有反感的态度也是可以理解的。但仅仅是因为一时嫌吵，就以各种莫须有的理由去各种举报，不得不让人怀疑是坏人变老了。&lt;/p&gt;
&lt;p&gt;更难受的是，装修的过程再烦恼也只是一时的，而物业和邻居的膈应却会持续很久。只能希望大家好运吧。&lt;/p&gt;
&lt;h2&gt;风险&lt;/h2&gt;
&lt;p&gt;现在智能家居的概念正走入寻常百姓家，不少人在装修之前的计划环节就已经把各智能生态纳入了统一规划。不过出于我个人对它的担忧，我并没有（刻意去）采用其中的任何一种。&lt;/p&gt;
&lt;p&gt;智能家居的理想无疑很美好，也有很大的想象空间，但有时我们需要扪心自问，是否真的需要一盏能够用手机控制开关和颜色的灯。当然，也可以配合传感器实现自动的开关，但只要你的动线设计合理，按个开关只不过是举手之劳。自动感应固然更胜一筹，但优势并不明显，可这却需要你在装修时花费不菲的人力物力去实现整套系统，显得并不那么值得。&lt;/p&gt;
&lt;p&gt;智能家居的各大厂商出于赢利目的，必然会希望构建自己的生态，对不属于其生态的产品支持很差或干脆不支持。这就导致了如果你想要构建智能家居，你购买家电的选择将大大变小，以至于有时不得不捏着鼻子选择性价比较低的产品。更糟的是这使得某一厂家的产品在你的家里形成了垄断，虽说正常使用起来比较方便，但假如他们开始作恶（例如给你家的电视加个广告），你也将毫无还手之力。&lt;/p&gt;
&lt;p&gt;此外，对于涉及安全隐私类的产品，例如电子锁、摄像头等，出于对程序员这一职业的了解，我对它们的安全性并不能抱有太大的信心。记得之前有新闻曝出联网摄像头可以被任何人看到直播的，实在是触目惊心。&lt;/p&gt;
&lt;p&gt;当然对于能大幅提升体验的产品也不要一味排斥。现在有很多小家电单品都提供了一定程度的智能化能力，不需要非得放到整个体系中来。&lt;/p&gt;
&lt;h2&gt;展望未来&lt;/h2&gt;
&lt;p&gt;作为一名软件工程师，习惯了赛博世界的工程，面对装修这一现实工程，满满的都是吐槽。&lt;/p&gt;
&lt;p&gt;在写代码时，虽然写出来的代码并不十全十美，但至少我们知道什么是好的，有原则可循。而在装修中，似乎一切都在与最佳实践对着干。各工序之间耦合严重，各种装修的隐蔽工程，一旦实现完成之后，就再也没有改动的可能了。缺乏通用的接口与意识，各要素之间的对接还是需要很多的定制化开发。这些使得装修是一条有去无回的单行线，一旦哪一步出了问题而又没有及时改正，就得忍受着这样的问题几十年，或者只能砸掉重来。&lt;/p&gt;
&lt;p&gt;那么，就没有更好的办法了吗？&lt;/p&gt;
&lt;p&gt;我们可以尽量选择灵活易于插拔的架构。我们选择了落地式的台盆、电视柜，独立的浴室柜、晾衣架等，以尽量避免在墙面和顶面打洞。&lt;/p&gt;
&lt;p&gt;装修行业中也自发地产生了一些统一的规范，例如插座盒的大小，所有厂商都遵循统一的规格，这样不同的产品之间就可以轻松地替换。另一个典型的例子是厨房与卫生间的集成吊顶，由统一大小的方形拼接而成，便于拆卸组装。在购买灯、暖风机时也不用再纠结大小尺寸，只要是兼容集成吊顶的都可以放心购买。考虑到厨房与卫生间往往是天花板漏水的重灾区，需要时常打开检修，这样模块化便于拆卸的吊顶就显得尤为实用。&lt;/p&gt;
&lt;p&gt;能够把这样的理念推广到整个装修过程中吗？就在装修的过程当中，我在网上看到了&lt;a href=&quot;https://zhuanlan.zhihu.com/p/80449951&quot;&gt;装配式装修&lt;/a&gt;的概念，就像游戏中的家园系统，划分成一个个标准块进行拼装，理论上完全避免了传统装修的各种缺点，十分心动。不过目前网上能找到的信息都是以宣传为主，没什么实操的帖子，倘若让我做小白鼠我也是不肯的。&lt;/p&gt;
&lt;p&gt;希望在未来能看到这种模式的发扬光大吧。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;装修实在是一个过于累人的过程，以至于仅仅是写这篇文章我都感到心累得几欲弃坑&lt;del&gt;甚至已经推翻重写了一次&lt;/del&gt;。也有很多很多上文没提到的知识和感受但无力再写出来了。&lt;del&gt;要不是另一半分担了太多我一定光速逃避去买精装修。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;最后还是想碎碎念几句。装修过程千头万绪，最理想的情况还是有人能够随叫随到，如果和长辈在同一个城市，或许能好很多。不过实在管不过来的话，就得学会抓大放小，不能太追求完美。看到网上很多和装修公司的纠纷，其实就是过于追求细节了，有些问题实际上住进去过两个月就会觉得没什么大不了。&lt;/p&gt;
&lt;p&gt;说起来看到链家也推出了装修服务，会把较老的二手房重新装修后挂牌出售，看来也是瞄准了装修这一痛点吧。不过这样的装修质量如何就不好说了，而且也不适用于自己有些特殊需求的场景。&lt;/p&gt;
&lt;p&gt;有网友说，装修是你在离开学校以后学习能力最强的时刻。诚不我欺。共勉。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[不合时宜的 CSS：Tailwind CSS 引发的思考]]></title><description><![CDATA[最近时常在社交网络上看到 Tailwind CSS 被提起，自己前段时间出于兴趣也拿它来做了个项目。与其他很多技术不同，Tailwind CSS 自出现起就在网上有不小的争议。与网上很多文章不同，在这篇文章中，我并不想讨论它的技术细节，而是对目前原子 CSS…]]></description><link>https://maliut.space/p/out-of-date-css/</link><guid isPermaLink="false">https://maliut.space/p/out-of-date-css/</guid><pubDate>Mon, 14 Jun 2021 23:17:00 GMT</pubDate><content:encoded>&lt;p&gt;最近时常在社交网络上看到 Tailwind CSS 被提起，自己前段时间出于兴趣也拿它来做了个项目。与其他很多技术不同，Tailwind CSS 自出现起就在网上有不小的争议。与网上很多文章不同，在这篇文章中，我并不想讨论它的技术细节，而是对目前原子 CSS 这一做法回潮的现象，以及前端样式写法的发展，表达一些个人的想法，也希望能为读者带来一些启发。&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;“前端三剑客”&lt;/h2&gt;
&lt;p&gt;即使在非专业人士中，前端的三大技术栈 —— HTML、CSS、JavaScript 也有不小的知名度。究其原因，一方面是因为它们是组成当今各种丰富多彩页面的基础，另一方面也是因为我们可以用很简单的语言解释其各自的用途，让人们&lt;del&gt;自以为&lt;/del&gt;理解了。经典的解释是：HTML 决定了网页的骨架、内容；CSS 决定了网页的样式；而 JavaScript 决定了网页的行为。&lt;/p&gt;
&lt;p&gt;看上去这三者各司其职，配合得十分美好。但它们真的是完全正交的吗？现实并不总是这么理想的。HTML 总是想插一脚样式的事情，且不说曾经的纯样式标签，例如 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;b&gt;&lt;/code&gt; 等，就算是现在的浏览器中，&lt;code class=&quot;language-text&quot;&gt;&amp;lt;body&gt;&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;&amp;lt;h1&gt;~&amp;lt;h6&gt;&lt;/code&gt; 等基本的标签也会带默认的样式（这也是 &lt;a href=&quot;https://www.css-reset.com/&quot;&gt;CSS Reset&lt;/a&gt;、&lt;a href=&quot;https://csstools.github.io/normalize.css&quot;&gt;Normalize.css&lt;/a&gt; 等存在的原因）。还有些 HTML 标签还自带了交互的能力，美其名曰，可以提高用户在禁用 JavaScript 时的使用体验。&lt;/p&gt;
&lt;p&gt;而 CSS 也逐渐扩展着自己的能力边界。最早 CSS 只是为了附加一些简单的样式，但随着人们对样式和交互的需求不断复杂，旧有的能力已远远无法满足需要，因此新的能力不断被加入 CSS 标准中，也不断有各种 CSS 框架如雨后春笋般冒了出来。一开始它们只是增强原始 CSS 的语法，到后来逐步发展成了一套几乎&lt;a href=&quot;https://stackoverflow.com/questions/2497146/is-css-turing-complete&quot;&gt;图灵完备&lt;/a&gt;的闭环。经常能在网上看到一些纯 CSS 实现的酷炫交互效果，令人为之惊叹。&lt;/p&gt;
&lt;p&gt;JavaScript 嘛就更不用说了，随着 DOM API 不断完善，没有什么 HTML 和 CSS 能做的事情是 JavaScript 不能做的。&lt;/p&gt;
&lt;p&gt;但在这三者的能力都不断增强的时候，它们之间的互操作性却成了瓶颈所在。例如当你需要根据某些复杂的条件为不同的 HTML 元素附上不同的样式时，就需要用 JavaScript 写上胶水代码。并不是不能做，只是这一方面让 JavaScript 僭越了三者的分工，另一方面你的思维需要在三者之间切换，了解它们各自在对方的世界中暴露的 API，这一定程度上加重了你的心智负担。而有趣的是，这一点正好成为了 CSS 能力不足，需要进一步增强的理由，而增强后的 CSS 迫使你学习它引入的新语法，却只是做了与 JavaScript 能做的同样的事，进一步加重了你的心智负担。&lt;/p&gt;
&lt;p&gt;既然这三者是你中有我，我中有你的关系，不知大家有没有想过为什么非得把这三者区分得这么死，而不是统一到一套体系中来呢？或许我们需要从其他不同的角度寻找答案。&lt;/p&gt;
&lt;h2&gt;Web 与其他 GUI 技术的发展史&lt;/h2&gt;
&lt;p&gt;虽说一切能用 JavaScript 的实现的东西最终都会被 JavaScript 实现，以至于前端开发者无所不能。但也不妨让我们跳出传统网页端这一亩三分地，以大前端的视角看一看其他 GUI 技术，它们分别代表了在它们出现的那个年代，开发者对于如何设计 GUI 开发方式的思考与设想。&lt;/p&gt;
&lt;h3&gt;Java Swing&lt;/h3&gt;
&lt;p&gt;Swing 是 Java 原生支持的跨平台 GUI 开发框架。由于 Java 本身是一个非常严格的面向对象语言，Swing 自然也继承了其面向对象的风格。具体来说，Swing 中的每一个组件都是一个对象，而组件的内容、样式等，就是这个对象的属性。对象之间通过组合的方式，共同构成了复杂的应用界面。&lt;/p&gt;
&lt;p&gt;一个 Swing 程序的例子如下所示：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 创建一个窗口&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;JFrame&lt;/span&gt; frame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;HelloWorld&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 关闭窗口时就退出程序&lt;/span&gt;
frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setDefaultCloseOperation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;EXIT_ON_CLOSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 创建一个标签&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;JLabel&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 设置标签的一些样式和属性&lt;/span&gt;
label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setIcon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ImageIcon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;path/to/your/image.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setLocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setIconTextGap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBorder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setOpaque&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 把标签添加到窗口中&lt;/span&gt;
frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContentPane&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 把窗口设成能包裹住内容的合适大小&lt;/span&gt;
frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 使窗口可见&lt;/span&gt;
frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setVisible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这是比较简单的场景，如果需要复杂的自定义布局，还可以拿底层 awt 的 Graphic 对象自己去绘制。虽然从代码示例上看还是比较清晰的，但在实际项目中，容易与逻辑代码混杂在一起，很难直观看出布局与样式。&lt;/p&gt;
&lt;h3&gt;Android&lt;/h3&gt;
&lt;p&gt;虽然 Android 有自己的一套 API，不过其设计与 Swing 类似，都是基于面向对象的思路。Android 的一大特点是引入了一套 XML 的布局方案，不知是不是从 HTML 中得到的启发。但 XML 本质上也只是对下层 API 的一个封装，用 XML 描述的布局使用 Java 代码也完全可以实现。在极端注重性能的场景下，甚至需要使用异步解析、或完全手写布局代码的方式来减小解析 XML 文本的开销。在布局或内容变化时，也需要通过命令式的语法，人工追踪变化的范围和过程，获取到控件对象进行操作。&lt;/p&gt;
&lt;p&gt;一个 Android 布局的样例如下所示：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;LinearLayout&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;android&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://schemas.android.com/apk/res/android&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@+id/title_bar&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;match_parent&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;48dp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_gravity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;center&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;background&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@color/header_background&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;gravity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;center&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ImageView&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;wrap_content&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;layout_height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;wrap_content&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;paddingBottom&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;4dp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;paddingLeft&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;4dp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;paddingTop&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;4dp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@drawable/header_icon2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;LinearLayout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;与之相对应的 Java 写法如下（简单起见省略了部分代码）：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;LinearLayout&lt;/span&gt; layout &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinearLayout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
layout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title_bar&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
layout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBackgroundColor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
layout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setGravity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Gravity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CENTER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ImageView&lt;/span&gt; image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ImageView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPadding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setDrawable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
layout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;XML 的出现解决了纯代码写法的一些问题。在面对大部分情况下不太改变的静态布局时，使用 XML 编写可以显著提高可读性，更为直观，同时也便于结合 IDE 实现布局的实时预览与拖动调整。不过它的本质并没有改变，在实际场景中仍然需要大量在代码中获取对象进行操作，在布局变化时也需要写代码对布局对象进行手动管理。&lt;/p&gt;
&lt;h3&gt;React&lt;/h3&gt;
&lt;p&gt;虽然 React 也是 Web 技术中的一种，但我觉得值得将其单独拿出来一说。以 React 为代表的新一代 Web 技术创造性地使用函数式编程思想，实现了声明式 UI 的写法。其核心思想是 &lt;code class=&quot;language-text&quot;&gt;UI = f(State)&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;在声明式 UI 的写法中，我们只需关注如何根据最新的状态渲染出 UI，而无需根据每次状态的变化去手动改变 UI 组件的状态，从而大大降低了代码编写的复杂度。而它的底层则使用一套通用的 Diff 算法，使得 UI 重渲染的过程尽可能高效。&lt;/p&gt;
&lt;p&gt;React 的另一个特点就是 JSX 语法。JSX 看上去像 HTML，但实际上是 JavaScript 语法的扩充。之所以设计成 HTML 的样式，是为了让熟悉 HTML 的开发人员能够更快地上手。如果你愿意，只使用纯 JavaScript 的 API 来写也是可以的。&lt;/p&gt;
&lt;p&gt;使用 JSX 而非 HTML 的好处是，由于它本质上是 JavaScript 语法糖，因此在编写布局时便可以与其他逻辑语句无缝衔接，这使得开发时的思路非常顺滑。例如，在 React 中，我们可以这样将一个数组渲染为列表元素：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;reactjsx&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-reactjsx line-numbers&quot;&gt;&lt;code class=&quot;language-reactjsx&quot;&gt;function List({ data }) {
  return (
    &amp;lt;ol&amp;gt;
      {data.map(item =&amp;gt; &amp;lt;li key={item.key}&amp;gt;{item.value}&amp;lt;/li&amp;gt;)}
    &amp;lt;/ol&amp;gt;
  )
}&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;面对 HTML、CSS、JavaScript 三分离导致的问题，React 给出了自己的答案。它将 HTML 与 JavaScript 相统一，彻底解决了两者交互不便的局面。而对于 CSS，React 也做了一层简单的封装，使得人们可以用一个 JavaScript 对象来表达一组样式。这层封装也使得 React 代码可以无障碍地迁移到其他没有 CSS 的平台，例如桌面和移动端上。&lt;/p&gt;
&lt;h3&gt;Flutter&lt;/h3&gt;
&lt;p&gt;Flutter 的主要思想与如今流行的声明式 UI 做法相同，但它作为一门全新的技术，没有那么多历史包袱，且整个框架及其实现语言 Dart 都控制在 Google 手中，因此可以从语法、工具链等层面直接支持想要的特性。这也使得它们在运行时更加高效，而语法看上去也更为激进：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;dart&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-dart line-numbers&quot;&gt;&lt;code class=&quot;language-dart&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyApp&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StatelessWidget&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token metadata function&quot;&gt;@override&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Widget&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BuildContext&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      decoration&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BoxDecoration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Colors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;white&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      child&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Center&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        child&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Hello World&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            textDirection&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextDirection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ltr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            style&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextStyle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fontSize&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; color&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Colors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;black87&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，使用 Flutter 开发的应用程序，无论布局、样式还是逻辑，全部都由 Dart 实现，使得它们之间的互操作毫无障碍。对于一些布局和样式，Flutter 直接为它们提供了单独的组件，鼓励大家多多使用组合的方式，而不必过多纠结于复杂的布局规则与嵌套深度。在构建布局时也只要像构建普通对象那样直接 new 就可以了，并不需要学习一门其他语言或 DSL。也不用担心由此引发的嵌套过深以及临时对象过多等问题，底层运行时自有优化的方式。&lt;/p&gt;
&lt;h3&gt;回到 Web 端&lt;/h3&gt;
&lt;p&gt;我们从时间维度列举了几个&lt;del&gt;我自己略有了解的&lt;/del&gt;具有代表性的 GUI 技术，从中可以一窥技术发展的趋势。一方面是在编写界面布局时，逐渐采用声明式的方式来写，这确实为我们减轻了不少心智负担。而另一方面是，大家几乎都不认为布局、样式和逻辑三者应当被严格分离，而是应该统一在一起，由同一个体系进行描述。&lt;/p&gt;
&lt;p&gt;那么为什么偏偏是 Web 端成为了异类，分裂出了 HTML、CSS、JavaScript 这三种独立的技术呢？我想这可以从历史的角度给出解答。&lt;/p&gt;
&lt;p&gt;HTML 的出现最早，早于上面的所有其他 GUI 技术。HTML 的全程是 &lt;em&gt;HyperText Markup Language&lt;/em&gt; ，可以看出它的初衷只是想更好地描述一个文档，因此需要对普通的纯文本文档进行一些增强。&lt;/p&gt;
&lt;p&gt;CSS 出现在 HTML 之后，本意只是对 HTML 的展示效果做一些美化，且美化的结果可以因人而异、因浏览器而异（这也为不同浏览器 CSS 一致性问题埋下了伏笔）。另外 CSS 有层次之分，例如可以让用户指定的样式覆盖浏览器的默认样式，这也是为什么 CSS 叫做&lt;em&gt;层叠&lt;/em&gt; 样式表。HTML 不应该依赖于 CSS，即使没有 CSS 也要保证网页的内容能够正常显示。出于这样的设计目的，将 HTML 与 CSS 分开是很合理的。&lt;/p&gt;
&lt;p&gt;JavaScript 的出现则更晚了，且一开始也只是为了做一些简单的表单与交互效果，在发明它时大家完全没有想到会发展到如今这样一个庞大的生态。在我印象中，若干年之前大家还会提到的一个最佳实践是用户会出于安全的考虑禁用 JavaScript，而被禁用之后的网页也应尽量保证内容的正常展示，因此 JavaScript 能不用就不用。不过在 SPA 开始流行之后就渐渐没有这种声音了。&lt;/p&gt;
&lt;p&gt;Web 技术和上面提到的其他技术最大的区别在于，Web 起初仅仅是想被用作文档的展示，而其他技术在各自的平台上都是为了做一个完整的应用。一个应用的功能和交互逻辑远比文档展示要复杂，这就需要在代码层面上，交互层与表现层需要尽可能方便地互操作。但 Web 发展到现在已经远远偏离了它被发明的初衷。&lt;strong&gt;Web 已经事实上应用化了。&lt;/strong&gt; 因此我们需要从应用的角度去重新审视 Web。这也是为什么我们会觉得 Web 的三分离架构已经开始拖我们的后腿。&lt;/p&gt;
&lt;h2&gt;“分层架构”&lt;/h2&gt;
&lt;p&gt;分层是软件开发领域十分常见的做法，随着代码库规模不断扩大，分层可以强迫开发者理清楚代码中各个模块的依赖关系，避免代码变成一团浆糊。但具体到如何分层，也是有一番讲究的。&lt;/p&gt;
&lt;p&gt;传统上我们提到分层架构时，都是指横向分层，例如在前后端还未分离的时代大家言必提及的 MVC 架构。在真实的项目中，我们还可以进一步将其分为 entity、dao、biz、action 等细分层级。而在纯粹的前端领域，HTML、CSS 与 JavaScript 也正好构成了结构层、样式层、交互层这三个相互独立的层级，这也使得它非常清晰明了，易于被人们所接受。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 638px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABAABBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAFZ+sdnJcV//8QAGRABAAMBAQAAAAAAAAAAAAAAAQACAxET/9oACAEBAAEFAr5lDpPAlgQzr2f/xAAWEQADAAAAAAAAAAAAAAAAAAABEBH/2gAIAQMBAT8BpX//xAAWEQADAAAAAAAAAAAAAAAAAAABEBH/2gAIAQIBAT8BEX//xAAdEAABAgcAAAAAAAAAAAAAAAAAApEQERIhMTJC/9oACAEBAAY/Ap1Kc6c3W5cxD//EABkQAAIDAQAAAAAAAAAAAAAAAAABEBEhMf/aAAgBAQABPyG7HjfscMUsdeH/2gAMAwEAAgADAAAAEKzv/8QAFxEBAAMAAAAAAAAAAAAAAAAAARARkf/aAAgBAwEBPxBJoB2P/8QAFxEAAwEAAAAAAAAAAAAAAAAAARARIf/aAAgBAgEBPxAI1f/EABsQAQEBAAIDAAAAAAAAAAAAAAERADFxYeHx/9oACAEBAAE/EBlGyfOFRfr0woPIXI6Q84DTO3BCb//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;HTML、CSS 与 JavaScript&quot;
        title=&quot;&quot;
        src=&quot;/static/11ff0bf1fcab52693cc0d2f76cdd9b3b/8608d/1.jpg&quot;
        srcset=&quot;/static/11ff0bf1fcab52693cc0d2f76cdd9b3b/efcb5/1.jpg 203w,
/static/11ff0bf1fcab52693cc0d2f76cdd9b3b/02e66/1.jpg 405w,
/static/11ff0bf1fcab52693cc0d2f76cdd9b3b/8608d/1.jpg 638w&quot;
        sizes=&quot;(max-width: 638px) 100vw, 638px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;但随着时代渐渐发展，开发者们发现单纯这样横向分层，对于愈发复杂的业务场景及它们组成的大而全系统，越来越力不从心了。很难再用一套普适的方案去解决各种各样错综复杂的业务问题。因此，与其关注如何从技术功能上将它们分层，不如更加关心如何从业务模块的角度将它们分离开来。&lt;/p&gt;
&lt;p&gt;这方面的典型就是后端的 &lt;a href=&quot;https://www.ibm.com/cloud/learn/soa&quot;&gt;SOA 架构&lt;/a&gt;。我们将业务逻辑抽象为一个个相对独立的服务，服务与服务之间通过暴露接口的方式调用。而在服务内部就可以自行选择实现的方式，只要保持接口不变，无论内部代码怎么划分、怎么重构，对外部调用方都是黑盒。这也体现了高内聚低耦合的原则。更进一步地，我们发展出了&lt;a href=&quot;https://aws.amazon.com/cn/microservices/&quot;&gt;微服务架构&lt;/a&gt;，以更好适应云计算和分布式的发展。在微服务架构中，服务与服务之间通过网络 API 通信，甚至可以让不同的服务采用不同的技术栈来实现。&lt;/p&gt;
&lt;p&gt;业界很喜欢创造很多新的名词来指代每一次微小的技术革新。抛开这些名词中细枝末节的定义区别，将它们简单化来说，其实也可以概括成分层架构。只是不同于传统的横向分层，它做的是纵向分层，将整个应用以不同的业务模块分割，而每个模块内部，视业务的复杂程度可以包含横向的一层或多层，或 whatever，随开发者喜欢。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.60098522167488%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAABYlAAAWJQFJUiTwAAABFElEQVR42kXOXUvCUByA8X2bKGii4fLsnP/Oy15zbm5uMysssCjIAgm7ioKuhG4St3KlSNRXqNs+W8er4Hfz3D0Kt9w465m2qwPDlGNjzeBWpVbPX+bLxRtlJGwHYGBM0BpGhCCANQUDsTyvle4LywbKmRAS5bxSrRbFrPz82WanTv/e7o0hHNLoWsLBVcMfSko9mezEE9afBud55/LVGeRwNEXdpw334fnjt/z63qQXEN0EJ3fp2ePewW3zcCxT80e7rZGCzVAXAfeSqDcIs2MsAsRbYEeqJmbz1WJRquoW5yDP47gtt5He+N8mhoEBTNuJ06yTJNw0CYDBmNzOi+J9uappKE67fhhZbhOYKRGJCkLFH6VvXtsjbQxeAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;两种不同的分层方式&quot;
        title=&quot;&quot;
        src=&quot;/static/a57c9a901f0ef146028d779a81e84048/d7542/2.png&quot;
        srcset=&quot;/static/a57c9a901f0ef146028d779a81e84048/2efce/2.png 203w,
/static/a57c9a901f0ef146028d779a81e84048/1d180/2.png 405w,
/static/a57c9a901f0ef146028d779a81e84048/d7542/2.png 810w,
/static/a57c9a901f0ef146028d779a81e84048/d56b5/2.png 1215w,
/static/a57c9a901f0ef146028d779a81e84048/4c7dc/2.png 1599w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;按技术层级横向分层，与按业务类型纵向分层。图源 &lt;a href=&apos;https://kislayverma.com/programming/how-to-organize-your-code&apos;&gt;https://kislayverma.com/programming/how-to-organize-your-code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在前端领域也发生着这样的改变。随着前端工程化的逐渐成熟，开发者不再需要操作原始的 HTML、CSS 与 JavaScript，而是将它们视为最终的编译产物。在此之上，前端组件化悄然兴起。我们不再将 HTML、CSS 与 JavaScript 视为三个泾渭分明的层级，而是将整个项目划分为各个大大小小的业务组件，把三者打散到每个组件之中。React All-in-Js 自不必说，即使是沿用了三分离的 Vue 也需要使用单文件组件，把各自的碎片封装在同一个文件里。紧跟后端发展的步伐，对标微服务，就成了现在方兴未艾的&lt;a href=&quot;https://micro-frontends.org/&quot;&gt;微前端架构&lt;/a&gt;。说不定未来就会开始出现“微全栈架构”呢，让我们拭目以待吧。&lt;/p&gt;
&lt;p&gt;其实近年来代码组织结构的变化和开发者观念的转变也是容易理解的。这并不是单纯技术层面的问题，更是人性的问题。代码的分层，按照技术层面横向来分，还是按照业务模块和组件纵向来分，本身只是观察同一个问题的不同角度，并没有对错之分。但是随着项目规模越来越大，业务越来越复杂，没有人能够完全掌握整个项目的所有细节，这就必须考虑如何多人协作。而与人打交道可比与代码打交道麻烦多了。&lt;/p&gt;
&lt;p&gt;如果多人协作时，有人专门负责页面内容，有人专门负责交互，有人专门负责样式，那么一方面每个人都要了解所有需求的业务逻辑，另一方面他们必然会花很多时间在各自的耦合部分上对接&lt;del&gt;撕逼&lt;/del&gt;，这无疑会极大降低开发效率。因此正常情况下我们都是让每个人单独负责一整个独立的业务需求。而项目人员组织结构的变化，最终也将反映在代码结构之上。&lt;/p&gt;
&lt;p&gt;此外，每个人的个性不同，写代码的思路也不同。虽然我们可以用一些代码规范、格式化工具等，使大家写的代码看上去相似，并避免一些公认为不好的写法，但解决问题的思路仍然是不可能做到完全一致的。这也是为什么读别人的代码是最痛苦的。因此我们尽可能地追求代码的高内聚低耦合，这样只要保证对外接口正常，模块内部自己想怎么干就可以怎么干。&lt;/p&gt;
&lt;h2&gt;前端组件化与 class 的没落&lt;/h2&gt;
&lt;p&gt;在前端组件化之后，我们就要重新思考曾经的做法放到现在是否仍然成立。&lt;/p&gt;
&lt;p&gt;这里就不得不提到 HTML 的 class 属性。现在大家提到 class 属性的用途，基本上只是用来配合 CSS 给元素附加上样式。但是 class 设计之初可不是只有这样的用途。这一点从名字上也看的出来，class 意为“类名”，本意只是给元素增加一个（可以重复的）名字。&lt;/p&gt;
&lt;p&gt;在 HTML 诞生之初，支持的标签名还很少，而且整个 HTML 都是一个单文件，内容一多可读性就会变得比较差。这时我们给一些元素增加人类可读的名字，可以有效提升代码的观感。也就是说 class 并不是单纯服务于 CSS 的，这也是从传统来说大家都推荐给 class 起一个有意义名字的原因之一。&lt;/p&gt;
&lt;p&gt;HTML5 为 HTML 定义了更多的语义化标签，用来描述很多网站中共通的部分。它也起到了标准化的作用，更有助于 SEO，但也在一定程度上替代和削弱了 class 的作用。&lt;/p&gt;
&lt;p&gt;而在如今组件化的时期，开发者拥有了将各种原始标签封装成业务组件的能力，而在上层界面拼装时，只需将业务组件进行组合。这等同于我们拥有了创造新标签的能力，可以用标签的名字来表达这个组件的意义。这使得 class 的表意作用进一步被削弱。而配合声明式 UI 的写法，我们也甚少再去利用 class 操作原生 DOM 对象了。&lt;/p&gt;
&lt;p&gt;终于，class 只剩下了用于附加 CSS 样式的功能。&lt;/p&gt;
&lt;p&gt;但是，如果用 class 只是为了附加样式，是不是给人一种脱了裤子放屁的感觉呢？程序员都知道命名是最困难的事情之一。如果只是为了附加样式用那么一次，却需要你想这么多名字，甚至催生出了 &lt;a href=&quot;http://getbem.com/&quot;&gt;BEM&lt;/a&gt; 这种繁琐的解决方案，这一切值得吗？另外，将 HTML 与 CSS 的代码分离开也导致开发和阅读代码时你的注意力需要反复横跳，窗口反复翻页，无形中也破坏了开发思路的连贯性。&lt;/p&gt;
&lt;p&gt;那么，该使用 inline style 吗？相信受过专业训练的大家一下子就会对这个词产生本能的反感，并列举出种种不该这么做的理由。嘛，inline style 的代码给人的观感确实丑、繁琐，毋庸置疑。不过其他理由在今天是否还成立呢？这是一个值得商榷的问题。&lt;/p&gt;
&lt;p&gt;反对 inline style 的一个理由是 inline style 破坏了 CSS 的层次关系，因为行内样式的优先级最高，这使得项目中的其他地方失去了能够覆盖样式的能力，这违背了&lt;em&gt;层叠&lt;/em&gt; 样式表设计的本意。但覆盖样式的能力真的值得使用吗？我想大概很少有人能在不借助参考资料的情况下短时间回忆起各种 CSS 选择器的优先级规则吧。如果想把 CSS 的覆盖样式能力作为 feature 大规模使用在项目中，长此以往只会使逻辑更加混乱和难以维护，最终只能 &lt;code class=&quot;language-text&quot;&gt;!important&lt;/code&gt; 完事。&lt;/p&gt;
&lt;p&gt;可能会有人希望将这一特性用于组件样式自定义的接口。例如，有一个通常情况下是蓝色的按钮，但在特定的场景下，可以通过覆盖该按钮的样式将颜色改为红色。但请注意，这不是一个单纯 CSS 的问题，与其简单地使用 CSS 覆盖，我们更应该做的是去思考在什么业务场景下需要改变按钮的颜色，并给这个按钮组件提供一个属性来区分不同的场景。另外，随着组件样式越来越丰富，往往需要很多 CSS 样式共同呈现出完整的视觉效果，这种时候指望简单覆盖一个 CSS 就可以实现改变颜色的效果是不现实的。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.827586206896555%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABdklEQVR42o2QwWvUQBTG868KIoJXz568qBe9Fq9SRBBBQZCiINjdS8HCIqVKqWw3yXZmk02yk5nJJPPzJYvnbuCX9/Jm8s33TRJjpOsCfegwHcyWMBcuVOB3dca5/sGf+idZtubq6pq/y4xWXeJuTnHpnJBf0y9+ERYLBqVIRsGqbnCmZlnB/Q+RRx/h1XzH0cVjnp7d4/XlE06+fufN8TvefzohnR9RfH6A+vIQ++0t7sVL7PNnhNlsL/j/GVvfgw0QhhGP663UjmEYJElH6Hui9DE4Yu9h7L2fYIgkY1zrumnzMG0eCVM/9HGPqIcg1yKzkRAEmU/IPIjohPSJdZ6s2JEWhk25ZVNsKeoWXRlM2+JkvbUe7/eH3kUyvm7l5xu9Y1U05KpCKalrTZrn5FvDatPQinA4VLBsrDgSV+KslFrJAVprdCni8l00Lb470KGTy0wl8lI3ZKURhzV6cnhLtlbkMlvJ+hj9YMHaOIw9LNJdgv8A9h6tSx91LlsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Element UI 中的 button&quot;
        title=&quot;&quot;
        src=&quot;/static/b1d552b6490269fb49fb5c6209058482/d7542/3.png&quot;
        srcset=&quot;/static/b1d552b6490269fb49fb5c6209058482/2efce/3.png 203w,
/static/b1d552b6490269fb49fb5c6209058482/1d180/3.png 405w,
/static/b1d552b6490269fb49fb5c6209058482/d7542/3.png 810w,
/static/b1d552b6490269fb49fb5c6209058482/77672/3.png 1060w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;Element UI 中的 button 组件就是一个很好的例子。它通过 type 属性区分不同的业务场景，从而表现为不同的颜色，而不是直接暴露一个 class 供调用方覆盖。这一方面明确了业务场景，符合语义化的设计，另一方面也提供了色彩的规范，防止调用方滥用造成的视觉混乱。&lt;/p&gt;
&lt;p&gt;反对 inline style 的另一个理由（也许是最重要的理由）就是使用 class 可以对样式进行复用。在前组件化时代这一点确实非常重要，因为它是复用样式的唯一手段。但组件化出现之后，我们不禁面临一个尴尬的问题：是使用 class 复用样式，还是使用组件封装复用样式呢？不难意识到，当然是通过组件来复用了，毕竟采用组件的形式，什么都可以复用，而 class 只能用于复用样式。组件是完全的上位替代。&lt;/p&gt;
&lt;p&gt;另外，组件化的出现也一定程度导致使用 class 复用样式变得困难。组件化出现之前，无论你的 CSS 再复杂，终究全局共享同一张样式表，只要 class 名字相同，就可以复用样式。但在组件化出现之后，为了避免不同组件间 class 名字的冲突，我们往往会使用 &lt;a href=&quot;https://github.com/css-modules/css-modules&quot;&gt;CSS modules&lt;/a&gt;、&lt;a href=&quot;https://vue-loader.vuejs.org/zh/guide/scoped-css.html&quot;&gt;Scoped CSS&lt;/a&gt; 等方式规避，这反而对不同组件间复用样式造成了阻碍。&lt;/p&gt;
&lt;p&gt;再考虑一个很现实的因素。在实际项目中，不同的需求总是会有定制化的东西，你辛辛苦苦设计了复用体系，一旦产品改个需求，就一朝回到解放前了。这导致我们实际能够复用的样式总归是很局限的。例如我们设计了这样一个可复用的样式，一切看上去都很美好：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container1&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container2&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container3&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
&lt;span class=&quot;token selector&quot;&gt;.container&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但随着产品的不断迭代和调整，慢慢地它变成了这样：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container1&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container1&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container2&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container2&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container3&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container3&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
&lt;span class=&quot;token selector&quot;&gt;.container1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.container2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.container3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px 20px 0 5px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;面对这样不能复用，但又不是完全不能复用的代码，你十分难受。痛定思痛之后，你意识到如此粗粒度地复用 CSS 是没有前途的。于是你把样式代码按各自的功能进行更加细粒度地划分，直至可以复用的最小单元，改写成了这种模样：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container1 flex&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container1&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container2 flex&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container2&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;container3 flex&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;container3&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
&lt;span class=&quot;token selector&quot;&gt;.container1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.container2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.container3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px 20px 0 5px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;.flex&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;那么恭喜你，此时的你已经在发明 Tailwind CSS 了。&lt;/p&gt;
&lt;h2&gt;Tailwind CSS 为我们做了什么&lt;/h2&gt;
&lt;p&gt;CSS 在 Web 发展的初期很好地履行了自己用于样式美化的职责。但随着前端网页逐渐从简单的文档流变得复杂化、应用化，CSS 与 HTML、JavaScript 三分离的特点事实上阻碍了开发效率的提升。在前端工程化和组件化兴起之后，人们对前端软件工程的观念也有所转变和发展。原先我们在学习 CSS 时耳熟能详的各类原则和最佳实践，已经不那么正确且有用了。&lt;del&gt;大人，时代变了。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;但 Web 长久以来的发展，已经造就了一个无比庞大的互联网生态圈。纵观技术的发展史，一个成功的、活的久远的技术，其成功的秘诀往往并不是技术本身。比技术方案更重要的是先发优势，以及能不能形成足够庞大活跃的生态。因此，即使 Web 技术有着诸多现在看来不甚合理的地方，但这些历史包袱也是无法被轻易抛弃的。我们能做的，只是将不好的一面尽可能封装，使之变得更为好用罢了。&lt;/p&gt;
&lt;p&gt;在这一点上 Tailwind CSS 就做的恰到好处。它不像 Sass/Less，试图发明一个“更好的” CSS，也不像 Flutter for Web，试图让大家忘记 CSS 和其他的一切，重塑整个 Web 生态，也不像 CSS in JS，虽然拥抱了组件化，但依然要让繁琐的 CSS make your hands dirty。它用原子类的封装，为我们温柔地隔离了 CSS 的世界。它敏锐地发现了 class 属性的逐渐变味与没落，并利用它，使得开发者完全不需学习新的语法就能轻易看懂与上手。&lt;/p&gt;
&lt;p&gt;Tailwind CSS 的出现也是历史发展的必然结果。正如我在上一节中举的例子，其实在平时开发过程中我们也已经有意无意间使用了原子类来解决 CSS 细粒度复用的问题。而 Tailwind CSS 将这一切规范化、工程化了。&lt;/p&gt;
&lt;p&gt;当然 Tailwind CSS 还提供了一些其他的能力，例如对项目中各类边距、颜色、单位等的标准化。这也是相当值得称道的点，可以为我们的页面美观程度保持一定的下限。不过这些都没那么重要了，在合适的时间出现解决合适的问题，它必定会是当今这一时代最值得使用的 CSS 框架。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[抛弃 Hexo，拥抱 Gatsby]]></title><description><![CDATA[在去年年底的时候，在外观和功能没有明显变化的情况下，将本博客的技术栈从 Hexo 改为了 Gatsby。没错就是自己重写了一份。随后也为 @Fin 使用 Gatsby 重写了博客。既然是差不多的事情就放一起写了吧。 其实重写博客的想法早已有之，在之前自己写 Hexo…]]></description><link>https://maliut.space/p/from-hexo-to-gatsby/</link><guid isPermaLink="false">https://maliut.space/p/from-hexo-to-gatsby/</guid><pubDate>Sun, 23 May 2021 18:25:00 GMT</pubDate><content:encoded>&lt;p&gt;在去年年底的时候，在外观和功能没有明显变化的情况下，将本博客的技术栈从 &lt;a href=&quot;https://hexo.io/zh-cn/&quot;&gt;Hexo&lt;/a&gt; 改为了 &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt;。&lt;del&gt;没错就是自己重写了一份。&lt;/del&gt;随后也为 &lt;a href=&quot;https://finzhang.space/&quot;&gt;@Fin&lt;/a&gt; 使用 Gatsby 重写了博客。既然是差不多的事情就放一起写了吧。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;其实重写博客的想法早已有之，在之前&lt;a href=&quot;/p/hexo-theme-simpow&quot;&gt;自己写 Hexo 主题的时候&lt;/a&gt;，我就有感于 Hexo 的弊端。作为一个使用者，你只是正常的写 Markdown 然后发布，用什么都无所谓。但开发 Hexo 主题使用的是各类模板，让人梦回 JSP 时代。一方面编辑器的支持不太好，写代码没那么顺手；另一方面，在前端技术飞速发展的现代，模板式的写法确实显得落了一截，很难享受到很多前端发展的最新成果。这也是为什么之前我列了很多 TODO 项，因为一些复杂的功能开发起来实在麻烦，使我没动力继续下去了。&lt;/p&gt;
&lt;p&gt;之前写主题时，我就设想了许多动态化的功能。我理想中的博客已经不仅仅是一系列 Markdown 文章的集合，而是逐渐向一个应用化的方向发展。去年学会了 React 与 Vue 两大框架，就自然而然地与前端应用产生了联系。&lt;/p&gt;
&lt;p&gt;我设想中的博客网站基于 React 或 Vue 技术栈。将一个单页应用用于博客并不适合，因为会有 SEO 的问题，但作为我的个人空间，我并不在乎它能否被搜索引擎找到&lt;del&gt;，甚至想避世隐居&lt;/del&gt;。除了 Markdown 以外，博客的内容还可能来自其他很多数据源，因此将会有一个数据中间层，将所有数据源转变为 JSON 之类的通用数据格式。而前端页面在使用数据时，就可以像普通网站那样，通过 API 把数据请求回来。&lt;/p&gt;
&lt;p&gt;我甚至都开始设计接口，准备造轮子了。不过在搜索类似的做法时，我找到了 JAMStack 技术栈。它不仅能够完全符合我的需求，而且也解决了 SEO 的问题。它甚至有自己的&lt;a href=&quot;https://jamstack.org/&quot;&gt;官网&lt;/a&gt;。初次看到它的理念，就像遇到了一位老朋友一样，令我兴奋不已。我并不想把这篇文章变成 JAMStack 的介绍帖，因为它的官网已经写的足够好了，无需画蛇添足，感兴趣的朋友可以移步它的官网进一步阅读。&lt;/p&gt;
&lt;p&gt;在 JAMStack 的官网上列举了许多具体实现的框架（当然，Hexo 作为静态网站生成器，也是其中的一员）。结合 React + SSG 的标签，我们很容易将目光放在当今最为流行的框架之一 —— Gatsby 之上。在对比了其他定位类似的框架之后，我发现它不仅拥有上述 JAMStack 提到的所有优点，而且还有一个最大的特点，即 GraphQL 中间层，可以将任何来源的数据源统一转换，在前端代码中利用 GraphQL 做编译期查询。简直与我的想法不谋而合！&lt;/p&gt;
&lt;p&gt;除此之外，Gatsby 还提供了其他人性化的功能。例如内置的 Link 组件，在首次加载页面后即可预加载其他页面，用户跳转时无需刷新整个页面，得到 SPA 的丝滑体验。还有 gatsby-image 插件对图片的处理，为你自动压缩、优化，根据浏览器环境选择合适的图片格式与图片大小进行加载，同时还能拥有渐进式加载的过程。不仅省去了你之前自己处理图片、上传图床的功夫，还大大提升了用户浏览时的体验。&lt;/p&gt;
&lt;p&gt;Gatsby 上手难度并不高，唯一蛋疼的是在国内安装颇费一番功夫。虽然 npm 可以换国内源，但是底层需要一些图片处理的能力，安装过程会直接从 GitHub 原始地址下载，还有 node-gyp 之类的配置等需要安装，要么有个好的梯子，要么需要自己额外处理下。开发过程倒是轻松愉快，借助 React 的庞大生态，可以轻松实现之前做的很蛋疼的功能。在之前的开发记录最后，我留下了一堆 TODO 项，都是因为做起来很麻烦的，这次也都一口气实现了出来。&lt;/p&gt;
&lt;p&gt;顺便，在开发的过程中，也实现了不少副产物。在 Hexo 中有现成的评论插件，不能在 Gatsby 中使用，因此直接重写了一份。另外评论的邮件提醒功能，也调研了阿里云的邮件服务与云函数得以实现。由于它们都有每月一定的免费额度，我们就可以愉快白嫖。&lt;/p&gt;
&lt;p&gt;Gatsby 中也没有现成的文章加密功能，为此也自己造了一个轮子，研究了一下 gatsby-remark 插件的写法，并开源发布在了 npm 。地址在 &lt;a href=&quot;https://github.com/maliut/gatsby-remark-encrypt&quot;&gt;gatsby-remark-encrypt&lt;/a&gt; ，欢迎大家多多 star ←_← 。与 Hexo 版本的加密插件不同，它并不负责 UI 的展示，而只是专注于实现加密功能本身，提供加密后的数据源供 GraphQL 调用。当然，我在文档中也提供了简单的 Code Example 。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;之后也为 &lt;a href=&quot;https://finzhang.space/&quot;&gt;@Fin&lt;/a&gt; 写了一份博客。虽然用的是同一套技术栈，但由于网页的布局与涉及到的功能都和自己的有不少区别，因此也接触了不少新东西，例如对 Latex 的使用、pdf 嵌入、以及一些 Gatsby 的高级 API 等。碰巧在这期间 Gatsby 也升级到了 v3 版本，主要改进了图片的使用方式，变得更为简单，遂学习之。&lt;/p&gt;
&lt;p&gt;一个有趣的功能是全文搜索。虽然 Gatsby 生态中已经提供了一些现成的搜索插件，但是实际使用后发现它并不能支持中文分词搜索。将搜索插件所生成的索引文件打开来看后，发现所有中文的内容都默认被过滤掉了。所幸在网上找到了一篇介绍 VuePress 使用 FlexSearch 进行繁体中文搜索的文章，配合自己的调试，总算是做出了比较完美的解决方案。有时间的话后续也再深入了解一番，写篇文章。&lt;/p&gt;
&lt;p&gt;另一个值得一提的点是，为了让自己在新的项目中有所收获，我也尝试了一番前段时间老是见人提起的 TailwindCSS 框架。我个人觉得还是值得一用的，而对这种原子 CSS 的用法也有了一些个人感想，在未来也会单独发文。或许某一天我也会用 Gatsby v3 + TailwindCSS 更新我的博客。（又给自己挖坑了（笑&lt;/p&gt;</content:encoded></item><item><title><![CDATA[白嫖大厂！搭建自己的跑团平台]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/paotuan/</link><guid isPermaLink="false">https://maliut.space/p/paotuan/</guid><pubDate>Fri, 19 Feb 2021 11:30:07 GMT</pubDate><content:encoded>&lt;p&gt;本文介绍了一种搭建跑团平台的新思路，它具有配置简单、完全合法、使用免费等优点，适合朋友间小范围进行跑团活动。并基于此思路，搭建了一个现成的平台，提供骰子、线索面板、日志记录、人物卡等一系列开箱即用的功能。&lt;/p&gt;
&lt;p&gt;本文相对偏重设计理念与实现思路。如果读者想要以使用者的角度了解平台的使用方法，建议参考这个&lt;a href=&quot;https://www.bilibili.com/video/BV15K4y1H7tf&quot;&gt;视频版本&lt;/a&gt;，记得一键三连哦~&lt;/p&gt;
&lt;p&gt;访问 &lt;a href=&quot;https://paotuan.github.io&quot;&gt;paotuan.github.io&lt;/a&gt; 立即开玩，或通过微信扫描下方的二维码访问本平台的小程序版本。如果您对此感兴趣或有任何意见或建议，欢迎加群 115699014 一起交流。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 258px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a2c8bc93629165c87bb42c0ba631af5b/53f89/minacode.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAMFBP/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHYi4k2ExWYUB//xAAaEAACAwEBAAAAAAAAAAAAAAABAgADESES/9oACAEBAAEFAnfItwYg8uUlEVvS9GTNgGD/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAbEAEAAgIDAAAAAAAAAAAAAAABABEQMSBRYf/aAAgBAQAGPwJ6lF37ije4LYHCif/EABoQAQADAQEBAAAAAAAAAAAAAAEAETEhUWH/2gAIAQEAAT8hHYAdl8fCmy0XxjJOH4hT0LVmrYhbSA4chEFBP//aAAwDAQACAAMAAAAQDNdB/8QAFhEBAQEAAAAAAAAAAAAAAAAAAQAR/9oACAEDAQE/EFy2YMML/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAGxABAQADAQEBAAAAAAAAAAAAAREAITFBUXH/2gAIAQEAAT8QITuY97iG04cPxctJQKXCLQQ9RvfmCR8mGwTxb3uBRgLpqaw9sBLgJVV15pzx5Iz/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;跑团 IO 小程序码&quot;
        title=&quot;&quot;
        src=&quot;/static/a2c8bc93629165c87bb42c0ba631af5b/53f89/minacode.jpg&quot;
        srcset=&quot;/static/a2c8bc93629165c87bb42c0ba631af5b/efcb5/minacode.jpg 203w,
/static/a2c8bc93629165c87bb42c0ba631af5b/53f89/minacode.jpg 258w&quot;
        sizes=&quot;(max-width: 258px) 100vw, 258px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2&gt;我们的平台是什么&lt;/h2&gt;
&lt;p&gt;去年春节的时候，为了方便几个朋友一起跑团，就搭了一个平台 &lt;a href=&quot;/p/botim&quot;&gt;BotIM&lt;/a&gt; 。自己用了几次以后朋友给我提了些建议，但当时自己也不怎么懂一些现代前端框架，开发能力有限，再加之跑团本身也是个低频行为，就搁置了下来。而今年正好学了一些新技术，也想到了一个方法能把这个平台 SaaS 化提供给大家使用，于是趁放假的时间爆肝开发了一波，把之前提到的没提到的功能都加上了。前段时间跑了一次&lt;a href=&quot;/p/log-100-percent-angel/&quot;&gt;测试团&lt;/a&gt;，效果还挺好的，所以也想做个介绍推荐给大家。&lt;/p&gt;
&lt;p&gt;和其他的平台不同，严格意义上来说它并不是一个跑团平台，而是通过借助大厂的力量并加之一些跑团功能的二次开发，让任何人都可以无门槛地拥有一个自己的跑团平台。&lt;/p&gt;
&lt;h2&gt;为什么需要搭建自己的跑团平台&lt;/h2&gt;
&lt;p&gt;可能会有人说，现在已经有了很多现成的跑团平台和工具了，为什么还要再自己做一个。它与其它平台的差异点在哪里。&lt;/p&gt;
&lt;p&gt;一般我们进行网团时用的都是 QQ 机器人或者一些比较专业的跑团平台。 QQ 机器人的好处就是基本使用方便，不用去额外的平台&lt;del&gt;谁没有 QQ 呢&lt;/del&gt;，但受限于既有的交互方式，一些额外功能就需要用并不怎么人性化的指令来实现。另外，如果要看人物卡或者是其他资料就只能多个窗口切来切去了。&lt;/p&gt;
&lt;p&gt;另一点是，大概去年初的时候 QQ 封禁过了一片机器人，当时引发了一片哀嚎以及跑团圈子对腾讯的口诛笔伐。但其实完全可以理解，因为 QQ 从来没有给第三方提供过正规的机器人接口，现在的机器人都是通过对 QQ 协议的破解来实现的。你可以批评腾讯没有互联网的开放精神，但他们这么做也有客观的商业、安全、乃至我国国情的特殊因素。而使用破解协议这样的做法，本身就是风险极高的行为&lt;del&gt;开发者要抓狂了&lt;/del&gt;。你可以说我只是拿它来跑团，人畜无害，但其背后蕴含的商业价值使得它必定会被人拿去做坏事。因此这条路是首先要被排除的。&lt;/p&gt;
&lt;p&gt;然后再说说那些比较专业的跑团平台。我在网上也陆陆续续看到过很多功能很完善的跑团平台，大多是玩家自己用爱发电开发出来的。无论从技术上还是执行力上我都很敬佩这些开发者们。但是考虑到网团的特点，跑团本身圈子也比较小，也并不是一个高频的行为。在知乎上也看过一些讨论跑团平台的话题，这样的平台很难将其商业化盈利，养活自己。但用爱发电的话，服务器、带宽的费用，开发人员的时间和精力，都是一个不小的成本。这注定是一个难以持续的行为。因此，对这些平台能持续多久，我在平台上的信息是否安全等等，心里还是不太放心的。&lt;/p&gt;
&lt;p&gt;那么，就没有别的方法了吗？&lt;/p&gt;
&lt;h2&gt;我们的解决方案&lt;/h2&gt;
&lt;p&gt;一个网团平台的核心其实是一个即时通信系统。其他的功能可以慢慢做，但是如果连基础的消息都会延迟或者丢失的话，那是很令人抓狂的。即时通信系统背后的坑很多，无论是从产品、技术方案设计还是服务器资源的角度，都是一个很大的挑战。所幸的是我们并不需要从零开始自己实现这一切，各大厂商都提供了现成的服务，比如腾讯云通信、网易云信、融云 SDK 等。虽然它们都是商业产品，要价不菲，但为了吸引用户，各家都提供了免费的试用版本，这样就可以达到白嫖的目的。&lt;/p&gt;
&lt;p&gt;这里我选用的是腾讯云的 SDK，因为我个人比较熟悉。从官网上可以看到腾讯云 IM 定价每月￥999 起，对个人自用肯定是不现实的。但有可以只支持最多 100 人的体验版，而且这样的体验版没记错的话也能创建 100 个。这对于我们小圈子跑团完全够用了。&lt;/p&gt;
&lt;p&gt;看到这里可能又有人疑惑了，跑团圈子再小，总不至于 100 个人就够用。这你就陷入了思维定势。由于网团的核心是即时通信功能，大家在设想一个跑团平台时，就会容易把它想成一个我们熟悉的社交平台，大家每个人在这个平台上注册一个帐号，一起组局一起玩。甚至我看到一些平台在此基础上更加延伸，加入了主持人与玩家的评价、互相关注等等功能。但是，这些真的是我们需要的功能吗？如果我们需要社交，QQ 和微信不香吗？何况跑团圈素有先做朋友后跑团的说法。在我看来，跑团平台只是一个用完即走的工具，它不负责为你找到跑团伙伴，而只是为你的跑团过程提供尽可能多的方便。&lt;/p&gt;
&lt;p&gt;在明确了跑团平台的定位以后，我们的技术方案也就顺理成章了。并不是我一个人守着一个苦逼的 100 人帐号满足所有人，而是如果你有一群经常一起玩的小伙伴，或者你是个经常带团的主持人，你就可以开通一个体验版。甚至你不嫌麻烦每开个团都开通个体验版也没人拦着你。&lt;/p&gt;
&lt;p&gt;这样做不仅解决了如何白嫖试用版的问题，也同时解决了 Secret 的鉴权问题，一箭双雕。IMSDK 可以通过本地对 AppId 与 Secret 计算进行鉴权，但将 Secret 放在本地有泄露的风险，可以让别有用心之人恶意消耗你的资源。因此这种方式只能用于调试，在生产环境中则需要一个专门的业务服务用于服务端鉴权。但我们采用了多应用的实现方式以后，就无需写死一个特定的 Secret 了，而是由使用者自行输入他们自己申请的体验版信息，交由本地计算鉴权。这样既不用担心泄露风险，又不用为鉴权服务付出额外的成本。退一步说，即使 Secret 发生泄漏，它也只不过是一个体验版而已，完全不会造成什么损失。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;有趣的是腾讯云 SDK 自己也没想到这种骚操作用法，导致实际实现时还遇到了一点小问题，需要对 SDK 稍作修改才能实现&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;解决了后端即时通信的问题，我们还要解决前端界面的问题。好消息是 SDK 也提供了现成的 Demo，我们不用从头写起，而是在它的基础上加以修改就可以了。而代码的部署，我们可以放在 Github Pages 上，这样也不用付出额外的成本。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3e63bb6871439cc4108ca00da95b2462/4352a/1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.615763546798032%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAAsTAAALEwEAmpwYAAABNElEQVR42hXOO0/CUBgA0P5SE91cjTo4qImbMDjgwKCSiBCBQYSCEAzyCGLUoBGxLULlcdtLC20p0Pb29anj2Q5lErv/M+zxg5VJHM9fGubKsGzHMywXy4oozWwXJrLS7rAfnwz+p6dq2lzXie1SnQ6Tz+VoOtvr8yzXzd6kindZrtsDACyMxiOeOK5pWQIaIjSyiK2bIOvOdOEuLZ9KJRPhyFXg5PShXC4VMht74bWtYL1SMpbz4lO/2OB0RcZYbLwPHl+YhTqNvsJ+zj/IQ6IF1HU8dhw6OwqEatXqfYHePLxY3w4+1ivGQrttoGSzK6sIizhfY+l6U9GE8ybspP3dNESfgWq13uhMOh6Lchz3xbB/7dhlhGFZz4fZROSlb8lQiEkkEaHZWLfmSIW2AG0RkAa/3lj52C7RvIUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;腾讯云 SDK 提供的 Demos&quot;
        title=&quot;&quot;
        src=&quot;/static/3e63bb6871439cc4108ca00da95b2462/d7542/1.png&quot;
        srcset=&quot;/static/3e63bb6871439cc4108ca00da95b2462/2efce/1.png 203w,
/static/3e63bb6871439cc4108ca00da95b2462/1d180/1.png 405w,
/static/3e63bb6871439cc4108ca00da95b2462/d7542/1.png 810w,
/static/3e63bb6871439cc4108ca00da95b2462/d56b5/1.png 1215w,
/static/3e63bb6871439cc4108ca00da95b2462/4352a/1.png 1364w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;腾讯云 SDK 提供了几乎所有主流平台的 Demo，而且功能十分完善且开源，可以直接拿来使用&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;现在我们已经有了一整个现成的即时通信平台，接下来就是往上堆跑团相关的特色功能了。其实跑团平台的核心功能就是骰子，其他的都是 nice to have 。在一批 QQ 机器人惨遭封禁之后，也有不少人提出可以用支持合法聊天机器人的 IM 软件进行跑团，例如钉钉或是 Telegram 。这类机器人的实现原理是在监听到群消息或其他事件后，向你的业务服务器发送通知，你接收到通知后可以执行自己的逻辑，例如计算出骰子结果，然后再向群里以机器人的名义发消息等等。&lt;/p&gt;
&lt;p&gt;这样固然是最为安全严谨的做法，但也带来了额外的开发复杂度与成本。真的没有更简单的做法吗？在我们自己的平台里，大可不必这么复杂。只要把机器人作为一个特殊的用户加入到平台中就可以了。只需做一个开关让游戏主持人决定它的加入和离开。在最终用户看来，这一切并没有什么不同，但大大减少了我们的实现成本。&lt;/p&gt;
&lt;p&gt;其他的功能，例如笔记、log、人物卡等功能，就是无脑往上堆就完事了。当然最佳实践是把它们储存在后端，以获得漫游功能。但秉承着我们平台的白嫖理念，我们依然选择把逻辑放在前端实现，通过特定格式的消息以借助 IMSDK 进行同步。这样做的缺点是用户必须在线才能接收到这些消息，但本来人齐了才会开始跑团不是吗？问题不大问题不大。&lt;del&gt;其实硬要做离线漫游也是可以做一做的，但是引入的复杂度实在是让人感觉得不偿失。&lt;/del&gt;&lt;/p&gt;
&lt;h2&gt;功能介绍&lt;/h2&gt;
&lt;p&gt;详细的功能介绍建议大家看&lt;a href=&quot;https://www.bilibili.com/video/BV15K4y1H7tf&quot;&gt;视频版本&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这里就简要列举一下目前支持的功能吧。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基础功能（来自腾讯云 IMSDK）
&lt;ul&gt;
&lt;li&gt;C2C 会话&lt;/li&gt;
&lt;li&gt;群会话&lt;/li&gt;
&lt;li&gt;建立群（群主自动成为游戏主持人）&lt;/li&gt;
&lt;li&gt;黑名单&lt;/li&gt;
&lt;li&gt;消息 7 天漫游&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;邀请入群
&lt;ul&gt;
&lt;li&gt;可生成分享链接，他人点击后登录平台即可自动入群参加游戏&lt;/li&gt;
&lt;li&gt;可生成分享二维码，可通过小程序登录并自动入群&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;骰子指令
&lt;ul&gt;
&lt;li&gt;支持各类常用的骰子指令（基于开源库 &lt;a href=&quot;https://github.com/GreenImp/rpg-dice-roller&quot;&gt;rpg-dice-roller&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;支持在指令后接上属性或技能的名字、难度等级，若导入了人物卡，则会自动判断成功与否&lt;/li&gt;
&lt;li&gt;支持在聊天记录中点击属性或技能的名字，快速投骰&lt;/li&gt;
&lt;li&gt;支持在人物卡中直接点击属性或技能的数值投骰&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;设置 bgm
&lt;ul&gt;
&lt;li&gt;支持主持人将网易云音乐链接分享给大家，会生成嵌入式播放器发送给所有玩家&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;笔记功能
&lt;ul&gt;
&lt;li&gt;支持主持人发布重要笔记，笔记除了显示在聊天记录中，也会固定在右侧面板，方便随时查阅&lt;/li&gt;
&lt;li&gt;玩家可对笔记进行复制、拖动排序与删除等操作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;log 功能
&lt;ul&gt;
&lt;li&gt;支持玩家对游戏 log 进行复制。log 会保存在本地缓存，不受 7 天漫游限制，除非手动清除&lt;/li&gt;
&lt;li&gt;log 可以进行拖动排序与删除，以应对网团过程中聊天记录较为混乱的场景&lt;/li&gt;
&lt;li&gt;log 可以导出为纯文字、HTML、JSON 等多种格式，满足各种场景的需要&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;人物卡功能
&lt;ul&gt;
&lt;li&gt;主持人可以为玩家导入 excel 格式的人物卡（目前只支持 CoC 规则），导入后所有人可见&lt;/li&gt;
&lt;li&gt;人物卡显示当前玩家的基本信息、属性与技能的数值以及 1/2、1/5 速查表&lt;/li&gt;
&lt;li&gt;属性与技能可按照数值高低进行排序；可根据名字或首字母进行快速搜索&lt;/li&gt;
&lt;li&gt;点击属性或技能数值，可以根据当前对应的技能与难度进行进行快速投骰&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后特别鸣谢 &lt;a href=&quot;https://finzhang.space/&quot;&gt;@Fin&lt;/a&gt; 的大量点子与测试。大家若有任何意见建议欢迎通过留言、issue、加群等方式反馈。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[跑团实况 - 天使百分百]]></title><description><![CDATA[【防剧透预警】如果你未来有兴趣跑这个团，请不要再往下看了。 去年的原创模组今年终于有机会跑了，同时也是新跑团平台的第一次试用。在全新的平台上跑全新的团，令人高兴的是整体的效果还不错，有一种努力得到了回报的感觉，很开心。 角色： 马尔克（玩家：zh）： 2…]]></description><link>https://maliut.space/p/log-100-percent-angel/</link><guid isPermaLink="false">https://maliut.space/p/log-100-percent-angel/</guid><pubDate>Sun, 14 Feb 2021 15:07:00 GMT</pubDate><content:encoded>&lt;p&gt;【防剧透预警】如果你未来有兴趣跑这个团，请不要再往下看了。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;去年的原创模组今年终于有机会跑了，同时也是新跑团平台的第一次试用。在全新的平台上跑全新的团，令人高兴的是整体的效果还不错，有一种努力得到了回报的感觉，很开心。&lt;/p&gt;
&lt;p&gt;角色：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;马尔克（玩家：zh）：&lt;/p&gt;
&lt;p&gt;22 岁，温和礼貌的英国大学生。高中时曾在效实中学交换学习一段时间。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;铃木翼（玩家：boss）：&lt;/p&gt;
&lt;p&gt;35 岁，日本法官，在旅游的路上与马尔克偶遇。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:44&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;2012年，效实中学即将迎来百年校庆。在校庆之际，效实园里却流传起了一个神秘的言论——「只要考到100分，你就能见到天使」。为了揭开这个传言背后的真相，调查员们决定来到效实中学开始调查。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克在高中时期，曾经在效实中学交换学习了一个学期。值此百年校庆之际，他受到当时在效实里结识到的朋友的邀请，决定故地重游拜访母校。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;在从伦敦飞回去的飞机上，他认识了一名来自日本的法官铃木翼，巧的是他正好要去宁波旅游，但还有几天空闲时间不知该如何打发&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:48&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;[理论上只是交换算不上母校吧】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:48&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（我真闲））&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:49&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;得知铃木翼对各种怪谈颇感兴趣，马尔克便故弄玄虚到“我听我的朋友说，在效实中学里曾经流传着一个传言，「只要考到100分，你就能见到天使」。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（半个母校（笑），马尔克在中国的母校）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;果然这成功勾起了铃木翼的兴趣，于是他们约定一起去效实中学实地探访一番。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:50&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【全世界飞还找事干的boss】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:52&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;这是一个普通的工作日，两个人一起来到了效实中学门口。上午9点，对社畜来说正是上班高峰，但对高中学生来说，上午的第一堂课都快上完了。此时的校门口门可罗雀，只有门卫坐在门卫室里，百无聊赖地打量着外面。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【你们可以行动了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【震惊，第一步竟然是要想办法先进学校】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你好&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你会说中文吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【不对啊，按理说我们已经认识了，为什么boss你这是初次见面的说法】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:54&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（因为）日本人））&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:54&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“托交换的福，我还是会一点中文的，虽然并不算好，但日常交流应该没问题”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我去和门卫说说看”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“看看能不能让我们进去”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;走向门卫室，让门卫注意到我&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:56&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;看到有人靠近，门卫走出门：“你好，请问有什么事吗”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:56&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“你好，我是效实中学过去的学生，今天是回来看老师的，请问现在允许访客进入学校吗？”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【不允许，全团终】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:57&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“哦哦，是来看老师的啊，欢迎欢迎。不过我们现在管理比较严格，怕有闲杂人等混进去，能告诉我你要找哪位老师吗？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“教化学的郭洪涛老师。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“好的好的”门卫麻利地开了一张访客单，“你登记一下，然后把访客单让老师签个字，出来的时候记得交还给我。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 18:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你好&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我算是他的监护人&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我能进去吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【？？】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我是他父亲的朋友&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“对，这是我表叔，这回陪我一起来的”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;？&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【？】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【按boss为准！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（国籍不同）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（表叔过分了）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;门卫露出了疑惑的神情，毕竟要说一个东方人是一个英国人的表叔，实在是非常少见。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;拜把子的&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【跨国婚姻，跨国婚姻】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“所以可以让他进去吗”’&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:03&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“你好，你不是我们学校的学生或校友，那按照规定，请你出示一下身份证我们做个登记。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:04&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;外国人哪来的身份证啊&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;这是我的护照&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:05&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“居然是日本人，我都没看出来。”门卫小声咕哝着，“今天真是巧，平时都没见到外国人，今天这一大早就遇到两个。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;然后他登记完转过身来“好的，你们请进吧。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:06&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“确实蛮少见的”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“谢谢您了”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:06&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;两个人顺利进入了校园内，真巧第一节课下课了，路上三三两两有学生走过。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你们准备如何行动呢&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:07&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;要干什么呢maruko san&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:07&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我想想，我可以先去找一下那位邀请我回来看的同学”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【他是谁啊，有信息吗】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:07&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（你们可以自由提出行动，也可以按照地图行动，我先放一张地图）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【呃，没想这么细节】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:08&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;那好&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:08&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【你可以现编】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:08&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;去教学楼找人吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;是姓柳是吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我们日本也有这个姓&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【这为啥是我编啊！而且邀请我回来的同学应该是当年我的同学，现在也应该毕业了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【不过照理说你当时认识的人应该已经毕业了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【对啊】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【就设定成赛亚人吧！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【可以】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【那么问题来了，赛亚人原名是啥我已经忘记了！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【徐珑迪】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:10&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“恩，他之前说到他现在回到效实当化学老师了”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我们先去教学楼找个学生问下化学办公室在哪儿”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;尝试在路上随便逮一个学生问路&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:11&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你不是在这里念过书吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:12&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;两人尝试在路上随便找一个学生问路，不过也许是因为在校园里同时出现一个中年人和一个外国人的组合太少见了，学生们纷纷用警惕又好奇的眼神打量着你们。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:12&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【忘得差不多了，毕竟是交换】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:12&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（要问路的话可以过个信用评级或者任何语言类技能）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:13&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【而且三四年换办公室位置也蛮常见的吧，才没几年整个白杨路校区都要没了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:14&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 信用评级 d100: [36] = 36 ≤ 50 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:14&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“铃木先生，要不您去找个学生问问？我这人嘴笨，还是个西洋人。怕是学生都不信任我”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:14&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;那我找那个穿着痛T恤的男的吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;一看就是个宅男&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:15&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“很有道理！”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:16&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼自然地对着一个穿着痛T的男同学搭话，也许是看到铃木翼文质彬彬，温文尔雅，又或许是察觉到了死宅同类的气息，他并没有迟疑就与铃木翼交谈了起来。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“你好，有什么可以帮助你的吗？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:16&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你好化学办公室在哪里&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:17&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“化学办公室啊，你从教学楼的大门进去，一楼往左手边的走廊一路走到底就是了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:18&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【那不是女厕所吗！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:18&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;感谢那天有机会一定送几本书籍给你&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（改建了）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:18&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【2333】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“老板大气，还有别的什么要问的吗，没事的话我就上课去了”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;暂时没有了感谢&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:20&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“那我们过去吧，希望徐珑迪现在在办公室”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;前往化学组&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;好&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;照着这位同学的话所说，两人走进了教学楼的大门。效实中学的教学楼的布局是与别处不同的，进门是一块挑高的大厅，要先上半层楼高的台阶。左侧的墙上挂着效实中学名师们的照片，右边的大屏幕上则循环滚动着一些校园内的新闻。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（你们可以侦查一下）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 侦查 d%: [98] = 98 &lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 侦查 d%: [47] = 47 &lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【关键词识别好像不会自动判定】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 侦察 d%: [83] = 83 ≤ 85 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【我反正没过】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（诶，为啥没判定）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;错别字吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【侦查vs侦察】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;[流汗]&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【boss过了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:25&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;也许是这里的场景几年来都没什么变化，马尔克对此熟视无睹，但第一次来访的铃木翼却像个好奇宝宝一样左顾右盼起来。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:26&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【都35岁了！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:27&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;就像是在练习中文，铃木翼对着墙上的滚动式大屏幕上的字念了起来。“效实之星名单：xxx，xxx；绿卡名单：xxx，xxx；语文竞赛获奖名单：xxx,xxx”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克被铃木翼的话语所吸引，也不由自主抬头向大屏幕看去。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（两个人过一个灵感）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:27&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 灵感 d%: [5] = 5 &lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 灵感 d%: [98] = 98 &lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【看来和错别字没关系】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【holy shit】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（哦这个我知道了）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;技能没这个&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（灵感对应的是智力）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（人物卡里读的是智力，没灵感，这个要特殊处理一下）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;显然还是马尔克对中文的掌握更胜一筹，看到大屏上的信息，他发觉到几乎所有的获奖名单里面，都有【小晴】这一个名字。看来这位同学一定是一个风云人物吧。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:31&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【神tm小晴，我尴尬的记忆要苏醒了！】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这小晴看来不一般啊，把能拿的奖都拿了”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:32&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;哈鲁酱啊&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:32&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“看来什么时候都有天才，真让人羡慕”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:33&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;也是&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:33&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“铃木先生你想说什么？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:33&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我在想自己从来没获奖过的学生生涯&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:34&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“对比您现在的成就，看来学生时代的奖项确实算不上什么了！”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我们去化学办公室吧”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;走向化学组&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:34&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;哟西&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【真 · 家 乡 话】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;顺着走廊一路往前走，两个人来到了——女厕所。不过明显化学办公室不可能在女厕所里，两人拐了个弯以后顺利来到了化学办公室。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（两个人过幸运）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:36&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 幸运 d%: [95] = 95 &gt; 25 失败&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 幸运 d%: [98] = 98 &gt; 45 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:36&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【讲道理，凭什么不能在女厕所里，这是对女性的歧视！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:37&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;非常不幸的是，可能是化学组开会去了，或是都上课去了，总之现在的化学组办公室空无一人。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:37&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;既然没有人&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你带我参观参观吧&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:37&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;不过似乎门没有锁，两个人在办公室里四顾茫然。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（你们准备怎么做）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:38&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;进去不太好&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我们去图书馆看一看吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;正好我也识得几个汉字&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“虽然我很想做一次地主之谊，但过了好几年我怕是对这学校的熟悉程度还不如您呢”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“铃木先生有什么想看的书吗”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你介绍一下你喜欢的吧&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我想如果我们想调查这个天使的传说，最好还是找一个知情人大概了解一下情况”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这样漫无目的地走我担心一会被保安请出去！”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:41&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;既然是传说&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我还是想看看有什么文字记载的蛛丝马迹&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:41&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这也是个思路”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我想想”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我正好比较擅长在图书馆找东西，不如兵分两路？铃木先生你试着去找下知情人，然后我去图书馆看看文字记录”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【boss这么高的话术】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【不找npc吹逼浪费了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:43&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;可以&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你有什么推荐的人吗&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;比如工作了很久的好说话的&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:44&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我当年读书时倒是知道几位比较好说话的，就是不知道他们还在不在原来的岗位上”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“比如教物理的袁老师和教语文的叶老师”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“不过如果是传说的话，在学生里可能流传更广？”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“毕竟老师可能对这种事情都不会当真吧”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这是我的想法，铃木先生可以自己决定去找谁，老师或者学生”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:47&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;那我去体育馆试一试吧&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:47&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“可惜徐珑迪不在，否则他应该是一个理想的咨询对象”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“行，那一会见”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克前往图书馆&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:47&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（那马尔克先行动）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;虽然对学校的布局已经有些淡忘了，但身体的本能反应不会骗人，想着要去图书馆，马尔克的脚步自然而然地带领着他前进。没几步路便来到了图书馆。由于是上课时间，图书馆里的人很少。看到一个外国人，前台稍稍惊讶了一下，但也没有做出什么阻拦。想必他很信任外面的门卫，只要能进到学校里，就不会是坏人。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（可以行动了）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:50&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【我怀疑你在抄我的文章！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（真的吗，抄的哪一段）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【在学校里的都不是坏人.jpg】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（没印象了o(╥﹏╥)o我手打的）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【那我们是英雄所见略同】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（666&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克走到校史校历分类的书架前，浏览起书籍，尝试找到和天使相关的记载&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（过图书馆）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 图书馆 d%: [17] = 17 ≤ 67 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【不愧是我！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:56&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;校史校历分类的书架里有着不少老书籍，大多数的学生都对此不感兴趣，书上也因此蒙了薄薄一层灰。马尔克仔细地在其中翻找起有关于天使的记载，但翻了整整好几排都没有什么收获。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;正当马尔克感到气馁的时候，从他手上的书里掉出来一张纸条。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:57&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【结束了吗，感觉需要一个休止符】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克定睛一看，这本书名叫《效实学生》，它的封皮已经泛黄破旧，一看就相当古老了，可里面的纸条很新，就好像是最近才放进去一样。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（我发个图片）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【艹，我就知道】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（要读书吗）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【真就改编】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（233333）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我先侦查这张字条&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（请）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;roll&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 侦察 d%: [58] = 58 &gt; 55 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 19:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;{？？}&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克对着纸条看了看，这就是一张普通的纸条，没什么特殊的。纸条上的文字也十分清晰可辨。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“好吧，虽然我很奇怪这两种截然不同的字体，是某种行为艺术吗”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我读书&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:02&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克阅读了这本书，这本书中介绍了一些当时效实中学知名校友的资料，其中最大篇幅介绍的也是连如今都大名鼎鼎的童第周校友。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:03&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“虽然这位生物学家确实很厉害，但感觉这不是我现在需要的信息”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;看看周围有没有人，没有我就把字条拿走&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:04&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;由于是上课时间，图书馆里本来人就寥寥，更不用说是大多数时候都无人问津的校史部分书架。马尔克看了看四周，确定没有人以后，把字条放进了自己的兜里。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:05&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“不如看看别的书架，说不定会有额外收获”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【】我能对整个图书馆用图书馆吗】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:06&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【可以】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:06&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 图书馆 d%: [58] = 58 ≤ 67 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:08&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;眼看时间还早，马尔克信步走到了图书馆的其他区域搜索起来。图书馆里的大多数书籍都是正经的学术资料，并没有什么和天使有关的内容。倒是在一些角落里的娱乐杂志里，找了一些名字里带着“天使”的旅游景点，还有一些带着“天使”的 galgame 之类的。不过很明显和效实中学都没什么关系。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【什么！当年图书馆里有gal的杂志吗！】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“看来图书馆是没什么别的收获了，希望铃木先生那边能找到知情人吧”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（我也不知道诶嘿嘿）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;走向图书馆&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【boss交给你了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:10&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（那场景切换到boss）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:10&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;*走向体育馆&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:11&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;虽然不怎么认得效实的路，但是体育馆的建筑实在太有特点了，而且从教学楼的后门出去以后一眼就能看到，因此铃木翼毫不犹豫地向体育馆走去。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;有一个班的学生正在上体育课，似乎是到了自由活动的时间，一群学生在里面打篮球，充满了青春活力的气息。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【boss请】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:12&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;上去和休息的搭话&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;打得不错啊&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:14&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼对着一个正从场上换下来的学生搭话。也许是激烈的运动能降低人的防备心理，这名学生对突然出来的外人并没有丝毫客套，径直攀谈了起来。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:15&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【这河狸妈】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:15&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“还行吧，好久没打了，累死了，休息一会”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【合理吧，大老师就是用长跑的时候套叶山的话】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【boss继续】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:17&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我也是刚来&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你们这里谁打得最好啊&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:18&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“看，就是那个”，学生随手指向场地中央“又高又壮，大家都顶不住，投篮还准。我们都叫他boss”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;很有我当年的风范啊&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【这很河狸】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;对了你们学校最近有什么有趣的事情吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:21&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“有趣的事情吗？我们百年校庆快到了，我们这一届刚好赶上，真是幸运啊。到时候有很多活动，大家都很期待。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:21&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;哦&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;有什么有趣的活动吗&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;这种祭奠我当年也经常举行呢&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【祭……奠】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（问题不大&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“emm，我想想，现在确定的应该有校友签到活动、校庆的话剧、演出、晚会、各种各样都有啦”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“说起来看你也不是学生，你是校友吗？那一天欢迎来参观呀”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;一定一定&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;那除了这个呢&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;有什么传说这类的吗&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你看动画里常有的那种&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:26&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“传说？我不看动画的，你说的具体是哪种呀”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:26&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你看&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;比如那种半夜音乐教室传出的钢琴声啊&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;树下告白就能成功啊&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:29&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【有那味了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:29&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“哈哈哈哈哈，您可真会说笑，我们可都是学过高中政治的唯物主义者，怎么会相信这种无厘头的事情？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:30&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我听说你们有一档走进科学的节目&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;有这种的吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:31&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“顶多就是半夜发现厕所的灯突然亮了，结果发现是灯的声控坏掉了这种事情吧。你看，最后不还是可以用科学解释的。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:32&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;也是&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;天使什么的你们也不信的&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:34&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“天使？”看来这个词对于这个年龄的男生很少听到，他歪了歪头，随后就当你在自言自语，没有继续接你的话。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;回去和晗神会和&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:36&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;看来并没有问到什么有趣的东西，铃木翼起身返回，正好在体育馆的门口遇到了从图书馆出来的马尔克。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【你们继续】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:36&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我找了一圈图书馆，但没发现什么和天使传说有关的东西”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:37&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;那有什么特别的东西吗&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我看看‘&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:37&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“虽然在校史区一本旧书里找到一张奇怪的字条，但好像和那个传说也没关系”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;拿出字条给铃木翼看&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:38&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（过啥&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:38&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【这个不用过啊，纸条就如图所示】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:38&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【你可以侦查看看有没有进一步信息】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（艹卡关了&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我倒是有个想法”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（23333）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这个传说不是说考试考一百分就能见到天使吗”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;找那个小晴&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“那位总是考第一名的小晴同学想必一定考过很多一百分”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“确实如此”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“那么我们得找到她先”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【刚刚的大屏幕上我们看到她的班级了吗】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:41&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【看到了，就当是高二（1）班】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:42&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;去吧&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:42&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;出发，但去的路上先去看一眼化学组来人了没&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:42&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;可以&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:43&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;两个人原路返回教学楼，又来到了化学组办公室。幸运的是这次老师们都在场。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:44&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“你好，请问徐珑迪徐老师在吗”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:44&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“诶，这不是马尔克吗？”徐珑迪老师和郭洪涛老师都看到了马尔克，一脸惊喜。“真是难得啊，你居然漂洋过海过来了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:45&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【都看到了还行，我的见到老师就尴尬症要犯了】、&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“啊郭老师你也在，对的，这不正好校庆，徐珑迪就邀请我回来看看”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“最近工作顺利吗”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:47&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“哈哈哈哈，你们关系还是这么好啊，挺好挺好。”郭老师憨憨笑着。“最近还不错啦，这帮学生还挺给力的”，他说着指了指桌子上放着的试卷，“这次考得比二班好了不少”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:48&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【袁姐姐震怒】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“哦对了，这是我父亲的朋友，铃木翼，是这回陪我一起来的。”介绍同行人&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“你好你好，欢迎来参观百年名校效实中学。我是郭洪涛，化学老师，当年也教过马尔克一段时间。”郭老师客套着和铃木翼握了握手。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:52&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你好&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“对了郭老师，我们在门卫登记的时候是说来找 ，这个证明还需要麻烦您签字一下”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;把证明拿给熊猫（？）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;然后趁这个机会尝试用眼神示意徐珑迪出去说话&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:54&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“好的好的”郭老师爽快地签了字。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【我去，这个眼神示意要过什么】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【遇事不决过个幸运吧。因为你和徐珑迪以前关系好，给个奖励骰】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:55&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 幸运 d%: [5] = 5 ≤ 45 成功&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 幸运 d%: [66] = 66 &gt; 25 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:56&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【奖励骰怎么骰啊】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:56&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;KP 🎲  2d%kl1: [47, 81d] = 47 &lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲  2d%kl1: [93d, 53] = 53 &lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:57&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（我去，你幸运只有25）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:57&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 幸运 2d%kl1: [96d, 54] = 54 &gt; 25 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【我输了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;徐珑迪老师似乎在专注忙着别的事，没有注意到马尔克的眼神示意。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 20:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“对了，徐珑迪你一会有课吗，能不能带我们逛逛学校，说实话好久没来，都快不认识了！”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“郭老师您先忙吧。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“好啊，正好上午一节课上完了，那我们就去逛一逛吧”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“是啊，好久没见了，正好叙叙旧”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;走到走廊上&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“可把你小子拽出来了，最近过得如何”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:03&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“哈哈哈哈，最近沉迷工作。刚毕业出来就回效实做了老师，感觉有时候还会产生一种自己还是学生的错觉，倒是和学生的关系也都不错。不过还是得多提醒自己已经是老师了，要注意点形象。”难得见到老同学，徐珑迪老师兴致高昂，滔滔不绝了起来。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:04&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我觉得你可以的，毕竟熊猫对你可是青睐有加”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“说实话，这回我回来还为的是另外一件事”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我听说我们学校最近流传一个奇怪的传说，说是考试考一百分就会见到天使”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这传说蛮奇怪的，不像是高中水平的传说”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“所以我和这位铃木翼先生就想着来调查一下这件事”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“关于这个传说你有了解吗”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“既然你心态这么学生，我相信如果有这么个传说你一定对此有所耳闻（狗头）”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:07&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“哈哈哈哈，我以为是什么严肃的事情呢，这不就是我们还在念书的时候那一届传起来的吗？不过应该也没怎么传给后面几届的学生吧？反正现在那群学生我是没听他们有在聊这个。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“没想到你小子回去念大学，还念起了玄学了，这个转行有点厉害啊！”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“可拉倒吧，在下读的可是根正苗红的物理，我来这就是为了破除这种传说的迷信！”马尔克做出义正辞严状&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“嘿嘿”他似乎想到了什么，坏笑着说，“正好我手上有这次考试多余的试卷，要不你就当场做一做，看看有没有天使，不就知道了吗？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:10&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“算了算了，我觉得这种传说肯定限定是在读学生”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“而且我化学老早全还给熊猫了”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“但是你不觉得这传说不合理吗？传说要流行起来，一定要有不可证伪性或者暧昧性”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“考到一百分就能见到天使的可重复性太高了，只要有人考了一百分问问他见到天使不就行了么”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“难道效实中学后继无人，这么多年都没人考到一百分吗！”做出痛心疾首的滑稽样子&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“铃木翼先生，你觉得是不是这么一个逻辑”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克转向一旁的同行者寻求建议&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;也是&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;真的没有吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:20&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“至少我是没听说过”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:20&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;真正的科学&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;应当是大胆假设&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;小心求证&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;而不应该是觉得怎样就怎样&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:20&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这就是为什么我们在这了”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:21&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;那么那个小晴同学能引荐一下吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:21&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我个人是觉得这就是当初有个人无聊乱编的”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“小晴同学这么有名吗，连你们都知道？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;不有名我能知道吗&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;刚和这里的学生聊了一会&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;大家都觉得很强&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“门口大屏幕也有，啥榜她都是第一名”马尔克补充道&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我正想说呢，你们说找个考了100分的人问一下，就郭老师班上的学霸小晴，你们实在好奇是可以去问一下”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我们两个外国人直接去不大好吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;得你们引荐&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;找个由头&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你帮我们引荐下吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我们直接过去怕是马上被保安请出去了&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“那我们一起去一班教室看看”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;不是保安的问题&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;先对个口径吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;什么理由找&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;既然很多人不清楚这个事&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;还是不要太引人注目好&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“很有道理”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“就说某杂志要写效实中学的校庆新闻，想要采访一下在校优秀学生？”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这个理由如何”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:25&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;不好&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;日后没有杂志就暴露了&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;就凭你校友身份找来了解一下学校为由吧&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:27&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“那没有找一个学生的道理吧”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:27&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;以为他优秀&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;因为&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;所以可以来接见国际友人&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“啊这”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:25&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“小晴同学不仅是大学霸，还是学校文学社的社长，现在应该在准备校庆特刊的制作”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“不如就以帮助她做校庆特刊取材的名义，让她来给你们做往届学长前辈的采访如何？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你们这样讲最好&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;就定个时间吧&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这也可以”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“今天放学之后？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:29&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（怎么感觉在密谋拐卖女高中生）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:29&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【本来就很不对劲】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:29&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;徐老师，你有空吗&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;到时候希望你在场&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;这样对方不至于过于提防&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:29&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“现在就去看看吧，反正她拿了绿卡，不上课也无所谓”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;徐珑迪老师仿佛回到了学生时代，露出了恶作剧般的笑容&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我接下来应该都没课的，走吧走吧”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:31&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;不应该是把人叫来吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:33&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这样可能会让她紧张”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【冲！】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;前往高二一班&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:32&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;一行人上了二楼，来到了高二一班的教室。徐老师在教室门口张望了一番，然后抓了个学生问了问。你们与他相隔几步，隐隐约约能够听到他们的对话。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（两个人过聆听）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:33&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 聆听 d%: [64] = 64 &gt; 50 失败&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 聆听 d%: [73] = 73 &gt; 71 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;？&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;太小声了&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;听不见&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【老倒霉蛋了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;可惜，学生们的嬉戏打闹声盖过了他们的交谈声，两人并不能分辨出他们说了些什么。一会儿，徐老师走向你们“小晴不在，不过我问了她们同学，她应该是去做校庆特刊的取材了，刚刚好像是往中山厅的方向去了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:36&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;那顺便去看看这个中山厅吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;说来中山还是孙先生在日本的名字呢&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:36&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“看来也没别的事可做，过去看看吧”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“没想到铃木先生对此这么了解”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我都不知道呢，白在中国念了半个高中了”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:37&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“铃木先生真是知识渊博”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:38&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;毕竟我不像一般日本人&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我对政治还是有点兴趣的&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:38&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;在徐老师的带路下，一行人往中山厅的方向走去。中山厅的大门紧闭，从门里传来的声音推测，里面应该是在进行戏剧的排练。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（两个人过聆听）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 聆听 d%: [73] = 73 &gt; 71 失败&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 聆听 d%: [92] = 92 &gt; 50 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我怀疑你的代码&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【两个聋子】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;关键一样什么情况&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（总有概率的嘛）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【但是我知道里面一定会说，王老师你可要注意自己的言行啊】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【都这个时候了还敢和黑五类接触】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（艹）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（你现实作弊）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【但是这这信息也没啥意义啊】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;这不是玩梗嘛&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“看起来中山厅在使用中，我们可以进去吗”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:41&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“进去吧，没事，不打扰到他们就行”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:41&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;推门进去&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:42&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克准备推门进去，但他刚伸出手，突然门打开了，一个标致的女生冲了出来，口中还喊到“童第周？！”。没想到，正好与马尔克撞了个满怀。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:43&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【要不要过体质对抗】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:43&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;随后她抬起头，低声道歉“不好意思，没注意到外面有人。”从屋子里传来友人关切的询问：“小晴，怎么了，突然跑出去？”显而易见这位与你们撞了个满怀的女高中生就是小晴本人了。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（不用不用，不是打架情节）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（你们怎么行动）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:44&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;和她道歉&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;指责maruko&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:44&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（那我就当我没事了）“抱歉抱歉，我也没注意”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:45&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;被一个轻音柔体的妹子撞到，虽然马尔克的身体并没有受到什么大冲击，但是心里却砰砰直跳（误&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:46&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我猜你就是那位校内著名的小晴同学”马尔克看向徐珑迪&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【别这样，感觉我很可怜】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:47&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（又不是你，是你的角色，和你没关系&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:47&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【好！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:48&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“没关系，不好意思，是我开门太急了”。小晴再次道歉。
“是的，这就是小晴同学。”徐珑迪老师说道。随后他转向小晴，“小晴同学你好，这个是马尔克，当时也是我在效实读书时的同学。你不是正好要给校刊取材嘛，我想我的这个老同学也许能帮你一些忙”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“啊那真是太感谢了！”小晴忙不迭向你们道了谢。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:50&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我很高兴我能帮上忙。这样吧，我们找个地方坐下聊聊，你看看需要了解什么。”马尔克提议道&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:50&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;正好我有几个问题&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“恩，这是我父亲的朋友，是正好一起来效实参观的，不介意的话他也有些问题想了解”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这样我们算是互相帮助了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:52&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“好的呀。我在中山厅取材也差不多了，接下来准备去特别楼看看一些艺术社团，我们可以去那边坐坐。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“有什么问题尽管问我吧！”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【等会，那个特别楼就是露天舞台那个楼吗】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（对）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:53&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【原来这叫特别楼】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:54&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（我瞎命名的，叫特别楼有点像galgame的感觉了）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:54&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“那就出发吧”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:56&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;一行人向着特别楼走去。小晴向你们介绍到“特别楼主要是我们的美术、音乐、劳技教室。那边还有一个天然舞台，给有才艺的同学进行表演。效实园里的大家都挺多才多艺的。”看得出来她很喜欢她的母校与同学们。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:56&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;一边走着，马尔克一边假装漫不经心地提问&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“对了小晴同学，刚刚你从中山厅出来的时候，好像在喊童第周？那不是我们效实好几十年前的著名校友吗，你刚刚也在排练历史剧？”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【说起来小晴的友人没交代就被忽略了，这合理吗】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【工具人也有工具人的尊严啊！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 21:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【23333 工具人要排历史剧，小晴取材完跟她打了声招呼然后走了】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“啊，那个啊，对，我们是在排历史剧来着。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;小晴听到你的问题，稍微有点欲言又止的样子。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 心理学 d%: [2] = 2 ≤ 10 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【艹】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（nb!）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克敏锐地感觉到小晴隐瞒了一些什么。虽然确实是在排练历史剧，但小晴喊童第周却不是剧情的一部分，而是另有原因。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:03&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【虽然我知道但是我不能说！真难受！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:03&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（哈哈哈哈哈哈哈）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:03&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克点点头，表示理解&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:06&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;剧情是啥&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;是什么时间的呢&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;能和我讲一讲吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“你说历史剧的剧情吗？刚刚在排练的那一段是讲wg时期，一位老师遭到了迫害，但还是尽心尽责教育学生的故事”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:10&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;下次排练方便我们看吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:11&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“可以啊，不影响到排练就行。不过啊，这个节目是要在校庆当天给全体校友表演的，我个人还是建议不要把自己提前剧透了比较好哦”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:12&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“哈哈，有点道理。不过这么说的话，这段剧情里童第周有出场吗？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:13&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我不确定诶，这段应该没有吧，不过之前几段会有。我其实也就是去采访呆了一会，也没看全，所以不太确定。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:14&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【没问题了，去特别楼吧】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:15&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;说话间，大家已经来到了特别楼。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;走进楼里，耳边传来宏大的管弦乐声。原来是管弦乐队在为校庆进行排练&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（我切个bgm）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;环顾四周，马尔克和铃木翼发现角落里有一群人聚在一起，似乎在聊着些什么。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（你们可以行动了）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（你们都没音乐技能，分辨不出他们演奏的是啥）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:17&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【要 来 了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:18&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（晗神：我已经看穿了一切）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【总感觉这段可以skip】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:20&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（2333333 可是你们聆听就没成功过，不努力刷个技能成长吗）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:20&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 聆听 d%: [24] = 24 ≤ 50 成功&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 聆听 d%: [7] = 7 ≤ 71 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;在优美的音乐声中，他人的说话声就显得有些违和与突兀了。不过也正拜此所赐，两个人听到了那群人的聊天内容。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;原来是三个看上去比较焦虑的家长：“校长先生，别的学校都在补课，为什么效实中学不补课啊？”，“效实这么多的艺术课有什么用呢？高考又不考这些。”，“我的孩子文理科成绩都不错，选哪个好呢？”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;而校长只是平静和蔼地说：“任何选择都是对的。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:18&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;找个地方坐下&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;舞台周围的位子很空，你们随便坐了下来。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“就请小晴同学多多关照了，你看下你想了解些什么？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“其实我最近在烦恼校庆特刊的制作，不知道怎么样才能填满版面呢，两位前辈有什么好的建议吗？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:25&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;用图片&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:25&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“不妨考虑向在校学生和校友约稿？”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“百年校庆这样的大事，一定有很多人有话想说”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:26&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“嗯嗯，嗯嗯。”小晴忙不迭地掏出纸笔，把你们的建议都记录了下来。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“那正巧，马尔克前辈是效实的交换生吧，这么难得，有没有兴趣来给校庆特刊写一篇呀？”小晴的大眼睛对马尔克忽闪忽闪。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:29&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“惭愧惭愧，我倒是乐意效劳，不过我的中文写作这几年都基本还回去啦，恐怕写出来也是污染版面。你们校刊接受英文投稿吗？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:31&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“可以呀，不碍事不碍事，这不是挺有特色的吗？”听了马尔克的话，小晴露出了笑容。但紧接着笑容又变成了疑惑的神情。“童第周？！我看错了吗？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:32&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“什么童第周？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:32&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 侦察 d%: [86] = 86 &gt; 55 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:34&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;听到了小晴突然的话，马尔克努力向四周看了看，但是也没有发觉什么奇怪的地方。另一边，小晴轻轻摇了摇头：“可能是我最近压力太大了吧，总是想着校庆特刊的事情。刚把乐团里一个人看成童第周了。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;她揉了揉眼睛。“要不还是在外面走走，看点绿色放松一下心情吧，在这里看什么都能联想到校庆。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“小晴同学真不容易，一边高中学习这么忙，还要准备这些额外的工作”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你见过他吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:37&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“童第周啊，是啊，我怎么可能见过他呢……这都多久之前的人了……哈哈”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:36&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;要不去直接去那个铜像吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我看前面挺多贡品的&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;你们不是唯物主义吗&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;怎么还信这个吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“说起童第周”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“最近好像不止你一个人见到过他啊，你看，这是我在图书馆发现的字条，看上去哪位同学也很关心校庆特刊的事情把字条放进了旧书里，结果还得到了童第周的亲笔回答”马尔克用开玩笑的语气说道，拿出那张字条给小晴还有徐珑迪看&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:44&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这是啥？”徐珑迪满脸疑惑地看着这个纸条。
而一旁的小晴却似乎受到了巨大的精神冲击：“这是……什么？我？难道我现在是在做梦吗？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:44&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【对发生该团的时间线有了进一步把握】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:45&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;只见她一脸不可置信的表情，随后双手抱头蹲了下来。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:45&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“发生什么了，小晴同学？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:45&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 心理学 d%: [76] = 76 &gt; 10 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:45&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【boss快用你无敌的话术说点什么！】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:47&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 话术 d%: [70] = 70 ≤ 81 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:47&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;小晴&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;这纸条上的字是你写的吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:49&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“是……”小晴用微弱的声音说着，“为什么你会有这个……”
不过不用她说，你们也能看得出来，这上面的笔迹和她之前记录你们建议时的笔迹是一样的。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:49&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;但你知道另一个人是谁吗&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“童第周啊……虽然我也不知道是不是……我只是做了个梦……梦里他告诉我叫童第周。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:50&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我是在图书馆里的旧书里拿到的，抱歉啊，我不知道这是你的东西。”马尔克把字条交给小晴&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:52&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;小晴接过字条，心情慢慢平复下来，但脸色依然是像唯物主义者见到了鬼一般惊恐&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:52&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;不要怕鬼&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我倒是觉得你们应当希望鬼的存在&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;这样不就能有新的研究课题了吗&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;发生了不能解释的事情应当面对并得到真相&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;怕是没有用的&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;再说了&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;就算是鬼&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;童先生也不会把你们怎么样的&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:55&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;即使再不靠谱的事物，从散发着成熟大人气息的铃木翼先生口中说出来，都增加了好几份可信度。听了你的话，小晴也鼓起了勇气，重新站了起来。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:56&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;那么为了真理&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;说一说发生了什么吧&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“就是这段时间，我天天都能梦到穿越回了几十年前，在效实校园里偶遇了青年童第周。可没想到醒来以后，也总感觉能在学校的各个地方看到酷似梦里青年童第周的身影。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“连着几天都是这样，实在是让我神经都要衰弱了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 22:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;主动上前搭话吧&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;说不定能告诉你你不知道的这所学校的趣事的&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;还能写进校刊多好&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;听着铃木翼 小晴的交谈，马尔克回过头对着徐珑迪挤眉弄眼，小声说道：“你看看，这算不算是看到天使了？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;徐珑迪：“天使竟是童第周？！”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;小晴：“可是我觉得这只是日有所思夜有所梦罢了。”她又仔细看了看刚才觉得像是童第周的那个同学：“那个人应该是五班的简相如。乐团的人我基本上都认识的。只是刚才那一瞬间不知道为什么就看着特别像。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:04&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【艹这也差太远了】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“不过这字条上的毛笔字大概不是错觉，毕竟每个人都看得到，除非是有人恶作剧……大概也不会有人这么无聊”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:08&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“是啊，我本以为只是最近压力太大，但是这个毛笔字彻底让我动摇了。不过这么一说别人恶作剧也不是没有可能。”从震惊中恢复过来的小晴依然怀抱着最后一分对唯物主义的坚持。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“还是出去走走吧。”仿佛被一股无形的力量牵引着，大家默默向着银杏林中的童第周雕像走去。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 力量 d%: [22] = 22 ≤ 70 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【好，我不去！】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【皮一下我很开心】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:10&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（23333）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;银杏树是效实中学的校树。要是往常，小晴一定会很热情地向客人介绍这片银杏林的来历吧，可此时她也只是心事重重地走着。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;两个人侦查雕像&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:10&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 侦察 d%: [69] = 69 ≤ 85 成功&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 侦察 d%: [32] = 32 ≤ 55 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:12&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;两人发现，雕像的底座上堆满了柚子、橘子甚至各种零食。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:12&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我记得当年这种风气不是被批评过么，怎么变本加厉了”马尔克对徐珑迪疏导&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;说到&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:13&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;建议徐老师把关&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:13&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“没办法，同学们要放，我们又管不住，也追查不到人。总不至于为了这个事情放个监控摄像头吧，也不是什么原则性的事情”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“不过托他们的福，我们吃了好几次免费的水果和零食了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:15&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“那严格说可是贡品，居然敢吃，难怪有人说你们中国人好多坚定的唯物主义战士”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:13&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;阳光透过树叶斑驳地洒在地上，小晴似乎是有点犯困，她靠着雕像坐下，眼皮渐渐合拢了。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:15&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【艹旁人还在就坐下睡觉，不对劲】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:13&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;我想尝试一下能不能一起睡着&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;看看有什么情况&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:14&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（boss要睡觉那你过一个意志）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:16&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 意志 d%: [1] = 1 大成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:16&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【让boss睡】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【他大成功】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:17&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（可是这是意志检定，成功了应该抵抗睡眠才是）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:17&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【判定成功应该是遂意志吧】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【就是他想睡，成功了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:17&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（可以）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;阳光明媚，正是睡觉的好时机，看着此情此景，铃木翼也不由得眯起了眼睛，困意渐渐袭来。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:16&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;看到小晴旁若无人坐下睡觉，马尔克顿时有点奇怪：“小晴同学？你还在吗”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克尝试叫醒小晴&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克叫了叫小晴，没过一会儿，小晴苏醒了过来。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克过个聆听&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:20&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 聆听 d%: [85] = 85 &gt; 50 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:21&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【聋子我是专业的】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“小晴同学你还好吗，怎么忽然就睡着了？”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克似乎听到了小晴说了什么，但声音实在太小，根本听不出来。不过紧接着小晴就用所有人都能听到的正常音量自言自语起来“对——了——，不，不如做一版效实校友的介绍，比如童第周，还有得诺贝尔奖的屠呦呦，还有……”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克过灵感&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 灵感 d%: [56] = 56 &lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（过了）&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;听到小晴的话，马尔克感到了一股违和感。他仔细思考了一下违和感的来源。屠呦呦院士也是效实中学的知名校友，作为同样基础学科的大牛，马尔克对她自然是有所耳闻。但尽管她得诺贝尔奖的呼声很高，但并没有获奖啊，为什么小晴会这么说……？&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;意识到这一点的马尔克 sc 0/1d3&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 sc d%: [60] = 60 &gt; 36 失败&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲  d3: [3] = 3 &lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;[??]&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（23333）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【这河狸妈】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【我觉得这还是蛮不合理的，只是听到了这么个东西就掉3点，已经快赶上看到不该看的了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:31&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【数值平衡问题，那-1？】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:31&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【-1好】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:31&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【可以】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;意识到这一点的马尔克受到了极大的冲击，作为物理系学生，他一直认为自己是坚定的唯物主义者，但此时他的信仰也开始动摇了。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;另一边，没有理会马尔克的询问，小晴好像是突然被注入了无限的动力。她说着“我要去图书馆查资料了！”便飞快地向图书馆的方向冲去。她的表现与睡觉前判若两人。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:31&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【艹，这种行为倒是该sc】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克转向徐珑迪：“额，小青同学平常思维都这么跳脱的吗”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:33&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“呃……”徐珑迪在旁边也是一脸黑线，“我是没见过啦，不过我也不是教他们班的，对她也不是很了解”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:33&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我跟去图书馆看看，小晴同学这状态挺让人害怕的”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克跟去图书馆&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;另一边，铃木翼也靠在雕像旁边沉沉睡去。不知道过了多久，他缓缓睁开眼睛，却看到了与之前完全不同的景象。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼发现自己似乎置身在一座雄伟的拱顶房间里，那些位于高处的石头穹棱几乎隐没在了头顶的黑暗里。房间里有几乎与普通房间一样高的台座或者桌子。宽大的台座顶部散落着书籍、文件以及一些看起来像是用于书写的工具——由某种紫色金属铸造、表面带有古怪图案的罐子与一头染着颜色的长杆。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;看看书&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;写的啥&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;找报纸&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:41&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼想要伸手看看台座上散落着的那几本书。那些台座非常高大，看上去已经远远超过了人类的身高，但铃木翼似乎能够从上方俯瞰它们，这令他感到十分古怪和难受。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;当铃木翼伸出手时，映入眼帘的却不再是正常人的手，而是一只巨大的垂挂在头部下方的绿色触须。受到惊吓的他不由自主地低头看向自己的身体，看到的却是一个十英尺高，十英尺宽的满是皱纹的彩虹色圆锥体。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:42&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;好帅&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:42&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;看到这个景象，铃木翼 sc 1/1d6+1&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:43&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼 🎲 sc d%: [29] = 29 ≤ 46 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:45&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;铃木翼素来喜好怪力乱神，因此虽然看到的是十分诡异的景象，但也在他想象力所能覆盖的范围之内。但由于自己变成了奇怪的生物，还是受到了一点小小的惊吓。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;他翻开书，这本书其实是一本笔记本，扉页上的文字虽然是他从未见过的文字，但似乎并不影响铃木翼读懂它。上面写着“人类你好，我们正在收集宇宙各地的知识，请你把自己的专业内容写在这本笔记上。不用害怕，之后我将把你送回原来的世界。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【boss先下线】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【回到晗神】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克跟着小晴往图书馆进发。但在图书馆里绕了一圈，只看到三三两两个学生，但并没有看到小晴的身影。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【好家伙，消失了，果然美少女jk邂逅都是假的】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;四下搜寻看看能不能找到&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:51&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 侦察 d%: [28] = 28 ≤ 55 成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:54&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克在图书馆周围四处搜寻，但并没有发现什么值得留意的踪迹。但在路过其他的学生时，听到了他们的闲聊。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“你刚看到了吗？小晴刚刚来借了一堆书走，不愧是学霸啊，让我看这么多书我的头都要炸了。”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“是啊，而且我看到她和一个男生一起往阅览室方向去了。难道这就是学霸的约会吗？唉，女神都有男朋友了，我的青春结束了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:55&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“我懂了，按文艺作品剧情这个男生一定是童第周”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;但不信邪的马尔克还是要去阅览室看一看&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:57&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;来到阅览室，由于是上课时间，学生们都在教室里，阅览室里的人寥寥无几。因此马尔克一眼就发现了小晴和铃木翼坐在窗边读书。她面前的桌子上放了一叠厚厚的书。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:58&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 侦查 d%: [34] = 34 &lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【结果男生是铃木翼，童第周究极工具人】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克走上前去翻了翻桌子上的书，这些书大部分都是些类似《更高更妙的物理》、《基础有机化学》等各个学科的知识，但有一本破旧的没有名字的书在这书堆里面显的格格不入。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-13 23:59&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【35岁在高中生视角看怎么会用男生这个形容啊啊】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【因为只看到背影吧，而且铃木翼APP70】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:00&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【我翻书这两个人没有看到我吗】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:01&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【他们专注于眼前自己的书，并没有理你】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:02&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克觉得这不合理，因为他去图书馆的时候铃木翼还在睡觉，怎么一转眼就超到他前面从图书馆走到阅览室呢，于是他拿出手机打通之前铃木翼在中国的电话号码&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:03&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【因为你在图书馆搜索也要时间】&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;放在桌子上的铃木翼的手机响了，铃木翼奇怪地看着手机，就像是看什么新鲜事物一样，随后用并不是很熟练的操作接通了电话&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:05&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;看到是面前的铃木翼接起了电话，马尔克就把电话挂了&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:05&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“摩西摩西？”铃木翼困惑地放下了手机。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:05&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;走到他们面前试图吸引他们的注意力，“怎么回事，你们怎么忽然在这看起书了”&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“徐珑迪呢”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:07&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;小晴：“啊，我是想到了一些好点子，有办法继续做校庆特刊了。”
铃木：“对，对，我是帮助小晴一起做。徐珑迪？他说他有事先回去了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:09&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“虽然感觉怪怪的，但有点子总是不错的。需要我帮忙吗，反正我也没别的事”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:11&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“好啊，那能帮我们收集摘抄一些效实历史上知名校友的资料吗？谢谢了”小晴说到。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:11&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“好吧……”马尔克答应着，决定先去翻下桌子上那本没有名字的书&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:12&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克翻开了桌上那边看上去格格不入的无名之书。这本书看上去有一定的年头了，里面的文字晦涩且难以辨认。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;（过困难母语检定）&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:14&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 困难母语 d%: [58] = 58 &gt; 27 失败&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:14&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【老文盲了，读不懂也看不懂】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:16&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;并非古文专业的学生，书上的内容对马尔克来说还是过于困难了。反复翻了几页，只能勉强辨认出出现过很多次的「伟大种族伊斯」这几个关键词。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;但是当翻到书的后半部分时，后半部分记录了一些伟大种族的科学技术，里面配有不少通俗易懂的图片，作为物理专业的马尔克自然能轻松阅读。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克注意到有一种机器与交换身体相关。这个机器由许多杆子、轮子与镜子组成，中央有着一面圆形的凸面镜。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;过一个灵感&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:18&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 灵感 d%: [97] = 97 &lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【灵感也这么差，这种人真的是读物理的吗（质疑）】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:19&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这个机器构造看上去挺简单的，我说不定就能做出来。不过交换身体是什么鬼？”马尔克一脸迷茫&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:20&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“这本书是从那儿找到的？”马尔克问向两人&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:22&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;两人看到这本书，露出了十分惊讶的神色：“你看这个书干什么？这个书里面没有我们需要的信息啦，放回来吧放回来吧”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:23&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“好吧。”马尔克放下书，想办法偷偷把揣进怀里&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【这个过个啥？妙手？潜行？】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【敏捷】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【可以，哪个成功算哪个】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:24&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【不过你不是说他们特别精力集中么，我翻书他们都没注意】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:25&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【对，他们精力集中在自己看的书上】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:27&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【那我不应该很容易拿到才对】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-bot_2VUZCE7GL&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;bot&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:27&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克 🎲 潜行 d%: [1] = 1 大成功&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:27&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【那没事了】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克表面应承着，趁他们一个不注意，偷偷把书揣进了怀里。而另外两个人对此没有丝毫察觉。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:28&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【这感觉是成功啊，大成功没福利吗】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:29&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;【没吧，都快完结了。硬要说的话，就是你现在偷偷溜走他们也没意识到】&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:32&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克丢下两人偷偷离开了阅览室，找个地方把书处理了&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;让他想想，就烧了吧，找徐珑迪借个打火机&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:34&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;虽然徐珑迪不抽烟，但他还是在办公室的某个角落里找了个打火机。整本书就这样被化作了灰烬。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:35&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;让我回去康康两个人&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:36&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;马尔克回到阅览室，那两人还在专心致志地看书，和他离开时一样。唯一不同的是随着时间流逝，外面的天色逐渐暗了下来。&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:38&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;“铃木先生，我们该走了。”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:39&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;被马尔克搭话，铃木翼如梦初醒地抬起头来“原来已经这么晚了吗，那我们走吧”&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-1002082435&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;马尔克&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:40&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;走了走了&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-651112786&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;铃木翼&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:41&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;叫一下小晴&lt;/div&gt;&lt;/div&gt;&lt;div class=&apos;chat-item chat-user-410155683&apos;&gt;&lt;div class=&apos;meta&apos;&gt;&lt;span class=&apos;chat-item-name&apos;&gt;KP&lt;/span&gt;&lt;span class=&apos;chat-item-time&apos;&gt;2021-02-14 0:41&lt;/span&gt;&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;外面的天色逐渐变暗，校园里响起了放学的铃声，学生们三三两两走出了校园。和小晴和其他人说再见后，你们混在人群中走出了校门。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;探索了一天，还遇到了一些灵异事件，让你们感到身心疲惫，连带着记忆力也衰退了下去，甚至都忘了今天来到效实中学的目的是啥。拖着沉重的脚步，你们怅然若失地离开了校园。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;离开校园以后马尔克便与铃木翼道别，直到飞回英国，也没有再听到铃木翼与小晴的消息了。&lt;/div&gt;&lt;div class=&apos;content&apos;&gt;5年以后，铃木翼与小晴在一家医院里醒来，迎接你们的是周围医生和朋友的笑容“太好了，看上去你的双重人格已经治好了。”从周围人的话语中你们得知，5年前你们因为出现了双重人格的症状，被送到医院就诊。这5年里你们一边接受着精神科医生的照顾，一边广泛涉猎着各个领域的知识。但你们对此一点印象都没有了。&lt;/div&gt;&lt;/div&gt;&lt;style&gt;.chat-item{padding:4px 4px 4px 8px;position:relative;}.chat-item::before{position:absolute;content:&apos;&apos;;width:4px;top:0;bottom:0;left:0;}.chat-item-name{font-weight:bold;}.chat-item-time{margin-left:5px;}.chat-user-bot{background-color:#ffffe0;}.chat-user-651112786{background-color:rgb(253,226,226);}.chat-user-651112786::before{background-color:#F56C6C;}.chat-user-1002082435{background-color:rgb(250,236,216);}.chat-user-1002082435::before{background-color:#E6A23C;}.chat-user-410155683{background-color:rgb(225,243,216);}.chat-user-410155683::before{background-color:#67C23A;}.chat-user-bot_2VUZCE7GL{background-color:rgb(233,233,235);}.chat-user-bot_2VUZCE7GL::before{background-color:#909399;}&lt;/style&gt;</content:encoded></item><item><title><![CDATA[立即执行函数，你或许没想到过的妙用]]></title><description><![CDATA[之前发了一个有关 JavaScript 中立即执行函数（IIFE）的想法，但还想多写一点东西。立即执行函数是 JavaScript 中很特殊的一种用法。由于以前 JavaScript…]]></description><link>https://maliut.space/p/iife-statement-to-expression/</link><guid isPermaLink="false">https://maliut.space/p/iife-statement-to-expression/</guid><pubDate>Sun, 31 Jan 2021 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;之前发了一个有关 JavaScript 中&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Glossary/IIFE&quot;&gt;立即执行函数（IIFE）&lt;/a&gt;的想法，但还想多写一点东西。立即执行函数是 JavaScript 中很特殊的一种用法。由于以前 JavaScript 原生没有成熟的模块化机制，立即执行函数曾被广泛地用于编写独立的模块和库，以防止全局变量的名称冲突。但随着 ES6 标准对于模块化和块级作用域支持的完善，这种用法也逐渐退出了历史舞台，顶多出现在被 Polyfill 转译的代码里。&lt;/p&gt;
&lt;p&gt;在网上的讨论中，提到立即执行函数的作用，基本上都是说用于隔离作用域，防止全局命名空间冲突等。但实践中我们往往忽视了它的另一个作用，即将任意 &lt;strong&gt;statement（语句）&lt;/strong&gt; 转化为 &lt;strong&gt;expression（表达式）&lt;/strong&gt; 的能力。&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;statement 与 expression&lt;/h2&gt;
&lt;p&gt;在学习编程时，我们就学到过 statement 和 expression 这两个概念，但是当时我完全没有在意这些偏理论性的名词，因为觉得直接看代码就很直观了，不讨论这些名词也不妨碍我写代码。一直到代码经验比较多了，且接触了各种不同语言的语法和一些奇技淫巧，才逐渐意识到区分这两者的重要性。&lt;/p&gt;
&lt;p&gt;为了写这篇文章，我特意 Google 了一下 statement 和 expression 的准确定义，但发现网上也是众说纷纭，有把它们作为两个对等的概念的，也有说是包含关系的。不过抛开诸多定义上的细节不同，大家对两者的差别还是有共识的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;expression 是一段执行以后会返回一个值的代码，而 statement 只是执行一段逻辑，并不产生一个返回值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在大多数语言中，expression 主要包括数字、字符串等字面量以及连接它们的运算符，而 statement 会包含一些赋值与控制流语句。例如：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// expressions:&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;expression&quot;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; x 

&lt;span class=&quot;token comment&quot;&gt;// statements:&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;将 statement 转化为 expression&lt;/h2&gt;
&lt;p&gt;让我们关注一个常见的使用场景：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;some logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;some other logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这段代码根据 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; 的值不同，执行不同的逻辑，并将计算后的值赋给 &lt;code class=&quot;language-text&quot;&gt;this.name&lt;/code&gt; 变量。&lt;code class=&quot;language-text&quot;&gt;switch&lt;/code&gt; 语句是典型的 statement ，它根据值的不同进入不同的分支。但很多时候每个分支的逻辑是类似的，例如上面的例子，我们计算的结果最终都是为了赋值，那这个赋值的操作就被重复了多次，引起了代码冗余，很不优雅。&lt;/p&gt;
&lt;p&gt;有些语言中支持一种名为 &lt;strong&gt;switch expression&lt;/strong&gt; 的语法，顾名思义，这种语法支持将 &lt;code class=&quot;language-text&quot;&gt;switch&lt;/code&gt; 语句变成一个 expression，执行完后返回一个值。用这种语法改写后的代码形如：（伪代码）&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;some logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;some other logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到确实简洁了很多。&lt;/p&gt;
&lt;p&gt;JavaScript 目前并不支持这样的做法，但通过立即执行函数，我们可以变通地实现这一功能。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;some logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;some other logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; 
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;得益于它是一个函数，不仅是 &lt;code class=&quot;language-text&quot;&gt;switch&lt;/code&gt; 语句，条件判断、循环等语句，甚至是更加复杂的逻辑，都可以使用这种方式转化为一个 expression 。&lt;/p&gt;
&lt;h2&gt;这样做有意义吗？&lt;/h2&gt;
&lt;p&gt;从功能的角度，确实原本的 &lt;code class=&quot;language-text&quot;&gt;switch&lt;/code&gt; 语句功能也完全够用了，要说代码冗余也不是很严重，这么做并没有特别明显的好处。但是从代码的优雅与可读性的角度，改写成 expression 的形式确实更好一些。&lt;/p&gt;
&lt;p&gt;一个佐证是 Java 在 JDK 12 中也增加了对 switch expression 的支持。作为一门被广泛使用的工业级语言，Java 并不一直追求最时髦的语言特性，甚至常常被人诟病语法繁琐臃肿。连 Java 中都如此高优先级地对这一语法做了支持，足以说明它在程序员中的呼声是很大的。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Java 12 的 switch expression&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; today &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;day&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SAT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SUN&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Weekend day&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TUS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;THU&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FRI&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Working day&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Invalid day: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; day&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 JavaScript 中，除了与其他语言相同的应用场景之外，还有一个场合可能会用到立即执行函数封装后的 expression，那就是 React 的 JSX 语法。我们经常需要根据条件渲染 JSX 元素，但在 JSX 中混入逻辑代码时，代码的内容必须为 expression，这时我们就可以用上这个技巧。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-jsx line-numbers&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompA&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;B&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompB&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;C&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompC&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CompA&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从更宏观的角度来说，这实际上是需不需要将任何语句都设计为 expression 的问题。将代码分为 statement 和 expression 并不是理所当然的设计。例如，在&lt;a href=&quot;https://en.wikipedia.org/wiki/Functional_programming&quot;&gt;函数式编程&lt;/a&gt;中，就没有 statement，只有 expression，通过 expression 的串联使代码更为简洁流畅。&lt;/p&gt;
&lt;p&gt;有些面向对象的语言也借鉴了这一思路，例如 &lt;a href=&quot;https://stackoverflow.com/questions/9636503/whats-the-difference-of-statement-and-expression-in-ruby&quot;&gt;Ruby 中几乎一切都是 expression&lt;/a&gt;，连类定义和方法定义也不例外。即使在传统的 OO 设计中，也涌现出了 &lt;a href=&quot;https://zh.wikipedia.org/wiki/%E6%B5%81%E5%BC%8F%E6%8E%A5%E5%8F%A3&quot;&gt;Fluent interface&lt;/a&gt; 之类的方式，通过将 setter 改写为返回上下文的 expression 来实现优雅的流式接口调用。&lt;/p&gt;
&lt;p&gt;我们会在下文中简单介绍几种其他将 statement 转换为 expression 的方式。&lt;/p&gt;
&lt;h2&gt;为什么是匿名函数？&lt;/h2&gt;
&lt;p&gt;回到 JavaScript，也许有人会说，这没什么特别的，完全可以把逻辑抽取到一个独立的函数中来实现相同的效果。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNameById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;some logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;some other logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; 
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNameById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样做当然可以。一般来说，如果是多次出现的可复用的逻辑，都是建议单独抽取成函数，以减少代码重复。但函数也并不是拆分得越细越好的。如果这段逻辑与上下文关联比较紧密，拆分的成本会比较高，而且在阅读代码时需要上下文来回切换，理解多个参数之间的对应关系，反而降低了代码的可读性。&lt;/p&gt;
&lt;p&gt;函数的命名也是一个经常困扰开发者的问题。&lt;/p&gt;
&lt;p&gt;总的来说，虽然使用匿名函数实现的原理和拆分函数是一样的，但是在这里我们不要过于纠结它的本质有多么高大上，而是充分利用 JavaScript 提供的简洁语法，将其作为一种语法糖来使用。（可以类比同样 JavaScript 中的 &lt;code class=&quot;language-text&quot;&gt;!!&lt;/code&gt; 语法，虽然从原理上来说并没有什么特别之处，但也经常出现在 x 个不为人知的 JavaScript 小技巧中。）&lt;/p&gt;
&lt;h2&gt;迁移到其他语言&lt;/h2&gt;
&lt;p&gt;其他语言中没有 JavaScript 的立即执行函数语法，那么它们能借鉴这一思路实现类似的功能吗？也并不一定不行。这里我们给出一个低版本的 Java 语言实现：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Callable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;some logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;some other logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; logic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们使用 Java 中的匿名内部类来实现将 statement 转化为 expression 。&lt;code class=&quot;language-text&quot;&gt;Callable&lt;/code&gt; 老实说在 Java 中并不是很常用，可以理解为拥有了返回一个值的能力的 &lt;code class=&quot;language-text&quot;&gt;Runnable&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;可以对比一下它与 JavaScript 版本的区别。在 Java 版本中，匿名内部类的语法相对比较繁冗，虽然可以利用 Java 8 的 lambda 进一步简化，但终究省不了一些方法名和关键字的出现，而这会对阅读代码产生一定的视觉干扰；而 JavaScript 的立即执行函数配合箭头函数，全是由符号组成的，我们在阅读代码时可以很轻松地将其省略，聚焦到核心的逻辑上。&lt;/p&gt;
&lt;p&gt;另外 Java 中匿名内部类引用外部变量时，由于 Java 执行机制的限制，我们必须要把使用到的外部变量声明为 &lt;code class=&quot;language-text&quot;&gt;final&lt;/code&gt; 即不可变的。这也势必导致使用这种方法修改代码的成本变高，更加麻烦。&lt;/p&gt;
&lt;p&gt;再次，Java 的匿名内部类在编译之后都会成为一个单独的类，在运行时也有类加载、内存等的成本，总的来说还是比较重的，因此一般也不会去大量使用。&lt;/p&gt;
&lt;p&gt;其他语言中有没有类似的做法，我就不太清楚了。也许比不上 JavaScript 中那么简洁，不过本身也不失为一种有趣的用法，有兴趣的同学可以在项目中尝试一下。&lt;/p&gt;
&lt;h2&gt;Bonus：再谈将 statement 转化为 expression&lt;/h2&gt;
&lt;p&gt;虽然本文主要讨论了立即执行函数的用途，但倘若抛开这一用法，“将 statement 转化为 expression”这一目的本身，也不失为一个让代码更加优雅的重构手段。而在 JavaScript 中，为了实现这一点，使用立即执行函数并不是唯一的方法。&lt;/p&gt;
&lt;p&gt;考虑一个常见的功能，将一个配置数组转化为 key-value 的对象：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  obj&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; obj
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们很容易想到，使用 &lt;code class=&quot;language-text&quot;&gt;reduce&lt;/code&gt; 函数可以简洁地实现这一功能，但 &lt;code class=&quot;language-text&quot;&gt;reduce&lt;/code&gt; 函数的内部却要手动 &lt;code class=&quot;language-text&quot;&gt;return obj&lt;/code&gt;，显得十分碍眼。其本质原因就是此处赋值表达式返回的是 &lt;code class=&quot;language-text&quot;&gt;obj[item.prop]&lt;/code&gt;，而不是 &lt;code class=&quot;language-text&quot;&gt;obj&lt;/code&gt; 本身。&lt;/p&gt;
&lt;p&gt;那么有进一步简化这段代码的方法吗？一种方法是借助现成的函数，例如 &lt;code class=&quot;language-text&quot;&gt;Object.assign&lt;/code&gt; 或解构赋值：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;另一种方法是借助一个更冷门的语法——&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator&quot;&gt;逗号表达式&lt;/a&gt;。我们可以把一串表达式用逗号连接起来，它们会从左到右执行，并把最后一个表达式的返回值作为整体的返回值。在这个例子中，我们只需要：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不过这个语法有点 tricky 了，也不太容易看明白它的意图。总而言之我们需要根据不同的场景去选取合适的写法。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[【译】JavaScript 游戏循环详解]]></title><description><![CDATA[前言：游戏循环（Game Loop）是做游戏时绕不开的一个话题。网上已经有很多文章讲解了如何在网页中使用 JavaScript 实现游戏循环，但基本上都只提到  就完事了。这只能用来做个 demo…]]></description><link>https://maliut.space/p/javascript-game-loop/</link><guid isPermaLink="false">https://maliut.space/p/javascript-game-loop/</guid><pubDate>Wed, 23 Dec 2020 21:58:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;前言：游戏循环（Game Loop）是做游戏时绕不开的一个话题。网上已经有很多文章讲解了如何在网页中使用 JavaScript 实现游戏循环，但基本上都只提到 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 就完事了。这只能用来做个 demo ，对一个完整的游戏来说是远远不够的。正好之前毕设时查阅了相关资料，对比之下这篇文章讲的最为全面。因此我翻译了这篇文章，希望能给大家带来帮助。&lt;/p&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;http://isaacsukin.com/news/2015/01/detailed-explanation-javascript-game-loops-and-timing&quot;&gt;http://isaacsukin.com/news/2015/01/detailed-explanation-javascript-game-loops-and-timing&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!--more--&gt;
&lt;p&gt;在任何状态随时间改变的应用中，主循环都是核心的部分。在游戏中，主循环一般被称为&lt;em&gt;游戏循环&lt;/em&gt;，既要负责计算物理与 AI，也要把计算出来的画面渲染在屏幕上。很不幸，在网上能找到的大多数主循环的实现 —— 尤其是以 JavaScript 来编写的 —— 都存在着一些计时上的问题。不瞒你说，我自己也写过不少错误的实现。这篇文章旨在告诉你为什么这么多主循环需要被修正，以及如何实现一个正确的主循环。&lt;/p&gt;
&lt;p&gt;如果你不想看讲解，而只是想直接拿正确的代码来用，可以使用我的开源库 &lt;a href=&quot;https://github.com/IceCreamYou/MainLoop.js&quot;&gt;MainLoop.js&lt;/a&gt; 。&lt;/p&gt;
&lt;h2&gt;初次尝试&lt;/h2&gt;
&lt;p&gt;我们即将来编写一个“游戏”。简单起见，只是来画一个会左右来回移动的方块。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;box&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;让它展示出来：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-css line-numbers&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;#box&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 150px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看上去还不错。接下来我们来搭建 JavaScript 应用的脚手架。首先，我们的方块需要一些属性来控制它的位置和速度。我们用一个 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 函数来渲染它的位置。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; box &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;box&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 方块&lt;/span&gt;
    boxPos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 方块的位置&lt;/span&gt;
    boxVelocity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 方块的速度&lt;/span&gt;
    limit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 方块跑多远以后调头&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    box&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; boxPos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后是游戏的逻辑。我们希望方块来回移动，所以要给它加个速度。你很快就会发现，从这里就慢慢开始出错了。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    boxPos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; boxVelocity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 如果跑过头了，就让方块调头&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;boxPos &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; limit &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; boxPos &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; boxVelocity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;boxVelocity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;现在让我们把游戏跑起来。为了跑这个游戏，我们需要一个循环，不停地调用 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 函数让方块移动，然后调用 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 函数把移动后的位置渲染在屏幕上。我们要怎么做到这一点呢？&lt;/p&gt;
&lt;p&gt;如果你用其他语言写过游戏，你或许会想到使用 &lt;code class=&quot;language-text&quot;&gt;while&lt;/code&gt; 循环：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然而，JavaScript 是单线程的，这意味着如果你这么写，那么浏览器在这个页面里就做不了其他任何事了。几秒钟后浏览器会卡住，然后告诉用户出错了，问是否要终止程序。你肯定不想你的游戏只能跑几秒钟，所以这么搞肯定不行。我们需要一种方法，让游戏循环把控制权移交给浏览器，直到浏览器准备好再次执行我们的工作。&lt;/p&gt;
&lt;p&gt;如果你熟悉 JavaScript，你也许会想到 &lt;code class=&quot;language-text&quot;&gt;setTimeout()&lt;/code&gt; 或是 &lt;code class=&quot;language-text&quot;&gt;setInterval()&lt;/code&gt;。这两个方法允许你在指定时间之后继续执行代码。这看上去行得通，不过在浏览器中我们有更好的做法：&lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame()&lt;/code&gt;。这是个较新的函数但已经得到了良好的浏览器支持（在本文写作时的 2015 年，除 IE9 及以下的浏览器都支持它）。你向这个函数传递一个回调，它会在浏览器下次准备执行渲染时执行这个回调。在一台 60Hz 显示器上，一个优化良好的应用每秒可以更新画面（绘制帧）60 次。所以，为了达到最佳的 60 帧帧率，你的主循环有 1 / 60 = 16.667 毫秒的时间做完一次循环的工作。在后文中我们会对 node.js/io.js 和低版本浏览器做兼容。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;记住大多数显示器每秒不能展示大于 60 帧（FPS）。人类是否能够区分出高分辨率的区别要取决于应用类型，不过可以作为参考的是，电影一般是 24FPS，其他视频 30FPS，大多数游戏 30FPS 以上都可以接受，虚拟现实也许需要 75FPS 才能感觉自然。有些游戏显示器最高能到 144FPS。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;好了，让我们用 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame()&lt;/code&gt; 来实现主循环！&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mainLoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mainLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
 
&lt;span class=&quot;token comment&quot;&gt;// 入口点&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mainLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;跑起来了！&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;300&quot; src=&quot;//jsrun.net/NyLKp/embedded/all/light&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;注意 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 方法是在 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法之后调用的，这是因为我们希望能尽可能渲染出应用最新的状态。（注：有些基于 canvas 的应用需要在首帧，即任何更新都没有发生之前，渲染出应用的的初始状态。我们会在后文中探讨一种实现方式。）网上有些文章猜测在 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 的回调中先渲染再做逻辑会使屏幕绘制更快，但实际上并非如此。即使是，那也只是在绘制当前帧更快和下一帧更快之间做一个权衡。当 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 下一次调用时就无所谓了，毕竟一次只能更新一帧，但我觉得这样在逻辑上是更顺畅的。&lt;/p&gt;
&lt;h2&gt;计时问题&lt;/h2&gt;
&lt;p&gt;目前为止，我们的 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法有个问题，便是它依赖于帧率。换句话说，如果你的游戏跑的慢（即每秒内能够执行的帧数较少），那么方块移动的也越慢；而如果你的游戏跑的快（每秒内能够执行的帧数较多），那么方块移动的也越快。我们不希望出现如此不可预测的行为，尤其是在多人游戏中。没人会希望他们的游戏角色行动迟缓，只因他们电脑的配置没那么好。即使是在单机游戏中，游戏速度也会显著影响难度。在考验反应的游戏里，游戏速度越低就会更简单，而速度越高就会难，甚至没法玩下去。&lt;/p&gt;
&lt;p&gt;针对这个问题，我们来加入一个控制 FPS 的能力。我们可以利用 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame()&lt;/code&gt; 函数的能力，为回调提供一个时间戳。每次循环执行时，我们确认下是否达到了一段最小时间。如果是，我们就渲染这一帧，如果不是，我们就跳过这次循环，等待下一帧。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; lastFrameTimeMs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 上一轮循环运行的时间&lt;/span&gt;
    maxFPS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 我们想要限制的最大 FPS&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mainLoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 控制帧率   &lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; lastFrameTimeMs &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; maxFPS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mainLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    lastFrameTimeMs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你可以看到现在它的移动如此之慢：&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;300&quot; src=&quot;//jsrun.net/3yLKp/embedded/all/light&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;我们可以做的更好。这个问题在于我们的应用并不和现实时间挂钩，该怎么去修正呢？&lt;/p&gt;
&lt;p&gt;首先让我们尝试用速度乘以两帧之间的时间差（&lt;em&gt;delta&lt;/em&gt;）。我们把 &lt;code class=&quot;language-text&quot;&gt;boxPos += boxVelocity&lt;/code&gt; 替换成 &lt;code class=&quot;language-text&quot;&gt;boxPos += boxVelocity * delta&lt;/code&gt;。修改 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 方法，从主循环中接收 &lt;em&gt;delta&lt;/em&gt; 这个参数：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 调整速度，使它不与 FPS 挂钩&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; boxVelocity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.08&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    delta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 增加 delta 参数&lt;/span&gt;
    boxPos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; boxVelocity &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; delta&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 速度现在与时间相关&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mainLoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
 
    delta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; lastFrameTimeMs&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 获取当前时间与上一帧的时间差 delta&lt;/span&gt;
    lastFrameTimeMs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
    &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 传入 delta 参数&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;结果相当不错！现在我们的方块看上去不受帧率影响，随时间移动恒定的距离。&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;300&quot; src=&quot;//jsrun.net/VyLKp/embedded/all/light&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;如果它的表现与你预期不符……好吧，接着看。&lt;/p&gt;
&lt;h2&gt;物理问题&lt;/h2&gt;
&lt;p&gt;尝试把速度值上调，例如 0.8 。你会注意到有几秒种这个方块运动得很不平稳，也许会从可视范围里跑出去。这是不应该出现的。是哪里有问题呢？&lt;/p&gt;
&lt;p&gt;问题在于，之前方块每帧运动相同的距离，而现在我们加入了 delta 以后，每帧运动的距离都有所不同，而这些距离有时会相当大。在游戏中，这一现象可能会让玩家穿墙，或是阻碍他们跳过障碍物。&lt;/p&gt;
&lt;p&gt;另一个问题是，因为方块每帧移动的距离不同，在计算过程中一些小的四舍五入的误差，会随着时间推移而累积，造成方块位置的漂移。没有两个人可以玩到相同的游戏，因为他们的帧率不同，造成的误差也不同。这听起来挺微不足道的，但是实践中，哪怕是在正常帧率下，也只要几秒钟就能让误差变得可被玩家感知。这不仅对玩家不好，也对测试不好，因为我们希望给定相同的输入时，程序也能给出相同的输出。换句话说，我们希望程序是具有&lt;em&gt;确定性&lt;/em&gt; 的。&lt;/p&gt;
&lt;h2&gt;一种解决方案&lt;/h2&gt;
&lt;p&gt;解决这一物理问题的关键点在于我们想要两全其美：既要在每次执行 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 方法时模拟相等的游戏时间，又要模拟两帧之间并不每次都相等的现实时间。事实证明我们可以做到，只需在两帧之间以固定大小的 delta 值多次运行 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 方法，直到我们模拟完整段现实时间。让我们稍稍调整下主循环：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 每次调用 update 时只模拟 1000 ms / 60 FPS = 16.667 ms 的时间&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; timestep &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mainLoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
 
    &lt;span class=&quot;token comment&quot;&gt;// 计算我们累积下来的还没有被模拟过的时间&lt;/span&gt;
    delta &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; timestamp &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; lastFrameTimeMs&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 注意这里是 += &lt;/span&gt;
    lastFrameTimeMs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
    &lt;span class=&quot;token comment&quot;&gt;// 以固定大小的步长模拟整段 delta 时间&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; timestep&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestep&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        delta &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; timestep&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mainLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们把两帧之间的现实时间分隔成了小段，多次传递给 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法。&lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法本身无需修改，只要改变传递给它的参数，这样每次 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 时都会模拟相等的游戏时间。&lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法会在一帧之内调用多次以模拟完这一帧距离上一帧经过的真实时间。（如果这一帧距离上一帧经过的时间比单次 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 模拟的时间还要小，我们在这一帧就不调用 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法了。如果有剩余未被模拟的时间，我们就把它累积起来放到下一帧去模拟。这就是我们需要通过 &lt;code class=&quot;language-text&quot;&gt;+=&lt;/code&gt; 计算 &lt;code class=&quot;language-text&quot;&gt;delta&lt;/code&gt;，而不是直接赋值给它的原因，因为我们需要记录上一帧剩余下来没被模拟的时间。）这种方式避免了四舍五入不一致导致的误差错误，也保证了两帧之间不会出现大到能穿墙的巨大跨越。&lt;/p&gt;
&lt;p&gt;让我们实际看一看：&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;300&quot; src=&quot;//jsrun.net/NEIKp/embedded/all/light&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;如果你调整 &lt;code class=&quot;language-text&quot;&gt;boxVelocity&lt;/code&gt; 的值，你会看到方块会正确地呆在它应该在的地方。不错！&lt;/p&gt;
&lt;p&gt;注意： timestep 值的选择并不是任意的。分母有效地限制了用户能够感知的每秒帧数（除非绘制是插值的，如后文所述）。当实际 FPS 低于最大值时，降低 timestep 会增加可感知到的最大 FPS，但代价是每一帧执行 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 的次数会更多。由于执行 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 越多，消耗的时间也越多，这就又会降低帧率。如果帧率降的太多，就可能会陷入一种死亡螺旋。&lt;/p&gt;
&lt;h2&gt;死亡螺旋&lt;/h2&gt;
&lt;p&gt;遗憾的是我们又引入了一个新问题。在我们的初次尝试中，如果一帧花费了比较长的时间执行更新和渲染，帧率就会自然地降低，直到每一帧花费的时间足够更新和渲染完成。然而，现在我们的更新依赖于时间。如果某一帧花费了较长时间来模拟，那么下一帧会需要模拟更长的时间。这意味着我们需要更多次执行 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法，而这进一步意味着这新的一帧需要花费更多的时间来模拟，如此往复……直到整个应用无法响应然后挂掉。这就是死亡螺旋。&lt;/p&gt;
&lt;p&gt;一般来说，只要我们把 timestep 的值设的足够高，每次执行 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 的开销都比它模拟的时间要短，这样就不会有问题。但执行开销在不同的硬件和负载上都是不一样的。而且我们讨论的是 JavaScript 环境，意味着我们对执行环境只有很微小的控制权。如果用户切换到另一标签页，浏览器就会停止当前标签页的渲染，当用户再切回来时，我们就累积了很长一段时间要去做模拟。如果这消耗了太多时间，浏览器就会挂起。&lt;/p&gt;
&lt;h3&gt;合理性检查&lt;/h3&gt;
&lt;p&gt;我们需要一个逃生通道。让我们在 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 的循环中加入一个合理性检查：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; numUpdateSteps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; timestep&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestep&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        delta &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; timestep&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Sanity check&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;numUpdateSteps &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;240&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 出现了异常情况，需要做修复&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;// 跳出循环&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在我们的 &lt;code class=&quot;language-text&quot;&gt;panic&lt;/code&gt; 方法中要做些什么呢？这得看情况。&lt;/p&gt;
&lt;p&gt;回合制多人游戏用了一种叫“锁步（lockstep，亦称为帧同步）”的网络技术，它可以保证所有的玩家以相同步调进行游戏。这意味着所有玩家体验到的都是最慢的那个人的速度。如果一个玩家实在落后太多，他就会掉线，这样就不会拖慢其他玩家。因此，在锁步的游戏中，这个玩家就掉线了。&lt;/p&gt;
&lt;p&gt;在一个没有使用锁步的多人游戏，比如第一人称射击游戏中，一般都会有一个服务器维持着游戏的“权威状态”。也就是说，服务器会接收所有玩家的输入，并计算出游戏世界应有的样子，以避免玩家作弊。如果一个玩家的本地游戏距离权威状态太远，即 panic 的状态，那么这个玩家需要被拉回权威状态。（在实践中，如果直接把玩家拉回去会令人很迷惑，所以一般会有一个替代的 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 方法，让客户端能够更加平滑地回到服务端的权威状态。）一旦我们把用户拉回了最新的状态，我们就不需要再去本地模拟这一堆剩下的时间了，因为我们已经把这些时间高效快进掉了。因此我们可以把它们丢弃掉：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    delta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 丢弃未模拟的时间&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... 把玩家同步到权威状态&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果在服务端这么干，会引起不确定性的行为，不过只有服务端需要保证游戏运行的确定性，所以在多人游戏的客户端这么做是完全可以的。&lt;/p&gt;
&lt;p&gt;在单机游戏中，我们可以让游戏继续运行一会看看游戏运行速度能不能赶上来。但当游戏在中间状态过渡时，游戏会看上去在几帧之内运行得飞快。另一种可以接受的方法是，直接丢弃未模拟的时间，就像我们前面在非锁步的多人游戏中做的那样。这会引入不确定性的行为，不过你可能觉得这是个极端情况，可以另当别论。&lt;/p&gt;
&lt;p&gt;无论如何，如果游戏持续出现 panic 的情况，而且也不是因为标签页在后台引起的，这可能提示了游戏的主循环运行得实在太慢了。也许你需要增大 timestep 的值。&lt;/p&gt;
&lt;h3&gt;FPS 控制&lt;/h3&gt;
&lt;p&gt;另一种可以避免死亡螺旋（总的来说，避免低帧率）的方法是监控游戏的运行帧率，并在帧率过低时，调整主循环中的行为。往往在 panic 状态发生前，我们就能检测到掉帧的情况，可以由此来预防 panic 。&lt;/p&gt;
&lt;p&gt;有许多监测帧率的方式，其一是在一段时间内（比如最近 10 秒）持续监测每秒渲染的帧数，并取平均值。然而这稍微有点吃性能，而且我们希望最近几秒钟的帧数能获得更高的权重。一种简单做法是，使用&lt;em&gt;加权平均&lt;/em&gt;来计算权重：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    framesThisSecond &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lastFpsUpdate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mainLoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
 
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; lastFpsUpdate &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 每秒更新一次&lt;/span&gt;
        fps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; framesThisSecond &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; fps&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 计算新 FPS&lt;/span&gt;
 
        lastFpsUpdate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        framesThisSecond &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    framesThisSecond&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的 &lt;code class=&quot;language-text&quot;&gt;0.25&lt;/code&gt; 是衰减系数 - 这本质上体现了最近几秒钟的权重有多大。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;fps&lt;/code&gt; 变量保存了我们估算出来的 FPS。我们该拿它做什么用呢？首先可以想到的是把它显示出来：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 假设我们在 HTML 中增加了 &amp;lt;div id=&quot;fpsDisplay&quot;&gt;&amp;lt;/div&gt; 这个元素&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fpsDisplay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;fpsDisplay&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    box&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; boxPos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    fpsDisplay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos; FPS&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 展示 FPS&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;成功了：&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;300&quot; src=&quot;//jsrun.net/VEIKp/embedded/all/light&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;我们可以把 FPS 派上更多的用场。如果 FPS 太低了，我们可以退出游戏，降低画质，停止或减少主循环之外的行为，例如事件处理器、音频播放；降低非关键更新的频率，或增加 timestep。（注意这是最不得已的情况，因为这会导致每次 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 调用模拟更多的时间，进而使程序有更多的不确定性，因此应谨慎使用。）如果 FPS 回升了，就恢复这些行为。&lt;/p&gt;
&lt;h3&gt;开始与结束&lt;/h3&gt;
&lt;p&gt;在主循环开始和结束时分别调用一个回调方法（让我们叫它们 &lt;code class=&quot;language-text&quot;&gt;begin()&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;end()&lt;/code&gt; 方法）来做初始化和清理工作是很有用的。一般来说，&lt;code class=&quot;language-text&quot;&gt;begin()&lt;/code&gt; 可以用于在 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 执行前处理输入（例如当玩家按下开火键时刷出子弹）。如果需要对用户输入执行长时间运行的操作，那么在主循环中分块处理这些操作，而不是在事件处理程序中一次处理这些操作，可以避免帧的延迟。而 &lt;code class=&quot;language-text&quot;&gt;end()&lt;/code&gt; 方法则可以增量地执行不受时间影响的、需要较长运行时间的更新，以及根据 FPS 的变化作调整。&lt;/p&gt;
&lt;h3&gt;选择 timestep&lt;/h3&gt;
&lt;p&gt;一般来说，1000/60 在大多数情况是个好选择，因为大多数显示器以 60Hz 运行，如果你发现你的程序十分吃性能，也许你可以将其设为 1000/30。这有效地限制了你的可感知帧率为 30FPS（除非使用了插值，如后文所述）。注意帧率会根据你的显示器和显卡驱动进行调节，因此你设置的最大值可能与实际运行时测得的值不相同。如果你的游戏运行流畅，且你希望模拟得更加精确，你可以考虑使用高端游戏显示屏的 FPS，如 75、90、120 和 144。再高的话最终运行速度可能反而就会变慢了。&lt;/p&gt;
&lt;h2&gt;性能考量&lt;/h2&gt;
&lt;p&gt;如果程序的性能并不尽如人意，你可以使用插值绘制和 Web Worker 这两种重构方式来获得实实在在的性能提升。&lt;/p&gt;
&lt;h3&gt;插值绘制&lt;/h3&gt;
&lt;p&gt;在每次 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 结束之后，在 &lt;code class=&quot;language-text&quot;&gt;delta&lt;/code&gt; 中经常会有一段小于一整个 timestep 的剩余时间。将这段尚未被模拟的剩余时间所占 timestep 的百分比传入 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 方法就可以在两帧之间做一个插值。即使在高帧率下，这种视觉上的平滑效果也有助于降低画面的卡顿。&lt;/p&gt;
&lt;p&gt;卡顿之所以会出现，是因为 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法模拟的时间与两个 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 方法经过的时间往往是不同的。进一步说，假如 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 发生在下面第一行的每条竖线所代表的时间点，而 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 发生在下面第二行的每条竖线所代表的时间点，那么在 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 方法发生渲染的时间点，总会有一些剩余时间还没有被 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法所模拟：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;update() timesteps:  |  |  |  |  |  |  |  |  |
draw() calls:        |   |   |   |   |   |   |&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;为了使 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 对方块移动做插值以进行渲染，必须保留上次 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 之后对象的状态，并将其用于计算中间状态。注意这意味着渲染最多落后一次 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 。这仍然比外推（推测对象在下一次 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 之后的状态）要好，因为后者可能会产生奇怪的结果。要注意存储多个状态实现起来比较困难，而且这个过程也是耗时操作，可能会导致帧率下降。因此除非你观察到了卡顿现象，否则这么做很可能是不值得的。&lt;/p&gt;
&lt;p&gt;我们可以这样对我们的方块进行插值：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; boxLastPos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    boxLastPos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; boxPos&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 保存上一次 update 时方块的位置&lt;/span&gt;
    boxPos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; boxVelocity &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; delta&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;interp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    box&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;boxLastPos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;boxPos &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; boxLastPos&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; interp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 进行插值&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mainLoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; timestep&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 传入插值的百分比&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;把所有的代码放在一起：&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;300&quot; src=&quot;//jsrun.net/UEIKp/embedded/all/light&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;h3&gt;使用 Web Worker 来更新&lt;/h3&gt;
&lt;p&gt;与主循环中的任何事物一样，&lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法的执行时间直接影响了帧率。如果 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法花费的时间足够长，以至于帧率低于预期，那么我们可以将 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 方法中无需在每帧之间执行的部分放入 Web Worker 中。（网上的很多地方有时会建议使用 &lt;code class=&quot;language-text&quot;&gt;setTimeout()&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;setInterval()&lt;/code&gt; 来进行调度。这些方法只需对现有的代码进行较小的改动，但由于 JavaScript 是单线程的，这些改动仍然会阻止渲染并降低帧率。使用 Web Worker 需要做更大的改动，但它们在单独的线程中执行，故可以为主循环释放出更多时间。）&lt;/p&gt;
&lt;p&gt;这里列举了部分在使用 Web Worker 时需考虑的内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在迁移到 Web Worker 前分析你的代码。也许渲染过程才是瓶颈，此时你首先应当考虑的是降低场景的视觉复杂度。&lt;/li&gt;
&lt;li&gt;将 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 中的所有内容都迁移到 Web Worker 中是不可取的，除非你的 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 方法能够像我们之前讨论的那样进行插值。最容易移出 &lt;code class=&quot;language-text&quot;&gt;update()&lt;/code&gt; 的是后台更新（比如在城镇建造游戏中计算市民的幸福指数）、不影响场景的物理效果（比如风中飘动的旗帜）和任何被遮挡或是离场景很远的事物。&lt;/li&gt;
&lt;li&gt;如果 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 方法需要基于 Web Worker 中的行为对物理做插值，那么 Web Worker 需要把插值结果传回主线程，使其在 &lt;code class=&quot;language-text&quot;&gt;draw()&lt;/code&gt; 方法中可用。&lt;/li&gt;
&lt;li&gt;Web Worker 不能访问主线程中的状态，因此它们不能直接修改你场景中的物体。与 Web Worker 之间传递数据是一个痛点。最简单的办法是使用 Transferable Objects：你可以传递一个 ArrayBuffer 给 Web Worker，并销毁原始引用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你可以在 &lt;a href=&quot;https://www.html5rocks.com/en/tutorials/workers/basics/&quot;&gt;HTML5 Rocks&lt;/a&gt; 中了解更多有关于 Web Worker 和 Transferable Objects 的信息。&lt;/p&gt;
&lt;h2&gt;启动与停止&lt;/h2&gt;
&lt;p&gt;目前，一旦我们的游戏启动了主循环，我们是没有任何方法停止的。让我们引入 &lt;code class=&quot;language-text&quot;&gt;start()&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;stop()&lt;/code&gt; 函数来管理游戏的运行。首先我们要找到停止 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 的方法。一种方式是维持一个 &lt;code class=&quot;language-text&quot;&gt;running&lt;/code&gt; 的布尔变量来控制主循环下一次是否要继续调用 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt;。一般来说没啥问题，不过如果游戏开始后立马停止，那么无论如何都有一帧会被运行，无法取消。因此我们需要一个能够真正取消渲染循环的方法。幸运的是我们有 &lt;code class=&quot;language-text&quot;&gt;cancelAnimationFrame()&lt;/code&gt; 方法。当调用 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame&lt;/code&gt; 时会返回一个 frame ID，可以传递给&lt;code class=&quot;language-text&quot;&gt;cancelAnimationFrame()&lt;/code&gt; 方法。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;frameID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mainLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;切记如果你做了 FPS 控制，别忘了在 FPS 控制的节流条件中也做下改动，记录 frame ID。&lt;/p&gt;
&lt;p&gt;现在我们来实现 &lt;code class=&quot;language-text&quot;&gt;stop()&lt;/code&gt; 方法：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; running &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    started &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    running &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    started &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frameID&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此外，当主循环暂停时，也要暂停事件处理和其他的后台任务（比如通过 &lt;code class=&quot;language-text&quot;&gt;setInterval()&lt;/code&gt; 执行的任务或是 Web Worker 中的任务）。这通常不难，因为它们只要检查下 &lt;code class=&quot;language-text&quot;&gt;running&lt;/code&gt; 变量就可以决定自身是否要执行了。另一个需要注意的点是，在多人游戏中暂停会导致该玩家的客户端失去同步，因此一般要让他们退出游戏，或是在主循环再次启动时把玩家拉到最新的位置（在确认玩家是否真的想要暂停之后）。&lt;/p&gt;
&lt;p&gt;开始游戏循环会更为棘手一些。我们需要关注以下四点。首先，如果主循环已经在运行，我们不能允许它再次运行，否则会导致同时请求多帧渲染，降低运行速度。其次，我们需要确保快速地切换游戏的开始和停止不会造成错误。再次，我们需要在游戏尚未发生任何更新时渲染出游戏的初始状态，因为我们主循环的 &lt;code class=&quot;language-text&quot;&gt;draw&lt;/code&gt; 是在 &lt;code class=&quot;language-text&quot;&gt;update&lt;/code&gt; 之后调用的。最后，当游戏暂停时，我们需要重置一些变量的值，以防暂停时我们记录的需要模拟的时间仍然在流逝。另外，在游戏重新启动后，事件处理和后台任务也要恢复运行。这是我们的代码：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;started&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 防止多次启动&lt;/span&gt;
        started &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 第一帧来获取时间戳，并绘制初始画面.&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 记录 frame ID 用于停止&lt;/span&gt;
        frameID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 首次渲染&lt;/span&gt;
            running &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 重置一些记录时间相关的变量&lt;/span&gt;
            lastFrameTimeMs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            lastFpsUpdate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            framesThisSecond &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 真正启动主循环&lt;/span&gt;
            frameID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mainLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;效果：&lt;/p&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;300&quot; src=&quot;//jsrun.net/JEIKp/embedded/all/light&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;h2&gt;Node.js/IO.js 与 IE9 支持&lt;/h2&gt;
&lt;p&gt;现在我们代码的主要问题，是 &lt;code class=&quot;language-text&quot;&gt;requestAnimationFrame()&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;cancelAnimationFrame()&lt;/code&gt; 缺乏对 node.js/IO.js 环境，和 IE9 以及更早的浏览器的兼容（如果你还关心那些浏览器的话）。我们可以利用 timer 做一个 polyfill：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 代码来自于 MIT 协议的 https://github.com/underscorediscovery/realtime-multiplayer-in-html5&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; requestAnimationFrame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; requestAnimationFrame &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;function&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; requestAnimationFrame &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; lastTimestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            timeout&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            timeout &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timestep &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; lastTimestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            lastTimestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 
    cancelAnimationFrame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; cancelAnimationFrame &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;function&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; cancelAnimationFrame &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; clearTimeout&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们使用了 &lt;code class=&quot;language-text&quot;&gt;Date.now()&lt;/code&gt; 以减少兼容性问题，但这在 IE8 中是不支持的。如果你真的需要支持 IE8，可以使用 &lt;code class=&quot;language-text&quot;&gt;+new Date()&lt;/code&gt; 代替，但这会产生一堆临时对象，增加垃圾回收的负载，进而导致你的游戏卡顿。此外 IE8 本身也很慢，很难支持有较多 JavaScript 逻辑的应用运行。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;我们需要考虑的东西很多。如果你想简单地实现游戏循环，只要用我的 &lt;a href=&quot;https://github.com/IceCreamYou/MainLoop.js&quot;&gt;MainLoop.js&lt;/a&gt; 开源库就可以了。这样你就不用担心上面这么多的问题。&lt;/p&gt;
&lt;p&gt;如果你自己动手，那么还可以做一些通用性的代码优化。例如，可以把小方块封装在自己单独的类中。整个脚本应该被包裹在一个 &lt;a href=&quot;https://en.wikipedia.org/wiki/Immediately_invoked_function_expression&quot;&gt;IIFE（立即执行函数）&lt;/a&gt; 中，仅暴露接口给外部，以防止污染浏览器的全局命名空间，或是把代码打包成 CommonJS 或 AMD 的模块。MainLoop.js 已经把这些都做好了（甚至做得更好），不过总而言之我们已经做得相当不错了。&lt;/p&gt;
&lt;p&gt;最后，我要感谢 Glenn Fiedler 编写了经典的 &lt;a href=&quot;http://gafferongames.com/game-physics/fix-your-timestep/&quot;&gt;Fix your timestep!&lt;/a&gt; 一文，它是本文中如此多工作的起点。也感谢 &lt;a href=&quot;https://github.com/statico&quot;&gt;Ian Langworth&lt;/a&gt; 为我审阅了我在&lt;a href=&quot;https://www.packtpub.com/game-development-with-three-js/book&quot;&gt;关于制作 3D 页游的书&lt;/a&gt;中包含的本文的简略版本，并提出了 Web Worker 相关的一些建议。最后的最后，如果你仍然想寻找关于这个话题的更多资源，也许可以考虑看下 &lt;a href=&quot;http://gameprogrammingpatterns.com/game-loop.html&quot;&gt;Game Programming Patterns&lt;/a&gt; 这本书，或者 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Games/Anatomy&quot;&gt;MDN&lt;/a&gt; 上一篇相对简略的文章。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[CoC 7th 模组 - 天使百分百]]></title><description><![CDATA[模组简介 CoC 7th ，现代，推荐 2~…]]></description><link>https://maliut.space/p/100-percent-angel/</link><guid isPermaLink="false">https://maliut.space/p/100-percent-angel/</guid><pubDate>Sun, 16 Feb 2020 22:57:00 GMT</pubDate><content:encoded>&lt;h2&gt;模组简介&lt;/h2&gt;
&lt;p&gt;CoC 7th ，现代，推荐 2~3 人。调查员身份可以是学生、校友等，能与学校扯上关系即可。无战斗。推荐图书馆、机械维修等技能。也许智力、教育也高一点会比较好？&lt;/p&gt;
&lt;p&gt;因为本身是一个偏玩梗的团，所以适合的玩家比较局限，以效实中学学生为佳，如果经历过百年校庆，尤其是看过那部微电影，就再好不过了。别的人玩可能会比较懵逼吧。&lt;/p&gt;
&lt;h2&gt;模组导入&lt;/h2&gt;
&lt;p&gt;2012年，效实中学即将迎来百年校庆。在校庆之际，效实园里却流传起了一个神秘的言论 ——「只要考到100分，你就能见到天使」。为了揭开这个传言背后的真相，调查员们决定来到效实中学开始调查……&lt;/p&gt;
&lt;h2&gt;模组下载&lt;/h2&gt;
&lt;p&gt;可以联系作者本人索取。已有一次跑团记录，&lt;a href=&quot;/p/log-100-percent-angel/&quot;&gt;点击这里查看&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[明日方舟作业帮]]></title><description><![CDATA[顾名思义，即方便明日方舟玩家抄作业的工具，点此进入。不过很遗憾，目前已经停止维护。 开这个坑的背景是：随着游戏干员数量变多以及能力的差异化， 未来抄作业将越来越难，因此希望提供一个工具，能根据自己现有的 Box…]]></description><link>https://maliut.space/p/arknights/</link><guid isPermaLink="false">https://maliut.space/p/arknights/</guid><pubDate>Sun, 16 Feb 2020 22:35:00 GMT</pubDate><content:encoded>&lt;p&gt;顾名思义，即方便明日方舟玩家抄作业的工具，&lt;a href=&quot;https://maliut.space/arknights/&quot;&gt;点此进入&lt;/a&gt;。不过很遗憾，目前已经停止维护。&lt;/p&gt;
&lt;p&gt;开这个坑的背景是：随着游戏干员数量变多以及能力的差异化， 未来抄作业将越来越难，因此希望提供一个工具，能根据自己现有的 Box 快速筛选适合自己的作业。&lt;/p&gt;
&lt;p&gt;这个工具分为两个部分，对于用户端，工具提供了一个可以选择关卡和干员的界面。只要玩家选择自己想上的干员和自己没有的干员，便可以一键筛选出符合要求的作业。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s2.ax1x.com/2020/02/16/39NaJs.jpg&quot; alt=&quot;用户端&quot;&gt;&lt;/p&gt;
&lt;p&gt;而这些作业是怎么录入进去的呢？由于本人并没有这么强的水平从 B 站直接爬所有的作业，然后提取出里面的干员，因此不得不采用人工录入的方式。在此也感谢 &lt;a href=&quot;https://github.com/FinZhang&quot;&gt;@Fin&lt;/a&gt; &lt;a href=&quot;https://github.com/Xraigor&quot;&gt;@Xraigor&lt;/a&gt; 两位帮我录入。同时 &lt;a href=&quot;https://github.com/FinZhang&quot;&gt;@Fin&lt;/a&gt; 同学也是这个项目本身创意的提出者，且为后续优化提供了不少宝贵建议。为了简化录入，我开发了一个 Chrome 插件，可以自动提取当前作业的一些基本信息，且方便录入者在同一个页面完成输入和提交工作，无需频繁切换页面。&lt;/p&gt;
&lt;p&gt;对于干员的录入，一开始我并没有什么好办法，如果要截取视频画面并完成图片分类，我没有这些技能不说，即使有，那也是十分费时费力的工作。不过后来灵光一闪，干员选取的界面里，有每个干员的名字，而 OCR 已经是一项成熟的能力了，最关键的是，QQ 截图里就有现成的功能。这样只需一次截图框选，就可以把干员的名字都提取出来了。然后再对一些常见的识别错误稍加处理，OCR 的准确程度几乎能有 99%。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s2.ax1x.com/2020/02/16/39NUij.jpg&quot; alt=&quot;录入端&quot;&gt;&lt;/p&gt;
&lt;p&gt;在技术上面，由于这个工具也是纯前端网页，因此保存作业数据、查询的功能就放在了 LeanCloud 上面（结果发现复杂查询的时候有 bug，不过影响不大，暂时不提）。另外值得一提的是，以前因为不会设计，也懒得为小项目去引入一些大规模的框架，因此做出来的工具都是毫无样式的纯 HTML 页面。而这次则尝试使用了从现在的眼光来看已经完全过时了的 Bootstrap 。虽然大一在学校做项目时接触过一次，但那时并没有懂它怎么玩的，而现在用起来才发现真香。虽然已经是个过时东西，但是对我这种小规模追求快速开发的小工具再合适不过了，只需引入几个 CSS JS 完事。最终的效果不仅美观，也天然拥有了适应移动端的能力。&lt;/p&gt;
&lt;p&gt;话说回来，最后这个项目还是弃坑了，原因是虽然我已经最大程度简化了作业录入的流程，但需要人工录入作业终究是一个绕不过去的坎。另一方面，明日方舟目前的难度和多样性，也远没有达到需要为每个人个性化定制作业的程度。这种情况下，花费大量时间去做作业录入终究还是显得吃力不讨好。&lt;/p&gt;
&lt;p&gt;不过也许是因为我在 Github 上写了明日方舟被人搜索到了吧，我的站长统计显示这个站还是有过自然流量的，这不禁令我感到一丝欣慰。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SimPow - 简洁强大的 Hexo 博客主题]]></title><description><![CDATA[没错，就是你现在正在浏览的这个网站的主题。 在经历了从各种 XX 空间到自建 WordPress 再到静态博客，尝试了各种主题之后，终于也不可避免地走到了看各种主题皆丑陋，不得不自己写一个独一无二的主题的程度了。 这个主题具体什么时候开始使用的已不可考，虽说有 Git…]]></description><link>https://maliut.space/p/hexo-theme-simpow/</link><guid isPermaLink="false">https://maliut.space/p/hexo-theme-simpow/</guid><pubDate>Sun, 16 Feb 2020 21:41:00 GMT</pubDate><content:encoded>&lt;p&gt;没错，就是你现在正在浏览的这个网站的主题。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;在经历了从各种 XX 空间到自建 WordPress 再到静态博客，尝试了各种主题之后，终于也不可避免地走到了看各种主题皆丑陋，不得不自己写一个独一无二的主题的程度了。&lt;/p&gt;
&lt;p&gt;这个主题具体什么时候开始使用的已不可考，虽说有 Git 的提交记录，但也不完全准确。当然，目前的功能也还在完善当中。&lt;/p&gt;
&lt;p&gt;让我直接说重点，即这个主题的最主要的设计诉求。首先我对主题的偏好是简洁，拒绝了所有多栏的布局，力图保留最纯粹的阅读体验。而由于自己在浏览博客时，主要的场景是在 PC 端，主流屏幕分辨率 16：9 较宽，但博客的文本一般则是竖向细长型的，此为一个不和谐因素。而很多主题（包括我之前使用的）会使用一个框把文本的内容左右框起来，这看上去就更凸显了这一不和谐因素，而显得博客小家子气。因此我的主题希望打破文章与容器的边界，体现一种浑然一体的感觉。&lt;/p&gt;
&lt;p&gt;当然，我是一个设计白痴，只能直觉地判断最终效果顺不顺眼。因此也必然借鉴了不少他人的优秀主题。在此需要感谢这些我的灵感来源们：从网上看到的大神 &lt;a href=&quot;https://orzfly.com/&quot;&gt;orzFly 的博客&lt;/a&gt;借鉴了页面布局，从以前的主题 &lt;a href=&quot;https://otakism.com/hexo-theme-typescript/&quot;&gt;Typescript&lt;/a&gt; 借鉴了字体和配色，对于朋友的朋友的博客  &lt;a href=&quot;https://doubilee.com/&quot;&gt;BUWAI LEE&lt;/a&gt; 亦有借鉴。&lt;/p&gt;
&lt;p&gt;说到这个主题的名字 SimPow ，即 Simple &amp;#x26; Powerful ，简洁且强大。简洁相信不用多说大家都能看得出来，而强大则体现在，虽然它是一个静态网页，但动态网页能干的事情它也能干。得益于现在 SaaS 的兴起，并不需要自己购买或搭建服务器，我就可以实现一些动态化的功能。这里必须提一嘴博客的评论功能，起初我是想利用 SaaS 服务自己写一个的，后来发现了一个好用的插件 &lt;a href=&quot;https://valine.js.org/&quot;&gt;Valine&lt;/a&gt; ，它的实现思路和我的一模一样，而功能已经很完善了，因此我就拿过来稍加魔改便使用了。另外就是我想做的一个想法功能，初衷是无需繁琐的发布过程，能随时随地把自己的一些有趣的小点子小句子记录下来，这也需要动态化的能力。当然，要想实现重型的比如注册登录功能也是可以的，只是我目前没这个需求罢了。&lt;/p&gt;
&lt;p&gt;总的来说现在用起来还挺满意的。最后记录几个 TODO 项吧，虽然短时间应该懒得去做：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主界面 Logo - 没有做 Logo 的能力，比起丑的 Logo ，还是纯文字顺眼一点&lt;/li&gt;
&lt;li&gt;有人评论时，使用 Submail 等服务提示作者或被 @ 的人 - 纯粹懒得搞，毕竟并没有人来评论&lt;/li&gt;
&lt;li&gt;文章内图片下方支持小字备注&lt;/li&gt;
&lt;li&gt;“项目”分类下的文章使用图片瀑布流展示 - 之前搞过一下不过效果总归不太理想&lt;/li&gt;
&lt;li&gt;“想法”分类，即上文提到的功能&lt;/li&gt;
&lt;li&gt;支持手机版 - 有生之年&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[工科生的音乐浪漫 - Sonic Pi]]></title><description><![CDATA[说起音乐创作，大多数人立刻就会将其划入艺术的范畴。而除非有专门学过相关的才艺，对于大多数像我们这样的工科生来说，中间仿佛横亘着一条巨大的鸿沟。但今天，我想向大家介绍一款神奇的软件，它在两者之间架起了一座桥梁。 这款软件叫做 Sonic Pi…]]></description><link>https://maliut.space/p/sonic-pi/</link><guid isPermaLink="false">https://maliut.space/p/sonic-pi/</guid><pubDate>Sun, 22 Dec 2019 23:07:00 GMT</pubDate><content:encoded>&lt;p&gt;说起音乐创作，大多数人立刻就会将其划入艺术的范畴。而除非有专门学过相关的才艺，对于大多数像我们这样的工科生来说，中间仿佛横亘着一条巨大的鸿沟。但今天，我想向大家介绍一款神奇的软件，它在两者之间架起了一座桥梁。&lt;/p&gt;
&lt;p&gt;这款软件叫做 Sonic Pi ，来自剑桥大学计算机实验室，目前支持 Windows、Mac、树莓派三个平台。它也是一款开源软件，在 Github 上获得了 5k+ stars 。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;从功能上来说，它可以被视为一个简单的 DAW（Digital Audio Workstation ，数字音频工作站），类似 Cubase 和 FL Studio 等的下位替代。但它的特点在于，使用编写代码而不是输入音符的方式来完成乐曲的制作（←但用到的代码很简单）。它的设计旨在于简单、强大和富有乐趣间取得平衡，而它也的确做到了。一方面，它被用于校园之中， 在创造乐曲的过程中教授学生以编程思维；另一方面，它强大的实时编码作曲能力，也被不少专业的艺术家和 DJ 作现场表演和音乐可视化等用途。&lt;/p&gt;
&lt;p&gt;我花了点时间用 Sonic Pi 简单复刻了一首我非常喜欢的 BGM —— 来自《塞尔达传说 旷野之息》的 Kass&apos; Theme，但加入了些电音的风格。不如让我们在音乐声中，探索一下 Sonic Pi 的独特之处吧。&lt;/p&gt;
&lt;audio src=&quot;/54f35cf360f66012e580707d99ff9d4d/kasstheme.mp3&quot; controls=&quot;controls&quot;&gt;
你的浏览器不支持播放音乐
&lt;/audio&gt;
&lt;h2&gt;简单易上手的程序设计&lt;/h2&gt;
&lt;p&gt;阻碍大众进入音乐世界的第一道坎就是识谱了，各种五花八门的符号往往令人望而却步。不少人或许会觉得自己与音乐缘尽于此，但其实不然，作为艺术的音乐却与理工科有着不可分割的联系。让我们回想起曾经学过的知识。声音的本质是机械波。音乐的三要素中，响度对应波的振幅，音调对应波的频率，音色虽复杂些，但也逃不过是波形和频率的组合。而一首乐曲，无非是一系列声音在时间维度上的排列罢了。而五线谱，就是描述这一种排列的 DSL(Domain Specific Language, 领域特定语言)。能被沿用至今，它形象直观、信息密度大，一定程度上代表了业界的最佳实践。&lt;/p&gt;
&lt;p&gt;但这东西毕竟还有不小的学习成本，而且在如今的信息时代，五线谱的图形化特征却也带来数字化表示与传播上的不便。那该怎么办呢？改用代码实现便再自然不过了。我们早已熟悉使用代码对全世界建模，区区一个音乐当然更是不在话下。随时间流淌的音符，是顺序结构；有时会有整段的重复，那就加个循环；重复中又会带点变化，一个 if-else 了事。Sonic Pi 使用 Ruby 作为它的宿主语言，利用它灵活、可读性强的优势，封装了一整套用于音乐制作的 API，即使对代码知之甚少的用户也能很快理解它的含义。而对熟悉代码的用户而言，能够编写代码，就意味着强大的可扩展性、无限的可能性。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 710px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2f252303dda4385b8166d542724a069a/7131f/sonic1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.26108374384237%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA60lEQVR42qWSy07DMBBFswyNE7ckNJD4Mc6rbQTiUYoQEgskNvwEn19VlG/wZegf1FkcydLIR2csR2XR+bbqoWwDYVoI6SDmYaSLxkfbasT39g6fXx2yjw3SlIeLcGl0IQknhIPkuiwPl52Ey4Lw7mqsS43xZcD41CMWxPmBwvyScK8U3myN5sagtNPeMbrKCTujsL7WWN12oFWHWTZBWLDwleueVQ0ih8q1SKYIeWX/qJXf6dpvevJm6DwLPQ+D4EKHB63hlhY09qCBV075T87DiBLp9nFmDzNpf+LEHJlfPp/D8f8uc+DC/R+PjvsGuxZzoQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;example&quot;
        title=&quot;&quot;
        src=&quot;/static/2f252303dda4385b8166d542724a069a/7131f/sonic1.png&quot;
        srcset=&quot;/static/2f252303dda4385b8166d542724a069a/2efce/sonic1.png 203w,
/static/2f252303dda4385b8166d542724a069a/1d180/sonic1.png 405w,
/static/2f252303dda4385b8166d542724a069a/7131f/sonic1.png 710w&quot;
        sizes=&quot;(max-width: 710px) 100vw, 710px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;一段演奏旋律的代码，即使不懂含义，也能猜个八九不离十吧&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;以随机性激发灵感&lt;/h2&gt;
&lt;p&gt;Sonic Pi 并不只是一个冷冰冰的代码编辑器而已，相反，它为了让初学者顺利进入音乐的世界，准备了齐全的教程，事无巨细地讲解了各类音乐和代码的基础知识，以及软件使用的技巧。作者鼓励大家多做尝试，他的口头禅是 &lt;em&gt;there are no mistakes, only opportunities&lt;/em&gt; 。&lt;/p&gt;
&lt;p&gt;在教程中，我印象最深刻的是作者花了不小的篇幅讲解在音乐制作过程中随机数的应用。在学会软件的基础操作后，我们除了临摹现成的曲子，如果想要自己创作些新的旋律出来，往往会容易陷入无从下手，没有灵感的局面。这时候随机性就可以派上用场了，我们可以让计算机随机演奏，毫不费力地就可以让计算机帮我们擦出灵感的火花。Sonic Pi 也很贴心地为我们封装了好几种随机函数，免去了我们从零开始自己处理随机数的麻烦。&lt;/p&gt;
&lt;p&gt;可能有人会问，我自己随便乱弹琴，不也能达到类似的效果么？且不说你的个人习惯是不是真的能让你的弹奏那么随机（想想自己乱按键盘的时候，是不是翻来覆去也就那么几个类似的 pattern），就算是有灵感产生了，过段时间以后你还确保自己能记得当时的旋律吗？但计算机的随机数就不一样了，它是一种伪随机数，只要设定相同的随机种子，得到的随机数序列永远是一样的，再也不用担心相同的旋律无法复现的问题。你甚至可以把它分享给朋友，让他们也能听到你的灵光一闪。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/316d9d4086b1642efda7db536dd8354c/5a190/sonic2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.39901477832512%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABJNAAASTQHzl8SnAAACe0lEQVR42o1T227TQBD1n/J5PMELFbRSW5wEN4lAoSVAwbnZTurY6cb3+G4fZrZJaShIjHS03p3ZWc+ZM4rnebBtG+v1GlEUoWkasGVZhu12i9VqBd/3sdvt0LYt0jQF3wmCQJ4LIaQvCEK5KmEYIstzuizgOI4MKIpCJmUfB/HKYD8/wmf8eF3VyNMMISXnc/4ppfNexUDrQ3uvyXXY1ZDuUplAVVX0+1e40q4wHAzQ63XR6agY0pmqXuJDX0OXvi86HfS0Ac4vVCgeZdZ1HcbaQ13WqIoSTd1IVGWFkvbP1pr+TETIpgLpTCCxPcRxgjiikuM4RpIkyCnZo7X4pzGPbM7iDh9PuhidavBXm4MTCpMsG9HiEW3Tyr9hjhi8f+pjW84MnL96i3HvE8TtCtVdjLbmhNnzhFxuSF1jRATeP/qrh4Tl2QLZizNUr+eAnaG2ArRVA4XlwRf45QNQt0f7p5DJyWzdRO/lKW6GI7i+QF0/UKbkJJkDL/9jh9iFYeDNuxOphI3joiFquAKFG7JxXViGCcuy4ExXyEY2VoYFy7SwpHVp8rcpY5bWEhlpL4li+MKDu3aouzQQ+6oU0zIx0SfQb39gOplg8U3HdmTCmMwwn86wIMwI05/6fj+HcO8ReYHUaxxGEPeCuI4klGCv8kPpckr+wkAblYQKSKjzYY7ST6l7x4GsY0VQss3GhU/zyVJJE5rZP5okQSXFYYzoPkAdF3sN/VaGTEg8Kvbld3y+/oLx9VeMb8Yw5gZNC01EXh6BH2O+g62HMi1Q7qfmgCIvENGDSuT6NNQOHNvBmhD6oSS4Jk09BZ+lSQqXGvggteOYise2rPALryF1cOPNt94AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;interface&quot;
        title=&quot;&quot;
        src=&quot;/static/316d9d4086b1642efda7db536dd8354c/5a190/sonic2.png&quot;
        srcset=&quot;/static/316d9d4086b1642efda7db536dd8354c/2efce/sonic2.png 203w,
/static/316d9d4086b1642efda7db536dd8354c/1d180/sonic2.png 405w,
/static/316d9d4086b1642efda7db536dd8354c/5a190/sonic2.png 800w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;Sonic Pi 的界面，有黑白两种模式可选&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;互操作性与工作流&lt;/h2&gt;
&lt;p&gt;Sonic Pi 毕竟不是十全十美的软件，我在使用过程中也遇到一些问题，例如音源种类太少且难以加入新的音源、软件本身也不太稳定等。而且它毕竟是个小众软件，如果想要拿它认真做点东西，也要考虑到和主流软件的互操作性。换句话说，如果我们能把 Sonic Pi 的作品放入功能更加强大的软件中进行二次加工，那像音源或是功能缺失的问题都将不再是问题。&lt;/p&gt;
&lt;p&gt;那么如何做到这一点呢？容易想到利用 MIDI 这一通用的计算机音乐格式。Sonic Pi 本身支持作为 MIDI 的输入或输出设备，但一般连接的都是实体的设备。理论上来说，我们也可以将另一个软件作为 Sonic Pi 的虚拟 MIDI 输出端，但本人对这方面实在是知之甚少，做了些尝试但都不算成功，不同系统下的区别也比较大。但换个思路想， MIDI 本来就是记录音乐的一种格式而已，只要我们知道它的格式定义，就可以把文件写出来，而写 MIDI 文件的方法，想必有现成的轮子可用。果然我找到了 MidiWriterJS 这个开源库，亲测可用。至于具体的用法我就不详细介绍了，有需要的朋友可以留言私下交流。&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;替代品&lt;/h2&gt;
&lt;p&gt;使用代码编写音乐的工具并非只有 Sonic Pi 一个。正如有句名言所说：“任何可以使用 JavaScript 来编写的应用，最终都会由 JavaScript 编写。” 有一个开源库 Tone.js 也可以实现同样的目标。但相比于 Sonic Pi，它就简陋了很多。它更像是 Three.js 之于 WebGL，是对 Web Audio API 的一层简单封装。如果你事先没有对 Web Audio API 的概念有一定了解的话，将会很难上手使用，更不用说没有 GUI 与各种音源、效果了。至于优点嘛，就是无需下载客户端，以及可以更方便地与我上文提到的 MidiWriterJS 互相调用。有进阶编程经验的同学可以尝试一下。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;附本文介绍的各种工具地址：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sonic-pi.net&quot;&gt;Sonic Pi&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/grimmdude/MidiWriterJS&quot;&gt;MidiWriterJS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Tonejs/Tone.js&quot;&gt;Tone.js&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[BotIM - 跑团辅助平台]]></title><description><![CDATA[BotIM 是一个为网络跑团设计的平台，也适用于其他需要在即时聊天应用中使用机器人的场景。 项目使用了腾讯云 IMSDK…]]></description><link>https://maliut.space/p/botim/</link><guid isPermaLink="false">https://maliut.space/p/botim/</guid><pubDate>Sun, 08 Dec 2019 18:48:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;warning&quot;&gt;此项目已被废弃，强烈建议大家使用我后续开发的完全上位替代 [paotuan.github.io] ，详细信息可参考 &lt;a href=&apos;/p/paotuan/&apos;&gt;这篇文章&lt;/a&gt;。&lt;/blockquote&gt;
&lt;p&gt;BotIM 是一个为网络跑团设计的平台，也适用于其他需要在即时聊天应用中使用机器人的场景。&lt;/p&gt;
&lt;p&gt;项目使用了腾讯云 IMSDK 的免费额度，客户端模拟机器人逻辑，优点是完全合法，配置简单，且完全免费，无维护成本。缺点是只适合小规模的使用。关于具体的项目背景与搭建方法，后续会在单独的文章中详细介绍。届时大家都可以搭建自己的跑团平台。&lt;/p&gt;
&lt;p&gt;跑团中的核心功能就是骰子。目前本项目的骰子集成了如下功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;基础的 CoC 骰子规则，并支持骰子指令的四则运算。&lt;del&gt;甚至可以拿它当计算器使用&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;跑团记录自动录制功能。这是因为大家都有记录跑团 log 的需求，复制聊天记录再人工处理太麻烦，而这正好是适合交给程序的事情。&lt;/p&gt;
&lt;p&gt;在录制这个功能上能玩的花样很多。由于我们是程序记录，能够记录到原始的结构化数据，而不是单纯的文本数据，因此我们可以自由选择导出的格式，可以是普通文本，也可以是 HTML + CSS 直接导入 markdown ，还可以是 JSON 格式，以便后续进一步处理。&lt;/p&gt;
&lt;p&gt;也可以对记录的内容进行预处理，例如合并连续消息，选择消息要展示的字段，还可以事先对消息进行重排序，这尤其适用于网团中大家发言顺序混乱的场合。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;感谢 &lt;a href=&quot;https://finzhang.space/&quot;&gt;@Fin&lt;/a&gt; 同学在使用中提出的大量宝贵建议，大家若有建议，也欢迎留言提出。&lt;/p&gt;
&lt;p&gt;未来的优化点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;考虑把控制面板的 UI 优化一下，使用现代的 UI 框架&lt;/li&gt;
&lt;li&gt;聊天界面本身完善，跑团中一些图片素材的快速查阅等&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[树]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/nice-tree/</link><guid isPermaLink="false">https://maliut.space/p/nice-tree/</guid><pubDate>Sun, 20 Oct 2019 00:05:00 GMT</pubDate><content:encoded>&lt;p&gt;又到了一年秋季，上班的路上，总能闻到桂花的独特芬芳。&lt;/p&gt;
&lt;p&gt;我不是一个善于分辨植物的人，看着路边形态各异的树木和鲜花，也叫不出它们的名字。即使这次强行记住了，没过多久也会忘记。也无法通过鸟叫声来区分鸟的品种，虽然它们并不是植物。而偏偏很容易就能分辨出桂花，也仅仅是因为它的香味太过独特了。而闻到桂花香，也为我带来了秋天终于到来了的实感。&lt;/p&gt;
&lt;p&gt;总而言之，桂花就是这么一种独特的存在。但桂花之于我的意义，除了提醒秋天已经到来以外，还有便是唤起了我对一篇快要鸽了四年的文章的回忆。没错，就是现在的这一篇，即使这四年里我未曾落下一笔，但对它的思路和内容却从未模糊半分。&lt;/p&gt;
&lt;p&gt;那还要追溯到四年前的秋天，我来到复旦大学张江校区。从食堂到二教，沿着斜穿草坪的小径，每一天都能闻到一股熟悉的香味——路边那仅此一棵的孤零零的桂花树散发的香味。无需多想便能记起这熟悉感的来源。在效实时，从教室到食堂的路上也同样立着这样一棵桂花树，每个秋天都能闻到这浓郁的花香。虽然当时每天都是匆匆忙忙地经过，从未驻足欣赏过一次，也从未与同学朋友谈论起它，但经由这香味，它早已潜移默化地进入我的记忆深处，成为我回忆里效实不可分割的一部分。&lt;/p&gt;
&lt;p&gt;桂花树毕竟是足够独特，以至于大家都能凭借嗅觉把它们分辨出来。可是大多数的树都默默无闻了许多，无论是在路边站岗还是在公园里错落，它们都几乎无法赢得路人游客的一丝注意力，仿佛只是理所当然地杵在那里罢了。当然这些也有可能是我的错觉，大家都很喜欢看树也说不定。至少对我来说，我对树的审美的觉醒，其实是有一个清晰的节点的。那应该也是在初入效实的某年初夏。从公交车站走到学校，路边是两排整齐的行道树。路早已熟悉到闭着眼睛都不会走错，路边的行道树自然也是熟视无睹。然而就在某一瞬间，也不知道为什么，这些树就突然变得可爱了起来。褐色的躯干，透着稳重的气息，但颜色又不至于深到令人乏味。错落有致的叶子，透过清晨的阳光，仿佛可以看到叶脉中流动着的水分子，翠绿色只能让人想到健康。树叶的绿色与树干的褐色又交织在一起，是顶级艺术家恰到好处的配色，挑不出半点瑕疵。一时间，我的目光完全无法从这些树中移开，腿也无法移动半分。当然后半句是编的，毕竟上学还是要上的。&lt;/p&gt;
&lt;p&gt;自此以后每次走在上学路上，我都会用目光对这些行道树完成一次检阅。随着四季变迁，这些树木的形态和颜色逐渐褪去了我一见钟情时的那么可爱，但仍然有着那一份独属于树的独特美感。它们再也不是我以前从来不会为之投去注意力的平凡生物了。再后来，我将这些树写进了我的周记，虽然我贫乏的文字天赋并不能表现出它们的美感。虽然再优美的文字也不能表现出你亲眼见到它们时所能带来的美感。&lt;/p&gt;
&lt;p&gt;自此以后，我对身边的树们也多多关注了起来。无论是上文说到的桂花树还是行道树，我与它们的结缘都与效实脱不了干系。可说到效实，就不能不说最能代表效实的树，效实的校树，银杏。&lt;/p&gt;
&lt;p&gt;银杏，又称为树中活化石，植物中的大熊猫，是十分濒危的物种。这些都是我小时候在百科全书上看到的。所以当我后来在各地都看到了许多银杏，甚至拿它们当行道树时，我不禁十分疑惑——说好的濒危物种呢？不过好像也没什么不好的，就释然了。至于银杏为什么能成为效实的代表，好像是因为效实的老校区里就种着两棵标志性的银杏。当然这我是无从得见了。我所能拥有的印象，就是现在的校园里那一片郁郁葱葱的银杏林。&lt;/p&gt;
&lt;p&gt;效实的银杏林的格局，是和别处不同的。都是依靠着学生必经之路旁的大草坪，树林之间见缝插针地立着童第周的雕像，当然最近也多了不少别人的。每每大考的时候，雕像前就会多出一些柚子、零食之类的东西，寄托着虔诚的学生希望取得佳绩的质朴心愿，虽然最终都被教务处笑纳。倘肯多花一些钱，就可以在毕业了以后认领其中一棵，在树底下插一块“ xx 届 xx 校友认栽”的小蓝牌子。不仅如此，每当学生毕业，大家都会三三两两自发聚在一起，以这些银杏树为背景留下高中最后的合影。&lt;del&gt;每当校庆时分，又会有一个女主角躺在银杏树的脚下，透过指缝散落的阳光让自己睡着从而穿越到过去。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;后来我去了清华和复旦，在那里我也见到了许多银杏。这两个校园里的银杏是何其相似：在一个巨大的、无比气派的、正对着学校大门的广场上，两排，或是不止两排的银杏树，整整齐齐地，就像接受检阅的士兵一样，一字排开，乖乖地呆在水泥瓷砖地面中特意为它们挖好的坑洞里面。那一瞬间我有点可怜它们。如果它们长在效实园里，它们不必如此刻板、拘谨。效实的银杏林的草坪足够大，足够让它们自由生长，自由发挥。虽然这些完全都是我的主观印象，但我想这确实是效实为我的思想带来的一些烙印。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;银杏是效实园里当之无愧的主角，但&lt;del&gt;就像在一个有很多妹子的氪金手游里面很多人都会找一个不那么热门的角色来主推一样，&lt;/del&gt;我偏爱的还是另一棵树。那棵树，就是一棵普通的平平无奇的树，我相信每个人都无数次地见过它的同类，但我从来叫不出它究竟叫什么名字。可这又有什么关系呢？我喜欢的只是那一株。我为它起好了特别的名字。我称它为「好树」。&lt;/p&gt;
&lt;p&gt;「好树」并不是我脑子一热，随便取的名字。顾名思义，它是一棵很好的树，但也只是普通的好，并没有某一部分好的特别突出，能让我用更有针对性的名字来命名它。确切来说，它就是一棵树最标准的样子，因此它不是那么起眼，但如果你注意到了它，它的颜色，它的体型，它树冠的大小，都是恰到好处，让你感到非常舒服。&lt;/p&gt;
&lt;p&gt;可作为一个理性人，舒服与否只是我的主观感受。有时候我就问自己，世界上的树这么多，凭什么我就认定它是一棵树的标准样式呢？后来我想了想，或许这和 Minecraft 有关。有段时间我疯狂迷恋这款游戏，直到现在我也只是因为开始晕 3D 才冷落了它。而作为一个 Minecraft 玩家，谁会不喜欢一棵树呢？当你两手空空地来到这个世界，没有树你就寸步难行，而哪怕只有一棵树，你就可以用它打造出整个世界。整个世界，用着默认材质包，开着最低特效，被某些人嘲笑玩的什么游戏。但我喜欢看着方形的太阳缓缓升起，金色的阳光撕裂浅蓝色的天空，越过锯齿状的河流，洒落在一片程序生成的橡树林里，在某一棵树下 Steve 举起右手，一个新世界在这里诞生。那些橡树，刨去一切随机要素的影响，本应该是一个模子刻出来的完全一致，但在&lt;del&gt;也许并不那么&lt;/del&gt;精巧的算法安排下，此刻它们错落有致，共同组成了这一片有机、真实的丛林。它们都是好树。&lt;/p&gt;
&lt;p&gt;大抵是每个人年少时，都觉得身边的世界充满了未曾见过的新鲜事物，声色犬马，它们无时不刻地拉扯着你有限的注意力。而只有在长大以后，面对周围一成不变、早已失去了新鲜感的现实，才能慢慢感受到平凡中蕴藏的美好。&lt;/p&gt;
&lt;p&gt;大抵是每个人年轻时，都觉得自己会是花园里那独一无二的一株鲜花，是人们心中毋庸置疑的焦点。而只有在长大以后，才能将自己代入角落里那不太起眼的一棵树，接受自己只是树林里平庸的一份子的现实，但仍有着自己的坚持。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/660181cc9104807b9b3fe4f04965c5e1/7bf67/tree.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 133.4975369458128%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAMEBQYC/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAbZFUlNSLFyypijknkf/xAAaEAACAwEBAAAAAAAAAAAAAAABAgADERIT/9oACAEBAAEFAvZYrHqXvhS0oUftLH6gzQz49awCCf/EABcRAAMBAAAAAAAAAAAAAAAAAAAQERL/2gAIAQMBAT8Bqhk//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAHRAAAQQCAwAAAAAAAAAAAAAAAAEQETECEiFCUf/aAAgBAQAGPwIuW1k4Ey9NlLOxT//EAB0QAAICAgMBAAAAAAAAAAAAAAERACExQRBRYYH/2gAIAQEAAT8hPaT8iUvZXwVCu4Cksa7gMEjhrFuh5Gevcx1OKEK1CKn/2gAMAwEAAgADAAAAEIMjc//EABkRAAIDAQAAAAAAAAAAAAAAAAABEBEhcf/aAAgBAwEBPxB0caZ0f//EABcRAQADAAAAAAAAAAAAAAAAAAAQESH/2gAIAQIBAT8QjVv/xAAdEAEAAwADAAMAAAAAAAAAAAABABEhMVFxQZHw/9oACAEBAAE/EB4IeAjIgrS7gZLMlAl5Gcidq09Qy5Oj8SgCLAGfncdxoWcSrH0AKEBdH7YtQ8XAEGif/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;好树&quot;
        title=&quot;&quot;
        src=&quot;/static/660181cc9104807b9b3fe4f04965c5e1/7bf67/tree.jpg&quot;
        srcset=&quot;/static/660181cc9104807b9b3fe4f04965c5e1/efcb5/tree.jpg 203w,
/static/660181cc9104807b9b3fe4f04965c5e1/02e66/tree.jpg 405w,
/static/660181cc9104807b9b3fe4f04965c5e1/7bf67/tree.jpg 680w&quot;
        sizes=&quot;(max-width: 680px) 100vw, 680px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;知道你们好奇是哪棵树，但请不要对我充满强烈个人主观的审美体验抱有过高的期望&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;那棵「好树」，大学毕业时我又回去看了它一次，今已亭亭如盖矣。我见证了它这几年的成长，正如同它见证了我的成长一样。褪去了初见时的青涩和稀疏，如今的它更加茂密且翠绿了，它依然是那棵「好树」。&lt;/p&gt;
&lt;p&gt;它依然是一棵好树。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[4.22.23.06]]></title><link>https://maliut.space/p/4-22-23-06/</link><guid isPermaLink="false">https://maliut.space/p/4-22-23-06/</guid><pubDate>Mon, 22 Apr 2019 23:06:00 GMT</pubDate><content:encoded>U2FsdGVkX1+29Rq8szhWXZC9cNEE939roIKNj1Nup7qKmLpgppWbJzZICk9kq4GDvvRViciE2nRAa32nLNRH37MVgUVwYINu7m8tGxI8qQHqk+tEqFxysRXuuAR/kmgLtonmup4S+CHEu87j2TMRebI15iMYUV8HAltdmwFd0Eq8DwumhrDQxyE4GNZz15SASukuTDx1kkXvlKwSfoIP2FKwLfBeiXzEtfo3kUAS/rYDfgxOI2z/qBc+wvatpCrMFi6f/Dec557x3101phqSpFOxw5pb9hdioEV8d5k4ZKianLDBakBFtknXCOp3ch2JcvCMvdRLzejbmg1kswJN/dI7c95ET+N4WwKiNN4y7GbFqZq/hVcEBETtv7RR6feh3yLanzW3LC4FoVvysePI9XwewI8nFvY7WN9/9KSsvBNd0TY7t7QL7grX/Z/0pOkq2ArTU+AyOvrlF9xb6AB+lP0Yl4zvvSwFMc2fxYG62rXUZo3vA7WiXc7HzNOPbTxmeiA1s0uofEnHD4mG/nPbTgWO/bPV2UxNFsg8oBofYOnUHXris7ilnlN3YWzJtXLflzbu0z2jrjOpYAMNLshn8sHR+LQqwe55sgEpQg7XWG8L2CyQBU5jTHdx0KkSqtRyIJDcLjAQcBWKqEjBHmGdZM9IyryrWlhfvuAlKw98rhiatMaikR5oVuLNUfMCEuq8fureJm/Rul2eY6vq46iBu0+Yu6zVgKTCEp7TOPglaNKsYCIpEU9BaSx3hZxxR865ny3b20/myaFxcQDwuzwFxDoSd1LxxaoPKtlv1aX/TH711mvsKKgZWzNhEny0rlSc4tH/BzeltLRN+TmynnrQppyVQk/2j8gJiMqhNTc/RP4rQL2cBbJoNnkcgk3UuCy0D+ET8B7gl98N0oJWpq72//NwJ3hyVTJlQ32idlgBN4oohvALGBsvBheHOueaRRfzNmzkNsg2hG01kTopxiHHllKz0E+sTJCSW8Jn+epL4zZ6FbhYgF+VtpXsFaadZsFEo51RHPnPc0jHWorNx84Yvp4NrJQl16+1qM+UjlVpsyFRbvkiUVRxhczOrDTQbiqrdeDhRPntYK/XtunAnzj9Puh9eOz2gMtrTv2GVgiDnWFNZTPcZwNC8zbKikCk66vO2xQeLy+VTnNMy4nzRNymE7ohScvY4n+d6zjIwjb5bD/cWLOCD+axVA+Kw5gqVBHv0J0XMl/oljkuvD3srUwVhGasHzSmYq34REQAXoue4cBZJs9jqz5bd2jJczV6b/lteYibSuU2kcc+b1IzBF4qcfoisMRVIAv1cu791iTBU42/QvQ+ygJ1vJBABa2LJalLz79DhPrpXTeLZB6u5cEMG5m2GeUJ7l/1Rk7K+bX4/U8cc+VHZbAALsQfyRVbNEcGOH+4mA9nAN0AzJQUX3NWhs8UAOHRiL4uvlp3w4fLMj78yP+McTfWaUfhukvOn+xYirlfzfqlckCM29Rq+zofToFusVsN2lf1SXG1VqZLXfP+0j06qW/Wnj3lTrctoZtEqW6EzqqSdRDAlzliXaVlIor5TrJlHkaB+q4j5mOVsUXbNPuFUKRd1B4qnEAxB6gFeg0+o6t3Ukob5Z5aquQEKtM4R+yOZMT3kxs05f+XTg2lD+sErvfMn80vtouQURDlwkz/EFexiCnl1WZQhd233qaty8TXlHOGxPOPamEdCmaEqeYe0pgGkDtJ/AUynqbEw5eMFYmhuzjZvA/GuKFePInsQC4+eoYQQMih/eFXbAraabLIPY+/c7HhqPCe8+sjQ0/UOdzovJ07KrrTGmga8eH/34o9O84dEu2tx8YLRXbM66mZKXTMjQM9rbYjJVhKRKBsfSZA0CMSdgDBGad1gUZj2ZFuRh5wX1UqcI8HCp9fDmoQGgIlVJLCXOxKxuZQFXF3wzI3DfxZFfTr6afYU8aQxiKdfVcodiPnFc9VLD/oH1ZFs0q8k83zpWZyshh43nhC6w5EakoQLwnLW6OJf3WtpL8w2XcjqSyCIxVLWmq06N6I/o8yjfdW9lTbEVz9VaT3YoV+t5tLjNNYUzcnqddyZbv58I5c4n80cnbvvMsfzlBkY1IEns8aAi6sHm8vMBynYm4u5orn5ayo+KgduC38J7+mF8WdawFvmIz6+Xhi1SGjmd+78EmEyXk+GzvVVHIj3eyLwnuKekIfqytSf70GcpLfSAndR8qXbStheCudRp0s3qF2PLlAyu5InllFDvn5PAmEtsRn6C1gQZNzsjyZ+G6rFImuuRYISndfK+QXgwxjrZ+uCTuN3u8984+3QvGxM90KNOIu3mFgfCI4w5kq0rR/h+bh/m94p47gX4PmwfqGSXk9V6AZqlYX+C9ogeKHkpe7sJB6BVrFwj9kincwKgEEMvlQbL3bXPiRamTVwjFW4/4tBmJVBtOlDlRtHNp5yhywmI1jkfpPEjMtSLvMFyQPjVp+agMwBeNu4HwfAA0Xq/R2PbpVVRf65F6LrmehZd4gKpRCFMmJ1ExccTfq64Dd1KIzTEG1xo222XK/q5sadotzTTrDF9Gl13jA596awJVkjZSp5wWeeXlpZE7LgIryWs9u2uKmRhhXNYq8trCpUUTbLblOweE3kptLc3CFKo8HFZtWG9p3QXpc1THoaYjFZUyc10WB5v5WOTsp+kfWzDmv9MFRZvyCJ894pB/VmbByLmnaSxg8Bh2SPdYz6xdogVa87z3EUWMHxlLX4WbiKflNF7Ng4kRkmVtrJWhG77AOoTQTbArcQ14vnicYCai9um8c/w2Bq6U7AyIR9LPvQFsh7eJjyH8cKxo9wclGmi10v6wUz78XgDls9pZ1dGKKgqO0Uxw9afd6T5N9yrOqx4Oqx33o86hzNwb6D1/l1BPLWhqCbmRo8l8UywRiW9fOQdc3WeriJ4JISxQYQ4NClpaUxufLOtUEKGR4FRDzhmNoObmfO4kGlyIZ1dNxZ4VJPkUtY2XTDt5BeCmmrsbduhowFe0aTgv0x2bEFTeeXAlgDI4yalxd9/pAYtlnVa8tcYcXd+mq5wEGz53MOkL8qHL3qjVip1/Hd4WumhzZ4JZD2dWV1p6yuaY0kWDwWshWJSV5C4ghZ7RMQo7CJi13Z14fuqsgSZywwTOeC+fekhXRqhQiHSEOnQF3ZZFRVdrcFWSScKebFtO4qW8Xf1nDNyRpzxSBmwu79yyP0x91PpITjNLqpDD/XNStglYR+ouWs+bHYBrJ0xbkUyAUt/wjrYqJvYjsoDkgc9Z2OUKfKwHpB+3sQ6oeVAfVRwCeXQN34NKKT87te8+9mIXyEeJ31aSrcpAFuHESOpnFxLIFuWs1QBf95GjVNfheUXmO/UTba8Cqz4h3jWQwqypxdBcLBvVCVkD4hPY2173ZhbO6btbFfC2dKqy5HKlicBe+J4LplVQSwD5GbHy0Mmdxq+/WGaamTVfn4ZFTOFCeQ05KtwSb4iYXEOPtuJNtp5ZepN2CrvH63riJn2VNHSlISvELneYhCt5biG67JGszrAvUOANEcrdqfilr5wsN9PSanp1FpNxDti54bpwdnHj8Z+JqObWQiPdpZOqz1zQ/XnIgRJxqAG9+pydBNNav9D8o0qE6vzH7rZbIEt3lG7u334vAKqMi1gp8bsvMfmblfYP24/oYLjuuB6sH3Q5ismUUv3iJqs5bUjw7pyxS/zMHKZOqjxnFWgY8FCOOuGSAcG09m0TKo+anmF659oVeldBdsLWCycGWnWqzgJ48LrY3xIDgtT590Qyx2N/3NWf2TavvHfmek/IwK34t9g8fGj8ynsRjMYeiWWUC3qZYFyN+8fohKGHqIlBXDGtt8qB4QuQ/t/o7NsJwTPbxx3/lFN93syMhGRqsAyYTYchepwSyznSRLp1xGObb38ORnCuOMkLcdyoxtyBaqCt7bxvFkxkdbfJi0gCBcu+VFzJKHHKjTND78TsKGQAvPGfyQHYUceNwK+iQ0fos3zagWQ/lfrVN99603iqVALLVG8+/4VtBWg7ftIGMNXl8/Yy7JgIqTkTEz7lh67sjqMdOTu6uyN7tmyT3/DRVcI3bpNuAFtb7VAnkWZtuC9RBfzZYs/oXu6YMdicByJQSl+fpE+Zbb2viRGVJChNkbLDJjD8xaMoP0tU6l3CILUJ0w/7gF7Kkb+yFhCNLQd7+OMJpDcKvOxR21U4QwUrGxkLwR+7WPR/d80DKC4rCBJrIvPWl+d8GIVWvjbbtTmLCAN4HTx8RBsNMYMWb1n9G1EshpfwntPsKQ+pK+JGM6qOilvwzmi7Z4ayZX4mAf/d2iCwZrHtuYvUz3PEZLc1jLZGPDpiCblK3fYMyXUUy417czn7bYIfPsigWFj8Uz9qlZiSvnmJY6iYo2/G47J5KSW7oFulg8xN+FB439ZA1kuUBSdv/Qub/xYMRdynwWo0Q/bgI9PAhvDCIbbFUy4w+vgyjymVMyv77sRvo6C0S2JbblBgW+NfxvDg3k9FbN0TfJDzdblpbASLmBMrQK8AML+HaC2g+vLjDzzmy6YY+cWdhZ3mlISwWju0JAzgzzfBzFVmRSYJWMMHXnzujiHU7qz3c5zjlwHV32XryaT7nN/Cz2sJ5weJg9GgUUp/vwNSmWBxVNambPwgrlPsut2KSu4yPgwjA7v0dnBjy8wbUbaZfzljvUwo+/7Eh/2KtiESFwQbmj2h41Qnp9mCUPatxjTJjaucQWSOEaMwrO9wtcAQLaG3jn29pWfokz9YJf84Vijti1hysNYQs+Zk+fFXkOiJTTO3nbXn+ky/WbC3pv6x8LpniuUcm2dFx++ZaXL+yzPTzjhJygPu3lnJBzXcPE4KFe+3ZxZOOAAoc5VXda5ke9oNC97DJUk8Bvkfp0xXOmYOcj/sCDLLwv91LskeTGAXd4OE39FAdpCM9/xFdPe4kwtIEe9Ozy2L+rUjkDbZnxT6kbuc9urggAYDFLd3NaMo2/PzoBOueBz4+cUtAJ1QKaUD9GGnezjKVwpv48yJRI8mfuYE0gmFuzJkK7tQ0HceV6XkSkTAt+fdBECoAUcY+qWxx7mqAI6RSwp9cVg67fZrGymBI3KC26oA5lWgCehvI5WvbwuqA2/k1zS9IriBf9m/SUijFxMb0fQPJBVib5cEU8KK6iIgxAhT88TjYBrNaNNJczIAOu/uqQ7MIJXOmOkkYdm17KhdBWVNSHmJuWtB4BoSymFJ3CS8koWtF5kGrGoZ934C0j7+1NJ6DdeUy1gNyDqCbc4mDkdLnxaPVB2gD6Af1wW6sPEJvsIiL7uYay+WkUS00Yvba7jLPjACyhNEn+gMfIXg0kms9KBV228Eg2MGDkzHTKijq1UdksNjRiwlWVyumMeDQLYTprSl3cXFhj6HY6y9VPNP25Gp8iQkIbk3icIBuxglSACxVEeoyLVkRyck2k7rH4+2IKDnw21WQ8BcL5K/O+3YbBgg9rk6jfs7i5nliETY51auAAsPrR9xqWckYZGmAxHJcPfiexQZkAu2HLKb3SaDR9fYst8tSvU/Cu3oSKkX5mtakdWkOE528xfUPLNb4UkGp8QaMdz57s25h9qK1Rhkw2NUnb0Y4kia81e5sr7MZA8dxAQq/rCXpGgRRiPiGQixhJjagMIUJezGiQ1/CHxfQs3W64N5zETdzBqrNvfsIzhvKtadd9Uw4TgTL4ppX8PdXO9OYBL9GzoCPpukCZNgZnGZL8ueyOXffFJXXCoE1KVOmSAEV0DbhtF44liUILQZlGKZBby6RSjdSSSQYjW8zsMWoyHJYNhKfMrvydbgzYc36etllMXbpcZ1ofK0itGfCOj1RAGH5V3xPZ9Kr+Cd/MmDZlioH+dxCQfSX5EQ8P9bB4AgSvQHJyD0pMZdNhpSMxmqRe3Yi2WfAm7UFeB/tI3813eL17VSwlMCS3LG0UqnpcQ27FuU1MPKRK3vRpdIIDRy8ed1ObtrAG0irDFK7gGMCeOXetlDRUUNtNO7b5xby1WMYrq8+rhtLaTWzw5ISyd1sJIOPTCoO5+uEhbI9vobjfeVgQN4G1LSNbp3LFXF9nLCT+dwMt9ZXPIAtNqCcekmUzPKHWypFqFZ8hCygmMfATi8KdfS6T4DvX0W2hR0xnIJHAD0U8O7vjHf/FTqKLBiweaIaDQnnB1P4UpsHW6NwzaypAC6tk9f2c0k7YL+zM5Qx9HssO/17Kb28sITOCfdLCiXnErd0FzawZUnslabo62fsaCaGe6eA68hmozbHqMjZmON8jZTmOX6tTPAJB4u45w/MuuWqDSE4OuRtHbOcWUxc0I+gEbJkI1kBFKtmvTPAdUzmLWMRUaJBproQyzdGPdo92RApIVj/hnWzrvMxmZTocCYpuPw858J5AY07mlQL8MNsqn+R30OVKTSl/a3Uy6Rl44s2+m+CucuYU6wpEtSEUIOCKflLXICa3f9CHZynQjUFQfB/yjpwKUUK+vQxQNLRXIZH+yom3W1c5enf0i37CRXCrjf2OR1coZEV1jJjB4wGx9NlosqE5GMD24euhEjD7bN0c22qvm2JIOwcT3/XiCbU6nFFs5Rw+ir4HKHtTdGLMH8QksWa1ND0nyeHeMJN7ua9yB8pkMatSIZCKpujE7KTUHS/IIf0u2I34bY16+8NYitptFyIn7yPNy2J1IUgh00XAbba5iEVI+4JxoOIz7ImjsgfythS8CSZfXmdFQhhji/rbrubwCt484kpvN3YF/H9vraYIDrsy/SxUveq1iIIrDkZf+2Vy/UrEBZQYoSSt9CfzzpCItVkwC0HFNFXO1U/qO9LGM+OAHyPITT5AHzZpXUmdM2zPUjGntuYLZbLQSrhoID8fbRcsDTAatSUNI9IVp2PHlGjmY6XDKV4BjSvhmc7ytRpPFSoWLB/AQMSiXZ09kRU1UA7Aq91PX0rNQ7Xd6HKo0wl8SX5xVhTKlXECfW14Vi5m4XE0k4F5Lq47BkRgrjIhR2Ls5hDBB5rQ4m/oPww6QVVgqfzB57pUXtIaHdFa5FqcRyrv/g7YcybDb/iXiTXX0C1VNgLrOT0Muz/+27gJ3vxu8xwZNkmctUPh8/CwC3cEARIE0BgWkaUei2pEWDjKFSBMt7VE0rkwBsPvFNIxL1kpU/n7k4hFk6q6V3rk/uhbndhJSvVpgN9cBnEvlmcYd8dwUoTyXuJoafi+a/SlVTUcxu/XU1NOKzajiykS1S091S7ig1viwMNacYdHLbKpRKwrghPulyZC87D7U45ZsVmLzztTmd3NURxP6hlhyeuwySw9QShm+kOF1DdnuFXNVEuhy/3oz2XFfs9Im11FM2vH2UFmVgZ/A7fkytyzE+JlvPG8D2P+ZXxkeohZEipCxUHsUyZL9dCpZZi8DVjXwm/zcicEMlWzczSxQiR9Xa2SEC+M9iWPHtsOLsgnHX74PVS1qyBQh5+rFpg+BkL2kgEDqO9jFQu9XnO6P4EwWgdP5e9mLoLtMMcY74N73/d1zqS31Oha9Ab9ARcUMPP3sFyVYU7YAW5pdEZSJxs/WSTET38UK/Y5EkTYQ70OwhkC+X0YF66AvUmXzrxDwR+cA+p53O5Tp97s0YyTjKElmiFB2uUBQ8xG1jM1f8GkTDEsk7FYCO0fiYp/YdQlxUHqFALN3xcfwJN6iw+1q2SAGzfoO+DpzG2a+5ojVSV4lMYgZPeKGHiY7oHYQact1TIWa2sWsBoRWB47xZgfGL8ZbTD1ajFdXkDs7xP+ebYes1QonLIKjpDUkYDG4laZKy4mqM0/0XpNftKNlbB0eik03VnUylmtda2NDvkNBlxRFM7DTzrYrvR2go5NvKC0ZFPFWI3CWe5mKeDWt0uUT93BQP3nzwafBKN1/pqzbjCY0s4slB4CIDHnBZu8Qhb6xNbjjrNCMqDACSza+972nIXtAtt78z3aDyGraN8zO48NWCYek0KroerlQmEhIYZdFi+/4kORSB0EERbYYnIl6eOKwmKMQwget1wmulvslghp/Zyy6fb98RhdraexeHiwai4QnLmOYakf1JuXbAb5nJKWe8AE6kQSq9fU2DFygh/THad58n7DCiJ6EhJouqVQ05IFr0pSrZ4CqB6epLC97+CEBBKcI6L1tu0ULT4q2UYSxuEahJqf4Ho4kBcNB+4dlGQ9Q12lMkg26uzaPBXKV8tOPZWxZfTY5zbvdNfuqw4hyTk+tOeGwQkUZ4lmb+Z/4CCiXHiZOkYECZikbBxC8jJAuhIYWGrneED4QafMesAFn4p06m65n0ySLOPd8BAGNZj/jO+SMFkoqMlfr9It8NfurwC5jGaAjgCsfEuB/WK1iKrEnJGNoznTu/fQh1detuKYF+Jb5xpZuPOpYLShD+xx+cpPklNLGpmYEK1nllW8dCgZwVvS4OWlBmX8Xd3sA8YyYq7Wefd1iyH0GhsQbxUx83zalp25vzSnkgjr+VWps8zCb1GuXBV3Go35ozA9zu6dfbzA8CsIUl4CiHuZEwmuPWZgAq47UHwFxOj2kdiTcyzS7plFRH09qs1PaN+Cut4/wjA4aXtNyIMjR8WNLh1Uhvj9dlOCzKAJziHMY0ryGFK23NI8jG3197CvC+LJ2YEHbz+ZH4fHNT00QO5/Kxvdzve//KlljPjlj+fIxRKW+sdJQS1TL3M8GxGTYrDCdt7FAhRjQFDFeMXeumY8a8cxw6cncT571tzOfMcAI2i9caWR42brRVOWwgx6Qp0syuvPKzreYz8ar3AU6xHk5B5cjD4Mn3uFOwUUKveIvV/sDbqxOfMM742RleDREzlgZSUFEI1qBCm3Zt5qcwcxVj7tjVcJZGQ6SXBQG47rejzfN2bj5hBEqrm+39l9f982c1i/VamhwgdUMm9MGl0jalx3fy3ToS7tp0bpwkL6e16KP7d7mmmqDGN2f02w3KU+KysgmUAIU1AkxeLB1gdSkEEvrxvp4g2Awl1wYg/ogOI3OJ/LNaHv3OXKteL70lf2QCaDqW0XqEVj1YB6UHWAkTjD7r3CH36IFQ0J08BU5nAOmnkITJbhadePmYn/gbmdhi/Zal4MbwmsTyLlIcv//R0PljJPKsx6DaZ/JETtCYlxswFJoBxMK2mK+r+nDDJo1pMQnjgxTobnqt1JG4nJTuAq78EGRNd71VPq0Mk44XWPlOWG6cm1Iu1HWyAlUvx04WAC9QqNcsyxaH53maEoPifCy7nnH1Y0+6w4MPg1C11j/Dpau+/n17mV1zhDvQT0bZbvdDUOYomA9a51I74+QvBNCf5x5nms/w5wvZEFzTRIR8dax01yJnzzs3X5gIc1eOX0MyEqGWWGcuhkjdf/aQSVVkCXjzEjVW2OKrPv1PXAvKqm3y+sy+c8yqEiRBXeN7GHubFVqirXTknQyeioyKp+9W7dwGt8CKXVsLsK21Ps+2mgfcxxLP/p+aaZp6DPZdsWIJVOSsrfP9Fns8RQyrjw97meqPT+yRXPzKDxwhzLD+jWfkwiG02ZUqaBSCmXxX8xezgHi9dEBHXW+7O8Ei5FE8Iw1NO0IPGYvuFkX8gCBjIfEv9RDnao1xquvja83wDVKgw5hxagaHWe+SOLCaL6wAokVL8ux88DJnjcESIf31Be6DKYpx996Xyae4fs9PR2yqohVslzlWZLERt7lKGP2QJPH6Zgyri+TuVx51BKpY22b6I0EDxq6VDC1AcM05afxO8Mkgz2WA09IwRkX8sO777GNfR0uh4mGGGUD0yTNU0KTHT6J/z4BprMpvZ8ls1Mxudg4fIgKE24gRvuvVValVG43BOLcut2az9UIX2TgWYW+WenstpmsJt2aV23+gQ/SWygHdwssOWSJ6ktOWdEA9hu9OXvfuUEOZi5IuZUKYLhjUbhbbaRjR5DkcFYK7jYMBtl9s4KD2mO5UQgWwu5gRezqwh/47Y0t4a+B0IQ7IzoWL6zW5vMOa8CjOl2lR8qlyIF3mI7E39CsUMtkbaGaM+73X86ZIEFNf6kw/uTP1k3ZGHnGVTb9nwLCADEJ5lNd2iuhoq98gj2oG7MQAui0mcRZn6ywnIxoZS4c8GqUPBVEHQJIQNEViTGYf7OBwSmbgdrJcmhA1Vn7g7nb7Kpyvz8z9aqMpbuJdqiMI8gsh+gsgRS0nkco1vH6g8NZqwdR0G2IjyK8a3+fjZGqV/x06YwMEvDfWMlCg0FBKYdHc/RpD1eEqr33sd+1kNqCP0usEfn5bIMtMmTVj48EG+LDGuSWDPaQ0B/LOgWGxyJK1M0oYV17irEc80aPKxGhN/fQMl65ZQz8hPMAjpoLPdgS0PpjpmGSSiwXPFgtPTShr9az8UvAvTaDC9g9VPc0zs8LxpWHRCa04us80D6bDe5uuPvzl+sFI9TxWSbiOnwAMxKlhJ1iypWfn6d9jqoP9QZ8oZjL0kU40RKSyvIi905Mc+BjYI/OvOW0nTKMOljHyvF+pOdl5rNipQWFQGhiuGZBKlwE182H6ca3PKFbUiWJ7MXTy5mLvLHmaY8009doRDlJDMSs2WXlNyRFMR6ldL8TTuxUTBXM/yGs/w+wzCPop327Cd0DRTigMcipUYwDpAqj57YYF2FhBMxCRU792CRnyhKuriffLJfMwVWl8fLRrZaW6wf2edZ88YryMIas2g2t3PXEHjSNLcfQ2e97pvlKONcfuBEKgicYqRBPLzdioMl8A3AaJM6bTkfvjNLXRiQsTLtHXuNW8+Vv5y8BZP85E1R2VvB3yKpqXkSyzMRaNzXVsehKb1TxewFtT2xrT34V26+ujkegWoUeUgWr0JWT9/chEimwzvPyijVrdotrFLwvQ5TWBQoClloBVdph1TxLObWEEHOGvYa9rzse0J00ZqG8Md+AAECZBX+crHhzP4xCOKbtx3ZuMQMxyxcVpLXFr3qfv0C2ne9moC/bu8zy4euAf5jen7vicC5Qx6qgIk3bOw7GYUg6Ed4uCMFg3fbd9klKMA+ErokLnczmFPCHXoZcuuM+cuNUgvOYhzrphG0De1XN2zh3LcH8nBt6d48zg9abM6x+39Y5WY0CfhnWy/UYqAIoaBaOErhft/GtGhyunJupdeBvCYNTHN1YSUX0j3E0SLvyFjPAzzu+UVpq7bnwuasZcs2hXA8jZLj5mxOeVVATXaB500kHjM6hFaz2KeWOg/mWvILUJ19vsaiOQxR2nJptOp5HVAOs6v7+ZEDwuuEo4+c/pfoJjcDEte30W8pfXfLHxp6UEvmVhgJYdcvoB5jfpB9D25VozdgEXYmVj4lQzgAd2pERRc3KJbUhRury10vkUU+rZwxFaKcyCRMzIp90RmQf5vG/Ym43aQE7KnpLBWHIr7bXLG0i2KFpycIKiNpKClD1q2K8joecZrAXIrLpFLajPdZAJWErDdD5QOZMId6Tk0v7Q4EBYhVDjtUlSZWP74zYPATBHVF26bd4MRzN+k5jSrzA9Tm9krIlUNKh68EQbNYERGY17W7dhw0zWfMDbcUNi/FUDwxp5/K+ERBRLwo4t/eoP1Z80B2psBr/3xYWIykCzqnKwnQ0+cJ5U2Kf+crdlvr0pjRyXKJLfU4yCDZdCl1WhIKl1nzjT3QG6EzgbChYAuOT6gfOS4FTj8xRBVSyfMI545TVNg9O3dBjxEIEH+AHtvvt19e6IA6ifm6P1Ydy97rlN2E+3ZND3pIf9yTfqo4B0XxSAUvBbtrS9p9XJCfYMHTvm/ys5GExp+z0j+kfN5AMCCrcxE49d70/7dT363qenUCdhKWwrTxWQPK6qw/a59J3lkfpDrIa8apjALm32nJYModdmejVKxqNnZI2RYXv+l21cUSeiq0GeSRZoWbBw80OhauUTPQw7XI/ONIH/jVVLvUYxA00OeSuZZtBzAakTnboRZoBBgOJOpYF0gXHNr+XPrO3XtJGreX6xoc9VuzUEuCA3WhqRXYsmSz2PmW1ZWHGUzLbJ+vWFwXbydsUsIDOs+lGJIxr9DEpnmRQE82TkMvudiDXAV3E5IbGDUclMlzmqhZtw7u9Q3XIsGGSfNmIeSJXFC2Yy1InuK+qU+3cNwukzN1qLrq5/gZS+lbGaFBVb6Manw/taLlp8aLnq4eg3RSiLDqwPo16isBHnt3l06QQCsbHWyC7heJ67xa1p0zpERn84dyThx/+Sb8jT9XnRmUt8bcPUiNEYOEZy9Di6ZgAxmP38eCn6rlr1AHgWa3MiUMwOYnSsQGbwb5honzhWalWVC7IMz5zYaVqTJ9sFAlSrQ88EAB8sfZLsXVoNE8V0pXnTd8i7i5wRANQI/k/+Otify9ZR9GlxR7Jl2mY0obkCuw6W3s+dyxDHQTjk75Tr2Eym06u7rzI/lj4GUL5oebdLbl3Wn1VuuTe6GXSFYlDW5SSV3wD+Kn5VW8yw7F2PCOySZAY1Q+nmInJP7jlS1OUENJ8+BvAB8JlRr4/birX5g6oP7aImQOMZw4LmpqXRn3a/TJAiTkry6bbvo7NtmrlEnzRPkxvuhe+JLESS+FhIz4g8UZRZZXPcLI6Ogm4XRBjLvdKXpDouu55/OCaNnpLFXlLY2YW0ci3b0b6p82kWEH1+yDq+jcyiQ0fa46xLO0RADwcaPbSRoFwP/5l8VdYGTfEcppONawBYVYLftG8GqVj6pzlK1jfyoidoWfvn2NzhQsY4EJa8DLHukfxxyC3EqpFdxh0rGnz/l9B7tLuwj46GctM4AT+jyk2WdqpraqzCUcyiOkTZf/y0xUKD+SKNIPYrUzNx54xzyXvyf7L79lIYqygW1lBVGaESyd6LBtOMIdwvQLi2R11sbZcEbudnGv0Q6w2NoTiQAM+ZsphkNgLVU/1/lWRwATl5UFW65OG7CrFxgJrLu5dMj3hkmvlLrBCwdRWbbNj1fx6iAV4bha35JyyDjHl3ShBahqx5dZ5ZBRIwt1JezEpochg0u9EvwJaUbJOzYkUFI4tddD8SXXUsxdkPWWCHVuy5easmmldeNDMzZycN21d5uOBZURxf21g3VhRdlq065JqAnKeHjvkYy12WENc97CLdeF9cKIchqxud91ILi6pQfznB68oGsEzBsxBRIQQ4k8QiJ5YfdjnFe2eJoKKK8cXJnVvGpa7ch8N/TcoGmaY0vCUsZ+Db4T2kDcxHRe5oMGBrJaoAhCFihpWzg561EwyUwhcWGJhYbeqfQAIEU/pxVo1iRVjywbT/UBhuxDabEdwMYsMSz7iP25piRmoMiyIB7AaHAz+DPp17JuwSZQw==</content:encoded></item><item><title><![CDATA[第三方收发微信消息主流技术小结]]></title><description><![CDATA[一篇水文。最近因为需要去了解了一下如何通过第三方技术收发微信的消息，以至于实现一些更复杂的功能。由于微信实际上并没有开放这方面的 API…]]></description><link>https://maliut.space/p/wechat-crack/</link><guid isPermaLink="false">https://maliut.space/p/wechat-crack/</guid><pubDate>Sun, 03 Mar 2019 21:33:36 GMT</pubDate><content:encoded>&lt;p&gt;一篇水文。最近因为需要去了解了一下如何通过第三方技术收发微信的消息，以至于实现一些更复杂的功能。由于微信实际上并没有开放这方面的 API ，所以通俗来讲，就是研究了下如何破解微信的体系。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;大体的思路也比较好理解，就是通过微信的各个端入手来模拟。主流的做法大概有以下几种：&lt;/p&gt;
&lt;h2&gt;Android 端 Hook&lt;/h2&gt;
&lt;p&gt;原理： Android 中有著名的 Xposed 框架，可以 Hook 任何 App 的方法，在调用前后插入自己的逻辑。&lt;/p&gt;
&lt;p&gt;对于收发微信消息来说，收消息相对比较简单，因为可以通过 Hook 数据库插入的方法来实现。而微信的数据库使用 wcdb ，表结构为了稳定也不会轻易变化。也就是说，只要能找到数据库插入的操作，稳定的 Hook 方式便可以一直存在。但是 Hook 发消息就没那么容易了。发消息是一组复杂的方法调用，当涉及到消息类型为图片、语音等复杂类型时还会更为复杂。找到发消息的方法已经是难事了，更何况混淆过后的代码还和天书一样，没有十足的耐心根本找不到线索。更甚，混淆后的方法名每个版本都不一样，可能你辛辛苦苦破解了一个版本，到了下一个版本，哪怕源码做了一丁点改动，你也要花费很大的工作量去适配。&lt;/p&gt;
&lt;p&gt;从市面上来看，使用 Xposed 破解微信的主要用途还是在于消息防撤回和一些 UI 上的定制化，对于发消息的需求并不算多。另一方面，针对发消息的 Hook 开源代码也比较少，且不太成熟，有些人在兜售成熟的解决方案，尚不知道是否有用。尝试了一些开源代码以后，我找到个 &lt;a href=&quot;https://github.com/Blankeer/WechatBotXposed&quot;&gt;WechatBotXposed&lt;/a&gt;（基于微信 6.6.6 版本） 还是比较靠谱的。&lt;/p&gt;
&lt;p&gt;由于使用 Xposed 需要 Root 手机比较麻烦，这里也提供了一个尝试代价小的方案可供试玩：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;手机安装 &lt;a href=&quot;https://vxposed.com/&quot;&gt;VirtualXposed&lt;/a&gt; 。&lt;/li&gt;
&lt;li&gt;VirtualXposed 安装微信 6.6.6 版本（旧版本的微信可以通过豌豆荚安装，这不是广告）&lt;/li&gt;
&lt;li&gt;VirtualXposed 安装上述插件&lt;/li&gt;
&lt;li&gt;启用插件并重启 VirtualXposed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/379b5adaa0b81c21c7de80b245fe2520/wechat1.gif&quot; alt=&quot;Demo&quot;&gt;&lt;/p&gt;
&lt;p&gt;如果想进一步开发，也可以看&lt;a href=&quot;https://blankeer.github.io/2018/05/09/%E5%88%A9%E7%94%A8-Xposed-%E5%BF%AB%E9%80%9F%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E6%98%93%E5%BE%AE%E4%BF%A1%E6%9C%BA%E5%99%A8%E4%BA%BA/&quot;&gt;这篇讲 WechatBotXposed 的思路的文章&lt;/a&gt;，学习如何反编译分析微信的方法。&lt;/p&gt;
&lt;p&gt;不过，对于这种破解方式，微信的打击还是比较厉害的。去年年中微信封禁了一波使用 Xposed 的帐号，据称是为了打击世界杯赌球。根据网上能看到的传闻，微信可能会读取手机中已安装应用的列表，判断手机是否被 Root 、是否安装了 Xposed 应用。也有一种方式是判断方法的调用栈中是否存在 Xposed 相关。当然，微信也存在一套行为分析的系统，会通过你登录频次、消息发送频次、设备信息等因素，判断帐号是否存在异常行为。&lt;/p&gt;
&lt;p&gt;个人认为，对于这种方式，微信还是比较容易判断出来的。收消息暂且不论，对于发消息，本来是需要用户手动点击发送按钮，而现在一定会有某个函数触发了发消息的逻辑，一定能通过调用栈判断出来。事实上，我用这种方式玩了两天以后，微信就限制了我的帐号在低版本上的登录，必须让我在新版本上才能登录了。&lt;/p&gt;
&lt;h2&gt;使用 Android 上的辅助功能&lt;/h2&gt;
&lt;p&gt;原理： Android 上的辅助功能，原本是给残障人士所使用，帮助他们更好地使用手机上的功能。但我们也可以利用这些功能来做到辅助读屏、模拟点击的功能。&lt;/p&gt;
&lt;p&gt;获取屏幕上的控件或点击对应的按钮，需要我们知道界面元素的资源 ID ，所幸这并不需要什么高超的破解技巧，只要使用旧版的 DDMS 对手机界面进行 Dump 就可以了。虽然每次微信升级后，由于混淆的原因，资源 ID 会发生变化，但重新适配的成本也比 Xposed 方案低的多。再退一步讲，即使不知道界面元素的资源 ID ，也可以通过按钮的文字等来区分，这样就完全不需要破解或 Dump 之类的工作了。&lt;/p&gt;
&lt;p&gt;辅助功能的适用场景正好和 Xposed 方案相反。对于发消息，它的实现非常容易，只要你能找到正确的发送对象，接下来输入框和发送按钮都是固定的，直接模拟点击即可。在 Github 上也可以找到&lt;a href=&quot;https://github.com/Clearlee/AutoSendWeChatMsg&quot;&gt;现成的实现&lt;/a&gt;。但如何判断收消息就困难了不少，因为你很难去监测收到消息这一不是我们主动触发的行为。在网上找到了两种不同的思路，其一是&lt;a href=&quot;https://blog.csdn.net/u014651216/article/details/52525009&quot;&gt;通过监测通知栏的变动&lt;/a&gt;，其二是&lt;a href=&quot;https://www.cnblogs.com/cxk1995/p/6366273.html&quot;&gt;通过监测聊天界面的改变&lt;/a&gt;。但两种方式的缺陷也很明显，前者通知栏的推送并不稳定，同时多条通知处理起来也比较麻烦；后者则必须停留在某人的聊天界面中才能监测到。&lt;/p&gt;
&lt;p&gt;也正是因为这样的特点，使用辅助功能的玩法一般用于实现各类微信群发助手。群发助手天然符合它的长处：一方面只管发消息，不管收消息；另一方面，对于所有人发送消息，也避免了判断发送对象是否正确的烦恼。&lt;/p&gt;
&lt;p&gt;这种玩法最大的优势在于完全合法，无需考虑被微信打击的因素。但缺点也很明显，模拟点击的原理使得你不得不为操作加上一些延迟，一些从代码层面来看很简单的操作（比如获取微信号 etc）也需要绕很大的圈子才能做到。同时它受外界影响极大，一旦手机出现些不可预知的卡顿，或其他界面弹出，或同时交流的人数过多，界面变化频繁时，自动执行的操作就会被打断，甚至产生消息发错对象的尴尬。这使它注定只能作为玩具，而无法成为大规模商业化的应用。&lt;/p&gt;
&lt;h2&gt;使用微信网页版 API&lt;/h2&gt;
&lt;p&gt;原理： Web 即意味着不设防，相比于其他客户端， Web 永远是防御力最薄弱的那个。另外 Web 并不是微信的主打方向，因此迭代速度较慢，版本适配难度也较低。只要破解了 Web API ，就可以模拟登录网页版微信收发消息了。&lt;/p&gt;
&lt;p&gt;这个方法实在没有什么好说的，原理简单粗暴，以前的 Web QQ 也遭遇过这样的毒手。现在基于 Web 微信的机器人程序已经形成了一个庞大而成熟的开源社区，有着完善而优雅的 RESTful API 、命令行工具等。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/chatie/wechaty&quot;&gt;&lt;img src=&quot;/128ea3b4800b6e243af1dc52a3d99567/wechat2.svg&quot; alt=&quot;Wechaty&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;相比前面两种方法，我认为这种做法是目前最成熟且具有可行性的。虽然这也属于破解的范畴，但是由于 Web 端的特定使然，微信并不能做出什么很有针对性的防御。不过微信并非没有采取措施。正如 Web QQ 现在已经被废弃一样，微信也一刀切地禁止了所有新注册帐号的 Web 端登录权限，坊间也一直有微信会取消 Web 版的传言。但即使微信不会取消 Web 版，光是新注册帐号无法登录这一举措，就使得这一方法未来的潜力大打折扣，沦为部分爱折腾的存量用户的玩具。另外，微信的风控体系，也会对那些明显超出频次调用接口的帐号进行临时的封禁，以阻止用户的为所欲为。&lt;/p&gt;
&lt;p&gt;除了以上提到的几点， Web 版本身的功能也是有限的。除了简单的收发消息，进阶的功能比如收发红包、朋友圈等并没有相关的 API ，对于野心更大的开发者来说是个局限。另外，由于 Web 端走 HTTP 协议，对于收发消息的稳定性会比 Native 端略低。&lt;/p&gt;
&lt;h2&gt;其他途径&lt;/h2&gt;
&lt;p&gt;上述提到的三种是目前的主流技术，但可以看到都有比较大的局限性，因此也有不少人在探索新的解决方案，例如基于 iPad 版和 PC 版的破解。当然，也曾见到技术实力较高的团队，通过自行定制 Android ROM 的方式，提供软硬件一体的解决方案，对微信做大规模的 Hack 。不过以上方案在网上都鲜有公开的资料流出，一方面是为了低调防打击，另一方面也是将其作为商业化的手段。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;作为普通用户的我们难以想象基于微信这一看似封闭的 App 衍生出了多大的产业链。朋友圈的微商、公众号，还有现在的小程序和小游戏，已经完全可以支撑起一个像样的创业团队。也因此，对微信的破解成了一件十分有利可图的事，吸引了不少人前仆后继。对于时长脑洞大开的开发者如我，自然希望微信可以像 Telegram 那样开放出更多的 API ，以便把更多创意落到实处，但也理解关系链之于微信的重要性，明白这几乎就是天方夜谭。只是，对于那些把身家性命放在微信破解上的公司和开发者，就好像空中楼阁，被微信反手就能玩死，也未免太可悲了。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[如何寻找 YaHaHa ？]]></title><description><![CDATA[我第一次发现 YaHaHa…]]></description><link>https://maliut.space/p/how-to-find-yahaha/</link><guid isPermaLink="false">https://maliut.space/p/how-to-find-yahaha/</guid><pubDate>Tue, 05 Jun 2018 20:45:16 GMT</pubDate><content:encoded>&lt;p&gt;我第一次发现 YaHaHa ，是在初始高地的沼泽。半截中空的木桩通过铁链连接着沉入沼泽底部的大铁球。“铁球的大小，似乎刚好可以立在木桩之上”。刚学会磁铁能力的我对周围可以操作的事物充满了新鲜感，恨不得将它们通通把玩一番，于是怀着这样近乎恶作剧般的心态，我吸起铁球，放到了木桩之上。&lt;/p&gt;
&lt;p&gt;&quot;Ya-ha-ha! You found me!&quot; 突如其来的场景吓了我一跳。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/04d82386be02916f68007c8602b8ae0f/a2510/yahaha1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECA//EABcBAAMBAAAAAAAAAAAAAAAAAAABAgP/2gAMAwEAAhADEAAAAXLzwVllH//EABoQAAEFAQAAAAAAAAAAAAAAAAEAAhAREjL/2gAIAQEAAQUCqXcgDK//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAgEBPwGq/8QAFhAAAwAAAAAAAAAAAAAAAAAAARAg/9oACAEBAAY/AoC//8QAGhAAAwEAAwAAAAAAAAAAAAAAAAERIRBBYf/aAAgBAQABPyFMK744aJXLjvj/2gAMAwEAAgADAAAAEPcP/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERIf/aAAgBAwEBPxCZSo//xAAWEQEBAQAAAAAAAAAAAAAAAAABADH/2gAIAQIBAT8QHsT/xAAaEAACAwEBAAAAAAAAAAAAAAABEQAhQXFR/9oACAEBAAE/ECtXY+4TOovUA26INFSozpszBP/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Yahaha！&quot;
        title=&quot;&quot;
        src=&quot;/static/04d82386be02916f68007c8602b8ae0f/627f4/yahaha1.jpg&quot;
        srcset=&quot;/static/04d82386be02916f68007c8602b8ae0f/efcb5/yahaha1.jpg 203w,
/static/04d82386be02916f68007c8602b8ae0f/02e66/yahaha1.jpg 405w,
/static/04d82386be02916f68007c8602b8ae0f/627f4/yahaha1.jpg 810w,
/static/04d82386be02916f68007c8602b8ae0f/a2510/yahaha1.jpg 1000w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;我第二次发现 YaHaHa ，是在路边的峭壁上。峭壁上的三个小立方体组成了一个缺了个角的大正方形，而另一个小立方体则落在地上。“它们正好能拼成一个完整的正方形诶”，我把小立方体放到缺了的角上——又一个 YaHaHa 。&lt;/p&gt;
&lt;p&gt;YaHaHa 的学名其实是克洛格，是《塞尔达传说 - 旷野之息》中的一种收集要素，它遍及整个大陆，有 900 个之多，但却都极为隐蔽，若是匆匆赶路，你完全不会意识到它们的存在，但如果你感到地图某处有说不出来的不对劲，往往就藏着它们的身影。&lt;/p&gt;
&lt;p&gt;随着游戏的进行，我寻找 YaHaHa 越来越熟练：见到三棵树就会去摘苹果，见到荷叶就去跳水，爬到山顶看到石头就会去搬一搬。《旷野之息》很好玩，寻找 YaHaHa 也令人乐此不疲，只是，这过程中再没有多少思考。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;再一次受到 YaHaHa 的惊喜，已经是游戏的后期。主线早已通关，而神庙也都找全。DLC 中有件“克洛格的面具”道具，能提示你周围还未找到的 YaHaHa 。有个同学告诉我卡卡利科村的村口响起了提示，但无论如何也找不到。我在周围上蹿下跳了半天也未果。&lt;/p&gt;
&lt;p&gt;周围的一草一木，已经是再清楚不过了，它到底藏在哪里呢？我不得不放弃了徒劳无功的搜索，转而停下来思考。突然我灵光一闪——村口大门的横梁上有村子的标志，而四周歪歪扭扭地射了好几根箭。我第一次来到这个村庄的时候，箭还是稀有物品，我甚至还试图爬到上面去把这些箭都拔下来。现在想想，一个与世无争的村子，大门上被射了好多箭，这难道不奇怪吗？&lt;/p&gt;
&lt;p&gt;正对着中间的标志射了一箭。&quot;Ya-ha-ha! You found me!&quot; 熟悉的场景应声而出。&lt;/p&gt;
&lt;p&gt;又一次，同学告诉我海利亚大桥上有个 YaHaHa 找不到。同样地来回绕桥探索但一无所获。我注意到桥洞下放着个小树枝。这个小树枝，其实我早已发现，但一直没放在心上，毕竟，满地图捡武器的塞尔达里，地上有个小树枝也是再正常不过的事情。然而再仔细一想，在茫茫大湖之上，全是石头做成的大桥，却出现了一个小树枝，也未免太突兀了吧。&lt;/p&gt;
&lt;p&gt;捡起树枝，果然又是熟悉的场景。&lt;/p&gt;
&lt;p&gt;尽管那个时候的我早已对游戏的各种机制都烂熟于心，见怪不怪，但当 YaHaha 蹦出来的那一瞬间，我的心还是不由自主地震颤了一下。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5bca02f30809210a9e733bd052a0e60f/eea4a/yahaha2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABYBAQEBAAAAAAAAAAAAAAAAAAECA//aAAwDAQACEAMQAAABkrTymZoF/8QAFxAAAwEAAAAAAAAAAAAAAAAAAAEhEP/aAAgBAQABBQIg1R5//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFhEBAQEAAAAAAAAAAAAAAAAAABES/9oACAECAQE/AdK//8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQAGPwJf/8QAGhAAAwEAAwAAAAAAAAAAAAAAAAERIRAxQf/aAAgBAQABPyGL0yi2LOO6Yj//2gAMAwEAAgADAAAAELgf/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERIf/aAAgBAwEBPxBayH//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPxCh/8QAGRABAAMBAQAAAAAAAAAAAAAAAQARIUEx/9oACAEBAAE/EFCgMRGhy7uArdxWyhx2AAY3WTxP/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;海利亚大桥&quot;
        title=&quot;&quot;
        src=&quot;/static/5bca02f30809210a9e733bd052a0e60f/627f4/yahaha2.jpg&quot;
        srcset=&quot;/static/5bca02f30809210a9e733bd052a0e60f/efcb5/yahaha2.jpg 203w,
/static/5bca02f30809210a9e733bd052a0e60f/02e66/yahaha2.jpg 405w,
/static/5bca02f30809210a9e733bd052a0e60f/627f4/yahaha2.jpg 810w,
/static/5bca02f30809210a9e733bd052a0e60f/78959/yahaha2.jpg 1215w,
/static/5bca02f30809210a9e733bd052a0e60f/eea4a/yahaha2.jpg 1280w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;海利亚大桥，游戏中 Yahaha 密度最高的区域之一&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;作为在同伴中，收集个数最多的那一个，我也时常被问到该如何寻找 YaHaHa 。可是，在最开始时，我甚至不知道 YaHaHa 的存在，我去做那些事情纯粹是因为觉得有趣，觉得“它们应该是这样的”。而 YaHaHa 的出现，就像是玩家与游戏设计师的会心一笑，是设计师告诉玩家“你想的没错，恭喜你发现了这个小彩蛋”，是玩家与设计师之间跨越时空的心照不宣。但后来，收集 YaHaHa 成了目的，而我们也似乎逐渐陷入思维定势，觉得似乎 YaHaHa 就只有这么固定的几种，而忽略了 YaHaHa 设计的初心。&lt;/p&gt;
&lt;p&gt;海拉鲁大陆 900 个 YaHaHa ，我踏遍世界，收集到的不过三分之一。想必，还有不少就隐藏在了那些被忽视了的非类型化的去处。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;我觉得 YaHaHa 的设计其实就像是整个《旷野之息》的一个缩影：没有强制性的任务要求，也没有地图上明晃晃的提示点，它需要的是你像孩子一样敏锐的观察力和旺盛的好奇心，凭着本能发现这细节中隐藏的奥秘。而在网上无数玩家对《旷野之息》的溢美之词中，也都像个孩子一样倾诉着自己神奇的小发现、独一无二的探索旅程，仿佛三天三夜也说不完；也都不约而同地提到了这作是现在市面上无数套路式的、 checklist 式的所谓开放世界游戏中的一股清流，甚至于，重新定义了“开放世界”。&lt;/p&gt;
&lt;p&gt;当然，天下苦 checklist 游戏久矣。无论是开放世界游戏中的任务点、收集点，还是平日里氪金手游中的日常周常等等，都是通过激发玩家的强迫症，而异化了最初玩游戏时那种纯粹追求快乐或是放松的心情。但想要让玩家以纯粹的兴趣和好奇心来驱动游戏却并非易事。而《旷野之息》的方方面面就是这样有机地结合在了一起，为这个问题交上了一份满分的答卷。在游戏伊始，当我操纵着林克从昏暗、窄小的复苏神庙走廊中奔跑出来，我和大多数人一样，被面前整片广阔的大陆所震撼。而更为震撼的是，我在游玩过程中慢慢发现，这映入眼帘的整片大陆的整个角落，完全没有空气墙之类的障碍，只要你愿意便都可以涉足&lt;del&gt;（严格来说迷雾森林部分地方还是不行。另外想特别吐槽下 GTA5 ，虽然建筑这么多，但基本上都进不去）&lt;/del&gt;。而经过了精心设计的塔、神庙以及地形的布局，让玩家时刻都能发现视野中新的兴趣点，根本停不下来。再加上早已饱受赞誉的物理、化学系统带来的涌现式的玩法，以及简单且非强迫的主线等等，一切的一切都很好地服务了游戏的核心乐趣。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/00c04c9a5e7d1eb19e951ce8169e5e1f/29007/yahaha3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACcUlEQVR42hXM2U/TcAAA4P6J+mKC6Ma6rsfaX6+13dWud7utXdnJ2OEY44ZlDBEGg6iYiGBiQnBvouGNROO7vomP38sHiY3XiepYrIykykgsj4TFvWSwk6yMMo3D7PJE606N3pm9+r60fbk8vt46/Xzw9su7y5ub29n9jzvI7I701q7Z2dPaI61zoHYO9Vdv9N6R3Z8U1s+KWxfB8GNt/Kl3cj04vmxuTKcfrr7Obu7vZr8fvkFpuwbUttsc+N216mCztrZdWtltbAzbu/u94Xj7+Hjv7HxwNN05PR+MJmph62R6/vD96u+v2z8/Z9B8BEQphZV9kCnLhZZgNEmlS6ltRu8QuY5kdWRvNeEMCK3H5VcEo5911zc39i9OJgfDQ+jpHPrsJbqAAZjgUSaNSE4sG+C5MrAakWw1ItcxvSkFfTHoC5WeWu2rlUHW6yveilUZQHNx8BywYTZBpNNkJpOwXbJQAsWA88tkPiDzJeAvsovVZH1JqjXTjVam3srWWm67Z7e6UETmYVVC1AxpK6Sjkp5M+QqeV2hfp7wc4TxSx/O5eFGjPAMUNN6zBc8xlspuqwbFzDjmMojOEXkGd/iQAVCbwWwec/mYzcEyF1FZWGNgjUZ0NprjcUNCdYmwBMFPQ3iejGo0rDKwwS2oNOoA9PFymP9FmhRNIAUC64uY+1iwUU1ALQk1E6gmIjkeQixmQU2Ec1zMpRELIA5AdBKYOGugnIKnTCZhR1MW7TQyUjlFu6mYycF6nChSmENDuCMiNhdW47iHYUUqJMaoLMwqUUFmBAMTTRxX5ubpJy+QUFDX/LYqFpNhhQzJBGJj/wDo91xSpCH/qAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;BoTW&quot;
        title=&quot;&quot;
        src=&quot;/static/00c04c9a5e7d1eb19e951ce8169e5e1f/d7542/yahaha3.png&quot;
        srcset=&quot;/static/00c04c9a5e7d1eb19e951ce8169e5e1f/2efce/yahaha3.png 203w,
/static/00c04c9a5e7d1eb19e951ce8169e5e1f/1d180/yahaha3.png 405w,
/static/00c04c9a5e7d1eb19e951ce8169e5e1f/d7542/yahaha3.png 810w,
/static/00c04c9a5e7d1eb19e951ce8169e5e1f/d56b5/yahaha3.png 1215w,
/static/00c04c9a5e7d1eb19e951ce8169e5e1f/29007/yahaha3.png 1600w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;是的，目之所及之处，你都可以去探索、去涉足&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;关于《旷野之息》的设计经验和哲学，网上早已经有一大把分析文了。我也不是专业的游戏评论者和开发人员，无意再深入地探讨下去。我只想谈谈我自己的游玩感受。《旷野之息》，它真的是那种，那种很少见的那种，可以让人完全沉浸的游戏。我相信大家但凡是游戏玩的多了以后都有这样的感受：每遇到一个新游戏，便将它归为某一类，然后用之前在其他相同类型游戏中学到的经验套上去，边玩边开始分析哪里设计好、哪里不好。基本体验完一遍核心流程，做出个“剧情 x/10，画面 x/10，音乐 x/10，总评 x/10”的套路评价，就将其盖棺定论了。这其实是人类进化过程中发展出的一个能力，能让我们以更快的方式上手、了解一件新鲜事物。但是，总觉得与游戏间多了一层距离。而《旷野之息》则不然，它就像有魔力一般，它让我忘掉了各种条条框框的设计原理，忘掉了吃饭，忘掉了睡觉，无视了周围的一切，仿佛我真的穿越到了这片世界之中，或策马或御风遨游在大陆的各个角落。而又在事后与他人讨论起游戏来，才猛然意识到游戏中许许多多的理所当然，其实是有意为之，大巧不工，不禁慨叹设计的精巧与细节的充沛。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f8eb2e42119a0bf15ab78f6f71a058ea/c7398/yahaha4.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.66502463054187%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAwX/xAAWAQEBAQAAAAAAAAAAAAAAAAACAAH/2gAMAwEAAhADEAAAAZazbNDwsG//xAAbEAABBAMAAAAAAAAAAAAAAAARAAECEgMQMv/aAAgBAQABBQKGQKzC+io8/wD/xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAwEBPwGI/8QAFhEAAwAAAAAAAAAAAAAAAAAAAAES/9oACAECAQE/AaZbP//EABgQAAMBAQAAAAAAAAAAAAAAAAEQMQAh/9oACAEBAAY/AuxQMb//xAAZEAEAAwEBAAAAAAAAAAAAAAABABEhYeH/2gAIAQEAAT8hTAEcg9gJT0jhBGDlTFE//9oADAMBAAIAAwAAABAHz//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAEDAQE/EJS//8QAFREBAQAAAAAAAAAAAAAAAAAAEGH/2gAIAQIBAT8Qsf8A/8QAHBABAAMAAgMAAAAAAAAAAAAAAQARITFhQVGB/9oACAEBAAE/EDgfUGE+zMecquT1IdKJxyBDBaDzCVgWW53P/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;沃托里村&quot;
        title=&quot;&quot;
        src=&quot;/static/f8eb2e42119a0bf15ab78f6f71a058ea/627f4/yahaha4.jpg&quot;
        srcset=&quot;/static/f8eb2e42119a0bf15ab78f6f71a058ea/efcb5/yahaha4.jpg 203w,
/static/f8eb2e42119a0bf15ab78f6f71a058ea/02e66/yahaha4.jpg 405w,
/static/f8eb2e42119a0bf15ab78f6f71a058ea/627f4/yahaha4.jpg 810w,
/static/f8eb2e42119a0bf15ab78f6f71a058ea/c7398/yahaha4.jpg 1154w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;metadata&quot;&gt;沃托里村，依山面海，地图角落的世外桃源，唯一没有主线的自然村庄，也是我最喜欢的地方&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;我很想用寻找 Yahaha 就是寻找玩游戏的初心，重塑看世界的方式这样既点题又漂亮的话来结束这篇文章。但在无脑吹完这个游戏以后，总还有一些话令人不吐不快。回想起在初始之塔上我举目四望，心情是难掩的激动。光小小一个初始台地，就让我产生了数不清的惊喜，那地图上初始台地以外的那么大那么大无边无际的黑暗之中，还有多么无尽的值得探索的去处在等着我呢？而当我完成大地图的最后一块拼图，完整的界面出现在我眼前，开心之余也多了一份遗憾。这个世界再大也有边界，终有一天我会踏遍海拉鲁的每一寸土地，世界在我面前将没有秘密可言。《旷野之息》我玩了上百个小时，游戏时间能在此之上的，也只有可以重复游玩的过程生成式游戏，和与人斗其乐无穷的竞技类游戏了。近年来这样的游戏充斥着业界，毕竟它们制作成本相对较低，又能换来玩家大量的游戏时间，在这个时间就是金钱的现代社会，谁都知道这意味着什么。而《旷野之息》这样的游戏就是消耗品了，纵然你对游戏世界怀有再多的爱与不舍，在几百个小时的游戏时间过后，也将不得不陷入“拔剑四顾心茫然”的境地。&lt;/p&gt;
&lt;p&gt;曾经沧海难为水，除却巫山不是云。感谢任天堂，感谢世界，让我在最适合打游戏的时光里遇到了这样一款伟大的游戏。可是在经历完这南柯一梦以后，下一个能再唤醒我的热情，让我废寝忘食的游戏又需要等多久才会出现呢？而即使出现了又一个如此伟大的游戏，那个时候的我，过着日复一日机械生活的我，还能做到像现在这样抛开世俗的一切繁冗，忘情地享受这一切吗……&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 298px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7a8988915e05c2e4d4cba33c2f49a331/300b8/yahaha5.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.14778325123154%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQCA//EABcBAQEBAQAAAAAAAAAAAAAAAAABAgP/2gAMAwEAAhADEAAAAY8nOUuJJ9m72B//xAAZEAEAAwEBAAAAAAAAAAAAAAABAAIRMTL/2gAIAQEAAQUCmynlcL1xAx6dn//EABkRAAEFAAAAAAAAAAAAAAAAAAEAAhAREv/aAAgBAwEBPwFxsrU//8QAGBEAAgMAAAAAAAAAAAAAAAAAABEBEBP/2gAIAQIBAT8BSgzv/8QAGxAAAgEFAAAAAAAAAAAAAAAAAAExEBEgIUH/2gAIAQEABj8C1FOEl00Th//EABwQAQEAAgIDAAAAAAAAAAAAAAEAESExQVFxkf/aAAgBAQABPyHth6DzYIXCTQEbhA9ztvpk4ScZDwX/2gAMAwEAAgADAAAAENvnvv/EABgRAAMBAQAAAAAAAAAAAAAAAAERIQAQ/9oACAEDAQE/EAxyiLv/xAAYEQEAAwEAAAAAAAAAAAAAAAABABARIf/aAAgBAgEBPxDeMA9W/wD/xAAdEAACAgIDAQAAAAAAAAAAAAABEQAhQWExUfBx/9oACAEBAAE/ELJAYZMKjeFPUIetFA+7cWDVZC8IrImiFmlB1QOAA773GNnrnQnIjZu439Mbn//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Meme&quot;
        title=&quot;&quot;
        src=&quot;/static/7a8988915e05c2e4d4cba33c2f49a331/300b8/yahaha5.jpg&quot;
        srcset=&quot;/static/7a8988915e05c2e4d4cba33c2f49a331/efcb5/yahaha5.jpg 203w,
/static/7a8988915e05c2e4d4cba33c2f49a331/300b8/yahaha5.jpg 298w&quot;
        sizes=&quot;(max-width: 298px) 100vw, 298px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[meta.js - JavaScript 元编程工具库]]></title><description><![CDATA[meta.js 为 Javascript 语言扩充了一些元编程功能，在使用 ES6 的 class 语法定义类时，你能够以更加直观自然的方式，像一门原生就是基于类的语言那样，使用这些元编程功能。 可以在这里获取它的使用文档与源代码。 开发这个库的灵感来源于之前阅读的《Ruby…]]></description><link>https://maliut.space/p/metajs/</link><guid isPermaLink="false">https://maliut.space/p/metajs/</guid><pubDate>Fri, 24 Nov 2017 23:44:00 GMT</pubDate><content:encoded>&lt;p&gt;meta.js 为 Javascript 语言扩充了一些元编程功能，在使用 ES6 的 class 语法定义类时，你能够以更加直观自然的方式，像一门原生就是基于类的语言那样，使用这些元编程功能。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;可以&lt;a href=&quot;https://github.com/maliut/meta.js&quot;&gt;在这里&lt;/a&gt;获取它的使用文档与源代码。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;开发这个库的灵感来源于之前阅读的《Ruby 元编程》一书。考虑到 JavaScript 语言与 Ruby 同为动态语言，灵活度相似（具体的读后感与对比可以参考&lt;a href=&quot;/p/javascript-metaprogramming&quot;&gt;这篇文章&lt;/a&gt;），且在 JavaScript 开发中也会经常遇到一些需要语言动态性的场景以简化代码。而在 JavaScript 中操作这些动态性，就免不了与 prototype 打交道，而在最新的 ES6 语言规范中，class 等语法糖的引入，也表明了这门语言希望尽量淡化 prototype 之类的概念，降低新手的学习门槛与熟悉了其他语言的开发者们的知识迁移成本。故在这基础之上，我的元编程库也把这些细节封装起来，以便大家能用更自然的方式使用这些元编程功能。&lt;/p&gt;
&lt;p&gt;开发这个库的另一个动机则是对于 &lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt; 在 JavaScript 中的实现。&lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt; 是讲解 Ruby 语言动态性的一个典型例子，在实际开发中也不乏实用性。JavaScript 在 ES6 中引入了 Proxy，使得实现 &lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt; 功能也成为了可能。但遗憾的是，网上所有讲解“如何在 JavaScript 中实现 &lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt;” 的文章全是有问题的。它们只是简单演示了一下对 Proxy 的使用，却丝毫没有真正去使用过它。所以我在这个库中尽量严谨地实现了这个功能，以供大家参考。&lt;/p&gt;
&lt;p&gt;代码本身不长，也很简单，欢迎大家多多使用反馈。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[五分钟看懂 Smalltalk 语言]]></title><description><![CDATA[旧笔记补档，原来是为 2017-10-06 线下 MAP 活动做的笔记 2018-08-02 第一版
2020-08-06 第二版
2021-10-19 部分修改 前言 为什么是 Smalltalk？ Smalltalk…]]></description><link>https://maliut.space/p/smalltalk/</link><guid isPermaLink="false">https://maliut.space/p/smalltalk/</guid><pubDate>Thu, 05 Oct 2017 20:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;旧笔记补档，原来是为 2017-10-06 线下 MAP 活动做的笔记&lt;/p&gt;
&lt;p&gt;2018-08-02 第一版&lt;br /&gt;
2020-08-06 第二版&lt;br /&gt;
2021-10-19 部分修改&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!--more--&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;为什么是 Smalltalk？&lt;/p&gt;
&lt;p&gt;Smalltalk 是一门历史悠久的语言。虽然如今我们几乎没有机会再去使用它，但它的不少开创性理念，仍影响了许多现代的编程语言和思想。尤其如今主流的面向对象语言 C++ 和 Java，它们本身并不是纯粹的、适合教学的「面向对象」语言，往往使得初学者陷入一些细枝末节之中，导致理解上的困难。&lt;/p&gt;
&lt;p&gt;而学习 Smalltalk，我们能从编程语言的演进中更深刻地理解一些重要概念的沿革，也可以打破固化思维，在不同语言的比较中得到新的编程灵感。&lt;/p&gt;
&lt;p&gt;如今中文互联网上关于 Smalltalk 的资料相对较少，且多以讲述理念/机翻为主，缺少实际的教程。前些年我出于个人爱好了解了一些 Smalltalk 入门知识，并和小伙伴们做了线下分享。这回把当时的一些笔记整理后发到网上，也算是添砖加瓦吧。&lt;/p&gt;
&lt;p&gt;本文脉络主要参考了 &lt;a href=&quot;http://www.eli.sdsu.edu/courses/spring01/cs635/readingSmalltalk.pdf&quot;&gt;I Can Read C++ and Java But I Can&apos;t Read Smalltalk&lt;/a&gt; 以及 &lt;a href=&quot;http://www.vaikan.com/why-i-love-smalltalk/&quot;&gt;Why I love smalltalk&lt;/a&gt; 。不过并不是照搬，而是加入了一些个人的理解&lt;del&gt;夹带私货&lt;/del&gt;。希望我的引申能够使读者更容易地理解这一有趣的语言。&lt;/p&gt;
&lt;h2&gt;基础语法&lt;/h2&gt;
&lt;p&gt;现代编程语言的语法往往大同小异，但 Smalltalk 和如今的语言语法差距不小，这也是大多数人第一眼看到代码觉得难以理解的原因之一（可以在&lt;a href=&quot;https://github.com/gnu-smalltalk/smalltalk/tree/master/examples&quot;&gt;这里&lt;/a&gt;看到真实的代码例子）。&lt;/p&gt;
&lt;p&gt;不过，虽然写法不同，编程语言的常规要素还是大差不差的，我们先浮光掠影地看一下这些基础语法，便能够读懂大部分的代码内容。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;注释：用双引号包围。&lt;code class=&quot;language-text&quot;&gt;&quot;这是注释&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;字符串：用单引号包围。&lt;code class=&quot;language-text&quot;&gt;&apos;这是一个字符串&apos;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单个字符：&lt;code class=&quot;language-text&quot;&gt;$c&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;符号（Symbol）：&lt;code class=&quot;language-text&quot;&gt;#thisIsASymbol&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;大家也许对符号这个概念比较陌生。简单来说，只要两个符号的值一样，那么它们在内存中也是相同的对象。后面 Ruby 等语言也继承了这一设计。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;变量声明：&lt;code class=&quot;language-text&quot;&gt;| a |&lt;/code&gt; ，也可以一次声明多个：&lt;code class=&quot;language-text&quot;&gt;| a b c |&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;赋值语句：&lt;code class=&quot;language-text&quot;&gt;a := 5&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相等性与同一性：相等性使用 &lt;code class=&quot;language-text&quot;&gt;=&lt;/code&gt; ，相当于 Java 中的 &lt;code class=&quot;language-text&quot;&gt;equals()&lt;/code&gt; 。同一性使用 &lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt; ，即判断内存地址相同。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;返回语句：&lt;code class=&quot;language-text&quot;&gt;^ a&lt;/code&gt; 即其他语言中的 &lt;code class=&quot;language-text&quot;&gt;return a&lt;/code&gt; 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;级联：不同于大多数语言，Smalltalk 使用 &lt;code class=&quot;language-text&quot;&gt;.&lt;/code&gt; 表示语句的末尾，而 &lt;code class=&quot;language-text&quot;&gt;;&lt;/code&gt; 用于实现级联的功能。所谓级联，是指可以连续在同一个对象上调用方法，而不必每次都重复写出这个对象。举个例子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;&lt;span class=&quot;token temporary-variables&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; 
p &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; Client &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;    
  name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Jack&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;     
  age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;     
  address&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Earth&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;现代语言中 Dart 就延续了类似的设定。对于其他语言，用 Builder 模式也可以实现类似的功能。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;方法与类&lt;/h2&gt;
&lt;p&gt;来看一个 Smalltalk 中方法定义和调用的例子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&quot;定义一个方法：使物体围绕某个轴旋转一定角度&quot;&lt;/span&gt;
rotateBy&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; angle around&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; vector
  &lt;span class=&quot;token temporary-variables&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
  result &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;省略具体内容&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;result

&lt;span class=&quot;token comment&quot;&gt;&quot;调用这个方法&quot;&lt;/span&gt;
t rotateBy&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; a around&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; v&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;相信除了 Objective-C 程序员，大家初看到都会觉得这样的方法签名有点奇怪。不过我们可以基于主流语言的语法，来看看一个方法是以何种逻辑演化成 Smalltalk 的样子的。&lt;/p&gt;
&lt;p&gt;先从我们熟悉的形式开始：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;t-&gt;rotate(a, v);  // In C++
t.rotate(a, v);   // In Java&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;省略掉一些不影响语义的符号：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;t rotate(a, v);    // 省略掉 .
t rotate a, v;     // 省略括号&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后我们发现参数的含义不太清晰，可以给每个参数前面加一个前缀来使含义更加明确：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;t rotate by a around v;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但是这样的话，哪个词语是前缀，哪个是参数就不太能分得清。因此我们给前缀加个冒号来区分：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;t rotate by: a around: v;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后，都这样了，方法名字本身也没必要单列出来，可以直接和第一个参数前缀合并起来，就成了 Smalltalk 现在的样子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;t rotateBy: a around: v; &lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
让我们再来看一个复杂点的例子：
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;a negative &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b between&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; c and&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; d&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  ifTrue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; c negated&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;它相当于其他语言中的：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; d&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;细品这个例子，我们可以看到很多有意思的地方。&lt;/p&gt;
&lt;p&gt;首先来看这个 &lt;code class=&quot;language-text&quot;&gt;c negated&lt;/code&gt; 。有人会说，直接写个 &lt;code class=&quot;language-text&quot;&gt;-c&lt;/code&gt; 不就完了，为什么要搞这么臃肿。表面看上去，只是写法的不同，但其实它代表着理念的不同。&lt;code class=&quot;language-text&quot;&gt;-c&lt;/code&gt; 代表对 &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; 这个数字做了一次数学运算，而 &lt;code class=&quot;language-text&quot;&gt;c negated&lt;/code&gt; 代表的是在 &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; 这个对象上调用 &lt;code class=&quot;language-text&quot;&gt;negated&lt;/code&gt; 方法，然后用这个方法的返回值去做下一步的操作。&lt;/p&gt;
&lt;p&gt;这里体现了 Smalltalk 的核心概念之一：&lt;strong&gt;一切皆是对象&lt;/strong&gt;。对比于另一门同样号称「一切皆是对象」的语言 Java，你是不能写出 &lt;code class=&quot;language-text&quot;&gt;5.negated()&lt;/code&gt; 这种代码的。而所谓的「基本类型」、「包装类型」让多少初学者陷入困惑，又让多少面试官洋洋得意。但在 Smalltalk 里，对象的概念真正贯穿始终，保持同一性，也避免了这种困惑。&lt;/p&gt;
&lt;p&gt;其次我们来讨论 Smalltalk 中另一个重要概念：&lt;strong&gt;消息&lt;/strong&gt;。在前文中我们称这种写法为「调用方法」。但实际上 Smalltalk 中并没有调用方法的概念，而是叫做&lt;strong&gt;给对象发送消息&lt;/strong&gt;。例如 &lt;code class=&quot;language-text&quot;&gt;c negated&lt;/code&gt; 用 Smalltalk 的话说，就是给对象 &lt;code class=&quot;language-text&quot;&gt;c&lt;/code&gt; 发送了一个名为 &lt;code class=&quot;language-text&quot;&gt;negated&lt;/code&gt; 的消息。&lt;/p&gt;
&lt;p&gt;在 Smalltalk 中有三种消息，它们在上面的例子中都能体现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一元（unary）消息：可以理解为一个无参方法，例如上面的 &lt;code class=&quot;language-text&quot;&gt;negated&lt;/code&gt; 消息。&lt;/li&gt;
&lt;li&gt;二元（binary）消息：就是常用的一组计算符号，比如四则运算符还有上面的 &lt;code class=&quot;language-text&quot;&gt;|&lt;/code&gt; 等逻辑运算符。不过需要注意的是，它的本质还是发送消息而不是直接运算，因此没有运算符优先级的概念。&lt;code class=&quot;language-text&quot;&gt;+&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;*&lt;/code&gt; 在 Smalltalk 中的优先级是相等的。&lt;/li&gt;
&lt;li&gt;Keyword 消息：可认为是普通的有参方法，在 Smalltalk 中就是每个参数以冒号连接来表示，比如上面的 &lt;code class=&quot;language-text&quot;&gt;rotateBy:around:&lt;/code&gt; 还有 &lt;code class=&quot;language-text&quot;&gt;between:and:&lt;/code&gt; 。这样的消息在 Smalltalk 中被称为 &lt;strong&gt;keyword&lt;/strong&gt; 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有人可能会说，把方法调用说成是发送消息，但运行的效果是一样的，这不是换汤不换药吗？话是这么说，但描述词汇的不同也会带来思维方式的不同。相比于「方法调用」，「发送消息」这个说法会显得更加泛用一些，也可以带来一些更接近本质的思考。&lt;/p&gt;
&lt;p&gt;举个例子，如果我们调用一个不存在的方法，那自然会报错。但如果给一个对象发送一个它不认识的消息，那非得报错吗？我大不了不处理不就行了。甚至，能不能在遇到这种情况时，有个兜底的逻辑统一处理下？—— Ruby 的 &lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt; 了解一下。&lt;/p&gt;
&lt;p&gt;再比如说当我们学习面向对象时提到的所谓「三大特征」：继承、封装、多态。它们真的能定义什么是「面向对象」吗？&lt;/p&gt;
&lt;p&gt;继承实质是面向对象语言中实现代码重用的一种手段，但现在大部分情况下「组合优于继承」早已成为共识。&lt;/p&gt;
&lt;p&gt;封装是为了隐藏底层或内部的实现细节，也并不是面向对象独有的概念。&lt;/p&gt;
&lt;p&gt;多态在传统面向对象教学中的解释颇为复杂。我们定义一个父类，并让两个不同的子类 override 父类的同一个方法，并编写不同的实现，来说明多态性。但是如果用发送消息的思路来解释就很简单了。给两个不同的对象发送同一个消息，这两个对象会做出不同的反应，不是理所当然的事情吗？压根不需要牵扯到类、继承之类的概念。&lt;/p&gt;
&lt;p&gt;对面向对象这一概念的误读影响了很多人。记得几年前见过一个问题，问的是「JavaScript 是一门面向对象的语言吗？」，下面的回答也是众说纷纭。而之所以大家会有争议，是因为 JavaScript 中并没有类的概念，取而代之的是原型的概念，搞的很多人一下子就混乱了。后来 ES6 的标准里增加了 class 语法糖，我就再也没见过有人提这个问题了。&lt;/p&gt;
&lt;p&gt;希望读者通过学习 Smalltalk 这门面向对象的鼻祖级语言，理解一切皆是对象以及对象间通过消息来通信这两个核心理念，减少一些对面向对象概念的误读。&lt;/p&gt;
&lt;p&gt;在理解了 Smalltalk 的核心概念之后，我们再来看一下怎么声明一个类：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;Object subclass&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;#MyClass&lt;/span&gt;      
  instanceVariableNames&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  classVariableNames&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  poolDictionaries&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  category&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pupeno&apos;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;大部分编程语言都会使用 &lt;code class=&quot;language-text&quot;&gt;class&lt;/code&gt; 关键字声明一个类，并用 &lt;code class=&quot;language-text&quot;&gt;extends&lt;/code&gt; 或类似的关键字来声明继承关系。而 Smalltalk 则另辟蹊径。让我们来看看它的逻辑：首先，由于一切皆是对象，因此 &lt;code class=&quot;language-text&quot;&gt;Object&lt;/code&gt; 类也是一个对象；其次，&lt;code class=&quot;language-text&quot;&gt;Object&lt;/code&gt; 类可以接收名为 &lt;code class=&quot;language-text&quot;&gt;subclass:&lt;/code&gt; 的消息，创建一个新的对象作为它的子类。无需再引入任何特殊的语法，仅用语言本身的两个核心理念，就十分自洽地把类的概念创建了出来。&lt;/p&gt;
&lt;p&gt;不仅仅是定义类，还有我们常用的分支、循环语句等，在 Smalltalk 中都可以通过发送消息的形式来实现，而无需定义额外的诸如 &lt;code class=&quot;language-text&quot;&gt;if&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;while&lt;/code&gt; 等关键字。这或许就是 Smalltalk 把类似 &lt;code class=&quot;language-text&quot;&gt;subclass:&lt;/code&gt; 之类的带参消息都称作 keyword 的原因。它们完全可以替代其他语言中各种关键字的职能。&lt;/p&gt;
&lt;h2&gt;代码块&lt;/h2&gt;
&lt;p&gt;继续回到上一节的例子：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;a negative &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b between&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; c and&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; d&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  ifTrue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; c negated&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;关注这个 &lt;code class=&quot;language-text&quot;&gt;[a := c negated]&lt;/code&gt; 。这里由中括号包围的部分，大家或许会以为就像是 C++/Java 里跟在 &lt;code class=&quot;language-text&quot;&gt;if&lt;/code&gt; 语句后面的大括号——对，但不全对。在 Smalltalk 中它被称为代码块，是语言中的一等公民，可以放在一个变量里面，自由地控制它的调用时机，也可以把它像参数一样传递。如果你熟悉 JavaScript 就一定不会感到陌生。做个简单对比：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&quot;in Smalltalk&quot;&lt;/span&gt;
a &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token block-arguments&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;:x&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;       &lt;span class=&quot;token comment&quot;&gt;&quot;将一个代码块赋值给 a&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a value&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;           &lt;span class=&quot;token comment&quot;&gt;&quot;运行这个代码块并传入参数 10&quot;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-javascript line-numbers&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// in Javascript&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;其实这就是匿名函数的概念。函数式编程的理念近年来也重回大众视野，即使是 Java 也在 1.8 中加入了相关的特性。可以看到虽说 Smalltalk 是面向对象语言的代表，但论函数式编程的能力也完全不输现代的主流语言们。&lt;/p&gt;
&lt;h2&gt;控制流&lt;/h2&gt;
&lt;p&gt;结合 Smalltalk 对象间发送消息的思想和代码块的能力，我们将变得无所不能。上文中提到 Smalltalk 无需额外的关键字就能实现 &lt;code class=&quot;language-text&quot;&gt;if&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;while&lt;/code&gt; 等的功能。在最后一节中就让我们具体地看一下如何利用这些能力来实现 &lt;code class=&quot;language-text&quot;&gt;if&lt;/code&gt; 的功能吧。这也是我当初学习 Smalltalk 时最喜欢的一个例子。&lt;/p&gt;
&lt;p&gt;首先，我们需要定义出 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt; 这两个常量：&lt;/p&gt;
&lt;p&gt;先定义出 &lt;code class=&quot;language-text&quot;&gt;PBoolean&lt;/code&gt; 类，然后将 &lt;code class=&quot;language-text&quot;&gt;PTrue&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;PFalse&lt;/code&gt; 作为它的子类。&lt;code class=&quot;language-text&quot;&gt;PTrue&lt;/code&gt; 类的实例就可以认为是 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt; 也是同理。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;Object subclass&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;#PBoolean&lt;/span&gt;      
  instanceVariableNames&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  classVariableNames&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  poolDictionaries&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  category&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pupeno&apos;&lt;/span&gt;
PBoolean subclass&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;#PTrue&lt;/span&gt;      
  instanceVariableNames&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  classVariableNames&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  poolDictionaries&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  category&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pupeno&apos;&lt;/span&gt;
PBoolean subclass&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;#PFalse&lt;/span&gt;      
  instanceVariableNames&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  classVariableNames&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  poolDictionaries&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;      
  category&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pupeno&apos;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;其次，我们定义 &lt;code class=&quot;language-text&quot;&gt;ifTrue:else:&lt;/code&gt; 消息。它接受两个代码块作为参数。当 &lt;code class=&quot;language-text&quot;&gt;PTrue&lt;/code&gt; 类的实例接收到消息时，它执行 &lt;code class=&quot;language-text&quot;&gt;ifTrue:&lt;/code&gt; 携带的代码块，忽略 &lt;code class=&quot;language-text&quot;&gt;else:&lt;/code&gt; 携带的代码块。反之亦然。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&quot;PTrue 中定义&quot;&lt;/span&gt;
ifTrue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; do else&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; notdo  
  &lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt; do value
  
&lt;span class=&quot;token comment&quot;&gt;&quot;PFalse 中定义&quot;&lt;/span&gt;
ifTrue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; notdo else&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; do  
  &lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt; do value&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;再次，我们定义一个 &lt;code class=&quot;language-text&quot;&gt;MyClass&lt;/code&gt; 类用作测试，它可以接受 &lt;code class=&quot;language-text&quot;&gt;equals:&lt;/code&gt; 消息判断两个对象是否相等。为了方便起见，我们永远返回 &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt; 。实际中可以自行实现其业务逻辑。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;equals&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; other  
  &lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt; PTrue &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后，我们就可以愉快地使用 if-else 了：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;smalltalk&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-smalltalk line-numbers&quot;&gt;&lt;code class=&quot;language-smalltalk&quot;&gt;m1 &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; MyClass &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
m2 &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; MyClass &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m1 equals&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; m2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; ifTrue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;  
  Transcript show&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;They are equal&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; else&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;  
  Transcript show&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;They are not equal&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;写在最后&lt;/h2&gt;
&lt;p&gt;本文介绍了 Smalltalk 中最为基础的语言规则与设计思想。在它出现的时候，它引领了面向对象、IDE 等等前沿的概念，而即使放到现在，它的语言特性也毫不过时。愿大家都能在了解 Smalltalk 的过程中有所收获。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Update Log 20171005]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/update-log-20171005/</link><guid isPermaLink="false">https://maliut.space/p/update-log-20171005/</guid><pubDate>Thu, 05 Oct 2017 19:43:00 GMT</pubDate><content:encoded>&lt;p&gt;距离上次更新也快一年了。虽说有篇元编程的文章不过也是坑着，别的文章也是坑着。一年以来也发生了很多事情（主要还是进入职场了吧喂），不过其实一直没有忘记这里，只是自己懒罢了。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;于是趁着这次一时兴起，也对网站做了次大更新。契机应该是多说的倒闭（虽说也是很久之前了），然后网传规定说评论都要实名制，于是要将网站的评论系统迁移到 Disqus 。实际上呢更新了很多东西，在这里记录一下，也算是对自己的一次 restart 和激励好了。&lt;/p&gt;
&lt;p&gt;主要做了三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;网站主题换用 Typescript&lt;/p&gt;
&lt;p&gt;老的主题有些看厌了，选择了很久选中了这个主题，简洁而富有设计感。字体不再是微软雅黑了，依然很舒服。图片会有一个 114% 的放大，很适合图片的展示，甚至想把每篇文章配上一张美美的图。不过这样的话倒是有点像微信公众号了（笑）。&lt;/p&gt;
&lt;p&gt;个人感觉，这个主题放些设计的东西是很漂亮啦，放些严肃的博文倒是感觉有点怪？不过好看就是正义啦。 114% 放大效果用来展示 UML 图倒是有点谜 23333 虽说 class=small-img 就可以取消掉。&lt;/p&gt;
&lt;p&gt;为了配合这个主题没有个人展示的区域，新建了一个 about 页面，不过仍在施工中。另外配置了一下站内的搜索，为了构建成功还升级了一波 node 和 npm ，倒也是挺有趣的。&lt;/p&gt;
&lt;p&gt;此外还对这个主题做了一些魔改：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;略微放大了一下正文和 archive 页的字体。原来的字体实在小了些。&lt;/li&gt;
&lt;li&gt;修改了底部社交网络链接的图标。主要增加了原来没有的 steam 图标，也顺便删了些用不到的。依旧使用 icomoon 的服务。&lt;/li&gt;
&lt;li&gt;修改了头部的 logo ，毕竟是 blog 的名字不得不改。还好主题作者提供了原始的 psd 文件，少了很多工夫。名字变长以后， css 也要做出一定的修改。副作用是移动端上来看显得不太美观，不过先不管了。&lt;/li&gt;
&lt;li&gt;原本使用的 adobe typekit 字体貌似要收费，于是干脆弃用之。 fallback 方案看着也还行， 如果有更好的字体欢迎推荐。同样，更换了字体以后也需要小改 css 。&lt;/li&gt;
&lt;li&gt;对日期格式也做了修改。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;评论系统换用 Disqus 。需要翻墙以后才能看到。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;全站升级 https 。这里参考了&lt;a href=&quot;https://molunerfinn.com/hexo-travisci-https/&quot;&gt;这篇教程&lt;/a&gt;。旧文章里还有几张图片是 http 的，有空最好也做个替换。不过没有大碍，就先放进 TODO List 好了。是不是也抽空做个 favicon 呢？ emmm&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;很惭愧，就做了一些微小的工作，谢谢大家。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[JavaScript 元编程]]></title><description><![CDATA[最近阅读《Ruby 元编程》一书，拍案叫绝，又想到 JavaScript 语言和 Ruby 类似，相当灵活且具有一些元编程的特性，而且最近以及不久的将来都可能需要和 JavaScript 打交道，因此希望对照着这本书，探究些 JavaScript…]]></description><link>https://maliut.space/p/javascript-metaprogramming/</link><guid isPermaLink="false">https://maliut.space/p/javascript-metaprogramming/</guid><pubDate>Fri, 30 Jun 2017 20:51:39 GMT</pubDate><content:encoded>&lt;p&gt;最近阅读《Ruby 元编程》一书，拍案叫绝，又想到 JavaScript 语言和 Ruby 类似，相当灵活且具有一些元编程的特性，而且最近以及不久的将来都可能需要和 JavaScript 打交道，因此希望对照着这本书，探究些 JavaScript 元编程的特性。&lt;/p&gt;
&lt;!--more--&gt;
&lt;h1&gt;语言构件&lt;/h1&gt;
&lt;p&gt;元编程是对语言构件的操作，因此为了了解元编程，需要先了解它们语言构件的异同。&lt;/p&gt;
&lt;p&gt;在 Ruby 中，一切都是对象。一个对象则必然属于一个类。一个类是 &lt;code class=&quot;language-text&quot;&gt;Class&lt;/code&gt; 类的对象，而所有的类都是 &lt;code class=&quot;language-text&quot;&gt;Object&lt;/code&gt; 类的子类（严格来说是 &lt;code class=&quot;language-text&quot;&gt;BasicObject &lt;/code&gt;）。可以用一张图来展示：&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/80c1264b3ffa9bf98d891b9755130b6a/15ec7/meta1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.30541871921182%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAIBAwX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHbS8FJD//EABoQAAEFAQAAAAAAAAAAAAAAAAEAAhAhMUH/2gAIAQEAAQUCJrjcgL//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAWEAEBAQAAAAAAAAAAAAAAAABBACD/2gAIAQEABj8CZz//xAAaEAACAgMAAAAAAAAAAAAAAAAAARARMbHB/9oACAEBAAE/Ib2Wg66DWs4H/9oADAMBAAIAAwAAABDzz//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABsQAQEAAwADAAAAAAAAAAAAAAERACExEFGB/9oACAEBAAE/EAOA8piNwD6NPuDsWE34eZzz/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;img&quot;
        title=&quot;&quot;
        src=&quot;/static/80c1264b3ffa9bf98d891b9755130b6a/15ec7/meta1.jpg&quot;
        srcset=&quot;/static/80c1264b3ffa9bf98d891b9755130b6a/efcb5/meta1.jpg 203w,
/static/80c1264b3ffa9bf98d891b9755130b6a/02e66/meta1.jpg 405w,
/static/80c1264b3ffa9bf98d891b9755130b6a/15ec7/meta1.jpg 690w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;在 JavaScript 中，一切依然是对象。但是它并不是基于类和继承构建的，而是基于独特的原型机制。JavaScript 的对象分为普通对象和函数。而函数除了普通调用以外，还可以通过 &lt;code class=&quot;language-text&quot;&gt;new&lt;/code&gt; 关键字调用，生成一个新的对象，而此时这个函数被称为构造函数。从形式上看，就像 Java 之类的用法。而每一个函数都会有一个 &lt;code class=&quot;language-text&quot;&gt;prototype&lt;/code&gt; 属性（除 &lt;code class=&quot;language-text&quot;&gt;Function.prototype&lt;/code&gt;），每一个对象都会有 &lt;code class=&quot;language-text&quot;&gt;__proto__&lt;/code&gt;  属性指向它的 &lt;code class=&quot;language-text&quot;&gt;prototype&lt;/code&gt; 。当访问一个对象的属性时，先在这个对象自身上找，若没有则在 &lt;code class=&quot;language-text&quot;&gt;__proto__&lt;/code&gt; 属性上寻找。这就是字面上“原型机制”的意义，即通过同一个构造函数构造的对象，都会表现得像这个构造函数的原型。&lt;/p&gt;
&lt;p&gt;JavaScript 的原型机制很有趣但令人费解。这里也只是蜻蜓点水，详细的解释可以参考网上的各种文章和知乎问答。只是随着 JavaScript 的用途越发广泛，对于适应了主流语言的程序员来说，很难利用它的原型机制构建起可靠的大规模应用，因此不停地有各种将 JavaScript 类化的方案。一种比较常规的做法是，将构造函数作为类，对于各个对象的属性，定义在对象自身上；而对于各个对象共享的方法，定义在类的原型对象上。对于继承，由于类的原型对象也是一个普通的对象，将其 &lt;code class=&quot;language-text&quot;&gt;__proto__&lt;/code&gt; 属性指向父类的原型对象，就可以继承父类的方法了。（讲的比较简略，读者可以自己推演下。另外，这并不是完美的做法，关于 JS 的继承衍生出不下五六种方法，感兴趣可以自行搜索）&lt;/p&gt;
&lt;p&gt;面对程序员们的广泛需求，在最新的 es6 标准中引入了 class 语法糖，帮助程序员以他们熟悉的方式来编写代码。 class 的基本原理正是上述的方案（当然，总有各种细节上的注意点，具体请参考 es6 标准的解读）。模仿上面那张图可以得到：&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e5734b8d7a9c7f0452a451ed93ef1e15/15ec7/meta2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.87192118226601%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAIABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAECBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAdyAuD//xAAXEAADAQAAAAAAAAAAAAAAAAAAAREh/9oACAEBAAEFAqbEf//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABgQAAIDAAAAAAAAAAAAAAAAAAAQIUGh/9oACAEBAAY/ArI1f//EABgQAQEAAwAAAAAAAAAAAAAAAAEhABCR/9oACAEBAAE/IUIzjRJSlz//2gAMAwEAAgADAAAAEHAP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGhABAQACAwAAAAAAAAAAAAAAAREAMSFB8P/aAAgBAQABPxA2Z6ZggJZNL3OHWfaZ/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;img&quot;
        title=&quot;&quot;
        src=&quot;/static/e5734b8d7a9c7f0452a451ed93ef1e15/15ec7/meta2.jpg&quot;
        srcset=&quot;/static/e5734b8d7a9c7f0452a451ed93ef1e15/efcb5/meta2.jpg 203w,
/static/e5734b8d7a9c7f0452a451ed93ef1e15/02e66/meta2.jpg 405w,
/static/e5734b8d7a9c7f0452a451ed93ef1e15/15ec7/meta2.jpg 690w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;首先 JavaScript 中并没有和 Ruby 意义类似的 Module。其次，Function 在很大程度上类似其他语言的 Class 概念。再次，我没有在图中列出 superclass 关系。如果按照 es6 的 extends 关键字，那么 superclass 一定指代正确的父类，而对于图中这三个顶层的类，若按照上文的逻辑来分析，则会显得有些奇怪。&lt;/p&gt;
&lt;p&gt;姑且称图中的 &lt;code class=&quot;language-text&quot;&gt;Object&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;Function&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;MyClass&lt;/code&gt; 是顶层类。按照上文逻辑分析它们的 superclass，则都指向 &lt;code class=&quot;language-text&quot;&gt;Function.prototype&lt;/code&gt;，这是一个函数对象。而它的 superclass 指向 &lt;code class=&quot;language-text&quot;&gt;Object.prototype&lt;/code&gt;，这是一个普通对象。尽管一个普通对象不再是一个类了，但你仍然可以找到它的 &lt;code class=&quot;language-text&quot;&gt;__proto__&lt;/code&gt; 属性指向 null。在网上也能找到继承这三个奇怪的东西的分析。扯得有点远了。由于我们希望对程序员隐藏 JavaScript 的原型机制，我个人并不希望将 &lt;code class=&quot;language-text&quot;&gt;Function.prototype&lt;/code&gt; 视作所有类的父类。尽管如此，你依然可以在 &lt;code class=&quot;language-text&quot;&gt;Function.prototype&lt;/code&gt; 中定义所有函数对象可以访问的属性/方法，在 &lt;code class=&quot;language-text&quot;&gt;Object.prototype&lt;/code&gt; 中定义所有对象可以访问的属性/方法。&lt;/p&gt;
&lt;h1&gt;元编程&lt;/h1&gt;
&lt;p&gt;在上文中，我们实际上已经完成了第一步：将 JavaScript 类化。接下来，不妨按照《Ruby 元编程》中所列出的各种“法术”，寻求在 JavaScript 中的实现方法。&lt;/p&gt;
&lt;h2&gt;内省&lt;/h2&gt;
&lt;p&gt;内省使对象可以在运行时查询关于自己的一些信息，例如对象的类，类的父类，对象拥有哪些属性和方法等等。我们知道 JavaScript 中的类就是一个对象的构造函数，正好 JavaScript 提供了 &lt;code class=&quot;language-text&quot;&gt;obj.constructor&lt;/code&gt; 属性来查询对象的构造函数。得到父类同样简单，根据继承的原理，一个类的父类正是这个类的原型的原型所在的类，即 &lt;code class=&quot;language-text&quot;&gt;class.prototype.__proto__.constuctor&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;至于对象身上的属性和方法， es6 提供了 Reflect 类来查询，其中很多方法来自于原先的 Object 类。将类命名为 Reflect 更好地体现了“反射”这一意图。&lt;/p&gt;
&lt;h2&gt;动态派发&lt;/h2&gt;
&lt;p&gt;调用一个对象的方法，实际上是向这个对象发送一条消息。基于这个思想，Ruby 中可以使用 &lt;code class=&quot;language-text&quot;&gt;obj.send(&quot;method_name&quot;, args)&lt;/code&gt;，动态地调用一个方法。方法名可以使用字符串拼装出来，因此大大提高了代码的灵活性。而在 JavaScript 中，方法不过是对象的一个函数属性，而属性本身也可以用字符串表示，也就是说，动态派发在 JavaScript 的设计中，并不是什么高级的用法，仅仅是一种再自然不过的语言特性罢了。在 Javascript 中上述代码等价于 &lt;code class=&quot;language-text&quot;&gt;obj[&quot;method_name&quot;]()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;作用域绑定、define/delete、eval 和 new Function&lt;/p&gt;
&lt;p&gt;不能做的要素：类宏&lt;/p&gt;
&lt;p&gt;proxy能做的：methodmissing&lt;/p&gt;
&lt;h1&gt;一些感想&lt;/h1&gt;</content:encoded></item><item><title><![CDATA[Awesome Soccer - Rocking Soccer 游戏插件]]></title><description><![CDATA[Awesome Soccer 是为 Rocking Soccer 提供的 chrome 插件，为 chrome 用户在游戏时提供更好的体验。 你可以从此处获取插件并查看使用方法。 Rocking Soccer…]]></description><link>https://maliut.space/p/awesome-soccer/</link><guid isPermaLink="false">https://maliut.space/p/awesome-soccer/</guid><pubDate>Sat, 13 May 2017 20:15:00 GMT</pubDate><content:encoded>&lt;p&gt;Awesome Soccer 是为 &lt;a href=&quot;https://rockingsoccer.com/zh/soccer&quot;&gt;Rocking Soccer&lt;/a&gt; 提供的 chrome 插件，为 chrome 用户在游戏时提供更好的体验。&lt;/p&gt;
&lt;p&gt;你可以&lt;a href=&quot;https://github.com/maliut/awesome-soccer&quot;&gt;从此处获取插件并查看使用方法&lt;/a&gt;。&lt;/p&gt;
&lt;!--more--&gt;
&lt;br&gt;
&lt;p&gt;Rocking Soccer 是一款国外的足球经理网页游戏。相比于网络上形形色色的足球经理的游戏，它一方面比较良心，不需要氪金变强，而是靠长期的努力养成变强；另一方面，它的整个游戏系统也较为简单，无需投入很大的时间和精力去研究，适合休闲游玩。&lt;/p&gt;
&lt;p&gt;有段时间里我和群友们纷纷入坑了这个游戏，而游戏需要一些数据分析辅助决策以及优化体验，因此诞生了这个插件。&lt;/p&gt;
&lt;p&gt;在这个插件之前，还有一个 rshelper 插件，提供了球员经验分值计算的核心功能，对球员的培养选择很有帮助。但是当我们想要更多的功能时，却发现原插件的代码也比较一言难尽（也许程序员看别人的代码都是这么想的吧，看自己以前的代码也是一样的（笑））。既然如此，那就自己写一个吧——于是这个插件就诞生了。&lt;/p&gt;
&lt;p&gt;插件目前主要提供了 3 个功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;球员经验评分和未来经验、主属性预测。这部分基于 rshelper 插件。&lt;/li&gt;
&lt;li&gt;多货币显示。鼠标移到价格上时会显示 RSD 、日元、人民币三种价格。这是因为群友们分布在各个不同的国家，看到的价格都有所不同，需要有个统一的单位进行交流。&lt;/li&gt;
&lt;li&gt;本地表格排序。在球员界面对各个属性排序时无需刷新网页，忍受很慢的速度。相当于把服务端排序改成了客户端排序。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;虽然目前的功能还不是很多，但从开发理念上，我们希望一开始就给项目定下一个良好的框架，即对游戏中的球员、建筑等各个概念进行建模，使用面向对象的思想组织代码，而不是过程式一把梭，从而在未来增加和修改功能时能够有良好的维护性与可扩展性。&lt;/p&gt;
&lt;h2&gt;手机版&lt;/h2&gt;
&lt;p&gt;考虑到如今大部分时间大家都在手机上玩游戏了，而 chrome 插件是没法在手机上使用的，因此也萌生了手机版 Awesome Soccer 的想法。&lt;/p&gt;
&lt;p&gt;从技术上来说有两种实现方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;套壳浏览器：整个应用就是一个 WebView，在加载 Rocking Soccer 的网页后注入 JavaScript 代码，实现与浏览器插件同样的功能。优点是开发简单，缺点是体验较差，毕竟 Rocking Soccer 老掉牙的页面根本不支持手机展示，在手机上操作十分蛋疼。&lt;/li&gt;
&lt;li&gt;Headless 模式：即我们在手机上重新实现整个游戏的界面，而需要交互的部分就通过发请求的方式去实现。这样做的用户体验是比较好的，缺点是工作量较大，且需要一定预研。而且有些功能例如比赛直播等应该还是要跳到网页去。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不过无论哪种模式，内在的逻辑应该都是可以与网页端的插件共用的。这也是使用面向对象进行逻辑层开发的好处。&lt;/p&gt;
&lt;p&gt;目前已经使用第一种方案做了 Android 版的应用出来，不过代码已经不可考。&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;当然，随着大家逐渐弃坑，这个项目也不再维护了。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[关于密码管理的一个想法]]></title><description><![CDATA[…]]></description><link>https://maliut.space/p/thought-for-password-management/</link><guid isPermaLink="false">https://maliut.space/p/thought-for-password-management/</guid><pubDate>Mon, 19 Sep 2016 22:26:49 GMT</pubDate><content:encoded>&lt;p&gt;密码的重要性毋庸多言，尤其是在当今人们越来越离不开互联网的情况之下。最近各种密码泄露事件层出不穷，社工技术也是比想象中厉害很多。虽说密码的原则，例如不要重复啊，要复杂啊，大家都知道，然而真正能做到的人也是少数，大多数仍然是偷懒几个密码走天下。然而这终究不是长久之计，因此一些密码管理工具诸如 1Password， LastPass 等开始流行起来。这类工具一般的做法是需要你设定一个主密码，进入后就可以查看各个网站的单独密码，也可以为你生成比较安全的密码，这样比较灵活。但它们仍然需要在本地保存各种密码（或许是用主密码作为 key 的对称加密？），并且会把这些信息加密后保存在网上。然而需要保存密码本身加之安全性全系于主密码一身，仍让人对其可靠性产生担忧。于是突发奇想，能否设计一套密码管理机制，使得我们并不需要保存每个密码，而是在需要时由其实时计算而出。&lt;/p&gt;
&lt;!--more--&gt;
&lt;h1&gt;初步设想&lt;/h1&gt;
&lt;p&gt;在这个机制中，用户输入一个自定义 key，得到一个输出，即计算出来的密码。密码计算出后可以自动复制到剪贴板，用户不需要知道密码的具体内容。&lt;/p&gt;
&lt;p&gt;假设存在这样一个系统，那么它需要保证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;针对不同的网站，需要生成不同的密码。这个可以通过自定义 key 来保证。&lt;/li&gt;
&lt;li&gt;对于同样的自定义 key，不同的用户得到的密码应当不同。否则 key 实质就成了密码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个机制不需要联网，也不需要本地存储各种密码。key 实质就是一个助记符，用户需要将它记在心中，但相比于各种复杂密码，记忆的难度已经大大降低。此外，它不应该被除主人外的其他人使用，否则其他人只要知道了主人的各个 key，也就相当于知道了密码。为此我们仍然需要一个主密码。不过和传统意义上的主密码不同，由于我们的机制并不记录密码或联网，我们并不能验证主密码的正确性，而是将主密码和自定义 key 共同组成算法的输入。这样他人即使知道了主人的 key，但因为不知道主密码，得到的输出也将是错误的。为此，虽然不是必需（因为主密码只是参与运算的一个普通字符串），我们仍然建议主密码足够复杂（例如包含数字、大小写字母、特殊符号且足够长）以至于难以猜测（因为自定义 key 为了方便记忆必然是容易被猜到的）。&lt;/p&gt;
&lt;p&gt;让我们回到上文的第二点，因为同样的输入必然需要得到同样的输出，而算法本身并不能改变，除非你对每个用户都特殊定制一个软件。也不能使用什么元编程之类的手段，因为若不直接编译好，就等于公开了你的计算算法从而增加被破解的可能性。最简单的方法便是在用户第一次使用时产生一个随机数或者 UUID 加入输入。不过加入了主密码以后也就相当于解决了这个问题。然而主密码是用户自己选取的，仍然存在重复的可能，因此保险起见仍然可以加入随机数或者 UUID，将重复概率降低到约等于 0。&lt;/p&gt;
&lt;h1&gt;如何更换单个密码&lt;/h1&gt;
&lt;p&gt;更换单个密码是一个常用功能，假如怀疑某个密码遭遇到泄露可以方便地重置它而不影响其他密码。目前我们使用主密码 + key + 一次生成的随机数来唯一确定一个密码。最简单的想法是用户修改自己的 key 就行。然而 key 作为助记符，若是频繁修改，也会给用户带来记忆上的困难。而修改主密码和随机数即意味着所有的密码都要进行修改，这显然是无法接受的。照道理应该通过修改随机数实现，但为了不影响其他密码，我们必须建立每个 key 和随机数之间的映射关系，即需要保存这些 key 和对应的随机数，而这却恰恰是设计之初想要极力避免的。&lt;/p&gt;
&lt;p&gt;为此需要设计一个妥协的方案，目前的设想如下：生成一个随机数的表，这不得不保存为文件。另外将 key 和对应的随机数组合，通过非对称加密后保存为文件（可以进一步通过改变组合方式、加 salt 等增加安全性）。这样即使文件被他人得到也无法破译出 key 及对应关系。软件工作时也无从得知，而是需要穷举随机数表，加密后比对，才能确定该 key 对应了哪个随机数。此外，对于大多数密码而言，它们永远不会变化，即不需要使用新的随机数，也就不需要保存在文件中。对于一些特定的重要密码，用户也可以选择修改自己记忆中的 key 值，通过增加记忆成本来降低风险。&lt;/p&gt;
&lt;h1&gt;多端的数据一致性&lt;/h1&gt;
&lt;p&gt;需要输入密码的场合，电脑端仍是大头，然而手机端也越来越需要了，为此需要开发手机上能用的版本。移植工作简单，但对于这样一个非联网的密码管理工具，如何才能保证电脑上和手机上得到的密码是一样的？自然主密码， key 和算法都是一样的，那么只需要保证随机数表相同。然而你不能要求用户来同步整个随机数表。容易想到，我们只需要系统生成一个 appkey，再由算法利用这个 appkey 生成整个随机数表即可。用户在一个新的端第一次使用时需要输入这个 appkey，这样问题解决。&lt;/p&gt;
&lt;h1&gt;软件升级&lt;/h1&gt;
&lt;p&gt;理想状态下只要做出来就永远不会改变，然而信息技术的发展日新月异，也不能预测这个工具中使用的各种算法等是否需要更改。一旦更改则用户的全部密码都需要改变，而用户很难记起自己通过这一工具“保存”了多少密码。在此提个可行的解决方法：使用时双开前后两个版本，先用新版本得到的密码尝试，若错误则说明该网站的密码依然需要通过旧版本得到，此时用户应当及时修改密码为新版本。长此以往所有密码均被修改，版本过渡完成。&lt;/p&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;p&gt;相比于目前常用的密码管理工具，本工具不联网，不保存，减少密码本身被简单泄露的危险。但也丢失了灵活性，即用户不能保存自定义的密码，而是只能用计算出来的密码。不过一般来说这都是更加安全的，既然选择使用密码管理工具，就应该有这样的安全意识。&lt;/p&gt;
&lt;p&gt;这套机制中，用户需要记忆自己的主密码和各网站的 key。工具则需要提供一个随机的 appkey，以及三套算法：通过 appkey 可重复地生成随机数表、非对称加密验证 key 和随机数的对应关系、通过主密码 + key + 随机数可重复地计算出密码。&lt;/p&gt;
&lt;p&gt;那么什么时候能做出来呢？待续……&lt;/p&gt;</content:encoded></item><item><title><![CDATA[TIS-100]]></title><description><![CDATA[通过编写汇编代码，根据给定的输入得到正确的输出，以完成关卡目标。《TIS-100》初看并不像是一个游戏：黑乎乎的堪比命令行的界面，不存在的新手教程，查看游戏手册也仅仅是在游戏外给你打开一个 PDF 文件而已。也许它更像是一个游戏化的编程模拟软件。 我并不知道 TIS-10…]]></description><link>https://maliut.space/p/tis-100/</link><guid isPermaLink="false">https://maliut.space/p/tis-100/</guid><pubDate>Sun, 22 May 2016 15:24:31 GMT</pubDate><content:encoded>&lt;p&gt;通过编写汇编代码，根据给定的输入得到正确的输出，以完成关卡目标。《TIS-100》初看并不像是一个游戏：黑乎乎的堪比命令行的界面，不存在的新手教程，查看游戏手册也仅仅是在游戏外给你打开一个 PDF 文件而已。也许它更像是一个游戏化的编程模拟软件。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;我并不知道 TIS-100 的架构是否有历史上的原型，但其的确与现实的汇编有着不小差别。指令集被精简得可怜，只支持简单的赋值、加减、判断等操作，并不支持位运算和寻址等。如果说这是为了让之前没有汇编知识的玩家能够低门槛上手的话，那只提供了一个半寄存器、限制代码的长度和宽度，就是单纯为了增加游戏的难度了。此外，和其他大多数编程题材游戏不同，它提供了多个按拓扑关系连接的节点（相当于一个处理器内核），节点与节点之间通过端口传递数据，这就需要你合理利用各个节点拆分代码，或是提供额外的寄存器，或是用来并行处理以提高程序的性能。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0fc128823297074cd4dedf57e5e03b8e/ad00e/image.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABsUlEQVR42m2TyXLCQAxEB2yDjQHbLMaYfYcLcOBAFTf+/58UPZFJpVI5qGaR1N3SaFyn05GyLCXLMmHfarXMwjCUJEkkjmM5nU5yu91kOBzauSgKabfbZnmey3Q6tXzyHAG/QdI0tSTnnEwmExmPx5bAnvV8Psv7/ZbH4yGbzUZK9VffPggdbKvVStbr9edCgWBmJQj17LG6rmW32xkQ1bDv9XrmY4XUzWYzuV6vcr/fZTQamZMyIKqqyhSTjHFHaZDM5/NPC7QyBPS7XelprMOJA7AoiqTRaBjz4XCwJO5fr5eVSCX9fl8Gg4H5SiXMm025x4nUYSRtzTVAmAnygL5kFAL8fD5N2Xa7NfXWFlW21nMRBvLUx5tHLUmCQBxNJ+g/QHyAHo9HU8a5q6Xh6yhIre1KVWGlYIMglLjRFLdcLg0M46UBpG+MDMppNn3mdXkUgCn9crn89DBVAZkSdbXPVjJgOP8q5NXoIXdNVQIwpftJWCwWn9nTM8qHPKqfM9gBQJUP4u732KDMAxLD+HhylNvYAEggbPv93hofaHMxyiaRQcc4U4X/Qax8Bsz/nC+70wXm22US3AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;image&quot;
        title=&quot;&quot;
        src=&quot;/static/0fc128823297074cd4dedf57e5e03b8e/d7542/image.png&quot;
        srcset=&quot;/static/0fc128823297074cd4dedf57e5e03b8e/2efce/image.png 203w,
/static/0fc128823297074cd4dedf57e5e03b8e/1d180/image.png 405w,
/static/0fc128823297074cd4dedf57e5e03b8e/d7542/image.png 810w,
/static/0fc128823297074cd4dedf57e5e03b8e/d56b5/image.png 1215w,
/static/0fc128823297074cd4dedf57e5e03b8e/ad00e/image.png 1366w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;虽说其与现实编程存在差距，但倘若接受了这个设定，《TIS-100》就是一个相当不错的解谜游戏。对数据进行加减乘除、比较、排序、作图等，这些看似简单的功能，在游戏严苛的限制下，也成为了极具挑战性的谜题。如果你还嫌不够，你还可以尝试下完成增加了更多限制的游戏成就。此外，游戏自带了关卡编辑器，你可以下载其他玩家的谜题，也可以使用 Lua 语言编写自己的谜题考验他人。&lt;/p&gt;
&lt;p&gt;不过过关仅仅是这游戏的一半。在你运行程序的时候，游戏会记录下你的程序使用的节点数、指令数和运行的 cycle 数（相当于运行的时间），并在好友中排名。这给了玩家不断优化程序性能的动力，也为游戏带来了较高的可重玩价值。由于这三项指标往往难以同时达到最优，游戏为每个谜题提供了 3 个存档以便于你试验各种代码结构和算法。完成一个谜题已属不易，找到它的最优解更是难上加难。当然，在这之后超越了好友，排名第一的成就感也是难以用语言来形容的。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5398ca1c92a4b8d81ebcd401e40c45ff/ad00e/image_1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.15763546798029%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABi0lEQVR42nWSyWoCURBF23lWnOcRjIoiqAhqRHCna3/Cvf/PjadIiQnJovpVv+HWqSGIRCJKJBJKJpO2hkIhxeNxFYtFVatVlUolVSqVl49xxh3uuoXDYVuDWCymdDqtTCZjaxAE6na7Go/HarVams/n2m63Wq1WWi6X6vf7mk6narfbJoLxBjC0AhxX98NOp6NCoaDZbKZ8Pm+X6/W6crmcneMj6G9/CPJhM5vNqlwumzCCkDweD91uN41GoxcRj0kZenynRBALqFuz2dRkMtFms7Ho/CPIGYGoH6KNRsMoOEfwvXYvQS5ANBgM7JE34HK5aL1eW8rsQfVOSMBoNPp/yo7vNYSGh9D0er0XIdPghJ6uExLABDGoPM3a06e79/vdAqRSKaNkVPhndJgEJ3MxA8IZDodaLBY2HhS/84zOmFyuV308a0u3CYYQgSkP1H92GUEf3lqtZvPYf0b/PBxMdLfbme33e51OJx2PR53PZyP07rqgZeApewQ2w9STDn5b6Jd57bx+73tfijgHXHfdvGcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;image 1&quot;
        title=&quot;&quot;
        src=&quot;/static/5398ca1c92a4b8d81ebcd401e40c45ff/d7542/image_1.png&quot;
        srcset=&quot;/static/5398ca1c92a4b8d81ebcd401e40c45ff/2efce/image_1.png 203w,
/static/5398ca1c92a4b8d81ebcd401e40c45ff/1d180/image_1.png 405w,
/static/5398ca1c92a4b8d81ebcd401e40c45ff/d7542/image_1.png 810w,
/static/5398ca1c92a4b8d81ebcd401e40c45ff/d56b5/image_1.png 1215w,
/static/5398ca1c92a4b8d81ebcd401e40c45ff/ad00e/image_1.png 1366w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;值得一提的是，《TIS-100》并不单纯是一个程序题的集合，它还自带了一个幕后剧情。手册封面的书信、里面的涂涂画画、每个谜题中 DEBUG 节点中的日记、全是乱码的隐藏谜题，似乎都在说明这台机器背后的故事。然而作为一个还没打通第一页关卡的菜鸡码农预备役，我目前并没有余裕去探索，而 steam 评测和社区中讨论这个的人似乎也不多。希望有大神能还原它背后的故事。&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 810px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/acc06028ac8b7600d22cc06df0924ca0/ef916/image_2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.65024630541872%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABnklEQVR42l1Tx67CQAzM/38JB04IhBCdCCR6ES2AaKL33mGexsiQhyXHu5v1ZMZ2jGAwiNFohNfrhefzKVHXuqf9nqv9vje63S4Oh4NsHo/HP2ficrkE73Q6HfT7fez3e1wuF0ynU/HdbieEGGnG/X7H9XqVS/Tz+fyJBBwMBvD5fOIOhwPRaBTJZBJerxcejweRSAQulwulUukNyCRlRPDb7SauH2q1WojH45LodDoRCoWQyWQQCATgdrs/gNls9gt4Op2wWCxEnvp6vcZwOJQkMiGA3+8X8EQiIUxN00ShUEAsFkOxWPxKJqCCqK9WK4mszXa7xWazwfF4RK1WE2A2M5VKCXg4HEaj0RBVhr2zBGb9KN/eOe0kjUosy0K9XpdGNZtNVKtVzGazN0MdBdatXC6L53I5KXKlUhEpBGAt2c35fC5ToU2zm4yNApIVJTGB8plEyWTR6/UkTiYT5PN5YcXa8b2SYb4A6oJOEI4JzT7UduM5u69TwDtc66gZfOgQsz6UyWKn02mRSjbtdls+NB6PRQUBmEOQ3z/oD2KbNcvzjgF2AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;image 2&quot;
        title=&quot;&quot;
        src=&quot;/static/acc06028ac8b7600d22cc06df0924ca0/d7542/image_2.png&quot;
        srcset=&quot;/static/acc06028ac8b7600d22cc06df0924ca0/2efce/image_2.png 203w,
/static/acc06028ac8b7600d22cc06df0924ca0/1d180/image_2.png 405w,
/static/acc06028ac8b7600d22cc06df0924ca0/d7542/image_2.png 810w,
/static/acc06028ac8b7600d22cc06df0924ca0/ef916/image_2.png 815w&quot;
        sizes=&quot;(max-width: 810px) 100vw, 810px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;然而《TIS-100》终究只能是一个小众的游戏。对于只想体验下编程思想的非程序员，相比于《Human Resource Machine》和《Lightbot》之类，它缺乏形象化的反馈，远不如它们直观和友好。对于希望用编程发挥创造力的玩家，尽管游戏有沙盒模式，但也只能做做简单的数字比较和画个分辨率感人的像素图，可发挥的余地也不大。而以程序员的角度，则可能会不满足于其过于精简的指令集和诸多严苛的限制。不过与其他编程题材的同类游戏相比，《TIS-100》很好地塑造了编程体验本身，开关机的动画音效、手敲代码的输入方式以及程序运行时的嗒嗒声等，都能让你身临其境地感受到你是在一台老机器上从事着真正的编程工作。如果你对这样的体验很感兴趣，也愿意静下心来盯着屏幕烧上几个小时的脑子，那么我向你强烈安利这款游戏。&lt;/p&gt;
&lt;p&gt;最后说句题外话，看到有不少人希望通过这种类型的游戏来学习些编程，我想说人生苦短，还是学门 Python 、Ruby 这样的脚本语言比较好。拿这样被极度精简的类汇编语言只会让你事倍功半。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Unity 使用多个 Android 插件与二次开发]]></title><description><![CDATA[利用 Unity 开发 Android 应用时往往需要调用 Android 原生开发的方法，或是导出 Android 工程进行二次开发。单独一个问题很容易就能在互联网上找到答案，但当需要导入多个 Jar 包（ Android 原生方法通过打成 Jar 包在 Unity…]]></description><link>https://maliut.space/p/unity-with-multiply-jars/</link><guid isPermaLink="false">https://maliut.space/p/unity-with-multiply-jars/</guid><pubDate>Wed, 16 Mar 2016 19:51:05 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;warning&quot;&gt;本文的写作时间距今已久，其中涉及到的 API 可能发生了较大的变化，且 Unity 集成 Vuforia 已有了官方支持。本文的解决方案可能不再适合您当前遇到的问题，请酌情谨慎参考。&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;利用 Unity 开发 Android 应用时往往需要调用 Android 原生开发的方法，或是导出 Android 工程进行二次开发。单独一个问题很容易就能在互联网上找到答案，但当需要导入多个 Jar 包（ Android 原生方法通过打成 Jar 包在 Unity 工程中使用）、又需要导出 Android 工程继续开发时，就有可能遇到问题了。&lt;/p&gt;
&lt;!--more--&gt;
&lt;blockquote&gt;
&lt;p&gt;本文记录了解决过程。想看结论及解决方案可以直接拉到最后。&lt;/p&gt;
&lt;p&gt;本文所述的解决方案不具有普适性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;场景&lt;/h1&gt;
&lt;p&gt;场景来源于一个实际面对的任务，即在「使用高通 Vuforia 的 Unity 工程」中「调用 Android 方法（即引入新的 Jar 包）」，并「接入 KTPlay 的 SDK 」。由于 Vuforia 的 Unity SDK 本身就引入了 Jar 包，若引入新的 Jar 包，就会牵涉到如何修改共同的 XML 文件，如何处理和已有 Jar 包的关系等问题。而后者 KTPlay 的接入需要 Unity 工程导出 Android 工程继续开发。这本来并不会有问题，但事实上 Unity 使用了 Jar 包后进行二次开发的确会出现一些变化。当然这也是因为我半吊子水平，对 Unity 和 Android 开发都不够了解。不过总会有这样的时候，人也是这样过来的，网上难以轻易找到的东西，总归做个记录有利无弊。&lt;/p&gt;
&lt;h1&gt;使用第二个 Android 插件&lt;/h1&gt;
&lt;p&gt;Unity 调用单个 Android 插件的方法其实网上都找得到，主要的思路是新建一个 Android 工程，在里面编写好方法后，打成 Jar 包放入 Unity 的插件文件夹中。然后利用 Unity 中的 AndroidJavaClass 来调用 Android 工程中的方法。但是在 Vuforia 工程中本身已经用到了 Android 插件，如果把新的内容覆盖进去，别的倒是没什么，但是 AndroidManifest.xml 文件却会被覆盖掉。这个文件是对 Android 应用很关键的配置文件， Vuforia 插件显然也用到了这个文件，将其覆盖掉明显是不行的。&lt;/p&gt;
&lt;p&gt;于是我开始搜索网上的解决方案。一开始搜索的是如何在 Unity 中导入2个 Jar 包，但根本无法找到答案。倒是很多此类问题中提到了他们要解决的也是关于 Vuforia 工程导入 jar 包的问题，这启发了我，最后在 Vuforia 的官网文档中找到了官方撰写的教程。这对我也是一个启示，搜索问题时要先 specific 再 general 。&lt;/p&gt;
&lt;p&gt;我的解决参考了 &lt;a href=&quot;https://developer.vuforia.com/library/articles/Solution/How-To-Use-Android-Plugins-in-Unity-Apps&quot;&gt;官方文档&lt;/a&gt; 和 &lt;a href=&quot;http://blog.csdn.net/xv_ly15/article/details/9314199/&quot;&gt;这篇文章&lt;/a&gt; （讲的是通常调用 1 个 Jar 包的情况）。两篇文章都挺好，就是由于作者和读者的思路问题在细节上理解容易出偏差，我是综合了两篇文章并自己试验后最终成功的。&lt;/p&gt;
&lt;p&gt;具体可以顺着官方文档来，首先新建工程，但是注意&lt;strong&gt;新建工程的包名要与 Unity 工程导出 Android 应用时使用的包名一致&lt;/strong&gt;，这会省很多麻烦。官方文档似乎没说明这一点。然后导入 Unity 和 Vuforia 相关的 Jar 包。注意 classes.jar 随 Unity 版本的变化，放置的位置也有区别，需要自己找一找。编写完以后需要导出 Jar 包，在工程属性中勾选 isLibrary 然后可在工程目录中找到自动生成的 Jar 包。然后把 Jar 包复制到 Unity 插件目录下，并修改  Unity 目录下 AndroidManifest.xml 文件中的主 Activity 名。（不能直接覆盖这个xml文件，因为 Vuforia 在里面也有些配置）。以上两步官方文档都说的很好，不过它用的是一个简单的 toast 的例子。实际我们开发时往往还会用到一些第三方的 Jar 包、 so 文件等，因此也要把这些文件也放入 Unity 插件目录对应位置下；使用这些东西导致的  AndroidManifest.xml 文件修改（如增加权限等）也需要在 Unity 目录下 AndroidManifest.xml 文件中对应修改。如果有素材变动啥的最好把 res 文件夹也复制过去。这里官方文档就说把 Jar 包放过去，可能是觉得这些都是常识性问题吧，我说的就比较细了，新手说不定觉得那些第三方的 Jar 和 so 都打包进自己的 Jar 了呢？（虽说看文件大小还是很容易判断的）&lt;/p&gt;
&lt;p&gt;顺便一提，如果你自己的 Jar 用到了第三方的 Jar 包，那么做完导入 Unity 后那些第三方 Jar 包的地位和 Vuforia 的两个 Jjar 包目测是平等的。而我们在开发自己的 Jar 时并没有用到 Vuforia 那两个 Jar 包的东西，是不是说明如果我们不用 Vuforia 那两个 Jar 包里的东西，不导入那两个 Jar 包也没事？待测试。&lt;/p&gt;
&lt;p&gt;还有，至于我说为什么没有普适性，因为我们是用自己的包名、主方法名取代了 AndroidManifest.xml 原来的相应内容，看来 Vuforia 的两个 Jar 包并不需要这两个东西。因此这两个 Jar 包和我们自己写的 Jar 包有很大的区别。虽然如此，应该不会有个第三方 Jar 包需要让你修改这些才能加进去吧……&lt;/p&gt;
&lt;h1&gt;加入插件后的二次开发&lt;/h1&gt;
&lt;p&gt;其实就是关于 KTPlay ，这个游戏内社区总的来说挺好用的。接入 Unity 的过程比较奇葩，首先要在 Unity 中做相关的配置，然后还要导出 Android 工程，在主 Activity 的 onStart 等一系列方法中加入相关的代码。其实也很简单，我试验的时候也很顺利，以至于当我把导入插件后的实际项目再拿去加入 KTPlay 却发现毫无反应时，大脑简直一片空白，连大概的可能性都猜不出。后来还是顿悟了，因为测试后发现不是 KTPlay 有问题，而是二次开发加入的相关代码根本没有被执行，然后就想到了，当之前加入 Android 插件后，这个应用的主方法已经不是那个 UnityActivity 了，而是自己编写的插件中的那个 Activity （由于之前更改了 AndroidManifest.xml ）。于是问题解决。&lt;/p&gt;
&lt;p&gt;当然这样就十分有趣了，因为我编写插件在前，引入 KTPlay 在后，也就是说没引入 KTPlay 之前就加入了它的相关代码。当然这并不是一件坏事，事实上反而是一件好事，因为这样你可以直接在 Unity 中打包 apk 了，而不用再使用蛋疼的 Eclipse 。KTPlay 可以考虑下这样的用法啊。&lt;/p&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;p&gt;对于此类需要在 Unity 中使用多个 Android 插件，并进行二次开发的问题，需要注意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新建 Android 插件的工程时，确保包名与 Unity 导出 Android 的包名一致。&lt;/li&gt;
&lt;li&gt;导入 classes.jar 和其他 Unity 插件工程下的 Jar 包和 so 文件。&lt;/li&gt;
&lt;li&gt;事先观察需要二次开发的内容，把二次开发需要在主 Activity 中改动的地方事先改动好（主 Activity 就是你现在写的插件的 Activity）。&lt;/li&gt;
&lt;li&gt;工程属性下勾选 isLibrary 导出 Jar 包。&lt;/li&gt;
&lt;li&gt;把导出的 Jar 包和你开发插件过程中另外用到的第三方 jar 包和 so 文件复制到 Unity 工程的相关目录下。 res 文件夹也复制过去。&lt;/li&gt;
&lt;li&gt;相应修改 AndroidManifest.xml （而不是粗暴覆盖原有的文件）。&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[Hello World Again and Again]]></title><description><![CDATA[终于在 Github…]]></description><link>https://maliut.space/p/hello-world-again-and-again/</link><guid isPermaLink="false">https://maliut.space/p/hello-world-again-and-again/</guid><pubDate>Wed, 09 Mar 2016 00:01:04 GMT</pubDate><content:encoded>&lt;p&gt;终于在 Github 上开了静态博客。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;从知乎上看到然而找不到原文的大大曾经说过写博客的三个阶段，正好和我的经历完全吻合。曾经的百度空间，得益于百度，还有不少关键词排名靠前的文章，虽然现在看来都是些小学生烂文章罢了。其实还是挺怀念当时的心态，敢写，有自信，甚至将其当作一件很自豪的事情。虽然都是些垃圾文，然而随着百度空间的灰飞烟灭，现在就是想看也看不到了。还有那个益达，也许要不是现在在这里写这个，我已经将它永久遗忘了吧，还有我甚至也是有网友的人[破涕为笑]……别的零零碎碎也有不少吧，在当时那个博客流行的时代，随便啥网站都愿意配一个博客系统来留住用户。那个时候微博还没有被人听说，腾讯微博还叫滔滔，轻博客更是以后的事。而现在点点网早已时常抽风，只有宿舍二楼开水房墙上的告示贴纸还诉说着它曾经的辉煌。只剩下 Lofter 。&lt;/p&gt;
&lt;p&gt;后来就是自搭博客的历程了，这得从我的建站经历说起。始于小学毕业的那个暑假从 5d6d 开始，后来的 Edicy ， Weebly ， Tap.cn ……还有好多我记不起来的名字，其中好多也都消失在了历史的长河里。虽然这些本质上和提供博客的服务并无太大不同，但对于习惯了博客的我，能建立一个论坛，一个主站，还是一个非常令人激动的事情。我也忘记了我是怎么进入虚拟主机的这个大坑，很早在 6R 的某个帖子下听说了 kilu.de 并觉得以后会有用便加入了收藏夹，而又在好久以后不明但又确定地觉得它就是我所听说的那个“虚拟主机”。然后看教程，搭 Discuz! ，虽然一片陌生却也尝试成功了，渐渐开始熟悉相关的流程和概念。&lt;/p&gt;
&lt;p&gt;提到了 kilu.de 就又不得不说到 clistock 。虽然在 kilu.de 上做了很多尝试但 clistock 无疑是付出心血最大的一个，从搭建到选模板选图标选插件设置版块主站运营搞校网和贴吧然后还把走廊电脑的主页都设了一遍，虽然如今只剩下了 favicon.ico ，还有 QQ 每周一封啥都没有的数据分析邮件。还是要感谢当时的环保社还有后来的文学社我也不知道我怎么抱到的大腿总之作为圈外人士却也经历了很多事情。当时的自己还是 too naive 觉得能找到免费的资源是一件很了不起的事情然后用着那么慢的服务器，晦涩的域名，还有进入时的大幅广告，包括后来甚至被成为了危险网站。不知道那些素不相识的同学们为了提交一个三行诗还要来到这种可疑的地方忍受麻烦的服务的时候是一个什么心态，是新奇冲淡了疑惑还是心里鄙视了我好几十遍呢。不过对我来说那大概也是一段梦一般的时间吧，看着我熟悉的不熟悉的那么多人能够用上我的服务。不过有些事情，从一开始就是注定的，就像我即使用上了最好的服务也无法阻止论坛等在互联网上的整体式微。我的努力只不过是那个从 ASP 末期随各种论坛走来的一点情怀。想来这种情怀，从小六起，到了大学也没有消散。&lt;/p&gt;
&lt;p&gt;的确，有些事情，从开始就注定了。比如遇到 6R ，比如进入 xs 的随机座位，比如决定推广 MAP ，比如我接触到独立博客。不知道什么时候接触的概念，但注定了以后迟早会成为其中的一员。实话说我根本不知道独立博客有什么用，明明基本功能都是那样，都能绑独立域名，论自由度，百度空间也能自己写 CSS ，即使独立博客你可以改源文件，大多数时候我们也只是选择一个既定的主题，不会自己费力修改。相反，走独立意味着你要选择一家靠谱的服务提供商，自己上传安装程序，设定数据库，即使静态空间也要搭各种环境，处理各种匪夷所思的问题。免费空间倒的倒，难用的更加难用，我最终还是意识到没有免费的午餐。这个时候付出的还有金钱成本。我想这只能用独立精神来解释。不像各种博客服务为了留住用户努力把自己变成一个大社区，独立博客让自己成为茫茫互联网海洋中的一个孤岛，而你就是这个孤岛完全的主人，即使是孤岛，即使岛很小。（当然也有些独立博客是朝着扩大知名度建立自己品牌的方向去努力运营的，那其实就是一个普通网站了）&lt;/p&gt;
&lt;p&gt;真正开始认真建站也是在大学了，用的华夏名网的香港虚机+独立域名，各方面还是差强人意的。后来出了阿里云的云翼计划，说来也有趣，明明是针对学生的优惠计划，我却是在 RS 的广告里看到的。云翼计划实在是太优惠，价钱比虚机便宜，容量、带宽、网速却又远高。于是迅速买下一年。云主机其实就是个远程的只有命令行的 Linux ，自由度是大不少，适合经验人士。我嘛只能靠他给的自带 PHP+MySQL 环境加之少的可怜的 Linux 知识勉强搭上网站。懒得备案，因此也任由域名过期，通过 IP 访问。反正也写得不多。如果一切顺利的话我会就这样用到大学毕业然后再考虑搬去哪里，然而就在最近当我访问的时候却发现无法访问了。进后台看看，先跳出来的却是让我购买云盾服务的广告。然后我就看到主机被攻击。强行重启之后，数据库明显损坏了，我作为管理员也无法登录。真的很想以最大的恶意去揣测阿里云。总之寻找下一个落脚之处就显得非常迫切了，然后这段时间频繁看到 Hexo ，就尝试了。其实以前也不是没有过搞静态的想法，不过那时没搞成功 Jekyll ，就搁置了。这大概也算是机缘巧合吧，总之现在我还是很喜欢这个 blog 的，小规模的用静态也是正好，除了没有用户管理都能够用。&lt;/p&gt;
&lt;p&gt;行文万里，不知所云，看到这里的都是真爱。也是突然想到，时下流行的人工智能、深度学习等等，将大数据用于分析以形成总体意义上的统计学规律。然而对于我们个体而言，我们毕竟独一无二，我们也有必要独一无二。&lt;/p&gt;
&lt;p&gt;愿独立精神长存。&lt;/p&gt;
&lt;p&gt;“好巧，我也不讨厌这样的自己。”&lt;/p&gt;</content:encoded></item><item><title><![CDATA[CSAPP:Bufbomb]]></title><description><![CDATA[from wordpress, 放 tietuku 的图都挂光了 关于 CSAPP 的各个经典的 Lab ，网上的解答已是一搜一大堆。本文的目的是记录自己亲身做完后的一些感悟，不过多探讨具体的做法，而是归纳一些平时看书不太注意的，而需要在 Lab 里理解透彻的地方，和做 Lab…]]></description><link>https://maliut.space/p/csapp-bufbomb/</link><guid isPermaLink="false">https://maliut.space/p/csapp-bufbomb/</guid><pubDate>Sat, 01 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;from wordpress, 放 tietuku 的图都挂光了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;关于 CSAPP 的各个经典的 Lab ，网上的解答已是一搜一大堆。本文的目的是记录自己亲身做完后的一些感悟，不过多探讨具体的做法，而是归纳一些平时看书不太注意的，而需要在 Lab 里理解透彻的地方，和做 Lab 时需要默认知道的一些信息和经验，以便少走弯路。在此基础上略谈各题做法。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;我的解题主要参考了&lt;a href=&quot;http://blog.sina.com.cn/s/blog_65eb367a0101exfa.html&quot;&gt;这篇文章&lt;/a&gt;，尽管网上有许多解答，但这篇图文并茂的博文给我带来的帮助显然是最大的，此外还有&lt;a href=&quot;http://blog.youlingman.info/csapp-bufbomb-lab-solve/&quot;&gt;这篇文章&lt;/a&gt;，通过对比两篇文章，我才能更好理解最后一题。在此表示感谢。&lt;/p&gt;
&lt;p&gt;在我看来，这个 Lab 的主要目的在于，使你对栈帧的结构、调用过程中的寄存器变化（&lt;code class=&quot;language-text&quot;&gt;%esp&lt;/code&gt;,&lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt;,&lt;code class=&quot;language-text&quot;&gt;%eax&lt;/code&gt;）有更加深刻的了解。在这个 Lab 里你必须要对 &lt;code class=&quot;language-text&quot;&gt;leave&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 指令的具体操作有了解，而不能仅仅像上一个 Lab:Binarybomb 那样，仅仅将它们抽象为回到调用过程前的状态。因为通过缓冲区溢出，你已经破坏了原来的栈帧结构。&lt;/p&gt;
&lt;p&gt;关于解题，你需要知道的一些知识：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;栈帧结构&lt;/p&gt;
&lt;p&gt;当调用一个过程后，栈帧结构如下所示&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;......
传入参数2
传入参数1
返回地址
旧的 %ebp
......
栈顶&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;其中 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 指向旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; ， &lt;code class=&quot;language-text&quot;&gt;%esp&lt;/code&gt; 指向栈顶&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;本 Lab 中输入字符串的保存位置和合法的字符串最大长度&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;getbuf&lt;/code&gt; 函数的代码如下（图挂）&lt;/p&gt;
&lt;p&gt;其中调用了 &lt;code class=&quot;language-text&quot;&gt;Gets&lt;/code&gt; 读取输入，但这个大概和系统函数差不多，就不深究了。观察之前系统为这个函数分配了 0x38 的空间，但这并不代表字符串允许长度为 0x38 。下一行的 &lt;code class=&quot;language-text&quot;&gt;lea&lt;/code&gt; 指令计算了 &lt;code class=&quot;language-text&quot;&gt;%ebp-0x28&lt;/code&gt; 的值。这里先验地说明，这个值，即旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 往下 0x28 的空间里，才是存储字符串的地方。如果字符串超出了这个限制，则会继续向上覆盖旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; ，返回地址，etc。
同时这也说明真正合法字符串的长度并不是 PDF 给的代码中规定的值。 0x28 即十进制 40 说明最多 40 个字节，那么 41-44 字节就覆盖了旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; ， 45-48 字节覆盖了返回地址。最后一题的 &lt;code class=&quot;language-text&quot;&gt;getbufn&lt;/code&gt; 也是同理（开始做的时候直接以为是 512 但其实是 520 所以被坑死）。
那么如何查看输入的字符串？以使用 GDB 为例，在 &lt;code class=&quot;language-text&quot;&gt;0x804940d&lt;/code&gt; 设下断点，在此处可以查看 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 的值，其减去 0x28 即字符串的起始地址。也可直接查看 &lt;code class=&quot;language-text&quot;&gt;%eax&lt;/code&gt; ，先验认为从 &lt;code class=&quot;language-text&quot;&gt;Get&lt;/code&gt; 返回后， &lt;code class=&quot;language-text&quot;&gt;%eax&lt;/code&gt; 即保存了返回值的地址。查看时要注意代码为&lt;code class=&quot;language-text&quot;&gt;p (char *) %eax&lt;/code&gt;，因为 &lt;code class=&quot;language-text&quot;&gt;%eax&lt;/code&gt; 的值已经是字符串的地址，不能再在 &lt;code class=&quot;language-text&quot;&gt;%eax&lt;/code&gt; 前加 &lt;code class=&quot;language-text&quot;&gt;*&lt;/code&gt; 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;leave&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 指令&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 指令前必须要 &lt;code class=&quot;language-text&quot;&gt;leave&lt;/code&gt; ，否则无法完全恢复调用前的状态，但也可以自己手动模拟这两个指令的效果。
&lt;code class=&quot;language-text&quot;&gt;leave&lt;/code&gt; 指令等价于：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;assembly&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-assembly line-numbers&quot;&gt;&lt;code class=&quot;language-assembly&quot;&gt;movl %ebp, %esp  # %esp 指向 %ebp 指向的位置，即旧的 %ebp
                 # 由于 %esp 就是栈顶指针， pop 和 push 都相对与这个操作，
                 # 所以相当于 %esp 下面的东西都没了
popl %ebp        # 注意 %esp 指向旧的 %ebp ，所以将 %ebp 恢复为旧的 %ebp
                 # 同时 %esp+4，指向返回地址&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 指令的效果为从栈中弹出一个地址，并跳转到这个地址
因为此时 &lt;code class=&quot;language-text&quot;&gt;%esp&lt;/code&gt; 指向返回地址，因此程序跳转到返回地址，此时 &lt;code class=&quot;language-text&quot;&gt;%esp&lt;/code&gt; 继续 +4 ，指向返回地址的上一个&lt;/p&gt;
&lt;p&gt;利用 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 的性质，本 Lab 中后几题都要用到一个技巧，在这些题中我们要自己输入汇编代码，再修改 &lt;code class=&quot;language-text&quot;&gt;getbuf&lt;/code&gt; 的返回地址，让程序执行我们输入的代码，然后再 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 到别的函数中。但先让程序跳转到我们输入的地方已经使栈帧结构荡然无存，如何再让其 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 到一个特定位置？使用如下代码：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;assembly&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-assembly line-numbers&quot;&gt;&lt;code class=&quot;language-assembly&quot;&gt;push $0xabcdefg
ret&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这段代码使程序 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 到地址 &lt;code class=&quot;language-text&quot;&gt;0xabcdefg&lt;/code&gt; 处。知道了 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 的具体效果，应该不难理解它的原理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;小端法&lt;/p&gt;
&lt;p&gt;这只是一个小提醒了，直接输入地址作为 char 时要反一下，但如果是通过反编译生成的 char 就不用了，因为编译时系统已自动生成。看一下反编译出来的 asm 文件就能明白。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面开始题目相关：&lt;/p&gt;
&lt;h1&gt;Level 0：Candle&lt;/h1&gt;
&lt;p&gt;要求执行完 &lt;code class=&quot;language-text&quot;&gt;getbuf&lt;/code&gt; 后跳转到 &lt;code class=&quot;language-text&quot;&gt;smoke&lt;/code&gt; 中，只要覆盖掉返回地址就可以，把原来的返回地址改为 &lt;code class=&quot;language-text&quot;&gt;smoke&lt;/code&gt; 函数的起始地址，本机上是 &lt;code class=&quot;language-text&quot;&gt;0x08048c8c&lt;/code&gt; ，这样答案可写成&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00
8c 8c 04 08&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;前面 40 个是正常存储的字节，后 4 个是旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 区域，最后 4 个就是返回地址。前面 00 只要是除了 &lt;code class=&quot;language-text&quot;&gt;’n’&lt;/code&gt; 任意均可，最后写成 &lt;code class=&quot;language-text&quot;&gt;smoke&lt;/code&gt; 起始地址的反序。&lt;/p&gt;
&lt;h1&gt;Level 1： Sparkler&lt;/h1&gt;
&lt;p&gt;跳转到 &lt;code class=&quot;language-text&quot;&gt;fizz&lt;/code&gt; 中，但是这时要把你的 cookie 作为参数传进去。第一感觉参数放在返回地址上面，但实际上却不是。 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 完事后 &lt;code class=&quot;language-text&quot;&gt;%esp&lt;/code&gt; 指向返回地址上面一个地址，然后执行 &lt;code class=&quot;language-text&quot;&gt;fizz&lt;/code&gt; 时，先 push 了 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; ，此时 &lt;code class=&quot;language-text&quot;&gt;%esp-4&lt;/code&gt;  ，即原来保存返回地址的地方，然后 &lt;code class=&quot;language-text&quot;&gt;mov&lt;/code&gt; 指令将 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 也指向 &lt;code class=&quot;language-text&quot;&gt;%esp&lt;/code&gt; 指向的地方，再取了 &lt;code class=&quot;language-text&quot;&gt;%ebp+8&lt;/code&gt; 指向的值作为参数，所以参数应该保存在这个位置，即返回地址向上 2 格的位置处！&lt;/p&gt;
&lt;p&gt;（图挂）&lt;/p&gt;
&lt;p&gt;至于为什么和常识不一样，是因为我们篡改了 &lt;code class=&quot;language-text&quot;&gt;getbuf&lt;/code&gt; 的返回地址运行 &lt;code class=&quot;language-text&quot;&gt;fizz&lt;/code&gt; ，而没有 call &lt;code class=&quot;language-text&quot;&gt;fizz&lt;/code&gt; ，因此没有另保存一个返回地址，导致了偏差。&lt;/p&gt;
&lt;p&gt;答案与上题类似，不专门给出，可以自行推算或查看我开头给的两个别人的博客。&lt;/p&gt;
&lt;h1&gt;Level 2： Firecracker&lt;/h1&gt;
&lt;p&gt;跳转到 &lt;code class=&quot;language-text&quot;&gt;bang&lt;/code&gt; 中，但是需要更改一个全局变量。先验地知道全局变量存在程序中一个固定的地址处。&lt;/p&gt;
&lt;p&gt;查看 &lt;code class=&quot;language-text&quot;&gt;bang&lt;/code&gt; 中比较两数的代码：&lt;/p&gt;
&lt;p&gt;（图挂）&lt;/p&gt;
&lt;p&gt;不难发现 &lt;code class=&quot;language-text&quot;&gt;0x804d2a0&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;0x804d298&lt;/code&gt; 中存着全局变量和 cookie ，通过 GDB 调试进一步确定各自保存的东西（我在图中已经标出，另，据说也可通过 &lt;code class=&quot;language-text&quot;&gt;objdump -D&lt;/code&gt; 查看，此处不提）。知道了全局变量的保存地址，接下来就是自己构造代码了。思路如下：让代码以字符串的形式输入，然后修改 &lt;code class=&quot;language-text&quot;&gt;getbuf&lt;/code&gt; 的返回地址为保存的字符串的起始位置，使程序执行这些代码，然后 &lt;code class=&quot;language-text&quot;&gt;ret&lt;/code&gt; 到 &lt;code class=&quot;language-text&quot;&gt;bang&lt;/code&gt; 中。我们需要手写汇编代码，然后使用 gcc 编译后再利用 objdump 反编译得到对应的 char 。代码如下：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;assembly&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-assembly line-numbers&quot;&gt;&lt;code class=&quot;language-assembly&quot;&gt;movl $0xcookie, 0x804d2a0   # 把你的 cookie 放入全局变量，不用反序
push $0x8048d08             # 让这段代码执行完后返回 bang 函数
ret                         # 如果看不懂，复习本文之前的内容&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;反编译得到 char 后任意填充到 40 字节，再任意 4 字节覆盖旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; ，最后 4 字节写为保存字符串的起始地址即可。具体的就不再给出了。&lt;/p&gt;
&lt;h1&gt;Level 3：Dynamite&lt;/h1&gt;
&lt;p&gt;需要使 &lt;code class=&quot;language-text&quot;&gt;getbuf&lt;/code&gt; 返回 cookie 而不是 1 。返回值保存在 &lt;code class=&quot;language-text&quot;&gt;%eax&lt;/code&gt; 中，只需要使 &lt;code class=&quot;language-text&quot;&gt;getbuf&lt;/code&gt; 先返回到我们输入的代码处，替换掉 &lt;code class=&quot;language-text&quot;&gt;%eax&lt;/code&gt; 后再返回原来的 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 中即可。但是我们想让电脑以为什么错误都没发生，而之前的代码都把旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 覆盖掉了，这次不能覆盖它。（可以手工推一下，因为之前都是跳转到别的函数然后退出程序，这次返回到 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 中，而 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 是 &lt;code class=&quot;language-text&quot;&gt;getbuf&lt;/code&gt; 真正的调用者，返回后 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 要读自己指向的内容即旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 才能恢复 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 的栈帧，若被覆盖则不能还原）
因此这题的答案要注意两点。一是手工编写的汇编代码：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;assembly&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-assembly line-numbers&quot;&gt;&lt;code class=&quot;language-assembly&quot;&gt;movl $0xcookie, %eax  # 把 %eax 改为你的cookie
push $0x8048d75       # 返回地址设为 test 中 call&amp;lt;getbuf&amp;gt; 的下一条，即正常状态下的返回地址
ret&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;二是要先用 GDB 调试读出旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 的值，其地址即为此刻 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 的值。然后要用这个值反序写在答案中覆盖旧的 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 的部分。&lt;/p&gt;
&lt;h1&gt;Level 4：Nitroglycerin&lt;/h1&gt;
&lt;p&gt;运行一次程序，会提示你输入 5 次字符串，要求每次的字符串都要相等，但 5 次字符串保存的位置不一样。其余要求同上一题。同样面临两个问题。一是无法得知 &lt;code class=&quot;language-text&quot;&gt;getbufn&lt;/code&gt; 的返回地址应该设在哪里，因不知道每次字符串的保存位置。解决方法是用 &lt;code class=&quot;language-text&quot;&gt;nop&lt;/code&gt; 指令，至于为什么，可以参考文首给出的两篇博客，我虽可理解但也未必讲得出所以然。只想说注意跳转的位置指向 5 次随机数的最高处。这个静下心来想一想可以想清楚。还有就是不知道 &lt;code class=&quot;language-text&quot;&gt;%ebp&lt;/code&gt; 要恢复成哪一个值，解决方法是依靠其与 &lt;code class=&quot;language-text&quot;&gt;%esp&lt;/code&gt; 的数量关系，那两篇博客也讲得很好了，此处不提。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[解决 JSP 与 MySQL 下各种中文乱码问题]]></title><description><![CDATA[近来 Web 课需要处理 JSP、MySQL 前台后台各种，不可避免地遇到了乱码问题。经过无数折腾以后终于（算是）解决，遂将经验总结如下： 本文记录了解决过程。想看结论及解决方案可以直接拉到最后。 Lab7 中已经遇到了连接数据库乱码的问题，当时的情况是： MySQL…]]></description><link>https://maliut.space/p/jsp-mysql-deal-messy-code/</link><guid isPermaLink="false">https://maliut.space/p/jsp-mysql-deal-messy-code/</guid><pubDate>Fri, 22 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;近来 Web 课需要处理 JSP、MySQL 前台后台各种，不可避免地遇到了乱码问题。经过无数折腾以后终于（算是）解决，遂将经验总结如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本文记录了解决过程。想看结论及解决方案可以直接拉到最后。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!--more--&gt;
&lt;p&gt;Lab7 中已经遇到了连接数据库乱码的问题，当时的情况是：&lt;/p&gt;
&lt;p&gt;MySQL 默认安装的编码是 Latin1（ISO8859-1）
Eclipse 默认编码是 GBK ，后改为 UTF-8&lt;/p&gt;
&lt;p&gt;当时的现象：
MySQL 使用 UTF-8 建的数据库&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sql line-numbers&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CHARACTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; utf8 &lt;span class=&quot;token keyword&quot;&gt;COLLATE&lt;/span&gt; utf8_general_ci&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在命令行中可以正常输入中文并在命令行中显示，但是从 Java 中读取而且从 Java 中插入数据库的中文会乱码。&lt;/p&gt;
&lt;p&gt;当时的解决方法：
读取时：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SELECT * FROM user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Statement&lt;/span&gt; stm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createStatement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ResultSet&lt;/span&gt; rs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;executeQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; isbn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; bookName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Username&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    bookName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bookName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;iso8859-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gbk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bookName &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; isbn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;写入时：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;INSERT INTO ISBN_LIST (ISBN, BOOK_NAME) VALUES (&apos;%s&apos; , &apos;%s&apos;);&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isbn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bookName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gbk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;iso8859-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Statement&lt;/span&gt; stm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createStatement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; row &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;executeUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ok,&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; row &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;row(s) affected&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;为什么要转字符而且和 UTF-8 完全无关？不明！&lt;/strong&gt;
（ PS ：后来了解到 MySQL 变量因此转 ISO8859-1 应该是这方面的原因，但是 GBK 为什么有仍然不明）&lt;/p&gt;
&lt;p&gt;后来的探索：
因写 Lab 时中文是从 Eclipse 中打进去的，而 Project 的中文是从表单中用户输入获得的，上文的方法依旧导致乱码。
（ PS ：后来意识到中文从前台传到后台还没写入数据库就已经乱码，因此如果再转一次可能可以解决，但已经没办法回去试了，而且这样也不是长久之计，因为 MySQL 本身设置有问题）&lt;/p&gt;
&lt;p&gt;网上查询各种方法未果。
因为数据库建成时已经指定了 UTF-8 ，因此网上说的临时改字符集的方法都是没有用的。
网上某处看到 &lt;code class=&quot;language-text&quot;&gt;show variables like &apos;character_set_%&apos;;&lt;/code&gt;
查看 MySQL 的环境变量，发现除了指定了 database 是 UTF-8 ，其他有好多 Latin1 ，通过 &lt;code class=&quot;language-text&quot;&gt;SET character_set_database = utf8;&lt;/code&gt; 之类的方法全改成 UTF-8 并若干次重开 MySQL 服务（伴随删除数据表再新建）后发现重启 MySQL 后仍然还原成默认设置。也通过修改 my.ini 尝试过，但是仍有两项为 Latin1
这时候我认为把所有的改成 UTF-8 可以解决问题，因此在这种思路下在网上找到了真正的永久修改的方法：在 MySQL 安装目录下 bin 下找到 MYSQLInstanceConfig.exe 重新配置安装选项，在选择编码时选择 UTF-8 ，这样成功将所有环境变量变成 UTF-8 。（ PS ：这样也解决了 mysqldump 导出后中文乱码的问题）&lt;/p&gt;
&lt;p&gt;然而仍然不成功，而且发现这时候在命令行下也无法输入中文的值了。因为之前也看到过命令行的默认编码是 GBK ，会影响中文的显示（但是试过改成 UTF-8 结果出了一堆窗口上的变化导致不敢乱试而且觉得并不是主要），因此使用 &lt;code class=&quot;language-text&quot;&gt;SET NAMES &apos;GBK&apos;&lt;/code&gt; ，这样可以在命令行显示中文了，但在命令行看并没有什么用，最终还是要解决 JSP 下获取的问题.&lt;/p&gt;
&lt;p&gt;寄希望于能在 JSP 下正常得到，新建了一个 servlet 用来在网页中输出当前数据库的各条记录，使用本文开头 Lab 里所用的转换编码的方法多次尝试，但发现一点用处没有。这使我将目光转向生成 SQL 语句时的编码。生成 SQL 语句后同样按照本文开头 Lab 中的方法对其自身转换编码，虽然回回得到的还是乱码，但是乱码的形式在改变，证明这里可以起作用，或许只要找到正确的转换即可解决。这个时候我注意到之前插的一句调试语句：输出从前台得到的 name 的值，如果从前台得到的就是乱码，那么插进去肯定还是乱码（我也真是迟钝明明之前已经放了这个语句然而写完居然忘记了又去试验别的方法）
发现果然这个值得到的时候就已经是乱码了，观察乱码的形式感觉像是西欧文字，因此将转换的语句写为&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;iso8859-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;果然可以正常显示中文了，我认为至此得到了使用上的解决
以前因为网页啊 Eclipse 啊都是 UTF-8 因此前一个编码认为应该是 UTF-8 ，但是后面不管怎么试都没用。这样确定了前面是西欧文字后，因为 MySQL 等都被我改成了 UTF-8 ，后面自然就觉得是 UTF-8
&lt;strong&gt;但是，为什么从前台得到的中文是西欧文字，并不知道&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;另：此时重开 MySQL 命令行，因为没有设置 &lt;code class=&quot;language-text&quot;&gt;SET NAMES ‘GBK’&lt;/code&gt; ，所以在 MySQL 中显示中文变了乱码，但是既然 JSP 得到是正常了，我认为命令行中 &lt;code class=&quot;language-text&quot;&gt;SET NAMES ‘GBK’&lt;/code&gt; ，或者把命令行编码改成 UTF-8 应该能显示正常。
（ PS: 的确如此）&lt;/p&gt;
&lt;p&gt;// 一天过去了 5.22
昨天只解决了 name 字段的中文乱码问题，而 name 字段只是一个普通的字段。今天当测试 username 字段为中文时出现了其他的问题。
username 字段需要用在登录页面中进行 SQL 查询，而且需要保存在 cookie 中
首先登录时使用中文名字果然无法登录，提示用户名或密码错误，结合昨天的经验容易想到在登录验证的 SQL 查询语句后增加&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;iso8859-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然而并没有什么用（事实上是可以的，只是 Eclipse 的默认浏览器出了问题）&lt;/p&gt;
&lt;p&gt;又想到使用 Ajax 进行实时检测时也要进行 SQL 查询，试验后发现对于中文名字重复无法正常提示用户名已被占用，怀疑是编码的问题但是无论怎么改都没用，而后调试输出发现并没有什么问题，最后去掉了改编码的代码，在 chrome 下可以正常提示，网上的说法是 Ajax 是默认用 UTF-8 传值的，看来这个问题已经得到了解决
PS：此时 Eclipse 默认浏览器还是错误的。这个浏览器真是害人不浅，浪费了我大量时间，现在已经把运行的默认浏览器改成了 chrome &lt;del&gt;，只不过无法通过 &lt;code class=&quot;language-text&quot;&gt;system.out.println&lt;/code&gt; 方便调试了&lt;/del&gt;（更正：可以调试）。
知道了 Eclipse 浏览器的问题，换用 chrome ，登录问题也可以得到解决.&lt;/p&gt;
&lt;p&gt;此外我还探索了为什么前台后台传值使用 ISO8859-1 的问题，网上有说法说要改 Tomcat 的 server.xml 文件。我在安装目录和 Eclipse 的 project explorer 下的 Tomcat 都做了修改然而并没有什么用。或许是我没改对吧。不过更多的地方说更改这个文件并不是什么好方法，因为实际情况下有时不能改变服务器配置。因此作罢。&lt;/p&gt;
&lt;p&gt;随后是 cookie 存中文的问题，存中文会报错说有控制字符。这其实是一个小问题了，查阅网上资料后得知只要 &lt;code class=&quot;language-text&quot;&gt;URLEncoder.encode(username, &quot;UTF-8&quot;)&lt;/code&gt; 即可，这时候中文变成  &lt;code class=&quot;language-text&quot;&gt;%C3%A5%C2%95%C2%8A%C3%A5%C2%95%C2%8A%C3%A5%C2%95%C2%8A&lt;/code&gt; 这样的形式，可以正常存储了，但是在读取时却出了问题。
首先是 JS 读取 cookies 问题，用于在主页显示当前登录用户的用户名。然而用 JS 读出来并解码后发现还是乱码，西欧字符。经过检查， JS 读取 cookies 的内容是没问题的，就是之前存入的 cookies 的内容，这样问题就很明显了。因为从前台到后台得到的中文本来就是西欧字符，为了方便起见我在构成 SQL 查询字符串后进行了转码，这样不用单独对每一个得到的有可能是中文的前台表单元素在后台获得时就挨个进行转码。但是这样的话在存进 cookies 时所用来编码的 username 仍然是西欧字符的 username 。
因此，在保存 cookies 时先将 username 转码再 encode 存储即可。
注意： JS 解码时应使用 &lt;code class=&quot;language-text&quot;&gt;decodeURI&lt;/code&gt; 函数而不是 &lt;code class=&quot;language-text&quot;&gt;unescape&lt;/code&gt; 函数，后者仍然使用西欧编码解码。
注意：这样 SQL 和 cookie 处都有需要解码的地方，感觉看起来略杂乱，觉得还是不能偷懒，在获得前台表单值时就应立即转码。&lt;/p&gt;
&lt;p&gt;接下来是 servlet(JSP) 读取 cookies 问题，用于访问主页时根据 cookies 中是否有值判断应跳转至主页还是登录页面。既然前面已经将 cookies 正确储存了，这里就没什么问题了，直接在得到 cookies 以后 &lt;code class=&quot;language-text&quot;&gt;URLDecoder.decode(cUsername,&quot;UTF-8&quot;)&lt;/code&gt; 即可。
注意：这里的 SQL 查询构建 SQL 语句后就不用从西欧转到 UTF-8 了，因为这里有可能是中文的字段并不是从前台得到，而是从 cookie 中格式正确的中文得到。&lt;/p&gt;
&lt;h1&gt;结论&lt;/h1&gt;
&lt;p&gt;为了消灭乱码，请如下操作：&lt;/p&gt;
&lt;p&gt;一次性操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将 Eclipse 默认编码设置为 UTF-8&lt;/li&gt;
&lt;li&gt;在 MySQL 安装目录下 bin 下找到 MYSQLInstanceConfig.exe 重新配置安装选项，在选择编码时选择 UTF-8&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;（这样也不需要在每次建库时用长长一串指定 UTF-8 了）
3. （补充）Eclipse：Window =&gt; Preference =&gt; Web=&gt; Jsp file下也改编码为 UTF-8 ，这个值不随 Eclipse 默认值改变，如果不改的话每次新建 Jsp 还要手动把三处 ISO8859-1 改成 UTF-8&lt;/p&gt;
&lt;p&gt;编程时需要注意的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;后台接收前台传来的值时，如果值有可能是中文，接收完立即调用 &lt;code class=&quot;language-text&quot;&gt;str = new String(str.getBytes(&quot;iso8859-1&quot;), &quot;utf8&quot;) &lt;/code&gt;转码（立即调用：这样进行 SQL 查询和存 cookie 时都不用额外转码）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;（5.23 PS：按 ltl 同学的方法，在接收值之前加上&lt;code class=&quot;language-text&quot;&gt;request.setCharacterEncoding(&quot;UTF-8&quot;)&lt;/code&gt;可以解决这个问题，照原理来看应该也是可以的，然而我之前试验过没有成功，可能是那时候 Eclipse 自带浏览器的问题，待试验。如果这个证明可行，那么这一点可以和“其他”下的第 2 点归结为同一点，即后台 servlet 的 &lt;code class=&quot;language-text&quot;&gt;doGet&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;doPost&lt;/code&gt; 在开始写代码前都要把 request 和 response 的编码设一遍。）
2. 中文存进 cookies 时对可能为中文的字段需额外编码， &lt;code class=&quot;language-text&quot;&gt;URLEncoder.encode(str,&quot;UTF-8&quot;)&lt;/code&gt;
使用 Java 读取时使用 &lt;code class=&quot;language-text&quot;&gt;URLDecoder.decode(str, ”UTF-8″)&lt;/code&gt; 解码。使用 JS 读取时使用 &lt;code class=&quot;language-text&quot;&gt;decodeURI(str) &lt;/code&gt;解码
3. 以上步骤正确，那么 Ajax 不需要额外操作&lt;/p&gt;
&lt;p&gt;其他：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Eclipse 自带浏览器难以信任，请使用外部浏览器测试。（然而 chrome 多次测试后也出现了不正常的现象，可能需要休息一会？）&lt;/li&gt;
&lt;li&gt;关于在 servlet 中使用 &lt;code class=&quot;language-text&quot;&gt;out.print&lt;/code&gt; 输出引起乱码的问题：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCharacterEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setContentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text/html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCharacterEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;PrintWriter&lt;/span&gt; out &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getWriter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Java 使用 Gson 操作 JSON 数据]]></title><description><![CDATA[Lab8 需要操作 JSON 格式。毕竟用处广泛，而 Java 中有许多包可以用于操作 JSON （参见：http://www.json.org/） 起初使用 org.json ，但是将对象保存为 JSON…]]></description><link>https://maliut.space/p/java-use-gson/</link><guid isPermaLink="false">https://maliut.space/p/java-use-gson/</guid><pubDate>Fri, 12 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Lab8 需要操作 JSON 格式。毕竟用处广泛，而 Java 中有许多包可以用于操作 JSON （参见：&lt;a href=&quot;http://www.json.org/&quot;&gt;http://www.json.org/&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;起初使用 org.json ，但是将对象保存为 JSON 时似乎需要将对象的每个属性都手工写入，而我的设想是自动探测对象的所有属性并写为 JSON （毕竟重复劳动）。遍寻网络无果，后来在某个角落发现 Gson 似乎可以实现。遂用之，发现功能强大。&lt;/p&gt;
&lt;!--more--&gt;
&lt;h1&gt;生成 JSON&lt;/h1&gt;
&lt;p&gt;需要生成 JSON 的数据类：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Player&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; deposit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; deposit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deposit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; deposit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;用于生成的代码：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gson&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Gson&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Player&lt;/span&gt; players &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    players&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Gson&lt;/span&gt; gson &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Gson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// create Gson object&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; str &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gson&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toJson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;players&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注：生成 Gson 对象除了用 &lt;code class=&quot;language-text&quot;&gt;new Gson()&lt;/code&gt; 外还有一种据说更高端的方法，反正我也不知道用处和区别就不写了&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;toJson&lt;/code&gt; 方法后的参数可以为对象，也可以为对象数组。都可以生成符合规范的 JSON 格式&lt;/p&gt;
&lt;h1&gt;读取 JSON&lt;/h1&gt;
&lt;p&gt;以从 OpenWeatherMap 的 API 返回的 JSON 为例（查询天气：&lt;a href=&quot;http://api.openweathermap.org/data/2.5/weather?q=London&quot;&gt;http://api.openweathermap.org/data/2.5/weather?q=London&lt;/a&gt;，各参数可参考 &lt;a href=&quot;http://openweathermap.org/weather-data#current&quot;&gt;http://openweathermap.org/weather-data#current&lt;/a&gt;）&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-json line-numbers&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;88319&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;dt&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1345284000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Benghazi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;coord&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;lat&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;32.12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;lon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20.07&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;temp&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;306.15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;pressure&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1013&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;humidity&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;44&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;temp_min&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;306&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;temp_max&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;306&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;wind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;speed&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;deg&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;-7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;weather&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;520&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rain&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;light intensity shower rain&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;icon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;09d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rain&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;light rain&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;icon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;10d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;701&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;icon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;50d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;clouds&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;all&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;rain&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;3h&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这是一个复杂的 JSON 数据，为了读取需要先根据数据生成特定的类结构。结构如下：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WeatherState&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; main&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Weather&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; weather&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt; temp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; temp_min&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; temp_max&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Weather&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;类名随意&lt;/li&gt;
&lt;li&gt;变量名需要严格符合 JSON 中的内容&lt;/li&gt;
&lt;li&gt;内部类需要 static ，这里使用内部类看起来更加清晰，用继承也可以&lt;/li&gt;
&lt;li&gt;变量 private + get/set 方法应该也可以，图方便用 public&lt;/li&gt;
&lt;li&gt;不需要把 JSON 中所有的数据都建构出来，你建了多少就会读出来多少（这一点非常好啊）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用于读取的代码&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; jsonWeather &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* 前面的 Json 数据 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Gson&lt;/span&gt; gson &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Gson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// create gson object&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;WeatherState&lt;/span&gt; w &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gson&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromJson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jsonWeather&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WeatherState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;temp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weather&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以上即为 Gson 的基本用法&lt;/p&gt;</content:encoded></item><item><title><![CDATA[直与弯：我们喜欢的究竟是什么？]]></title><description><![CDATA[草稿 涉及到爱情本质的问题有空再写 喜欢 康德：物自体不可知 我们对外界事物的任何认识，实质上只不过是在脑中建构出关于事物的概念，也就是说我们并不是认识事物本身，而是它带给我们的感觉。 同理，当我们说『喜欢xxx』时，我们喜欢的也并不是xxx…]]></description><link>https://maliut.space/p/bent-and-straight/</link><guid isPermaLink="false">https://maliut.space/p/bent-and-straight/</guid><pubDate>Mon, 08 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;草稿&lt;/h1&gt;
&lt;p&gt;涉及到爱情本质的问题有空再写&lt;/p&gt;
&lt;!--more--&gt;
&lt;h1&gt;喜欢&lt;/h1&gt;
&lt;p&gt;康德：物自体不可知&lt;/p&gt;
&lt;p&gt;我们对外界事物的任何认识，实质上只不过是在脑中建构出关于事物的概念，也就是说我们并不是认识事物本身，而是它带给我们的感觉。&lt;/p&gt;
&lt;p&gt;同理，当我们说『喜欢xxx』时，我们喜欢的也并不是xxx本身，而是它带给我们的感觉。&lt;/p&gt;
&lt;h1&gt;性别&lt;/h1&gt;
&lt;p&gt;性别可以分成生理学性别和社会学性别&lt;/p&gt;
&lt;p&gt;生理学性别就是生理学上根据拥有染色体、器官种类等定义出来的。用哲学一点的话讲，就是根据第一世界的东西定义在第三世界中的概念。&lt;/p&gt;
&lt;p&gt;相对来说社会学性别就是第四世界中的概念（对较为公认的社会学性别来说）。&lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;这就是所谓【可爱的男孩子】，当你不知道他是男孩子之前也会蠢蠢欲动。也用来解释有些bl一个人扮演女生的角色。（当然心理学上也存在男生喜欢肌肉男的这并不否认）&lt;/p&gt;
&lt;p&gt;【所以只是喜欢看jpg也是很正常的啊&lt;/p&gt;
&lt;p&gt;【待续&lt;/p&gt;</content:encoded></item><item><title><![CDATA[游戏的艺术性]]></title><description><![CDATA[这篇文章需要重写 2018.08.0…]]></description><link>https://maliut.space/p/the-art-of-game/</link><guid isPermaLink="false">https://maliut.space/p/the-art-of-game/</guid><pubDate>Mon, 08 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;这篇文章需要重写 2018.08.02&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先我坚信游戏是一门艺术。&lt;/p&gt;
&lt;p&gt;但是游戏之所以成其为艺术，必有其独特之处。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;关于游戏的艺术性，我前后也看了不少他人的观点，接触了不少思想（关于何谓游戏，何谓艺术，以及游戏的艺术性本身），观点也有一个发展的过程。&lt;/p&gt;
&lt;p&gt;现在我的基本观点如下：游戏的艺术性，最重要的在于交互性，这是游戏特有的。（在这里，交互性的定义不应该太过于广泛，比如你被动的看然后心里产生思想，但是并不影响你被看的事物）&lt;/p&gt;
&lt;p&gt;一般我们说游戏很有艺术感，往往是指游戏的音乐啊美术啊之类，这些只不过是音乐艺术，美术艺术用在游戏中，不能将其称为游戏艺术。但是它们也不是独立的，它们用在游戏中，帮助展现了游戏的交互性，因此已经不是纯粹的音乐和美术艺术了。因此它们也是游戏艺术不可分割的一部分。&lt;/p&gt;
&lt;h1&gt;交互性所带来的&lt;/h1&gt;
&lt;p&gt;为什么游戏仅仅多了交互性就能显得如此种类丰富且让人入迷呢？交互性使得表现形式的多样性大大增加，而更为关键的是给了玩家代入感。其他的所有艺术形式，观众永远只能位于观赏者的地位，即使与作品产生共情，也难以改变作品本身。而对于玩家而言，每一位玩家的游戏过程，都是一个不同的故事（不考虑某些特殊、极端情况，下文不加说明时同）。而为了配合这份交互性，很多游戏都是由一个主角作为世界中的观察者，而不是全局视角。这就让玩家真切地感受到自己是这个世界的一份子，而不是世界的旁观者。不可否认人总是关注自己比关注别人更多一点。&lt;/p&gt;
&lt;p&gt;有人认为游戏的交互性让玩家可以改变世界，这说法没有错，但其实不严谨。改变世界只是交互性可以带来的结果之一，但即使并没有改变世界，玩家的输入能够得到反馈，也让玩家有能够改变世界的错觉，甚至在有些游戏中玩家知道不能改变世界，但仍喜欢着交互性本身，这些玩家会对诸如打击感之类的有很高的要求。有些高自由度的游戏像 minecraft ，玩家可以随意改变世界，而 RPG 这种游戏类型，尤其是那些单线的 RPG ，玩家能够操作的仅仅是走路、打怪等，这只是给了玩家自由度的错觉，而结局早已确定。而 never alone 这种游戏（ps：写到这里我正在下载这个游戏，我不确定这里是否合适，这里只是用其指代我心目中的一类理想游戏），交互性的引入根本不是追求改变世界，而是为了提供一种体验。顺带一提，正如果文字教程的效果不如视频教程，而视频教程又不如亲自动手实践，这样的交互性正是未来实现教育游戏化的重要手段。&lt;del&gt;（大蛋糕啊）&lt;/del&gt;&lt;/p&gt;
&lt;h1&gt;游戏的定义&lt;/h1&gt;
&lt;p&gt;游戏的定义是什么？现在我们所说的游戏，颇有点折衷的感觉。要从词语本身来看，游戏就是跟干正事相对的娱乐活动。然而，现在越来越多的游戏不再追求娱乐，而是给人以震撼、感动或是启示等。毕竟在古代小说戏剧什么也不过是娱乐而已，这是游戏词义的正常变化。如果从交互来看，我们和现实世界进行着远远更加频繁和真实的交互，这也是为什么很多人说一切都是游戏。这也是完全正确的，&lt;del&gt;毕竟现实就是个垃圾游戏（当然从这里也有很多可以说，比如为什么人们更愿意沉迷游戏等，不过现在不想扯了，而且已经有很多人扯的很好了）&lt;/del&gt;。当然大部分人的直觉当然是把发生在虚拟中的才叫做游戏，但是随着增强现实的兴起，这一划分界限也在慢慢模糊。比如我带着 Google Glass 玩 Ingress 或者玩体感的 Kinect 算什么？&lt;/p&gt;
&lt;p&gt;游戏和体育的界限也很模糊。本来体育也不过是一些原始的工作（狩猎活动）和游戏活动（篮球etc）而已，只不过后来为了追求更高更快更强而逐渐专业化走上了一条不归路（大误）。现在电子竞技也成为体育运动了。其实这类竞技运动和别的游戏也挺好区分的，前者主要是一局一局的有着一个或几个对手的竞技活动。 66rpg 的某次短篇比赛就曾举例将单纯的黑白棋游戏和一个主角通过黑白棋一步步战胜敌人最终如何如何这两类做对比。其实我个人是挺不喜欢把前者叫做游戏的，不过这样说起来我倒是异端了。不过反正也是个定义的问题也没有对错，曾经也将后者定义为交互艺术，（然而也很难说体育不能成为艺术&lt;del&gt;虽然这样又扯远了但是大概就算说体育是艺术这两个艺术所指代的概念也是不一样的&lt;/del&gt;）。写到这里感觉一些SLG游戏的区分就有点模糊了像三国志啊海岛大亨什么的……大家怎么想呢？&lt;/p&gt;
&lt;h1&gt;如何欣赏一个游戏？&lt;/h1&gt;
&lt;p&gt;从哪个角度评价游戏的好坏呢？当然是有多少玩家氪金了！唔开个玩笑。我尚不知道最好的方法，然而很多人将游戏与电影对比，毕竟它们更为接近。我也上了一学期的电影赏析课，就就着两者的区别和共同点慢慢探索吧。&lt;/p&gt;
&lt;p&gt;一部电影一般来说是讲述一个故事，但其与现实世界的不同在于电影通过剪辑可以打破线性的结构，也可以通过不同的视角来讲故事。但一部电影的目的并不单纯是展示一种剪辑风格，而是将其作为一种手段，为导演所想要表达的内容服务，这有可能是一个好故事，有可能是一种思想，也有可能就是想展现一种镜头语言和剪辑风格。类比到游戏，游戏的交互性也是为主题、故事、或是玩法服务的。&lt;/p&gt;
&lt;p&gt;【待续，待整理&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Hello World Again！]]></title><description><![CDATA[from the start of wordpress@aliyun…]]></description><link>https://maliut.space/p/Hello-world-again/</link><guid isPermaLink="false">https://maliut.space/p/Hello-world-again/</guid><pubDate>Sun, 07 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;from the start of wordpress@aliyun&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们总是希望有一个地方来记录自己，哪怕只是一次又一次的开坑。&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;我们总是渴望被人理解，却又逃避社交，渴望从回忆中得到慰藉。藉由回忆的极端化效应【←我自己编的你来打我啊】而使看待现实的眼光更加消极。&lt;/p&gt;
&lt;p&gt;但是我就是我。追求真实但是明明知道根本找不到真实，追求所谓的真实但有不忍直视。雪之下主张改变，大老师主张面对，这两者又有什么区别呢？关键是，我正走在成为我所希望成为的人的道路上，这就够了。&lt;/p&gt;</content:encoded></item></channel></rss>