<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>云隙随笔</title><description>记录自己的各种想法</description><link>https://www.ender-wiggin.com/</link><item><title>桌游相关 | 如何制作属于自己的魔法运动员卡牌！</title><link>https://www.ender-wiggin.com/posts/bg-magical-athlete-custom-card/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/bg-magical-athlete-custom-card/</guid><description>《魔法运动员》是一款画风魔性的欢乐聚会游戏，本文分享如何借助 AI 制作自用角色卡牌与实物。</description><pubDate>Tue, 05 May 2026 12:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://boardgamegeek.com/image/9106864/magical-athlete&quot;&gt;魔法运动员 (Magical Athlete)&lt;/a&gt; 是一款画风魔性的欢乐聚会游戏。游戏本身也非常支持玩家自己设计角色，因此本文分享一下如何在 AI 的帮助下制作出属于自己的卡牌。这个教程仅用于让玩家生成自用的角色，禁止用于商业用途。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20260505131915814.webp&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最终实物效果如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/9bab92a9f256782b71c5654b5966567d.webp&quot; alt=&quot;9bab92a9f256782b71c5654b5966567d.jpg&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;前置要求&lt;/h2&gt;
&lt;p&gt;本文使用的是 &lt;a href=&quot;https://developers.openai.com/api/docs/models/gpt-image-2&quot;&gt;gpt-image-2&lt;/a&gt; 模型，推荐渠道是 ChatGPT 官方订阅账号。如果没有订阅账号的话，可以尝试使用第三方 API 接入。这里也提供一个可用的第三方平台：&lt;/p&gt;
&lt;p&gt;https://right.codes/register?aff=e09d43ce&lt;/p&gt;
&lt;h2&gt;提示词&lt;/h2&gt;
&lt;p&gt;要让 AI 生成类似画风的图片，需要如下准备：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;原画风参考素材&lt;/li&gt;
&lt;li&gt;卡牌模版&lt;/li&gt;
&lt;li&gt;(可选) 想生成角色的参考素材 (如果你想生成的角色有一定知名度，比如某个动漫主角，不需要素材 AI 自己也能生成)&lt;/li&gt;
&lt;li&gt;提示词&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202026-05-05%20at%2013.36.00%402x.webp&quot; alt=&quot;CleanShot 2026-05-05 at 13.36.00@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;复制内容如下：&lt;/p&gt;
&lt;h3&gt;1、原画风参考素材&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/racer_info_top100_collage_all_cards.webp&quot; alt=&quot;racer_info_top100_collage_all_cards.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2、卡牌模版&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/9b3ffd6c1438fb782f8fd477e0c19f7f.webp&quot; alt=&quot;9b3ffd6c1438fb782f8fd477e0c19f7f.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3、提示词&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;参考图一的艺术风格，为我在图二里面填充空白地方，生成类似风格的角色，需要形象夸张。正确替换图二的模版，除了卡牌名字，其余都使用中文：

卡牌名字：xxxx
角色形象：xxxx
卡牌效果：xxxx
下方小字：xxxx

艺术风格是：稚拙艺术风格，局外人艺术，怪诞卡通角色，粗黑线条，手绘感强，线条不规则，平涂色块，复古90年代卡牌插画风，低俗艺术风格，造型夸张不符合解剖，儿童画风但带荒诞幽默，角色居中，背景简单，高饱和配色。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实物制作&lt;/h2&gt;
&lt;p&gt;这一步设计排版 &amp;amp; 制作。可以自行 P 图，卡背如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/card_back.webp&quot; alt=&quot;card_back.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果可以运行 Python 脚本，或者用 AI 可以帮忙运行，可以尝试使用如下脚本一键排版 &amp;amp; 导出：&lt;/p&gt;
&lt;p&gt;https://github.com/Ender-Wiggin2019/magical_athlete_generator&lt;/p&gt;
&lt;p&gt;生成后，推荐使用 A4 铜版纸双面彩打。&lt;/p&gt;
&lt;p&gt;接下来是角色立牌的制作，方案有二：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;让 AI 生成可裁剪人物立牌，然后自己打印并使用立牌支架固定：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;生成一张图片，把这张图片的人物主体提取出来，并且在每个角色外部填充一下符合背景颜色的圆润外边框，制作成用于打印的贴纸风格，让人物适合裁剪。
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在第一步的基础之上，如果有 3D 打印机，可以 3D 打印一个底座，然后双面贴图。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20260505152410435.webp&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>影评 | 《挽救计划》</title><link>https://www.ender-wiggin.com/posts/review-project-hail-mary/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-project-hail-mary/</guid><description>《挽救计划》是我近几年最喜欢的科幻小说之一，因此对于影视化虽说并不算期待，但也是必须要打卡的。</description><pubDate>Thu, 26 Mar 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;《挽救计划》是我近几年最喜欢的科幻小说之一，因此对于影视化虽说并不算期待，但也是必须要打卡的。但是首批影评让我有些困惑，出现了诸如&quot;同质化&quot;、&quot;合家欢&quot;、&quot;老套路&quot;等字眼。这些是我在看小说时从未想过的。&lt;/p&gt;
&lt;p&gt;观影后我释然了，也似乎发现了原著和电影本质的不同。
原著是生态迥异的外星人科研交流互助，而电影是人类中心主义视角的人与宠物叙事。&lt;/p&gt;
&lt;p&gt;觉得电影同质化是可解释的，Rocky 这个形象的生态位，和史迪仔，小黄人，甚至是忠犬八公没什么本质区别。&lt;/p&gt;
&lt;p&gt;但是原著就有着本质区别。&lt;/p&gt;
&lt;p&gt;从外星人设定的角度来说，Rocky 初登场的 29 倍大气压自带压迫感，这种环境差异第一眼就能让读者意识到 Rocky 是一种迥异的生物。电影中虽然刻意制造了 jump scare，但是很快就变成了用泡泡包裹的太空狗狗。关于氙岩的设定浮光掠影，防护罩仿若宇航员头盔，六进制只字未提，声学设定形如千里耳般的怪谈。&lt;/p&gt;
&lt;p&gt;从形象塑造来说，导演刻意给 Rocky 降智，让他从以为异星工程师，摇身一变为热情小狗狗。不论是一开始冒冒失失闯入人类飞船，还是向人类炫耀自己吃饭的优雅，都颠覆了原著的形象。原著中认为自己种族吃饭不雅的羞涩，可能在导演看来缺少戏剧张力，但在我看来恰恰是外星物种平等交流的微妙距离的体现。&lt;/p&gt;
&lt;p&gt;还有一处我认为很微妙的差异：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;洛基挑选了基本的英语语序。我认为他早早发现我无法生来就记住一切，所以选择配合我的语言系统，而不是教给我他的系统。说实话，我可能看起来相当愚蠢。不过他的某些语法偶尔会穿插进来。他总是以“问题”这个词来结束问句。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;同样的如同人机般的说话方式，在电影视角看起来像是努力模仿人类的小萌宠，而小说则是双方物种互相尊重的科幻乌托邦，这在国内盛行的黑暗森林法则的背景下尤其令人动容。&lt;/p&gt;
&lt;p&gt;从作品气质来说，原著有一种非常克制的冷静感，在危难关头携手理性解决问题，延续了《火星救援》的工程思维。电影中却莫名给情绪升温，Grace 大喊大叫，Rocky 活蹦乱跳，甚至还有意义不明的情感交流。比如，电影中似乎很温情的一段关于伴侣的讨论，在原著里面就是随口一提：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“什么意思？”
“这是我伴侣的名字。”
我瞪大了眼睛。这里有点不对劲！他从没跟我说过他有伴侣！我以为波江座人从不秀恩爱。
“你有伴侣？”
“不确定了，”洛基说，“伴侣可能有了新伴侣。我离开很长时间了。”
“难过。”我说。
“是，难过。但也不可避免，必须拯救波江b。你为♫♩♪♫挑个人类名称吧。”
“艾德里安。”我说。有何不可呢？“人类的称呼是‘艾德里安’。”
“明白。”说完他顺着通道进入实验室。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这种两边都如同人机的说话方式，某种意义上才是精髓，也是这本小说看起来气质不凡的原因。&lt;/p&gt;
&lt;p&gt;让我给分的话，我还是会给电影一个不错的分数，但在我看来只是原著的底子好罢了。影视改编比较失败，但如果细想一些其它影视化科幻，似乎这部电影已经算是可圈可点了。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>所思所想 | 观 CLI-Anything 有感</title><link>https://www.ender-wiggin.com/posts/thought-cli-anything/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/thought-cli-anything/</guid><description>没有什么东西是一成不变的，这点在 AI 时代尤其夸张。</description><pubDate>Thu, 26 Mar 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;没有什么东西是一成不变的。这一点在 AI 时代尤其夸张。&lt;/p&gt;
&lt;p&gt;比如 vscode，我从没有想过，一个曾经的 IDE 集大成者，程序员人手一个的必备软件，突然似乎不再重要了。vscode 官网的标语 &quot;The open source AI code editor&quot; 更加体现出一种追赶 AI 脚步的没落。曾经，它的 copilot 是 AI 应用的先行者，首创的 Tab 提示在当年看起来是如此的新颖。而如今，我已经不再把它作为主要编辑器了。copilot, cline, 甚至 IDE 本身都渐渐模糊。&lt;/p&gt;
&lt;p&gt;无独有偶，wakatime 曾经是我必装的插件。它可以记录下我敲下的每一个字符，见证我的时光流逝。前年，我还构想了一个小的集成应用——类似 QQ 宠物和 vscode-pet，我写代码的时间越长，我的宠物就越可以成长。当年我以为技术不够搁笔了，现在 AI 技术够了，但似乎 wakatime 本身都已经不再重要了。我已经不关心我一天花多少时间写代码了，似乎 token 花费和成果验收才是关键。&lt;/p&gt;
&lt;p&gt;但也不必悲观，虽然程序员群体确实愈加焦虑。Obsidian，在几年前曾经只是 Notion 的某种平替，它主张的 local-first 似乎只是一种设计哲学而非真理。但是随着 Markdown 被奉为 AI 第一语言，Obsidian 瞬间从本地知识库变成了 RAG 的载体，&lt;code&gt;.md&lt;/code&gt; 文件不再是一种普通的文本格式，而是人机交互的桥梁。&lt;code&gt;obsidian-cli&lt;/code&gt; 的推出更是可见 Obsidian 的野心与布局，而 Notion 的 AI 支持似乎还是 Web2 时代的余晖。&lt;/p&gt;
&lt;p&gt;曾经谁能想到几个 Markdown 文件可以在 GitHub 上获得超过千万用户使用的基建库的 Stars？Skills 的推出不只是一种 hype，更是一种思维模式的变化。有人是苦苦祈求不实业的传统软件行业从业者，也有人是某个看起来并不难实现但是成为爆款 Skills 的编写者。&lt;/p&gt;
&lt;p&gt;AI 如同海啸一般瞬间毁灭了很多看似屹立不倒的基建，但似乎又有些事物如同方舟般重生。后者或许很难，但还是有一丝慰藉。&lt;/p&gt;
</content:encoded><category>所思所想</category><author>Ender</author></item><item><title>所思所想 | 在2026年2月聊AI似乎有点为时过晚</title><link>https://www.ender-wiggin.com/posts/thought-ai-2026/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/thought-ai-2026/</guid><description>这本来应该是一篇23年写的文章，不知怎么一转眼就来到了2026年。</description><pubDate>Thu, 26 Feb 2026 01:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2022年底，因为正好在国外，第一时间体验到了 ChatGPT。当时的我被 LLM 的能力所震惊，兴奋地分享了自己的直观感受：&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;zh&quot; dir=&quot;ltr&quot;&amp;gt;ChatGPT多多少少有点强大的吓人了&amp;lt;/p&amp;gt;— Ender (@****) &amp;lt;a href=&quot;https://twitter.com/Ed2021Sz/status/1599206192311857152?ref_src=twsrc%5Etfw&quot;&amp;gt;December 4, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt; &amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p&gt;而如今正在打字的我，脑中萦绕着一句话：&lt;/p&gt;
&lt;p&gt;ChatGPT 发布已经是三年多之前的事情了。&lt;/p&gt;
&lt;p&gt;我曾在 23 年初充满期待地学习 Prompt Engineering，也在 23 年底加入了一家 AIGC 创业公司试图深入探索这一新兴概念。但直到25年中旬，我还是一如既往地手工敲打着代码，用着 Tab 键和 AI 辅助编程。&lt;/p&gt;
&lt;p&gt;但是到了 25 年冬，似乎一切都变了——虽说实际上早有征兆，但是还是有点突然：
早晨睡眼惺忪的我读着 AI 写的 PRD，让 AI 吐出设计文档和代码产出，然后听着 AI 生成的测试用例评审，一边摸鱼看看 GitHub 上分分钟突破几千 Stars 的 99% AI 实现的开源项目，周而复始。&lt;/p&gt;
&lt;p&gt;比起所谓的期待、焦虑与迷茫，我更关注一个实际问题：&lt;/p&gt;
&lt;p&gt;代码，似乎真的不再重要了。&lt;/p&gt;
&lt;p&gt;看着 AI 生成的冗长却能运行的代码，刻板却并无缺漏的文档，我已经不想去了解内部实现参悟曾经奉为圭臬的代码之美了，我现在只想用一个快捷键全部丢给 Agent 并让它总结出一份更加简洁的文档。&lt;/p&gt;
&lt;p&gt;实际上不只是代码，如今的信息生成和获取方式似乎变了，完整但是离散。我想到了刚推出 ChatGPT 我挺喜欢的科幻小说家 Ted Chaing 说的：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.newyorker.com/tech/annals-of-technology/chatgpt-is-a-blurry-jpeg-of-the-web&quot;&gt;ChatGPT Is a Blurry JPEG of the Web&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我在当时感同身受，也洋洋洒洒了几千字分享我的思考，大致内容是应该接受这种压缩，我们未来就是会生活在这样一个模糊的世界。&lt;/p&gt;
&lt;p&gt;但是如今回看，Chat 可能是有损压缩，但是 Agent 并不是。我很喜欢关于 Agent 的一个定义：&lt;/p&gt;
&lt;p&gt;Agent是不断循环最终完成任务的有限状态机。&lt;/p&gt;
&lt;p&gt;换而言之，未来所谓的 AGI，它所执行的任务和生产的内容，是无损的而不是模糊的。这更像是将一张位图放大了数倍——我们人类看着是模糊了，但是信息并没有变化，只是我们人类的尺度有点匹配不上了。&lt;/p&gt;
&lt;p&gt;关于 AI，我有很多能聊的。但是当我今年春节看到饭桌上面几位中年长辈对 AI 高谈论阔时，我突然意识到很多所谓的&quot;聊&quot;只是一种无意义的消遣。因此，我并不准备再泛泛之谈，决定最后写下我个人的经验总结和 26 年的一些规划：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Agent/LLM 就是风口，虽然可以简单抽象为模型基座和工程化，但是Agent显然又不只是工程化这么简单，是有更高层方法论指导的。实际上这个风口已经持续几年了，现在依然还是在 AI Hype 阶段，但显而易见多数人(比如我)并没有把握风口的能力。&lt;/li&gt;
&lt;li&gt;Agent的成功让我想到了那句&quot;强者从不抱怨环境&quot;。尽管这句话可以找出数个逻辑漏洞，但是Agent偏偏就是从不抱怨环境中破茧而出的。不论是身边人自己平常使用AI对话，还是我参与AIGC项目的时候，非常频繁的一句话就是&quot;AI太蠢了&quot;。一些人抱怨AI，另一些人一边搭建着自己的AIGC工程一边选择赌未来AI模型能力提升。但是有人却在模型能力受限的环境中构想出了可以一定程度无视模型能力的Agent。这也是我所钦佩的态度，可以抱怨环境，但是却依然可以超越环境。&lt;/li&gt;
&lt;li&gt;一个有趣的观察是，程序员喜欢分享自己用 AI 又 vibe 出了什么工具，和以前程序员谈论自己的技术细节如出一辙。我愿意称这种为古典理想主义者，我现在依然也属于这一范畴，但是我也开始试图改变——因为现在 AI 远不止可以 vibe 一些小玩具。如果我现在想不出好点子那就应该多学习，如果自己(以及AI)的能力不是边界，那么想象力才是边界了。&lt;/li&gt;
&lt;li&gt;相较于其它行业，程序员可以是对AI能力最有直观感受的一个群体了。26年对于这个职业来说注定会是奇点般的一年，何去何从，如何转型，都是必须要考虑的问题。&lt;/li&gt;
&lt;li&gt;最利好的人群应该是完全不懂编程的人，和非常精通架构的人。前者上手门槛低，但后者上限高。不论是之前 Claude Code 的编译器复刻，还是 Cloudflare 的vinext，都很好的Plan的重要性，并且往往现在 Plan 的只需要那一个人。今年我应当在架构方面刻意学习，多从系统的角度考虑问题。毕竟，代码，真的已经不再是人写的了。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>所思所想</category><author>Ender</author></item><item><title>桌游 | 十个月之后的 SETI 极简攻略</title><link>https://www.ender-wiggin.com/posts/bg-seti-model-2/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/bg-seti-model-2/</guid><description> </description><pubDate>Sat, 06 Dec 2025 22:50:55 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;更多详细文章可以参考 &lt;a href=&quot;https://seti.ender-wiggin.com/zh-CN/posts&quot;&gt;SETI粉丝网站&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;本文力求用最少的文字解决新手最常问的大局观以及开局思路问题。所需的背景知识是 SETI 的模型理论。简而言之就是：&lt;/p&gt;
&lt;p&gt;1钱=1电=1明牌=2声望=2数据=2移动=2随机牌=5分。&lt;/p&gt;
&lt;p&gt;其中，一些模型理论数值和实际体感是不一样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据：随着蓝科的增加，愈加趋近于1&lt;/li&gt;
&lt;li&gt;移动：严格大于0.5，介于0.5和1之间&lt;/li&gt;
&lt;li&gt;随机牌：严格大于0.5，运气好甚至大于1，经验数字是0.75&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下来本文会说明一下游戏的核心策略。&lt;/p&gt;
&lt;h2&gt;核心策略&lt;/h2&gt;
&lt;p&gt;SETI 是一个滚雪球游戏，并且有着一个特性：越早期的资源和分越值钱，不像一些引擎构筑后爆发的游戏，后期可能换分效率不如前期。所以核心策略只有一个：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;尽可能快的启动。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;具体而言，这个&quot;启动&quot;所需的条件是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;足量的钱电收入(定量：&lt;strong&gt;5钱收入&lt;/strong&gt;，其余可以&lt;strong&gt;全部插电&lt;/strong&gt; by SETI 界最强大神——镜神)&lt;/li&gt;
&lt;li&gt;核心科技套装：紫4(扫描后发射)+2至3个蓝科&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;启动之后需要做的事情也就很简单了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不做亏模行动，剩下的行动随便做&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;补充科技，如扫描组件紫2、登陆组件橙3橙4&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但实际上只要有科技套装了，&lt;strong&gt;标动扫描&lt;/strong&gt;都不会亏模，不少大佬也用这个打法轻轻松松高分，有充足的数据支撑。一些常见的亏模行动是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1电走路&lt;/li&gt;
&lt;li&gt;2/3电登陆 (2电一些情况可能还可以)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;开局细节&lt;/h2&gt;
&lt;p&gt;在启动之后怎么玩大家应该都会了，因此主要难点是前期怎么快速运营出这个启动条件。一些思路如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;直接奔着核心科技去，尽可能避免过渡科技。过渡科技指橙1(双发)、紫1(2数据)这种，严格来说橙2这种也算半个过渡科技。这些科技如果背面条件好其实也不差，但是拿去它们的机会成本就是一个核心科技组件，也就意味着可能晚一些启动。科技的权衡属于复杂问题，也正是这游戏有策略深度的地方，让这游戏不至于&quot;被破解&quot;。&lt;/li&gt;
&lt;li&gt;R1 R2是发育轮次 (基础前三轮都是发育轮次)，收入是关键。理论上R1一收入其实已经可以了，如果有合适科技组件甚至可能强于硬凑额外的收入。另外需要把握好环绕/登陆窗口，尤其是一步火星的情况。如果能尽可能多的一步环火，优势会非常大。&lt;/li&gt;
&lt;li&gt;抢25分。分数上限最高的是扇区/环登计分，有这个的情况下是需要布局抢25的，其余优先级都没那么高。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>所思所想 | 关于设定系推理作品原则的思考</title><link>https://www.ender-wiggin.com/posts/thoughts-sheding/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/thoughts-sheding/</guid><description> </description><pubDate>Fri, 03 Oct 2025 18:00:00 GMT</pubDate><content:encoded>&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;h2&gt;相关作品参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;超自然+推理 《名侦探的献祭》 《无人逝去》&lt;/li&gt;
&lt;li&gt;科幻+解谜 《钢穴》《我，机器人》&lt;/li&gt;
&lt;li&gt;伪科幻+推理 《克莱因壶》《象首》&lt;/li&gt;
&lt;li&gt;超自然+推理游戏《Return of the Obra Dinn》《Type Help》《逆转裁判》《命运石之门》&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;个人设想的原则&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;设定&lt;/strong&gt;的规则和边界需要明确&lt;/li&gt;
&lt;li&gt;不能在不留下任何&lt;strong&gt;疑点&lt;/strong&gt;的前提下，隐藏虚假的线索&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;一、设定问题&lt;/h2&gt;
&lt;p&gt;我在一些设定系作品下面，看到最多的吐槽是&quot;我本来期待一个正统的传统本格，结果怎么是一个超自然故事&quot;？&lt;/p&gt;
&lt;p&gt;这种吐槽很常见，比如本来期待一个推理结果根本不是推理而是纯文学，本来期待一个严谨解答结果揭底是什么&quot;这个人身体异于常人可以折叠180度&quot;、&quot;这个头正好飞到了另一个人身上然后巧妙的接头成功了&quot;。这一部分原因可能是读者根本没有了解作品是什么范畴，另一部分原因可能就是作者没有提前定义好边界。&lt;/p&gt;
&lt;p&gt;那么，什么是好的设定呢？以机器人三大定律为例：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第一定律：机器人不得伤害人类个体，或者目睹人类个体将遭受危险而袖手不管
第二定律：机器人必须服从人给予它的命令，当该命令与第一定律冲突时例外
第三定律：机器人在不违反第一、第二定律的情况下要尽可能保护自己的生存&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;读者在一看到这三行，就可以立刻明白：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设定的规则是这三条&lt;/li&gt;
&lt;li&gt;设定的边界是机器人，别的物种没有这个设定&lt;/li&gt;
&lt;/ol&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;p&gt;同时，因为有对读者完全未知的设定，设定出现后的引导是很重要的。需要让读者/玩家知道自己要干什么。比如《Return of the Obra Dinn》中的回溯秒表，玩家玩个几次后就彻底明白机制了，学习成本极低。&lt;/p&gt;
&lt;h2&gt;二、伪线索问题&lt;/h2&gt;
&lt;p&gt;这本身算是后期奎因问题的一个分支了，但是又有一点区别，因为早期推理在十诫的范畴之下都不存在超自然设定。而有了超自然设定之后，一个非常典型的情况就是：&lt;strong&gt;讲述者自己的第一视角是有设定加持的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;比如自己掉 SAN 值近乎疯狂导致眼前所见非实，又或者自己不是人类而是某种另外的物种导致的认知偏差。&lt;/p&gt;
&lt;p&gt;因为只要是设定系作品，那可能性就是无穷多的。这种情况下，应当提供足够的疑点让读者可以质疑，同时对可能性减枝让读者不至于选择困难。&lt;/p&gt;
</content:encoded><category>所思所想</category><author>Ender</author></item><item><title>书评 | 《圣母》</title><link>https://www.ender-wiggin.com/posts/review-holy-mother/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-holy-mother/</guid><description> </description><pubDate>Fri, 23 May 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;个人对《圣母》的评价会从三个方面展开：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;叙事，包括了故事整体基调和推理风格。&lt;/li&gt;
&lt;li&gt;叙诡，包括对叙诡的感受以及合理性分析。&lt;/li&gt;
&lt;li&gt;女性视角与性别话题，书中能很明显的感受到作者在这些方面的编排。&lt;/li&gt;
&lt;/ol&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;保奈美的动机是合理的，但是伪造证据的情节是否合理呢，对此我有一些质疑。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;真琴半夜拎着护具袋出门，妈妈肯定担心地跟了出去。她看见真琴取出男孩的尸体时一定十分惊愕。从切下性器官这点，妈妈懂得了真琴的想法。然后为了不让人看出这是女儿所为，她拼命地做着掩护工作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;回顾保奈美见到真琴的那个夜晚，虽然作者暗示了保奈美发现了真琴脸上的划痕，但是我认为更合理的情况是直接问是什么情况而非犹疑地试探：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;母亲不住地看向真琴的脸上和房间里。“真没事吗？​”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;代入视角的话或许可以解读为是母亲对于真琴负面情绪的担忧不敢直言，但是一道脸上的划痕多数时候都应该是意外而非暴力事件或者自残导致的。并且如果问了，不论当时保奈美已经猜到了谋杀或是没猜到，都不影响真琴会自己做好善后工作，还能避免被二次发现的危险。毕竟即便保奈美自己帮忙善后，真琴也可能会因为缺少这个信息而再次前往。如果说是保奈美不希望真琴再次下手，那么或许在第一次谋杀之后就应该做出一些防范了，而不是一直作为善后者。&lt;/p&gt;
&lt;p&gt;而对于由纪夫的案件，保奈美应该更加没有理由知道吧？案发当时她并不在家，即便她搜查了女儿的房间顶多也就知道女儿涉嫌谋杀了也不能找到抛尸地点，完全无法做出善后行为。因此从逻辑上来说上面”妈妈跟踪“那段话一定是发生在第一次谋杀而非第二次，但是不论是哪一次都意味着由纪夫彻夜善后，即便她丈夫是个人机也会有所察觉，并且真琴在当晚肯定也是非常警觉的。并且从逻辑上说的话，真琴真的会把尸体放在护具袋中大晚上才出门吗，考虑到她母亲的监视程度，自己先躲在外面伺机而动应该更加合理。如果在家里等到深夜才出门，那更合理的解释只能是家中完全没有人，但那天显然只是”家里人都外出“的寻常一天，晚上大家肯定都回来了，包括熏。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;薰用小手握住保奈美的手。之前啃破了的大拇指指尖，长了个类似血泡的东西。“啊，这个啊，是受伤啦。​”&lt;/p&gt;
&lt;/blockquote&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;所以我才觉得女人真是麻烦。真琴在心中咂嘴。真琴长得很标致，至今为止，不光同校的学生，还有许多其他学校的学生如此这般来表达爱意。每次拒绝对方，或是被对方怨恨，都让真琴觉得十分麻烦。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这一段能看出作者在刻意模糊处理，但是整一段的主语都是“真琴”，她拒绝对方，或者“被”对方拒绝，以及后面重复的让她觉得麻烦，那么前面的主体应该是自己觉得麻烦，而不是觉得其他人麻烦。这段话确实是不太通顺的，如果不是叙诡的话肯定不会这么写。但是既然能感受到这一整章性别都比较模糊，那么这里如果要解读的话还是她觉得自己作为女人麻烦更合理一些。&lt;/p&gt;
&lt;p&gt;让我比较确认性别的后面的部分：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对方的眼睛还有些湿，递过来一个女生很喜欢的那种信封，满怀期待地盯着真琴。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;看的时候我感觉送给对方的东西肯定是希望对方喜欢，那么自然真琴就是女生了。&lt;/p&gt;
&lt;p&gt;而保奈美女儿的叙诡确实是非常暗的线，第一章的说法可谓是滴水不漏。作者刻意留了比较明确暗示的地方应该是下面这处，虽然我初次看得时候并没太注意：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“真让人坐立不安啊。竟然盯上这么小的孩子，真是怪物。一想到怪物如今就在附近，或许还在寻找下一个牺牲者，我就……”
恶心的家伙。怪物。女儿，恶心的怪物——保奈美的手哆嗦着。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;到第 16 章的时候，给出了更加明确的信息：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;跟这个孩子在一起，感觉之前失去的宝宝们也都回来了。其中肯定男孩子、女孩子都有吧，所以保奈美决定给宝宝起个男女都能用的名字。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;顺便一提对于保奈美生育的一整段描写仿若是压抑中的救赎，最初的动机化身为了一种执念，观点输出和剧情推动相辅相成，这也是最后揭底的合理性保证。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;所以，听到宝宝第一声响亮的哭声时，充斥在保奈美心中的不是欢喜，而是解放感。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是在一些情节上，感觉作者刻意隐瞒的有些多了，比如在邮箱中发现蓼科秀树时：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“蓼科……秀树。​”保奈美低声念道。&lt;/p&gt;
&lt;/blockquote&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;blockquote&gt;
&lt;p&gt;“啊，难道因为我是女的？​”像是读出了他的想法，谷崎问道。&lt;/p&gt;
&lt;/blockquote&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;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>书评 | 《不存在的骑士》</title><link>https://www.ender-wiggin.com/posts/review-il-cavaliere-inesistente/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-il-cavaliere-inesistente/</guid><description>个人感觉需要阅读完“三部曲”之后才能对卡尔维诺探讨的主题做一个相对系统的思考，因此本书评仅简单介绍一下阅读的感受。</description><pubDate>Wed, 23 Apr 2025 22:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&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;blockquote&gt;
&lt;p&gt;人们的血肉之躯在他心中引出一种类似嫉妒的烦恼，也产生出由自豪感和优越感造成的一阵激动。&lt;/p&gt;
&lt;/blockquote&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;blockquote&gt;
&lt;p&gt;每部骑士小说中必有的错综复杂的惊险故事情节，如今我认为它是一种表面装饰物，一种毫无生气的点缀，是我被罚做的功课中最费力不讨好的部分。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;现在我画的这些曲线就是海水，它们代表一片汪洋大海。这会儿我画阿季卢尔福乘坐的海船，在这边我再画一头巨大的鲸，它背上挂一条写着“奥切亚诺海”的纸带。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;卡尔维诺的书稿化作了修女的画布，以一种动态的方式叙述者故事，仿佛作者在观看一场实时的舞台表演而非尘封的记忆碎片。这是一种饶有趣味的阅读体验。&lt;/p&gt;
&lt;p&gt;最后将修女与女骑士关联起来，让叙事层级与时间线都成为了回环。作者的后记说他是写到最后突发奇想，感觉也挺合理且有趣。&lt;/p&gt;
&lt;p&gt;等有机会再读读另外几本，挺期待的！&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>桌游 | SETI 基础：模型计算</title><link>https://www.ender-wiggin.com/posts/bg-seti-model/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/bg-seti-model/</guid><description> </description><pubDate>Sat, 12 Apr 2025 22:50:55 GMT</pubDate><content:encoded>&lt;h2&gt;SETI 基础：模型计算&lt;/h2&gt;
&lt;h2&gt;重要说明&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;本文的最新版本在 SETI 卡表网站上面 &lt;a href=&quot;https://seti.ender-wiggin.com/zh-CN/posts/model&quot;&gt;https://seti.ender-wiggin.com/zh-CN/posts/model&lt;/a&gt;。博客上的文章仅是备份，可能不是最新版本。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;按我个人的习惯，我是不喜欢在我没有玩明白一款游戏前就写攻略的，这种感觉就像是没准备好就去考试一样。但是现在愈加发现自己已经没有这么多时间去研究一款桌游了，因此不得不做出一些妥协。这也是本文的初衷——仅仅是一些最基础的模型分享，没有任何上层建筑。&lt;/p&gt;
&lt;h2&gt;结论先行&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1 钱 = 1 电 = 1 明牌 = 1 信号 = 2 数据 = 2 宣传 = 2 移动 = 6 分。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1 盲抽牌 = 0.75 钱，外星人卡比基础卡多 0.5 钱收益，1 科技 = 3 钱，1 踪迹 = 2 钱。
打出基础卡牌的成本是 0.5 钱，打出外星人牌的成本是 1 钱。&lt;/p&gt;
&lt;p&gt;下面会逐一论证。&lt;/p&gt;
&lt;h2&gt;基础模型&lt;/h2&gt;
&lt;h3&gt;一、钱 (Credit)&lt;/h3&gt;
&lt;p&gt;游戏的核心资源，也是模型计算的计量单位。钱这个资源最特殊的地方在于，回复手段非常少，除了收入、蓝科、外星人的机制、几张可以回钱的卡牌（应该是只有 5 张）。&lt;/p&gt;
&lt;p&gt;另一方面，钱+牌理论上可以实现几乎所有游戏行动，因此钱是资源转换链的最顶端。&lt;/p&gt;
&lt;p&gt;基于以上的分析，可以得出几个简单的结论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;发射探测器要克制。因为发射是个标动，2 费点一下似乎当前回合没什么负担。第一轮确实一般要发射，但是中期的一个不合时宜的发射可能会直接打乱自己的操作节奏，另一方面实际上一局游戏并不需要这么多次发射（4-6 次环绕/登陆是合适的数字）。&lt;/li&gt;
&lt;li&gt;钱需要过牌量做支撑，这也是卡牌游戏的底层逻辑之一。对于卡牌的部分，会在后文详细介绍。&lt;/li&gt;
&lt;li&gt;由于机制设计，目前的基础卡牌暂不支持电表倒转的变魔术操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二、电 (Energy)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;模型：1 电 = 1 钱&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最基础的模型之一，几乎没有人会认为电和钱的价值是不等价的。但是电的获取方式多了科技板块的奖励，以及部分卡牌会直接送电，还有一张可以伪电表倒转的 &lt;a href=&quot;https://seti.ender-wiggin.com/zh-CN/card/90&quot;&gt;#90 建造燃料箱&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;这里会引入的一个抉择点就是，收入中钱和电的配比问题。从个人经验上来说，电收入不是关键，关键的是当前的存量。如果天天用电走路，3 电登陆啥的，那多少电都是不够用的。不过考虑到 1 钱 2 电的标动扫描，以及潜在的用电大户半人马族，对于电一定要有一定警觉感，电量见底是一个危险的信号，很可能会影响资源运转。&lt;/p&gt;
&lt;p&gt;几个小结论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;电是运转的核心组件，尤其是对于电脑行动和标动扫描来说，有时候多一个电就又是一波操作。&lt;/li&gt;
&lt;li&gt;电不同于钱，电会贬值。如果没有牌做支撑，电会经常用于做一些亏模行动，最典型的就是 1 电走路。这时候电就贬值为 0.5 费了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;三、宣传 (Publicity) &amp;amp; 数据 (Data)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;模型：1 宣传 = 1 数据 = 0.5 钱&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;卡牌左上角的免费行动角告诉了我们，这几种资源理论上是等价的，虽然数据的模型略微有点诡异：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一方面，数据卡牌的比例是最少的，只有 34 张&lt;/li&gt;
&lt;li&gt;另一方面，外星人牌里面强化了宣传 （1 -&amp;gt; 2 宣传），但数据和移动只多了一分，可以理解为这二者本身的价值已经偏高，没法做到双倍收益。&lt;/li&gt;
&lt;li&gt;以及，卡牌中有非常诡异的数值。如下图，右一减右二可得 1 数据 = 0.5 钱，而左一减右二可得 1 电 = 1 数据？对...对吗？不过我个人的理解是，数据会随着蓝科的增加而提高价值，因此收益是会水涨船高的。随着蓝科越来越多，数据的收益也会渐渐趋向于 1，虽然肯定还是达不到。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2002.31.15@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 02.31.15@2x.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;四、牌 (Card)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;模型：1 盲抽牌 = 0.75 钱，1 明牌 = 1 钱，打出卡牌自带 0.5 钱的机会成本&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对于具有展示区机制的游戏，盲抽和精选都是有模型差异的，这里我对比了多张卡牌后得出了这个结论，并且是比较自洽的。盲抽的 0.75 是一个经验数字，因为卡牌下限是弃掉拿 0.5 收益，如果一张牌愿意打出来，那么基本是至少 1 收益的，因此抽牌的收益肯定是大于 0.5 的。&lt;/p&gt;
&lt;p&gt;这里首先需要引入的一个概念就是，&lt;strong&gt;标准行动&lt;/strong&gt;（简称标动）模型和实际行动模型。这个概念在火星里面非常明显，所有人都一眼能知道标动是亏模的。但是在 SETI 里面，经过了一些包装以及本身卡牌比例的问题，标动被视为了一种基准模型，而部分卡牌显得很“超模”。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2002.00.50@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 02.00.50@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以上图为例，看似它们是超模牌，但实际上它们是真正意义上的&lt;strong&gt;均模牌&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发射行动的实际模型是 1.5 钱，正好等于卡牌费用+0.5钱机会成本&lt;/li&gt;
&lt;li&gt;扫描行动的实际模型是 2 钱，宣传的0.5钱 + 扫描的2钱正好等于 2.5。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SETI 本质是一个内卷的游戏，行动的&lt;strong&gt;实际价值&lt;/strong&gt;会随着游戏局势的变化而不断变化，但是实际模型是一成不变的。理解实际模型是让自己资源高效利用的一个小因素（但不太大，因为许多时候玩家还是不得不标动的）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2002.12.51@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 02.12.51@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;基于上述的分析，可以发现几个有趣的细节：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;重型猎鹰（左一）从计算上来说是不超模的。他是标准模型（3.5 = 1.5 + 1.5 + 0.5），这也是为什么设计师还会给它加上一个宣传。但是它又是超模的，一方面因为它赚了卡差，另一方面因为发射可能是刚需，没牌发射就只能标动发射，也就相对超模了。&lt;/li&gt;
&lt;li&gt;左二如果任务能完成，就严格意义超模 1 费发射 1 移动，这就是真正意义的超模牌。可以说，多数任务牌能做完任务都是超模的，因此&lt;strong&gt;尽量多做任务&lt;/strong&gt;是这游戏资源高效利用的一个大因素。&lt;/li&gt;
&lt;li&gt;同理，左三比重型猎鹰超模 0.5 费。虽然任务是延后的，但是我们姑且就这么算。右一的扫描也是轻轻松松超模 0.5 费。由此观之多数好任务牌都是至少超模 0.5 费的。&lt;/li&gt;
&lt;li&gt;因此多数时候收入插卡牌并不是一个划算的方案，因为它的期望收益是小于 1 的。但是又不能单纯这么看，因为除了钱是稳定的 1 收益，电和牌都会因为一些配比问题导致贬值。比如缺钱导致不得不弃牌，那一张牌就是 0.5 收益了；缺移动导致不得不用电走路，那电也贬值了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;五、移动 (Move)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;模型：1 移动 = 0.5 钱&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;两种推导方式，一是直接看左上角可得 0.5 收益，二是以下图为例，右一减右二可得 1信号 = 1 钱，因此 1 移动 = 0.5 钱。 左一和左二都可以用来佐证，多了些计分和特殊效果差不多值 0.5 钱。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2002.38.05@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 02.38.05@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;移动的模型解释了如下事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为什么不建议用电走路，因为理论上每走一步都亏 0.5 费&lt;/li&gt;
&lt;li&gt;角标弃牌移动还是比较强的。因为当你不得不移动的时候，左上角弃牌可以实现 1 电的效果。这也是第三层资源转换：钱 -&amp;gt; 电 -&amp;gt; 移动。每种资源有每种资源的本职工作，电可以移动，但是当你手上一手移动角标时你却变不出电了。另一方面，弃牌是快速行动，非常有战略意义。&lt;/li&gt;
&lt;li&gt;移动牌没有很强，因为和角标弃牌差不多收益，并且还要花一动，可能会误事。但是还是那个逻辑，最好备一点移动牌，以备不时之需，毕竟是比标动强的。&lt;/li&gt;
&lt;li&gt;移动要花在刀刃上。一个很有趣的现象是，如果玩家有一张 4 步移动牌，那么他可以会不怎么犹豫地打出来然后四步到达任何一个自己喜欢的地方。但是如果另一个玩家很贫穷，那么它可以等一等，在太阳系旋转后用 2 步达到了同样的效果。那么即便移动牌是超模的，但是移动这个行为本身亏了，也是一种亏。&lt;/li&gt;
&lt;li&gt;另外，如果一个移动经过了彗星或行星，那么本身就回了 0.5 收益。这会让 1 电移动成为标准模型。但还是那句话，这样只是不亏，如果可以在合适的时间用最少的步数以及最低的成本到达行星，路上还能获得声望的话，那不就赚麻了。这也是第二个橙色科技（无视小行星移动且获得宣传）非常强势的原因。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;六、信号 (Signal) &amp;amp; 扫描行动 (Scan)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;模型：1 信号 = 1 钱；1 扫描行动 = 2 钱&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2012.39.44@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 12.39.44@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;信号的模型是严格的 1 钱，难能可贵。从上面的几张牌随便计算一下可得。&lt;/p&gt;
&lt;p&gt;这个结论可以用于理解一些事情，对于信号而言：&lt;/p&gt;
&lt;p&gt;蹭扇区的最后一个格子（自己只有唯一一个信号），等于 1 信号换 1 数据 + 1 声望，对于自己而言是不亏的行动。但是会让扇区第一略赚。但如果自己可以蹭个第二，那自己也是赚的。这是互利互惠的基本逻辑。同理，只要一个信号蹭到了第二格的 2 分，那么这个信号基本至少也是回本的。&lt;/p&gt;
&lt;p&gt;对于扫描而言行动而言：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;扫描行动的标准模型是 2 钱，标动是 1 钱 2 电 简单换算成 3 钱。因此开局直接扫描等于硬生生亏 1 钱。所以强烈不建议开局扫描。&lt;/li&gt;
&lt;li&gt;自己扫描一个扇区，扫得越多越亏。比如如果自己扫 6 格的扇区 5 下，那么等于 5 钱换了 5 数据 + 1 宣传 + 扇区第一的奖励。除了早期的红色踪迹（红灯），其余情况基本都是亏的。因此一个思路是多伺机而动，一个人硬扫是不太划算的。有的玩家可能会说，当他蓝科叠起来了之后，那怎么扫描都是赚的了。但是也不能这么理解，因为你赚的是蓝科的收益，不是信号是收益，这就有点狐假虎威的意味了。如果能信号赚，蓝科又赚，那不就赢两次了。&lt;/li&gt;
&lt;li&gt;第二第三个粉色扫描科技带来的收益基本等于一次 0.5 费，两个都有等于标动扫描行动本身不亏模。但是至于扫出来的信号亏不亏信号本身的模型，那就得另算了。但是这背后透露出的信号是，即便双扫描科技了，标动扫描本身也只是不亏模，而这是占据了玩家的两个科技板位置的。如果换成其余科技说不定更赚了。当然，如果配合上第四个扫描科技，那标动扫描肯定是赚的了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;七、科技&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;模型：1 科技 = 3 钱&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从 6 声望换 1 科技，以及科技牌的费用基本都可以看出这个数值。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2016.38.53@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 16.38.53@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;由此观之，挺多科技牌实际上是小超模的，尤其是有任务和终局的。&lt;/p&gt;
&lt;p&gt;另一方面，科技的收益可以拆解出来看，拿取一个科技意味着它的收益需要超过 3 钱才算回本。比如发射飞船的科技，如果本身是一电奖励，那么拿取的那一刻已经回本了。而第一个扫描科技给的 2 数据实际上收益是非常低的，但是因为可能可以达到 4 数据获得一个收入，提前一回合拿到收入收益也就达到 3 钱了，属于不赚不亏，但是也确实不强。&lt;/p&gt;
&lt;h3&gt;八、分数&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;模型：1 钱 = 6 分&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其实我觉得更准确的数值是 0.5 钱 = 3 分，1 钱 = 5 分，但是不妨以 1 钱 6 分来看，只是这是一个比较高的标准。虫族的如下两张牌是我认为最好参考的例子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2016.55.16@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 16.55.16@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;多数任务卡也是这样的一个逻辑，任务本身超模 0.5 费，正好等于左二多出来的 3 分。右一和右二对比也是 3 分换了 1 数据：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2017.12.50@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 17.12.50@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里还有个非常有趣的计算，假设就以开局的资源，插一张收入牌，也就是5 钱 3 电 4 牌 4 宣传以及共计 6 费的收入。一共 5 轮的话总经济是 5 + 3 + 4 * 0.5 +  4 * 0.5 + 4.5 * 6 = 39 费用。按 1 钱 6 分的换算比，一个基础分数是 234。算上终局，总分是很有力的一号位争夺者。这也是这游戏和火星&lt;strong&gt;基础&lt;/strong&gt;非常相似的一点，如果你的所有操作都不亏模，那差不多就可以赢了。&lt;/p&gt;
&lt;h3&gt;九、外星人卡牌 (Alien Card) &amp;amp; 踪迹 (Trace)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;模型：1 外星人卡牌 = 1.5 钱，打出外星人卡牌自带 1 钱成本；1 任意踪迹 = 2 钱&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Anomalies (水滴) 是最标准的外星人种族，因为它的牌一点都不超模，和虫族不是一个级别的。下图一方面可以再次论证上面关于科技的模型，另一方面也可以非常明确的显示踪迹的模型：1 任意踪迹 = 2 钱。一个指定颜色的踪迹，收益会稍微下降一点点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2017.22.10@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 17.22.10@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;几个想法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;踪迹的收益也可以逆向计算回来：3分1外星人牌 = 0.5 + 1.5 = 2。第一个踪迹的收益是 5 分 1 宣传 1 外星人牌，收益是非常爆炸的。&lt;/li&gt;
&lt;li&gt;如果只有纯分收益了，那么踪迹几乎没有可以回本的位置。因此中后期的踪迹更像是其余操作的附带收益，而不是核心拿分手段。比如大后期的1电标记蓝色踪迹，主要目的是为了清空数据再次滚动起来，而不是为了拿那点少得可怜的分。&lt;/li&gt;
&lt;li&gt;卡牌中的一费标记踪迹牌都是有强度的，主要战略意义是可以抢到下面的好位置。&lt;/li&gt;
&lt;li&gt;&apos;oumuamua 的外星化石的模型差不多是 0.5 钱；半人马族 (电外星人)的模型比正常外星人模型再超模 0.5 费；虫族牌如果有钱/电奖励，比正常外星人牌超模了 1 费左右，非常离谱。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;十、着陆&lt;/h3&gt;
&lt;p&gt;因为基础卡牌中的着陆牌非常少，所以着陆很难谈及什么标动模型和实际模型。如果硬要说的话，设计师是认为着陆 = 1 钱的，很夸张。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202025-04-12%20at%2017.47.34@2x.png&quot; alt=&quot;CleanShot 2025-04-12 at 17.47.34@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;几个想法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;橙色的登陆省 1 电科技理论收益是非常不错的 (2 次回本)，但是比较微妙的点在于游戏中会有 2/5 概率开出虫族，并且基础的几张登陆牌也都是重量级。另一方面，前期多数人还是会倾向于环绕，倒不是环绕有多赚，而是一方面登陆太贵，另一方面一开始的黄色踪迹只有一个。&lt;/li&gt;
&lt;li&gt;开局如果玩家可以着陆，以全标动为例，那就是发射火箭 (2) + 移动 2 步 (1 - 0.5 宣传收益) + 着陆 (2) = 4.5 成本，换取了 2 数据 (1) + 着陆 5 分 (1) + 黄色踪迹 (第一个至少有 2.5 收益)，差不多是不赚不亏，但是大概率可以成为第一个标记终局的人。如果星球再好一点，不是用标动而是用卡牌行动，那么收益会更高。&lt;/li&gt;
&lt;li&gt;相比之下，环绕在资源收益上是更高的。以火星为例，第一个收入等于是 -0.5 +  5 = 4.5 收益，但是会在终局争夺上略显劣势。因此其实两种打法都可以，这游戏的前期路线还是比较多的。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;至此，游戏的一些最基础的模型就算完了。算完后可以发现，这游戏的底层逻辑是非常倾向于“打牌”的。看似游戏行动非常多，卡牌似乎只是游戏的一环，但实际上卡牌是核心中的核心。如果没有合适的牌（并且超模牌大多是任务牌），操作收益是很难非常高的。&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>影评 | 《猫猫的奇幻漂流》</title><link>https://www.ender-wiggin.com/posts/review-flow/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-flow/</guid><description> </description><pubDate>Mon, 10 Mar 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;电影的中文片名有一些喧宾夺主与蹭少年pi的热度之嫌，参考英文名《Flow》的话，一方面隐藏了主体性，另一方面对于 flow 的含义可以有多层解读，而非限定语境了的“漂流”。&lt;/p&gt;
&lt;p&gt;Flow 可以是具象的海水流动以及方舟飘荡，也可以是与猫猫内心照应的心中涟漪以及电影中出现多次的海中浮沉，亦可以是基调和谐统一且没有人声的这部电影的一种整体律动。&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><category>文学影视</category><author>Ender</author></item><item><title>桌游评价 | SETI</title><link>https://www.ender-wiggin.com/posts/bg-seti/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/bg-seti/</guid><description> </description><pubDate>Sun, 16 Feb 2025 22:50:55 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20250217011319.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;SETI: Search for Extraterrestrial Intelligence 差不多可以满足我对桌游的所有期待吧。经过了几年桌游的摸爬滚打，我对我的游戏喜好有了一定的总结：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;卡牌元素&lt;/strong&gt;。我不喜欢纯卡牌游戏，但是卡牌作为一个核心元素的打牌游戏或者卡驱都是不错的，例如火星园神TTA。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源种类不复杂&lt;/strong&gt;。我不太喜欢非常多资源种类然后互相转换的游戏，感觉算的很累，有点强行在水平方向增加复杂度的意味，点名乌老师。TTA 这种只有 3 种资源的游戏就很不错，园神只有钱就更不必说了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动数不多&lt;/strong&gt;。可以是纯竞速，也可以是固定轮数，但整体动数我不希望超过 60 动。其实多数游戏都不会多于这个动数，但是得点名批评复杂哥的无限穿插辅助行动和一百个行动起步的海商传奇。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可变玩家能力&lt;/strong&gt;。当然这可以是一个非常广泛的概念，核心就是玩家开局的非对称性，可以是不同的玩家版图，也可以是不同起始手牌和资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机性有一些，但不能太多&lt;/strong&gt;。随机性可以带来开盒的乐趣，但是过高的随机性会降低游戏的竞技性。火星的卡牌随机性我是可以接受的，但是大坝这种r3没有开出双坝身就直接崩盘的1/5高随机性我就觉得有待商榷。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主题性与代入感&lt;/strong&gt;。我不是那种不看游戏背景就硬玩的纯德式玩家。相反，我认为代入感是游戏中非常重要的一环，如果没有具体的代入感那么任何桌游都可以套皮，从而缺少了游戏性与主题性交相辉映的有机美感。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从上面的分析来看，SETI 有卡牌，资源简单，动数不算多，这些是它的优点。而可变玩家能力是扩展显而易见会增加的内容，敬请期待就完事了。另外美术和科幻背景都是有加分的。&lt;/p&gt;
&lt;p&gt;但是 SETI 无法回避的是高随机性，一方面初始卡牌会有明显强弱差距，也许可以靠轮抽来避免。另一方面外星人的随机出现，以及外星人强度的巨大差异也是有些问题的。另外轨道的推动是一个非常独特且有趣的机制，但是玩家手牌的不确定性让轨道的推动并不是那么可控。&lt;/p&gt;
&lt;p&gt;总的来说是一款非常贴合我喜好的游戏，可以给到 9.5/10 分,但是从目前（才玩过 2 次）的感受上来说，可能新鲜感和可玩性大于重开的竞技性。&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>书评 | 白井智之《死神广播》</title><link>https://www.ender-wiggin.com/posts/review-ssgb/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-ssgb/</guid><description> </description><pubDate>Wed, 05 Feb 2025 22:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;h2&gt;前言&lt;/h2&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;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>书评 | 《黄仁勋：英伟达之芯》</title><link>https://www.ender-wiggin.com/posts/review-nvidia/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-nvidia/</guid><description> </description><pubDate>Sun, 26 Jan 2025 22:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;对于传记作品，我一向秉承的观点就是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;作为一部传记，或者说是任何一部真实事件改变的作品，非常容易混淆的一点就是，我们究竟是感动于这部作品，还是传主或者事件本身。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而这本书或许还要更进一步，因为它似乎根本就算不上是一本传记。这篇书评中，我会先分析本书的行文结构，然后再谈谈实际上读者真正感兴趣的黄仁勋其人。&lt;/p&gt;
&lt;h2&gt;不是一本传记&lt;/h2&gt;
&lt;p&gt;虽然从本书的英文名《The Thinking Machine: Jensen Huang, Nvidia, and the World&apos;s Most Coveted Microchip》确实不能感受出他是一本严格的传记，但是从中文书名、本书推荐语、以及读者对这本书关注点的第一印象来说，他“应该”是一本传记。&lt;/p&gt;
&lt;blockquote&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;上来说，本书给人的感受是英伟达版本的《浪潮之巅》——往往一章就是以一位与英伟达有点关系的人物生平开始，然后众星荟萃齐聚“梁山泊”。从这个角度看，如果读者的预期是类似《浪潮之巅》这种早期创业者大杂烩风格，那么本书可以满足大概70%的预期。但是如果预期是《乔布斯传》这种严肃传记风格，那么可能会大失所望。&lt;/p&gt;
&lt;p&gt;从&lt;strong&gt;内容&lt;/strong&gt;本身上来说，这本书就更加灾难了，主要原因有三：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对黄仁勋本身较少的采访篇幅，以及大量的主观判断。我相信当读者看到本书最后一章的时候都会有些错愕——作者最后一次采访以近似被赶出去的方式不欢而散，而他竟然可以在这种戛然而止的采访活动中臆测出后面的一系列对黄仁勋的思想过程分析。并且考虑这次采访的时间节点，更是让人对他前文很多内容的真实性有一定质疑。&lt;/li&gt;
&lt;li&gt;大篇幅的个人观点输出。诚然，如果传记只是记叙传主而无作者的个人思考是失败的。但是如果作品中出现了大篇幅的作者思考，甚至有些喧宾夺主，那或许是更失败的。一方面作者对于 AI 对人类未来的思考并不高明，很多时候只是正反两方观点的传声筒在表层思考上面用个人权威作为论据；另一方面大量个人观点以及夸张化的吹捧会稀释传记作品最重要的纪实性，让作品仿佛管中窥豹的侧写。&lt;/li&gt;
&lt;li&gt;大篇幅的 AI 内容，以及重复内容。AI 仿佛是流量密码，不论是书籍领域还是社交媒体。但是大量重复性、概括性的 AI 相关内容会让人感到乏味且缺乏深度，尤其对于每天都关注 AI 动向的科技圈读者来说。但更加令我费解的是书中出现的重复性内容（完全一模一样的段落 ！），我不清楚这是电子书的排版错误还是作品本就如此，如果是后者的话那这书在我眼中的评价还要降低。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;黄仁勋其人&lt;/h2&gt;
&lt;p&gt;很遗憾的是，这本书并没有对黄仁勋有配得上书籍篇幅的描写，但是从这些传记碎片中我们还是可以稍微窥视一下。&lt;/p&gt;
&lt;p&gt;书中有两个二择问题都很深刻，并且我设身处地思考的话我的选择一定都与黄仁勋不同。&lt;/p&gt;
&lt;p&gt;第一次是在他已经成为公司的高管之后，在面对家庭经济压力的情况下选择是否要辞职加入一个三人创业公司。第二次是在他长期投入大量资金研究 CUDA 之后导致公司大幅亏损，被董事会要求放弃研究后的抉择。&lt;/p&gt;
&lt;p&gt;这两种情况下我都会放弃风险，也印证了书中提到的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不勇于冒险才是最大的风险。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其实即便他成功了，我依然没有勇气去做这种冒险，我也不认为按照概率他下一次就能成功。但是从另一个角度来说，这种看似赌博的行为似乎又是一种黄仁勋眼中的必然，也就是“&lt;strong&gt;基于愿景的运气&lt;/strong&gt;”。&lt;/p&gt;
&lt;p&gt;很难说黄仁勋的成功故事能给人带来什么启示，因为这种成功真的几乎无法复制。但是他的性格却是有参考性的：能孑然一身勇闯美国、童年遭受霸凌后依然无所畏惧、对身份政治不屑一顾、对 AI 的危害也充满蔑视。&lt;/p&gt;
&lt;p&gt;和多数经典叙述中的创业者不同，他不看科幻，甚至敌视科幻。他不是一个未来主义者，但是可以通过认真走好每一步稳健地踏向未来的彼岸。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“你们懂吗？我并非在诸多科幻故事的熏陶下长大，这可不是什么科幻片。这些人在做的是严肃的工作！再者，这是一家认真的公司，我是个认真的人，只是做着认真的工作。”&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>所思所想 | 宝可梦深圳大师赛赛后随想</title><link>https://www.ender-wiggin.com/posts/thought-ptcg/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/thought-ptcg/</guid><description>重在参与，体验为主。</description><pubDate>Sat, 21 Dec 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今天参加了下深圳宝可梦卡牌大师赛，对于活动本身没有太多感触，还不错，参加了活动，拿到了纪念品，还看到了心心念念的人才公园无人机表演，好耶~&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20241222002904.png&quot; alt=&quot;人才公园是真不错&quot; /&gt;&lt;/p&gt;
&lt;p&gt;另外对于前期的准备和个人心态有一些想法，简单记录一下。&lt;/p&gt;
&lt;p&gt;首先，参与这个比赛的动机就是主要是为了“体验”。我个人估计自己可以在一周之内大致了解游戏的玩法并正常进行对战，所以给自己多一些经历还是不错的，毕竟一方面自己还算喜欢宝可梦，另一方面深圳难得有个大型活动得珍惜一下。
从实际参与的情况来看，不少玩家都是从外地赶来的，有些还是飞机往返，因此这种送到我嘴边的机会确实是值得一试的。&lt;/p&gt;
&lt;p&gt;但是回头看的话，我的决策有几个比较明显的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;高估了自己的学习能力。实际上我从点击开游戏说明书到线上对决熟悉玩法再到比赛前夕，一共花费了6个小时左右。这个时间其实并不短，但是实际的效果只是熟悉了几套主流卡组，甚至还存在明显伏笔或者没有思考周全的操作。这就说明我思维上的一些盲目性，没有谨慎地预留合适时间，实际上一些技巧性的操作还是需要经验累积的。实际的体验就像是没有准备充足考试资料直接裸考一样，心里很没有底。这种感受挺久没遇到了，而这也是一个人成长必须要克服的一点，所以还是得多准备准备，&lt;/li&gt;
&lt;li&gt;高估的自己的时间管理能力。这周正值工作比较繁忙的一周，虽然我提前计划好了练习时间，但实操下来还是感觉安排太满了，节奏有点超出自己舒适的区间。另一方面，健身打球跑步的时间被打乱了，这种突然破坏计划的事件可能影响深远。再者，实际上等人齐、组牌、无效沟通等时间是可以通过一些手段优化掉，当时有些时刻想到了但是行为上有些犹豫，这种场合我就不该犹豫，尤其是在时间比较赶的情况下。&lt;/li&gt;
&lt;li&gt;怯战，遇到高手心里就已经输了。没啥好说的，舒适区一直是我的问题。我个人认为我舒适圈比较广，但是一旦超出了这个边界，就是各种胆小。只能在行为上强迫自己多参与这些让我有些怯场的事情了，要不然也太菜了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;另外，有张赛场的纪念【洛奇亚V】卡牌我特别想要，但是考虑到了费用还是望而却步了。有点可惜，毕竟我买东西主打一个体验感。但是理性来说确实没有购买的必要性，就在文章里记录一下这个遗憾吧。&lt;/p&gt;
</content:encoded><category>所思所想</category><author>Ender</author></item><item><title>书评 | 《席德·梅尔的回忆录！》</title><link>https://www.ender-wiggin.com/posts/review-sid-meiers-memoir/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-sid-meiers-memoir/</guid><description> </description><pubDate>Sat, 14 Dec 2024 21:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;这本书等了半天电子版都没有，只好买实体书了。不过意外发现装帧的一些细节有巧思，阅读体验不错。&lt;/p&gt;
&lt;h2&gt;正文&lt;/h2&gt;
&lt;p&gt;作为“回忆录”，这本书其实回忆得不太全面，就像只点亮了一部分的游戏地图，一些深不可测的未知区域同样令人好奇。&lt;/p&gt;
&lt;p&gt;一方面是题材限制，回忆录确实可以比传记更加自我一些，想到哪说到哪，也有一种娓娓道来的叙事感；另一方面是主题限制，“A Life in Computer Games”让内容完全聚焦于计算机游戏，只聊游戏不夹带私货对于这种阅历丰富的大佬来说难能可贵。最后一方面，也是我认为最重要的方面，在本书的最后才缓缓揭开：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;但我以积极的眼光看待世界。&lt;/p&gt;
&lt;/blockquote&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;另外一个身份，也是最少提及的身份，就是某种意义上的公司核心人物。在阅读的过程中我有一个明显的疑惑，就是微散文怎么会从他和比尔白手起家创立的公司，不明不白的变成一家结构分明的大型公司。仿佛席德梅尔的办公室一直是这么大，而随着斗转星移公司不断放大、放大，渐渐这间办公室就从唯一的一束光亮变成了万千窗户中的一个小点。这可以解读为是他在回忆过程中对这部分毫不在意，但也可能是他其实自己也有些不明不白。席德梅尔和比尔的分工太明确了，以至于他确实可以待在自己舒适的小天地中专心 coding。&lt;/p&gt;
&lt;p&gt;这本回忆录塑造的席德梅尔的形象，总的来说很“均衡”。很像他自己的游戏设计理念：做出有趣的决策。他的一生都在决策：有乐观自信也有反思自省，有旷世巨作也有积灰草案，有专心封闭的编程也有多种形式的合作，有对过去成就的认可也有对未来的持续跟进。一点点的决策持续推进，构成了让读者看了也觉得有趣的人生。&lt;/p&gt;
&lt;p&gt;人生和游戏相比，除了&lt;strong&gt;做出决策&lt;/strong&gt;之外，还有一点很大的不同：需要&lt;strong&gt;完成决策&lt;/strong&gt;。游戏的进度是由游戏机制自然而然推进的，但是人生不是如此。一方面完成决策需要更大的勇气，类比到文明，就像是玩家开拓领土的时候，领袖扭扭捏捏犹豫未决，最终放弃开拓一样。这在游戏中不可忍受，但这确是人生中的一种常态。&lt;/p&gt;
&lt;p&gt;或许在席德梅尔的眼中，这二者是近似等价的吧。“席德梅尔决定开发一款游戏 --&amp;gt; 进行程序概率计算 --&amp;gt; 游戏大获成功，幸福+10”。这也许就是一种更加清晰的视野吧。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>桌游评价 | 魔戒：中洲对決</title><link>https://www.ender-wiggin.com/posts/bg-thelordoftheringsduelformiddleearth/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/bg-thelordoftheringsduelformiddleearth/</guid><description> </description><pubDate>Fri, 29 Nov 2024 09:30:30 GMT</pubDate><content:encoded>&lt;p&gt;我真的好久没有这么有热情地买一款桌游了，甚至还认真到加了个收纳（好可爱的收纳，耶！）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-11-29-001757..jpg&quot; alt=&quot;收纳可爱捏&quot; /&gt;&lt;/p&gt;
&lt;p&gt;毕竟我早已退出桌游潮流圈多年，不知今是何世，有一天无意点开了皮杰大佬的视频才了解到《魔戒：中洲对决》。尽管实际只体验了一局，但是已经让我满是惊喜，并光速下单了一盒。从玩了多局全扩七大对决的视角来看，我愿意给这款游戏 9/10 分。&lt;/p&gt;
&lt;p&gt;顺便一提，七大基础个人感觉只有 7 分，而全扩可以达到 9+。我认为魔戒的体量本身就无法和七大全扩比拟，就如同动物园基础之于火星全扩一样。&lt;/p&gt;
&lt;p&gt;但正如动物园的基础优于火星基础的，魔戒也妙就妙在它用更小的篇幅集成了七大全扩中的诸多亮点，例如魔戒的地标类似于七大一扩的请神。《万神殿》被奉为神级扩展，因为它提用停一动的巧妙设计解决了七大基础的先后手不平衡问题，同时增加了游戏丰富度。但从现在的视角来看，一扩再怎么巧妙，它依然是某种意义上的“补丁”，请神很优雅但是神话标记这些还是显得有些许刻意。魔戒的地标虽说丰富度肯定比不上七大的奇迹+神，但是在很小的体量里面融合了二者的优点，可圈可点。&lt;/p&gt;
&lt;p&gt;同理，魔戒中的最核心机制——地图区控，则可以认为是七大二扩区控的另一种形式，或者说是一种更受大众喜爱的区控机制。七大二扩《市政广场》自上市以来就褒贬不一，虽然我个人非常喜欢，但不可否认的是这个扩展让原有的游戏增加了太多的复杂性，以至于我在线下推新时几乎没有加过二扩。而魔戒的区控清晰透明，舍弃了暗置的不确定性，让双方博弈尽是阳谋，酣畅淋漓，人称小《魔戒圣战》。&lt;/p&gt;
&lt;p&gt;另外，游戏最不重要又似乎最重要的无疑是魔戒这个“套皮”。其实我以前对于套皮有些反感，但这几年不论是宝可梦版宝石还是这款魔戒七大，都让我对游戏制作商的诚意刮目相看。魔戒作为 IP 界的顶流之一，又站在七大对决这一双人游戏巨人的肩膀上，感觉冲个 BGG 前20轻轻松松。其中套得最妙的一处无疑是七大的军力轨。和原版从实质上几乎一模一样，但是又非常形象地模拟出了护戒行动的你追我赶，在我听到这个机制的时候就知道这款游戏代入感无敌了。&lt;/p&gt;
&lt;p&gt;最后，来聊聊一些我主观感受上觉得魔戒做得没那么合我喜好的地方。&lt;/p&gt;
&lt;p&gt;其一，游戏的美术我认为是比较普通的，插画比较风格化但对我来说饱和得有点太浓烈了。并且，相较而言《七大奇迹》是有着几乎顶级的美术风格。在阅读世界各地的神话故事后再拿起《万神殿》的一张张修长的卡牌，有一种如沐春风的感受。魔戒这款游戏在我眼里更像是七大的重构+剪枝+分化，而不是单纯的子集或者完爆的关系。两款游戏都非常优秀，我也不认为出了魔戒之后就会让原版七大黯然无光。&lt;/p&gt;
&lt;p&gt;其二，游戏的区控占比有点太大了（主要是因为我不擅长区控！）。一方面游戏有着大量围绕着主版图的卡牌（尤其是三时代），另一方面游戏没有原版的计分机制，最终的胜利方式也是看区控，这就更让区控的地位大幅增加，军事和科技胜利（原版叫法实在太顺口了）成为了相对的附庸。这不算是什么缺点，只是一种个人的偏好。如果有人就是区控爱好者，那应该看到这款游戏直接爽到不行了。&lt;/p&gt;
&lt;p&gt;综合来说这款游戏绝对吹爆，在低重度的体量下有着非常丰富且高质量的游戏机制，2024年必吃榜。&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>影评 | 《好东西》</title><link>https://www.ender-wiggin.com/posts/review-her-story/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-her-story/</guid><description> </description><pubDate>Sat, 23 Nov 2024 22:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;猜性别挑战预警。我什么电影的影评都写。&lt;/p&gt;
&lt;h2&gt;正文&lt;/h2&gt;
&lt;p&gt;在聊“《好东西》在谈论些什么”之前，有一个似乎更重要的话题是：《好东西》是如何谈论的？&lt;/p&gt;
&lt;p&gt;这部作品能以一个喜剧片的姿态让许多男性不感受到明显冒犯的同时又让女性能感受到“Her Story”的内涵。《好东西》作为一部女性视角的女性主义电影或许不是最尖锐最深刻的那种，但在国内环境内确实是最“能”说的电影，如同一位擅长远交近攻的女权社会活动家。这背后的原因我总结为三点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;喜剧片的精美外衣。这层外衣一方面让电影具备基础的爆米花和娱乐属性，同时又为一些议题提供了可解构可消解的空间和基调。&lt;/li&gt;
&lt;li&gt;人物与观点输出的夸张化。在喜剧的预设立场下，人物的发言和行为相对映衬得平滑，如前夫这种人物即可以从女性主义视角进行轻度解读，也可以简单理解为反讽，甚至是某种跳脱电影本身像观众“表演女权”的反串。&lt;/li&gt;
&lt;li&gt;性别冲突的形式弱化。在影片的很多片段中，一些可扩展的矛盾点被突兀但有效的笑点打断，如同画面留白一般既可以让不细究矛盾内核的关注直接一笑而过，也可以让能联想的观众感受到留白背后的丰富画面。此外，影片中的冲突话题往往是比较浅的层级，同时用一些共性的社会性冲突的元素进行二次包裹，最终那可能赤裸裸的尖锐内核被包装得看似毫无锋芒。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于这种表达风格，影片的整体基调也是看似很随机的生活化叙事，镜头重点都在几位女性人物的性格与处境上，男性降格为“花瓶”作为一种不知所谓的调味品存在。这种表达有其意味和乐趣，但也类似《芭比》带来的一些观感一样——这是一种乌托邦式的成人童话。它没有美化女性作为社会个体共性的现实，例如事业、家庭、教育问题。但是他弱化了父权势力的阶级性压迫（全篇没有出现一位很典型的父权角色），以及弱化了社会阶级的固有矛盾。&lt;/p&gt;
&lt;p&gt;其中关于社会阶级的弱化，也是女性主义电影某种层面上的共通问题。从艺术创造的角度非常合理，因为冲突不能太过广泛且分散。但从作品的合理性角度，作品的背景的是一个只有小资阶级没有底层的大上海。个体的夸张与社会的夸张不可类比，因为人的多样性可以从大独裁者到连环杀手，但是社会有最基础的运行基础，比如经济来源。影片中所谓的贫穷是一种口头式的表达而非生活的实感，这种形式确实聚焦了生活上的冲突，但如果将镜头拉远，却也仅仅是舞台剧上的冲突。&lt;/p&gt;
&lt;p&gt;实际上，电影本身也并不是靠剧情致胜，更多的是靠“女权向彩蛋”的方式以高密度的细节和梗触达受众内心。既有跟踪和家务这种通用的共鸣，也有“月经是蓝色”、“看片学动作”这种女性经典梗，还有更加隐晦一些的LGBT 和中药梗。某种意义上来说就和漫威粉丝看到大量菜单的漫威电影差不多。因此对于这部电影我认为不需要过多谈论编排是否满或者某些镜头是否有其深意。&lt;/p&gt;
&lt;p&gt;导演只是想多埋些彩蛋罢了。作为“粉丝向电影”，粉丝看得爽就可以了。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>技术 | 方舟动物园项目一年后回望</title><link>https://www.ender-wiggin.com/posts/note-ark-nova-review/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-ark-nova-review/</guid><description>对该项目的技术回望</description><pubDate>Mon, 18 Nov 2024 10:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;2023年5月，在玩完《王国之泪》陷入贤者时间后，作为菜鸡程序员的我决定为我当时比较喜欢的桌游——《方舟动物园》写一款卡表。当时主要的动机是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我玩桌游喜欢轻度背板，因此有时候希望知道一些卡片的具体效果以及分布概率&lt;/li&gt;
&lt;li&gt;看到了一些新的前端技术想要尝试一下&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此项目很快就搭建起来了，最终在23年7月首次上线 (&lt;a href=&quot;https://boardgamegeek.com/thread/3114327/a-new-ark-nova-card-website-with-marine-world-and&quot;&gt;BGG宣传贴&lt;/a&gt;)，并经历了持续的维护与迭代，如今依然有着不错的数据：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;近6月浏览量 129k&lt;/li&gt;
&lt;li&gt;总评论数1240条&lt;/li&gt;
&lt;li&gt;每日选卡累计超过 130 天&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-11-17-001718..jpg&quot; alt=&quot;picgo-2024-11-17-001718..jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我对于我当时的行动力还是比较满意的。但是也正如同一位公司前辈说过的话：作为程序员新人，如果几个月后回头看自己之前写的代码，没感觉“这写的什么玩意”的话，就说明自己这几个月进步不大。如今回望这个项目，还是发现了很多值得改进的点。本文主要是从技术的角度记录一下。&lt;/p&gt;
&lt;h2&gt;优点&lt;/h2&gt;
&lt;p&gt;优点也简单提个几句，继续保持：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;前期数据结构的预定义。基于良好的数据结构，未来拓展模型计算、评分评论系统、卡牌diy、每日选卡都有着还算轻松的开发成本。&lt;/li&gt;
&lt;li&gt;前期 Python 批量自动化。用 Python 批量处理源 BGA 文件和 PDF 节约了不少重复繁琐的人力成本。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;缺点&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;数据结构的过度设计。当时有点太依赖于 TS 的类型提示支持，但忽略JS对象和DB的数据流转（序列号与反序列化）问题。实际上 DTO 应该是在项目初期就要设计好的。&lt;/li&gt;
&lt;li&gt;静态页面和API交互的耦合。实际上这个项目本身是个静态页面，后续才加入了评论等 API 交互。但是在新增功能的时候，应该将这种功能作为增量可插拔的插件以 HOC 的形式存在，而不是和静态页面耦合起来。这样会导致我后续在希望迁移到国内环境如微信小程序时有额外的开发成本。&lt;/li&gt;
&lt;li&gt;Markdown 未支持。不知道为什么没提前预置好 Markdown 解析，这样 About 页面不用这么麻烦，同时可以实现攻略分享的功能。&lt;/li&gt;
&lt;li&gt;卡牌文本 i18n 解析。因为卡牌文本包含了特殊元素（如图标），这部分解析没对中文做额外支持，导致有时候解析异常，很不优雅的设计。&lt;/li&gt;
&lt;li&gt;性能优化。首页懒加载都没做，惨不忍睹。&lt;/li&gt;
&lt;li&gt;状态管理。实际上评论区和卡牌筛选是应该做状态管理的，现在的筛选实现很丑陋。我在今年中旬的 Vtuber Logo 项目中用了高阶函数以管道的方式做筛选，但实现依然不够让我自己满意。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;这个项目幽幽又默默，金玉其外败絮其中。要是能重来，我一定好好优化。但是现在是不可能重构滴。&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>方舟动物园 | 如何在BGA上玩方舟动物园海洋扩</title><link>https://www.ender-wiggin.com/posts/ark-nova-mw/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/ark-nova-mw/</guid><description>如何申请账号提前体验海洋扩和新地图</description><pubDate>Mon, 11 Nov 2024 10:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;重要说明&lt;/h2&gt;
&lt;p&gt;由于近期大量玩家涌入群组申请权限, 开发人员精力不足, 流程改动如下:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;目前已经公开A测, 需要有A测权限或者有A测朋友拉你 (公开A测)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目前已经关闭A测申请通道&lt;/strong&gt; (即 Discord 的私人A测), 11月13号后申请的大概率不会通过&lt;/li&gt;
&lt;li&gt;已经拥有私人A测权限的玩家可以继续游玩, 没有权限的不能再申请了&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要再加群组申请了, 走正常A测渠道游玩!&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;以下部分已经不具有时效性, 请注意!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前置&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;有 Discord 账号或者有有 Discord 账号的朋友&lt;/li&gt;
&lt;li&gt;有 BGA 账号&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;如果暂时玩不上也不用着急，据小道消息 12 月份就会公测了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、加入测试群组&lt;/h2&gt;
&lt;p&gt;点击邀请链接加入群组&lt;a href=&quot;https://discord.gg/8ffMZATB&quot;&gt;Discord&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-11-11-001695..jpg&quot; alt=&quot;picgo-2024-11-11-001695..jpg&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、发送请求&lt;/h2&gt;
&lt;p&gt;提供一些参考句式，在末尾加上自己和朋友的 BGA 账号即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Hi all, I would like to be added to the Marine Worlds alpha please! Besides I&apos;d love to invite my friends to play together, thanks a lot!
BGA accounts (4 players): 玩家1名字, 玩家2名字, 玩家3名字
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Hello, I would like to sign up for alpha. My BGA nickname is XXX. Thx!
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、等待&lt;/h2&gt;
&lt;p&gt;通常需要 2-4 天不等，因为开发者也很忙。&lt;/p&gt;
&lt;p&gt;在 BGA 显示这个消息后，就说明可以开始玩了！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-11-11-001699..jpg&quot; alt=&quot;picgo-2024-11-11-001699..jpg&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、园神，启动！&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-11-11-001697..jpg&quot; alt=&quot;picgo-2024-11-11-001697..jpg&quot; /&gt;&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>漫评 | 《蓦然回首》</title><link>https://www.ender-wiggin.com/posts/review-look-back/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-look-back/</guid><description>第一次阅读到《蓦然回首》是22年的8月，如今再次阅读依然备受感动。</description><pubDate>Mon, 23 Sep 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;第一次阅读到《蓦然回首》是 22 年的 8 月，如今再次阅读依然备受感动。这两年对我个人而言是真正意义步入社会的两年。这并不是漫画中从四年级到六年级的个人成长，而是仿若中途二人“分道扬镳”的工作与学业的抉择。本身也不必纠结具体变化了什么，或许变化的就是时间。&lt;/p&gt;
&lt;p&gt;《蓦然回首》的主线是很明确的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;个人爱好的追求与他人认同感&lt;/li&gt;
&lt;li&gt;友情（与爱情）&lt;/li&gt;
&lt;li&gt;人生的抉择与行为的意义&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于绘画的主线，我个人还是比较触动的。小时候一直也是绘画爱好者，小学时也有另一位始终比我画得好一些的同学，我们课间也会分享彼此的“画作”。到了初中后当看到同班同学在学校画廊的素描后大受震撼，表情恰如藤野第一次看到京本第一次作品的样子。而后也开始了素描求学之旅，因此当我看到藤野贴在窗户上的骷髅头时，会心一笑。&lt;/p&gt;
&lt;p&gt;高一时还选择去艺术班上了半学期课，但是在艺术和其它专业课之间还行选择了“专业课”，虽然这词本身就代表了某种倾向性，仿佛艺术就是旁门左道一般。当时让我比较坚决做出决定的原因是一位同学的暗讽，大意就是艺术生低人一等，成绩差一些也正常。我当时在意的并不是个人的歧视，而是背后折射出的大众偏见。我似乎没有那种逆流而上的勇气，或许最终会变成世俗所最期待的模样，然而那种模样我必然不喜欢。&lt;/p&gt;
&lt;p&gt;《蓦然回首》在藤野小升初的时候第一次面临的这种二择，藤本树也用优美的平行宇宙蒙太奇展示了选择跆拳道的 if 线。这条 if 线没有落入宿命论的俗套，也没有批判放弃漫画的人生，甚至反而那是一个看似更好的选择。四格漫画的小纸条在门缝间穿梭，在回忆与现实、已然与未然之间流转，最终还是成为了坚定落下的画笔，嵌套在那个似乎一直没有变过的窗台旁的背影。&lt;/p&gt;
&lt;p&gt;这部作品也美化了“认同感”，甚至有种为他人而活的附庸感。藤野因为京本才有继续绘画的动力，而京本因为藤野才有了绘画以及生活方向。两人彼此依赖又彼此成就，为这个强调“独立”的时代献上了一抹乌托邦的色彩。&lt;/p&gt;
&lt;p&gt;但是这种似乎完美的配合，在高中毕业后立刻分道扬镳。但是这种分离并不是肝肠寸断的舍离，而只是为了一个纯粹目的的自然选择：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“我想画得更好。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;二人依然是彼此的挚友，直到一个非常意外的转折打破了这一整个叙事。藤野陷入了经典的自责，但藤本树巧妙的用平行宇宙说明了京本进入大学绝非她的引导，而就是出于“想画得更好”的必然归宿。&lt;/p&gt;
&lt;p&gt;这算一种宿命论吗？我觉得不算。宿命论更多是一种负面的对自由意志的否定，而京本的行为是一种爱好者追求爱好的必然结果，这是一种积极的自然规律。如果对自己喜欢的事业爱而不得，那才是一种真正的遗憾。&lt;/p&gt;
&lt;p&gt;我一直认为藤本树就是个短篇选手，他的鬼才构想和电影式镜头语言太适合绘制精炼优雅的短篇绘本风格的作品了。短篇幅意味着更少的“发病”次数，而如果只“发病”一两次那说不定就恰巧如同欧亨利的小说一样令人拍手称赞了。而一旦篇幅过长，例如现在的《电锯人 2》，这种偶现的灵光就降格为稳定的发病了。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>技术 | 从零开始的 Vscode 刷题之旅</title><link>https://www.ender-wiggin.com/posts/note-algorithm/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-algorithm/</guid><description>为小白准备的本地化刷题调试攻略</description><pubDate>Sat, 21 Sep 2024 22:03:00 GMT</pubDate><content:encoded>&lt;p&gt;本文是面向代码小白的 Vscode 刷题攻略，这里的小白可以是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不熟悉编程工具&lt;/li&gt;
&lt;li&gt;不熟悉编程语言&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;进入&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code 官网&lt;/a&gt;直接下载即可，这是目前最强大 IDE 之一。
为什么说是之一呢，因为现在还有一款站在 Vscode 肩膀上大幅加强 AI 功能的 &lt;a href=&quot;https://www.cursor.com/&quot;&gt;Cursor&lt;/a&gt;，号称可以让小白在 2 个小时之内写出一个应用，感兴趣的也可以试一试。
另外，还有一款开源的 AI 编辑器，截止到本文写作时间这款产品还在 waitlist 阶段，感兴趣的可以先排个队 &lt;a href=&quot;https://voideditor.com/&quot;&gt;Void&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;由于后两者本质上也是 Vscode，所以下面还是以 Vscode 为例。&lt;/p&gt;
&lt;h2&gt;二、配置环境&lt;/h2&gt;
&lt;p&gt;不论是什么语言，在本地运行都是需要配置环境的，下面以 JS 为例：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安装 &lt;a href=&quot;https://nodejs.org/en/download/prebuilt-installer&quot;&gt;Node.js&lt;/a&gt;。这是 JS 的运行时，有了它才可以在本地运行。&lt;/li&gt;
&lt;li&gt;在命令行输入 &lt;code&gt;node -v&lt;/code&gt;，检查是否安装成功。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;node -v
# v21.1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这部分如果有问题可以上网找教程，属于是第一难了，文章还是非常多的。&lt;/p&gt;
&lt;h2&gt;三、本地运行代码&lt;/h2&gt;
&lt;p&gt;本地相较于线上运行的优势在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;更好的语法提示和编程便携性&lt;/li&gt;
&lt;li&gt;0时延的流畅体验&lt;/li&gt;
&lt;li&gt;更方便的代码调试&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里推荐安装 &lt;code&gt;Code Runner&lt;/code&gt; 插件，提供了几乎所有主流语言的快捷运行方式。安装方式如下；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-09-21-001532..jpg&quot; alt=&quot;picgo-2024-09-21-001532..jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;另外，也可以自己在插件市场中寻寻宝，里面有非常多好东西。比如&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-zh-hans&quot;&gt;中文语言包插件&lt;/a&gt;，还有以前上班摸鱼时必做的换肤，甚至可以换成&lt;a href=&quot;https://vscodethemes.com/e/samuelepignone.fakedonalds/fakedonalds&quot;&gt;麦当劳主题&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;新建文件输入经典的 &lt;code&gt;Hello World&lt;/code&gt;，右键并找到 &lt;code&gt;Run Code&lt;/code&gt;，就可以看到代码的运行结果啦！好耶！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-09-21-001533..jpg&quot; alt=&quot;picgo-2024-09-21-001533..jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;注意，如果代码无限循环了，是无法直接关闭 Tab 终止的。需要在 &lt;code&gt;OUTPUT&lt;/code&gt; 里面右键并点击停止。运行代码和终止代码都是有内置快捷键的，记忆力好的可以记一下，用起来很爽。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-09-21-001526..jpg&quot; alt=&quot;picgo-2024-09-21-001526..jpg&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、调试基础&lt;/h2&gt;
&lt;p&gt;大道至简的调试方式就是打印，比如 JS 就是 &lt;code&gt;console.log&lt;/code&gt;。在遇到 bug 时，打日志是最“低级”，但又最有效的方式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20240921005735.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当然，可能有些懒人会觉得输入几个单词才能调试有点累，那么可以使用 Vscode 对应语言的 Snippet 插件。比如下方的 JS Snippet，意义就是当你输入 &lt;code&gt;cl&lt;/code&gt; 的时候，点击 tab 就会自动补全
&lt;code&gt;console.log&lt;/code&gt;，这就节约了宝贵了一秒钟时间！用个 3600次就可以节约下来一小时打黑神话了！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-09-21-001534..jpg&quot; alt=&quot;picgo-2024-09-21-001534..jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-09-21-001529..jpg&quot; alt=&quot;picgo-2024-09-21-001529..jpg&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;五、进阶一：断点调试&lt;/h2&gt;
&lt;p&gt;因为本文关注的是本地刷题调试，因此不涉及各种工程调试和Web端调试，仅介绍一下非常简单的 Vscode 断点调试功能，并且这对于刷题来说已经绰绰有余了。
一般什么时候需要断点呢？以我的感受来说，当涉及多变量交互，以及递归等多轮执行的时候，断点会比直接 print 更直观，因为可以一步一步执行。&lt;/p&gt;
&lt;p&gt;其实流程也非常简单，点击 Vscode 的调试页面，点击启动 Debugger，然后&lt;strong&gt;打断点&lt;/strong&gt;（点击代码行数数字的左侧，会冒出一个小红点）。断点的意思就是代码运行到这一步时会暂停，需要手动点击继续运行（上方播放条），就和逐帧播放电影一样。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-09-21-001535..jpg&quot; alt=&quot;picgo-2024-09-21-001535..jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;断点的好处是可以非常清晰的显示当前所有变量 （左侧区域），因此对于小白了解代码运行大有裨益。某种意义上也算“程序自己会说话”了，那些图解算法本质上也只是动态展示运行变量。&lt;/p&gt;
&lt;h2&gt;六、进阶二：Leetcode&lt;/h2&gt;
&lt;p&gt;如果想刷 Leetcode，强烈推荐 Leetcode 插件。安装后登录，就可以在本地写代码，然后点击提交后上传到网站上校验。关于配置网上教程很多，例如 &lt;a href=&quot;https://juejin.cn/post/7044565186656600072&quot;&gt;在 VsCode 中优雅的刷 LeetCode&lt;/a&gt;，暂不赘述。&lt;/p&gt;
&lt;p&gt;这个插件最大的意义是，可以和前文所述的调试结合起来，带来非常高效的编程体验。实际上算法题思考和调试的时间是远大于编程时间的，毕竟有些 Hard 题代码量其实也不大。&lt;/p&gt;
&lt;p&gt;另外，在 &lt;code&gt;// @lc code=end&lt;/code&gt; 这一行之后是不会提交到网站上的。我一般的实践就是，在这一行后面模拟数据。并且因为是本地文件，随时我都可以运行这个文件，而不需要连接网络。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2024-09-21-001536..jpg&quot; alt=&quot;picgo-2024-09-21-001536..jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;就我个人而言，当配置好这一套之后，刷题的动力都多了几分，感觉还有点意思~&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>影评 | 《异形：夺命舰》</title><link>https://www.ender-wiggin.com/posts/review-alien/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-alien/</guid><description> </description><pubDate>Mon, 19 Aug 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在朋友告诉我这部电影有 4D 版后，我就更期待了。4D 观看后的体验确实非同凡响，座椅和吹风可以说是和电影本身完美适配：空间站与飞船的偏转、人物的失重漂浮、以及打破平静后的怪物搏杀、电梯陡降、船舱破裂的急气流，这些恰到好处的感官体验为电影添色不少。另外，这个 ”4D“ 是没有电影本身的 3D 效果的，虽然说视觉上会少了些立体感，但是裸眼观看可以避免让本就漆黑一片的画面更加看不清，其实可能反而算是优点了。&lt;/p&gt;
&lt;p&gt;电影本身倒没太多值得细究的，毕竟《异形》系列就是图一乐。当然，这部电影的人物背景我倒觉得较《普罗米修斯》和《契约》更自洽一些，作死的程度虽然也不少但没有到人均降智的地步，仿生人的几次最高指令修改也有些意思。女主的塑造也不错，主角光环拉满了属于是。&lt;/p&gt;
&lt;p&gt;不过，一直萦绕在我脑子的一个问题是：为什么我会喜欢《异形》系列？&lt;/p&gt;
&lt;p&gt;作为一个比较胆小的人，我向来是不太看恐怖片。但是自从高一在手机上看了《异形 1》，我又很快把后续几部看完了，甚至还看了几部同类型太空惊悚片如《深空失忆》。我试着总结一下我潜意识中喜欢这种题材的点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;披着科幻外衣。其实这类电影的科幻成分很少，但绝对没有人会认为这不是科幻片，反而复联之流可能会落入鄙视链底端（虽然我都喜欢哈哈）。其实《异形》的内核很科幻，只是这个系列更注重围观上的个人遭遇和情感冲击，而非宏观上的智能设计与物种宿命。《契约》在更深的层面有不少讨论，但让多数人入坑这个系列的本源应该还是看似非常”简单粗暴“的第一部。它的科幻内核在于对人类太空殖民的再诠释，打破了《星际迷航》式的物种乌托邦以及《银河帝国》式的人类中心主义叙事，以一种”各方面强于人类“的新物种带给了观众第一个层面的精神震撼，并以花式的残暴画面带来第二次的物理冲击。&lt;/li&gt;
&lt;li&gt;具象的未知感。”具象“在于除开第一次观影，在后续系列中多数观众基本都知道会发生什么，而”“未知在于即便已经知道了多数信息，还是会对于部分未知信息以及已知信息何时揭晓带有不确定的忐忑。未知的恐惧属于惊悚/恐怖片的常见风格，但是未知的维度是有层级的。就像设定系推理的可能答案比古典推理多出太多一样，带有科幻设定的未知性可以比纯当前时代（甚至历史时代）的广度多了不少，同时又没有我个人很不喜欢的灵异层面的设定，这种有实感又不确定的未知感很有趣味性。&lt;/li&gt;
&lt;li&gt;半宗教式的隐喻。这点在只有一集是雷导操刀的《Rasied by Wolves》中体现得更多一些，但是这其中的世界观还是挺有相似度的。异形本身造型上的隐喻，与基因在科学层面上的复杂性，以及偶尔浮现的符号性象征，都为系列蒙上了一层”神神鬼鬼“的面纱。但是作品又以非常具体的异形这种生物以及具有实感的他者（公司、CEO、大卫）在物质层面很好的压制了这种超自然感，这一切的隐喻和宗教语言似乎都在印证阿瑟克拉克的那句话：&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;任何足够先进的科技，皆与魔法无异。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;另外还有一点，虽说不是起因，但作为陪伴了多年的系列电影，还是有很多情怀的。记得很清楚的是，《契约》是我高考完和同学去看的，当时就感觉这可能是高中生涯的最后一部电影了。大学后出了一款类似异形背景的桌游《复仇女神号》，也是我非常少有的喜欢的没试游戏，也陪伴了我不少的大学社团时光。看到现在这款桌游在桌游排行榜的 20 多位，我也挺欣慰的。尤其是这部电影，感觉和这款桌游的角色都挺搭的。不说了，这两个周末必找一天组织一下嘿嘿。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>技术 | 个人 Ubuntu 装机配置记录</title><link>https://www.ender-wiggin.com/posts/note-ubuntu/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-ubuntu/</guid><description>作为 Mac 用户，必须把 Ubuntu 配得和 Mac 几乎一模一样！</description><pubDate>Fri, 05 Jul 2024 22:03:00 GMT</pubDate><content:encoded>&lt;p&gt;马上要告别陪伴自己快一年的 Ubuntu 系统了，一想到自己作为坚定的 Mac 党，都不知道未来什么时候会再次重拾 Linux 系统。但为了那个可能的时间点，我觉得把目前我对于 Linux 的配置做一个记录，避免到时候懊悔自己为什么不早点记录。&lt;/p&gt;
&lt;p&gt;虽然我现在已经有点后悔了，因为我也快忘了一开始是怎么配置的了。作为前端娱乐圈的一员，配置 Ubuntu 主打一个怎么好看怎么来。需要操作的比如 Vim，那是碰都不会碰的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/2024-07-05_14-27.png&quot; alt=&quot;完成图&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;桌面美化&lt;/h2&gt;
&lt;p&gt;进入 Ubuntu 的第一眼就会看到 GUI，这原生 GUI 我是真的欣赏不来，尤其是会联想到大学时用 Ubuntu 虚拟机写操作系统作业的痛苦场景。因此上来第一件事换个皮肤很合理吧！&lt;/p&gt;
&lt;p&gt;桌面主题很多，可以在 &lt;a href=&quot;https://www.gnome-look.org/browse?cat=135&amp;amp;ord=latest&quot;&gt;GTK Themes&lt;/a&gt; 里面找到，主题对于桌面使用体验基本都是颠覆性的，这也是 Linux 高定制化系统的一大亮点。我在尝试了多种主题之后，最满意的还是这款高仿 Mac 主题：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vinceliuice/WhiteSur-gtk-theme&quot;&gt;WhiteSur GTK Theme&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;可以参考这里面的步骤一步步操作。注意 &lt;a href=&quot;https://www.gnome.org/&quot;&gt;Gnome&lt;/a&gt; 的前置配置。安装好的效果和 Mac 基本一模一样：
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/2024-07-05_14-27.png&quot; alt=&quot;完成图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;另外，Tweak Gnome 基本也是必安装的，可以非常灵活的配置桌面：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install gnome-tweaks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gnome-tweaks
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Terminal 配置&lt;/h2&gt;
&lt;h3&gt;终端选择&lt;/h3&gt;
&lt;p&gt;作为颜控，自带的 Terminal 有点太丑了。由于在我去年装机时 warp 还没有发布 Linux 版，因此摸索了一些其它终端，也简单列举一下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://hyper.is/&quot;&gt;Hyper&lt;/a&gt;，Vercel出品，基于 Electron，主打一个好看，但是流畅性感觉稍微差点意思。 &lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/2024-07-05_14-40.png&quot; alt=&quot;hyper&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kovidgoyal/kitty&quot;&gt;Kitty&lt;/a&gt;，基于Python，使用 GPU 加速。&lt;/li&gt;
&lt;li&gt;Terminator，老牌终端，但是丑是真丑。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是因为 &lt;a href=&quot;https://www.warp.dev/&quot;&gt;Warp&lt;/a&gt; 在去年年底出了 Linux 发行版，我觉得这基本算是最优解了。基于 rust，颜值满分，还有很多新特性。除了需要登录才能使用这个被人诟病的隐私问题之外，几乎没有任何缺点。如果介意可能存在的隐私问题，那还是选择上面列举的一些替代品为好。&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/2024-07-05_14-49.png&quot; alt=&quot;warp&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Shell 选择&lt;/h3&gt;
&lt;p&gt;目前看下来 zsh 和 fish 都不错，还有一个很新的 &lt;a href=&quot;https://www.nushell.sh/&quot;&gt;Nushell&lt;/a&gt;看着很酷炫，如果我下次装机的话我应该会尝试一下。 但这次我使用的还是 zsh：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安装 zsh&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;安装oh my zsh&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sh -c &quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;安装插件，如 &lt;code&gt;zsh-autosuggestions&lt;/code&gt; 和 &lt;code&gt;zsh-syntax-highlighting&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;更新 zshrc&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# 更改theme
ZSH_THEME=&quot;af-magic&quot;

# 添加plugins
plugins=(git zsh-autosuggestions zsh-syntax-highlighting)

# 避免snap污染问题
alias lsblk=&quot;lsblk -e 7&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Prompt（可选）&lt;/h3&gt;
&lt;p&gt;安装 &lt;a href=&quot;https://github.com/starship/starship&quot;&gt;Starship&lt;/a&gt; 作为 prompt，有非常多配置项，可以参考官网配置。&lt;img src=&quot;https://raw.githubusercontent.com/starship/starship/master/media/demo.gif&quot; alt=&quot;starship&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里有个可能的 &lt;a href=&quot;https://docs.warp.dev/appearance/prompt#starship&quot;&gt;Warp 兼容性问题&lt;/a&gt; 需要注意。&lt;/p&gt;
&lt;h2&gt;效率工具&lt;/h2&gt;
&lt;p&gt;我也是在用了 Mac 之后才开始关注一些效率工具的。对于这一块我的观点是，避免花里胡哨。新的工具有一定学习成本，如果它带来的收益没有安装和学习成本高的话，那可能根本就不是“有效率的”。&lt;/p&gt;
&lt;h3&gt;全局搜索工具：Albert&lt;/h3&gt;
&lt;p&gt;全局搜索工具应该是最能提效的工具之一了，用于平替 Mac 的 Alfred，虽然现在 Mac 上 Raycast 已经完胜 Alfred了。用处就是快捷键呼出弹窗，然后搜索各种内容比如文件和应用。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/Peek%202024-07-05%2016-01.gif&quot; alt=&quot;albert&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;p&gt;根据官网教程找到发行版下载：
&lt;a href=&quot;https://software.opensuse.org/download.html?project=home:manuelschneid3r&amp;amp;package=albert&quot;&gt;Albert&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;（可选）主题美化&lt;/h3&gt;
&lt;p&gt;Albert的默认主题非常一般，可以替换主题。我很多时候都会选择使用 Dracula 的主题，因为生态比较好，对各种应用都有主题支持，这款 Albert 也不例外。安装好后就会和上面的截图一样，是一个好看的紫色框。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://draculatheme.com/albert&quot;&gt;Albert • Dracula Theme&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;截屏工具 Flameshot&lt;/h2&gt;
&lt;p&gt;这款软件我使用率极高，毕竟截图需求太常见了。虽然 UI 丑了点，但是实用性太高了，几乎不可替代。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/flameshot-org/flameshot/master/data/img/preview/animatedUsage.gif&quot; alt=&quot;flameshot&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/flameshot-org/flameshot&quot;&gt;Flameshot&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;录屏工具 Peek/Kooha&lt;/h2&gt;
&lt;p&gt;提供了截屏之外的 Gif 或 MP4 录制功能，也是不可或缺的，比如我这篇文章就会使用到录制功能。需要注意的是，我目前使用的是 Peek，但这款软件已经不再维护了。可以尝试另一款持续更新的软件 &lt;a href=&quot;https://github.com/SeaDve/Kooha&quot;&gt;Kooha&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/phw/peek/master/data/screenshots/peek-recording-itself.gif&quot; alt=&quot;peek&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/phw/peek&quot;&gt;Peek&lt;/a&gt;
&lt;a href=&quot;https://github.com/SeaDve/Kooha&quot;&gt;Kooha&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;剪贴板管理工具 CopyQ&lt;/h2&gt;
&lt;p&gt;原则上来说剪贴板管理是最常用的功能了，毕竟程序员时时刻刻都在 ctrl+c/v。Mac 上的 Paste 是绝对的体验天花板，我在 Windows 时找了半天也没找到最满意的，就一直用跨平台的 CopyQ 将就下（功能很丰富，就是依然丑），不过用久了也习惯了。&lt;/p&gt;
&lt;p&gt;主要功能就是复制过的历史记录搜索，以及可以持久保存的分区。&lt;/p&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/hluk/CopyQ&quot;&gt;CopyQ&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;（可选）主题美化&lt;/h2&gt;
&lt;p&gt;同样的，Dracula 也有适配 CopyQ 的主题，很棒：&lt;a href=&quot;https://draculatheme.com/copyq&quot;&gt;CopyQ • Dracula Theme&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;我觉得比较重要的配置大概就这些了，其余的基建往往就看个人喜好了。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>技术 | Clerk+Next.js 从开发到生产配置全流程与坑点一览</title><link>https://www.ender-wiggin.com/posts/note-clerk-and-next/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-clerk-and-next/</guid><description>Clerk 号称是最开箱即用的登录鉴权serverless服务，这篇文章会介绍一下配置的流程，以及其中的坑点</description><pubDate>Thu, 27 Jun 2024 22:03:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://clerk.com/&quot;&gt;Clerk&lt;/a&gt; 号称是最开箱即用的登录鉴权serverless服务，确实可以帮助开发者避免前期重复无聊的鉴权逻辑从而快速开发一些小玩具。但是这里面也有不少坑，这篇文章会介绍一下配置的流程，以及其中的坑点。&lt;/p&gt;
&lt;h2&gt;0. 技术栈选择&lt;/h2&gt;
&lt;p&gt;本文使用 Next.js 作为前端框架，配合 Clerk 实现登录功能。如果说想要实现全面的白嫖方案搭建功能强大（但是没啥使用量）的网站，建议增加如下配置：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://neon.tech/&quot;&gt;Neon&lt;/a&gt;，继 Planetscale 背刺收费之后的第二个serverless数据库界赛博活菩萨。另外的选择还有 &lt;a href=&quot;https://supabase.com/&quot;&gt;Supabase&lt;/a&gt;，同样是基于pg的，但是国内访问特别卡，不建议使用。&lt;/li&gt;
&lt;li&gt;域名&amp;amp;Cloudflare。域名可以自己买一个自己喜欢的，或者去白嫖一个不太好看的免费域名使用。然后通过 Cloudflare 这个真·赛博菩萨绑定 ssl 证书。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在这种全副武装之下，这网站可以说是白嫖界的天花板了，有前端有边缘函数还有后端，抛开用户量暴涨带来的额外计费的可能性，限制这网站能力边界的就只有自己的编码能力了。&lt;/p&gt;
&lt;h2&gt;1. Hello World! 项目搭建&lt;/h2&gt;
&lt;p&gt;首先需要明确的一点是，不论是Clerk，还是其它的鉴权解决方案（NextAuth/Supabase Auth/Firebase Auth），都是封装了底层鉴权基础知识的技术黑盒。因此，如果是还不太理解鉴权原理的新人，建议先通过学习基础的鉴权知识（如jwt/OAuth/加密算法），这样在选择这些五花八门的服务时不至于“乱花渐欲迷人眼”。&lt;/p&gt;
&lt;p&gt;创建一个新项目的方式有很多种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;（推荐）找一个 Next + Clerk 的starter，例如&lt;a href=&quot;https://github.com/clerk/clerk-nextjs-demo-app-router&quot;&gt;这个&lt;/a&gt;，克隆即可。这种方式适合想快速体验 Clerk 功能的人。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;从一些其它 starter 开始搭建，然后整合 Clerk，这种方式比较灵活。最原始的就是从 &lt;a href=&quot;https://nextjs.org/docs/getting-started/installation&quot;&gt;Next.js 官方文档&lt;/a&gt;开始创建，其余的可以自己搜索。创建后再按照 &lt;a href=&quot;https://clerk.com/docs/quickstarts/nextjs&quot;&gt;Clerk 官方文档&lt;/a&gt; 配置。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;2. 配置 Clerk 开发环境&lt;/h2&gt;
&lt;p&gt;Clerk 中最重要的两个概念是&lt;strong&gt;中间件&lt;/strong&gt;和&lt;strong&gt;开发/生产环境&lt;/strong&gt;。一般的实践是，本地开发时使用 Clerk 开发环境，只要项目正式上线了，就必须配置生产环境。在代码侧区分环境使用的就是环境变量。&lt;/p&gt;
&lt;p&gt;配置环境变量的步骤如下，也可以直接参考&lt;a href=&quot;https://clerk.com/docs/quickstarts/setup-clerk&quot;&gt;官方文档&lt;/a&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://dashboard.clerk.com/sign-up&quot;&gt;注册 Clerk 账号&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Create application&lt;/strong&gt; 创建一个应用，也就是我们现在开发的这个项目。&lt;/li&gt;
&lt;li&gt;配置鉴权选项，例如第三方登录提供商。这里需要事先声明的是，第三方登录提供商在开发环境是可以随意增加的，&lt;strong&gt;但是在生产环境每一个都需要额外的配置&lt;/strong&gt;。所以这边量力而行。&lt;/li&gt;
&lt;li&gt;进入 &lt;a href=&quot;https://dashboard.clerk.com/last-active?path=api-keys&quot;&gt;Clerk 控制台&lt;/a&gt; 找到 API keys，分别是一个公钥和私钥，将这个配置复制到我们项目中的环境变量里。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;补充：环境变量是文件夹中的 &lt;code&gt;.env&lt;/code&gt; 文件，如果没有的话可以手动创建，或者从 starter 中的样例文件（&lt;code&gt;.env.example&lt;/code&gt; 或者 &lt;code&gt;.env.template&lt;/code&gt; 啥的，反正一般都会有一个这样的文件）。内容大致是这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=需要粘贴的内容
CLERK_SECRET_KEY=需要粘贴的内容
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
# NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
# NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不论如何，都不要将这个文件上传到公共网络例如 GitHub 上。&lt;/p&gt;
&lt;p&gt;至此，如果是从 starter 克隆的项目的话，已经可以根据自己对应的包管理器运行了。&lt;/p&gt;
&lt;p&gt;如果是自己手动创建的，那么还需要配置 Middleware 和 &lt;code&gt;&amp;lt;ClerkProvider&amp;gt;&lt;/code&gt;，可以参考&lt;a href=&quot;https://clerk.com/docs/quickstarts/nextjs&quot;&gt;官网&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;3. 中间件 Middleware 配置&lt;/h2&gt;
&lt;p&gt;这是 Clerk 的第一个坑。Clerk 有两种不同版本的鉴权中间件，一定要注意。&lt;/p&gt;
&lt;p&gt;如果你使用的是 &lt;code&gt;authMiddleware()&lt;/code&gt; 函数，这个函数虽然可以使用但是已经deprecated了，见&lt;a href=&quot;https://clerk.com/docs/references/nextjs/auth-middleware&quot;&gt;文档&lt;/a&gt;。这个中间件会&lt;strong&gt;默认拦截整个项目的所有页面&lt;/strong&gt;，必须用户显示声明允许在未登录状态下可以访问的页面。因此，如果配置稍有不慎，就会发现自己所有页面全都是404。因此建议弃用，改为使用新的API。&lt;/p&gt;
&lt;p&gt;新的 API 是 &lt;code&gt;clerkMiddleware()&lt;/code&gt;，它默认不保护任何路由，这确实更加符合开发规范，不会禁用一些原本可以正常使用的功能。&lt;/p&gt;
&lt;h2&gt;4. 组件配置&lt;/h2&gt;
&lt;p&gt;接下来就是在项目中加入注册登录按钮了，这也是 Clerk 最大快人心的一步。还有什么比写一个 &lt;code&gt;&amp;lt;SignedIn&amp;gt;&lt;/code&gt; 即可实现登录更加方便的开发体验了？&lt;/p&gt;
&lt;p&gt;步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在项目根目录下配置 &lt;code&gt;&amp;lt;ClerkProvider&amp;gt;&lt;/code&gt;，这也是很多库使用的全局配置方案。根目录取决于你使用的是 Next.js 的 page 路由还是 app 路由，这里也有一些小坑，但是因为属于 Next,js 相关的问题，这篇文章就不讨论了。&lt;/li&gt;
&lt;li&gt;在想加入的地方加入对应注册/登录/用户信息组件，这里就建议参考官网和 starter 来写了，一步步走就行了，没什么坑点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果是使用 starter 的话，应该这一步什么都不用做就可以使用了。&lt;/p&gt;
&lt;h2&gt;5. 走向生产环境&lt;/h2&gt;
&lt;p&gt;其实从用户体验上来说，使用测试环境除了当用户跳转第三方登录的时候会显示一个奇奇怪怪的域名（Clerk 开发环境域名）之外，并没有什么区别。因此似乎不上生产环境也不错，还不需要搞其它的配置。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但是！但是！但是！Clerk 开发环境有一个非常坑的点，就是 google search console 无法访问，会直接报错 401，也就是等于根本没有seo！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个问题我很晚才发现，寻思着明明我的网站访问量不低并且有外链，为什么一直上不了 google 首页。在知道是 401 的问题后，我也一直无法定位到原因，stack overflow 上也没有答案。直到今年3月份才有一位好心人&lt;a href=&quot;https://stackoverflow.com/questions/76816776/getting-unauthorised-401-for-my-nextjs-13-app-on-search-console&quot;&gt;一语道破天机&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;所以，只要你的网站是想上线的，就要做好生产环境配置。步骤如下，也可以参考&lt;a href=&quot;https://clerk.com/docs/deployments/overview#deploy-your-clerk-app-to-production&quot;&gt;官网教程&lt;/a&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建生产实例。&lt;/li&gt;
&lt;li&gt;更新环境变量。&lt;strong&gt;这里也有一个坑点，就是只要在本地环境使用生产环境的API key，就会报错。&lt;/strong&gt; 解决方案就是老老实实的本地用测试的环境变量，部署到云端再用生产环境的环境变量，做好严格隔离。&lt;/li&gt;
&lt;li&gt;配置第三方鉴权和路径。&lt;strong&gt;生产实例和开发实例几乎完全不一样，所有配置项都要重新配置。&lt;/strong&gt; 开发环境的快乐在配置生产环境时几乎全还回去了，或许鉴权也没有银弹——开发效率和安全性不可兼得。&lt;/li&gt;
&lt;li&gt;找到自己的域名，都生产环境了有个域名很合理吧。&lt;/li&gt;
&lt;li&gt;每个 OAuth 证书都需要去对应平台自己配置。这一步建议照着&lt;a href=&quot;https://clerk.com/docs/authentication/social-connections/overview&quot;&gt;文档&lt;/a&gt;慢慢走，只能说几乎 OAuth 的配置方案都是类似的，虽然第一次很累但是一劳永逸了属于是。&lt;/li&gt;
&lt;li&gt;DNS 记录。生产环境需要使用 Clerk 的一些服务来实现会话管理和邮件管理。总的来说就是得把自己域名和 Clerk 绑在一起。这一步一方面验证很慢，差不多得要一天；另一方面 Cloudflare 还有坑，需要把这个子域名设置为 &lt;code&gt;DNS only&lt;/code&gt;。具体操作还是见&lt;a href=&quot;https://clerk.com/docs/deployments/overview#deploy-your-clerk-app-to-production&quot;&gt;文档的 Troubleshooting 部分&lt;/a&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;等一切就绪后，应该就终于可以使用了。一方面可以自己登录网站看看能不能登录，另一方面访问一下 google search console 看看能不能登记自己的子域名就可以了。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>技术 | 基于 React-DnD 的通用拖拽库设计方案</title><link>https://www.ender-wiggin.com/posts/note-react-dnd-2/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-react-dnd-2/</guid><description>本文是对于我之前的文章的更新，将一个小需求封装为一个工具库。</description><pubDate>Mon, 24 Jun 2024 22:03:00 GMT</pubDate><content:encoded>&lt;p&gt;本文是对于我之前的文章&lt;a href=&quot;https://blog.ender-wiggin.com/posts/note-react-dnd/&quot;&gt;React-DnD 预览层实现的两种方案&lt;/a&gt;的更新，将一个小需求扩大为一个工具库。&lt;/p&gt;
&lt;p&gt;在工作中我需要实现一个比较灵活的拖拽库，可以实现几乎从任何地方拖拽组件到中间的编辑器中的效果，见下图。这篇文章会介绍这个通用拖拽库的架构和具体技术实现方案。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/Peek%202024-06-24%2016-19.gif&quot; alt=&quot;demo&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;架构&lt;/h2&gt;
&lt;p&gt;需求如下，大致拆分为4个核心步骤：
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/dnd-logic.png&quot; alt=&quot;dnd-logic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;而作为一个可接入业务的抽象库，整合业务逻辑的库架构如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/dnd-architecture.png&quot; alt=&quot;architecture&quot; /&gt;&lt;/p&gt;
&lt;p&gt;能看到这里有5个核心文件夹，接下来我会一一介绍功能。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;一、dnd上下文（context）搭建&lt;/h2&gt;
&lt;p&gt;这里的上下文分为两部分：&lt;/p&gt;
&lt;h3&gt;React-dnd的全局上下文&lt;/h3&gt;
&lt;p&gt;只要是使用了react-dnd的项目，都需要一个上下文，用于全局管理拖拽事件。
放置在项目frontend/pages/_app.tsx这个根目录中。&lt;/p&gt;
&lt;h3&gt;包内置上下文&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;packages/common/dnd/src/dndContext/dndContext.tsx&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这一块参数目前设定为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export type DndContextType = {
  theme?: ITheme; // 外部传入的主题，主要用于drop时的样式传入
  onDragAction?: DragItemActionCallback;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;theme&lt;/code&gt;，也就是贯穿整个项目的主题。传入的目的是为了在业务层获取到主题，并在drop的过程中作为桥梁传回给编辑器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PS：这里我个人建议未来还是要在编辑器层面进行优化，直接可以将主题作为参数，在内部进行管理。如果这么实现的话，也要记得修改这个包中关于主题部分的代码。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;onDragAction&lt;/code&gt;，是一个在每次拖拽前和放置后都会调用的回调，把它单独抽离出来也是为了和业务逻辑解耦。目前我们项目中使用到的逻辑为：&lt;/p&gt;
&lt;p&gt;在拖拽开始时，让整个编辑器变成富文本编辑模式。当拖拽结束后，恢复常规模式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const onDragAction = (action: &quot;start&quot; | &quot;end&quot;) =&amp;gt; {
  if (action === &quot;start&quot;) {
    const blocks = document.querySelectorAll(&quot;.decorative_block&quot;);
    blocks.forEach((block) =&amp;gt; {
      (block.parentNode as HTMLElement).style.zIndex = &quot;2&quot;; // 2是编辑模式
    });
  } else {
    const blocks = document.querySelectorAll(&quot;.decorative_block&quot;);
    blocks.forEach((block) =&amp;gt; {
      (block.parentNode as HTMLElement).style.zIndex = &quot;0&quot;; // 0是拖拽模式
    });
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、drag接口&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;packages/common/dnd/src/drag/withDraggable.tsx 文件&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这步在第一版设计是使用的是容器形式，在第二版中改为了HOC形式。区别在于只需要在独立的组件中维护即可。&lt;/p&gt;
&lt;p&gt;参数如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const defaultOptions: WithDraggableOptions = {
  dragByIcon: false,
  dragIconPosition: { top: 0, left: -14 },
};

type WithDraggableProps = {
  item: DragItem&amp;lt;DragType&amp;gt;;
  onSelectItem?: (item: DragItem&amp;lt;DragType&amp;gt;) =&amp;gt; void;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dragByIcon&lt;/code&gt;决定了组件是否通过一个抓手图标进行拖拽，有两种表现形式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;悬浮到可拖拽内容后左侧会显示一个灰色拖拽按钮，点击后才可以拖拽。主要是为了针对文本的情况，这样用户依然可以使用浏览器自带的“选中文本复制”的操作。&lt;/li&gt;
&lt;li&gt;悬浮后光标变成抓手，点击一整个可拖拽内容即可直接拖动。这种主要是为了针对非文本情况，或者文本空间很小不适合添加拖拽按钮的情况。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;dragIconPosition&lt;/code&gt;是这个图标的定位，默认是在左侧一点的位置。&lt;/p&gt;
&lt;h3&gt;补充：类型定义的关系&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/dnd-type.png&quot; alt=&quot;dnd-type&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、drop接口&lt;/h2&gt;
&lt;p&gt;drop部分的核心是一个可放置容器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export interface DroppableContainerProps {
  canDropType?: DragType[]; // 这个容器下允许放置的类型，默认是这个package下定义的所有类型
  hideBar?: boolean;
  onDrop: (item: DragItem&amp;lt;DragType&amp;gt;, itemType: DragType) =&amp;gt; void;
  children: React.ReactNode;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;canDropType&lt;/code&gt;，决定那些类型可以被放置。在编辑器内部是所有容器，而在不同slide之间就只是个别类型了。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hideBar&lt;/code&gt;，这是一个临时使用的参数，如果启用会让这个容器在没有被hover时严格隐藏（透明度为0）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onDrop&lt;/code&gt;，这是核心回调，对于不同的业务逻辑回调应该是不一样的。关于编辑器部分，详见packages/common/flexlayoutApp/src/view/Splitter.tsx。可以注意到，这是在最底层FlexLayout级别的使用的，未来如果编辑器迁移了也会是类型的用法。对于这一步还有一些优化，即一般并不是悬浮到分割线才显示，而是悬浮到靠近分割线的block时就会显示分割线的位置。在编辑器迁移之后可以考虑迭代。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个&lt;code&gt;onDrop&lt;/code&gt;在编辑器中是在&lt;code&gt;packages/common/autoSliderApp/components/autoSlider/view/index.tsx&lt;/code&gt;里面调用的，核心逻辑如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const handleInnerSplitLineDrop = (id: string, path: string, rect: Rect, item: any, itemType: any) =&amp;gt; {
      const res = innerInsert(initLayoutData, itemType, item, id, path, rect);
      if (res) {
      // 更新Layout
      }
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这边&lt;code&gt;any&lt;/code&gt;是为了节约时间，本质上&lt;code&gt;item&lt;/code&gt;是前面提到的&lt;code&gt;DragItem&lt;/code&gt;。
能看到，这里主要的工作就是接受到&lt;code&gt;item&lt;/code&gt;之后，运行一次内部插入的逻辑。但是这个函数有点小问题，偶尔会导致无法插入（因为没有定位到分割线），这时候只需要轻微移动分割线即可解决。底层逻辑是将新的定位更新回&lt;code&gt;LayoutModel&lt;/code&gt;，这一块未来理论上会在新的编辑器里面优化了。具体解释见编辑器（FlexLayout）二次开发业务逻辑。&lt;/p&gt;
&lt;h2&gt;四、预览层&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/dnd-preview.png&quot; alt=&quot;dnd-preview&quot; /&gt;&lt;/p&gt;
&lt;p&gt;要实现预览层，首先需要禁用对默认预览效果，这部分的逻辑在抽离出来的 &lt;code&gt;useDraggable&lt;/code&gt; hook中实现，关键代码即&lt;code&gt;useEffect&lt;/code&gt; hook的处理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const useDraggable = ({ item }: UseDraggableProps) =&amp;gt; {
  const { theme, onDragAction } = useDndContext();

  const [{ isDragging }, drag, dragPreview] = useDrag(
    () =&amp;gt; ({
      type: item.type,
      item: () =&amp;gt; {
        onDragAction?.(&quot;start&quot;);
        return getStyledDndItem(item, theme);
      },
      end: () =&amp;gt; {
        onDragAction?.(&quot;end&quot;);
      },
      collect: (monitor) =&amp;gt; ({
        isDragging: monitor.isDragging(),
      }),
    }),
    [item] // 注意依赖数组问题
  );

  useEffect(() =&amp;gt; {
    dragPreview(getEmptyImage(), { captureDraggingState: true });
  }, [dragPreview]);

  return { isDragging, drag };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在禁用默认预览效果后，需要通过 &lt;code&gt;useDragLayer&lt;/code&gt; 人为指定一个预览层，即通过捕获鼠标位置，并在对应位置渲染出一个新的组件。
在渲染组件这步里，会使用下一个章节介绍的组件工厂，展示的是&lt;code&gt;dragStatus = &apos;dragging&apos;&lt;/code&gt;的状态。另外，这里使用了一下&lt;code&gt;createPortal&lt;/code&gt;，将组件渲染到&lt;code&gt;document.body&lt;/code&gt;上，这样可以避免父组件由于一些CSS样式导致的定位偏差问题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  return ReactDOM.createPortal(
    &amp;lt;div style={getItemStyles(currentCursorOffset)}&amp;gt;{getDragComponent(itemType as any, item.content)}&amp;lt;/div&amp;gt;,
    document.body // 目标容器
  );
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、组件工厂&lt;/h2&gt;
&lt;p&gt;组件工厂负责管理整个前端组件显示，核心是包括了3种状态&lt;strong&gt;纯函数&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;常规态，即正常没有被拖拽的状态。&lt;/li&gt;
&lt;li&gt;预览态，当组件正在被拖拽时发生的样子。&lt;/li&gt;
&lt;li&gt;遗留态，当组件被拖拽时，原有空间的状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，所有需要实现拖拽的组件，都需要在纯函数中加入这个参数。另外一个参数默认是&lt;code&gt;data&lt;/code&gt;，是在整个流程中会被传输的数据，不论是拖拽、预览还是放置。通过一个贯穿全文的&lt;code&gt;data&lt;/code&gt;，就可以实现低负担的数据传输与UI显示。&lt;/p&gt;
&lt;p&gt;最终通过一个函数即可在不同类型、不同状态中返回一个纯函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const getComponentByStatus = (type: DragType, data: DragData, dragStatus: DragStatus): JSX.Element =&amp;gt; {
  const Component = DragComponentMap[type];
  if (!Component) {
    return null;
  }
  return &amp;lt;Component data={data} dragStatus={dragStatus} /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里也可以使用面向对象的工厂模式，对于大规模组件来说是一个优化方向。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>影评 | 《头脑特工队 2》</title><link>https://www.ender-wiggin.com/posts/review-inside-out-2/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-inside-out-2/</guid><description> </description><pubDate>Sun, 23 Jun 2024 22:02:00 GMT</pubDate><content:encoded>&lt;p&gt;当我意识到第一部距离现在已经过去9年时，突然有一点怅然若失的情绪，尤其是在这样一部以“成长”作为主题的作品中。
首先的感受是某种喜好上的变化，在9年前看第一部时，影片中头脑中情绪拟人化和记忆具象化都令我眼前一亮，看完当场就毫不犹豫低把它排到了我年度最喜欢动画电影，在个人整个动画排名中也非常靠前。但是当我准备看第二部前，我都有点犹豫要不要看这样一部以“青春期”为核心的、看起来对我有些过时的“儿童”电影。某种方面来说，我并不把这种心境上的变化视为成长，正如《肖申克的救赎》逐渐适应监狱生活的罪犯和口口声声“作为过来人”的中年人并不一定有什么成长，可能只是一个新阶段的思维固化罢了。小时候很佩服那些绘制童书、制作儿童电影的大人，现在就更加钦佩了。
其次就是对我个人是否“成长”的疑问。显然，人不可能没有成长，但是对于还处在各方面上升期（也是最后的上升期了）的我来说，定性比较是没有意义的，我关注的是程度。我自认为我在15年的时候的认知是超过很多人的，但是考虑斜率变化的话感觉我现在的思维程度配不上9年的时间流逝。24岁，已经是电竞选手要退役，运动员能力最鼎盛的时期了。15岁的我还尚有一丝年轻的优越感，而现在的我已经在世界上很多领域是大龄选手了。&lt;/p&gt;
&lt;p&gt;上述只是些题外话，说回电影本身，总的来说作为续作已经很优秀了。但是从设定上，我想到了一些以前的我没有思考的东西。
一是女主的主体性。我首先意识到她主体性的缺失是在开头，我发现我能记得前作的几位情绪小人甚至那只被遗忘的大象，但是我想不起来太多和女主有关的内容。在这个作品的演绎中，女主的行为是浅层的情绪主导+深层记忆构成“信念”决定的。那么女主的行为似乎全都是非自主的，因为信念也是记忆的映射，不论是前面甄选后的优质记忆还是最后的复杂混合体，都有一种记忆决定潜意识行为的思想存在。当情绪不在存在，女主的自发行为似乎就是被纯后天的内容所塑造了，先天的个人差异完全没有体现。再者，她的自主行为似乎就少了一层丰富的活力，但是我对于情绪在行为中扮演的角色始终抱有一些怀疑：首先，情绪不是行为的前置必要条件。其次，应该是更深层次的personality决定行为，而不是情绪主导行为。&lt;/p&gt;
&lt;p&gt;二是情绪小人的自由意志。即，小人们为什么一定要卖力的工作？从机械的层面，物质的供给（如血液、激素）是单纯由生物结构以及背后的物理定律所决定的，本身是一个不存在意识的行为，所以根本上就不存在这个问题。而从某种拟人化的角度，例如《自私的基因》或《血音乐》，将一些身体成分视为一个有机体也是可以的，但这种有机体的行为逻辑一定是基于自身，而不是情绪小人这种“希望女主过得好”的完全利他的目的。
因此，情绪的拟人化本身也不应该存在什么目的性，这也是影片设定上一个有些突兀的地方，也就是为什么要有不同的情绪小人来主动按按钮。厌恶、恐惧这种情绪尚有自我保护的目的，但是愤怒与忧郁这种情绪虽然是天性但是在作品的这种世界观看来是可以被优化的。没有愤怒绝对是更优解，毕竟这都是七宗罪之一了。情绪小人的行为逻辑似乎是，“我（情绪小人）觉得自己有这种情绪了”-&amp;gt;于是去按下按钮。这个逻辑链本身就不合理。
此外，这背后还有某种宿命论的问题。虽然情绪小人们每天冒险都惊心动魄，但是世界上绝大多数人都会经历类似的境遇，那么难道绝大多数的情绪小人都要遭受这般折磨吗？从博弈论的角度来看，不如一起摆烂，抵抗这个生来就不公平的世界。从宿命论的角度而言，绝大多数人最终都可以找到一个平衡点（暂时认为情绪失常者就是内部的情绪小人们崩溃了），那是不是情绪小人摆烂也不影响最终的平衡？&lt;/p&gt;
&lt;p&gt;最后谈下剧情，算是非常经典的毕业困境了。这个系列算是少有的折磨人风格的动画电影，看得很焦灼，看完一遍就不想看第二遍（不是不好看，而是不忍卒读），有点类似《虹猫蓝兔七侠传》莎丽的那种感觉。新情绪小人很亮眼，属于之前没想到的惊喜。但这就又不得不提一个设定上的小小bug了，根据剧情发展情绪明显是越来越多的，那么怎么大人的头脑中还是几个小人，配上几乎一样的操作台？按理说一个成年人早应该是五百人议事会那样了。虽然最终让女主父母的焦焦亮了个相，但是这种情绪显然是一直会在的，作为冷板凳还是有些不合理。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>影评 | 《机器人之梦》</title><link>https://www.ender-wiggin.com/posts/review-robot-dreams/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-robot-dreams/</guid><description> </description><pubDate>Mon, 10 Jun 2024 22:02: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;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>书评 | 《攀登者的世界》与《徒手攀岩》</title><link>https://www.ender-wiggin.com/posts/review-the-impossible-climb/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-the-impossible-climb/</guid><description> </description><pubDate>Sat, 25 May 2024 22:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;我在《徒手攀岩》刚上映时就看了这部作品，但是当时的感触与很多其它极限运动电影大同小异。最近自己开始尝试攀岩，在攀岩馆看到了这本记录 Alex 的书，也因此又看了一遍这部电影，体悟就完全不同了。比较明显的三个差异是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第一遍是和父亲在荧幕上看的，我俩就是全程震撼于这场攀岩奇迹。而第二遍是在b站看的，弹幕充斥着各种莫名其妙的发言，比如指责 Alex 女朋友桑娜，或者对攀岩这项运动发表一些比较无知的评论。&lt;/li&gt;
&lt;li&gt;在自己尝试攀岩之后，对很多数据有了粗浅的认知，比如线路的难度评级，以及一些攀岩动作技巧。这在欣赏影片时有一种更加深入的观察。&lt;/li&gt;
&lt;li&gt;最明显的一个差异是在同步阅读这本书时感受到的，也就是影片中的一种 &lt;strong&gt;“刻意”的造神行为&lt;/strong&gt;。这本事也不是贬义，因为一方面 Alex 自己也希望自己是超级英雄，另一方面观众们本身也喜欢造神这个行为，可以说是一拍即合。但是这种造神的行为只能让观众了解到这一切的表象，而非实质。这也是这本书作为影片补充的一个非常突出的优点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，这篇文章第二部分将会列举并补充一下影片中没有提到的细节，以及一些有意为之的引导，视图还原一个更真实的攀岩世界。第三部分将是我个人对这部作品的读后感。&lt;/p&gt;
&lt;h2&gt;书籍与影片的事实比对&lt;/h2&gt;
&lt;h3&gt;1. Alex的恐惧感&lt;/h3&gt;
&lt;p&gt;书中的这一段话一针见血：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“人们想把他归为另类，不想对自己有要求。因为如果亚历克斯只是一个普通人，他能在压力之下将自己变成冷静无畏的超人，那其他人也应该能做到。没人愿意相信这是事实。我们都想把他变成与众不同、独一无二的人，这样就不必向他学习了。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这一点也在电影中有所体现，一些春秋笔法似乎在塑造一个不知畏惧的超人形象。尽管电影中也通过一些细节暗示了 Alex “没那么特殊”，但是给观众的感觉还是认为他远异于常人。&lt;/p&gt;
&lt;p&gt;书中的描述就显得真实又合理了许多：Alex 绝非常人，但是他也有着许多正常人的思维模式与行为。不论是共情、爱人的能力，还是恐惧感与紧张感，这些让他看起来没那么“斯波克”的特性也是完整的他不可或缺的部分。&lt;/p&gt;
&lt;p&gt;我很喜欢书前段介绍的一些基本生平：通过门萨测试、进入加州大学又退学、喜欢看书（书中提及了几次我也很爱的《卡拉玛佐夫兄弟》）。这些生活细节塑造了一个更加真实且立体的 Alex，而不单纯是一个疯狂的极限运动者。&lt;/p&gt;
&lt;h3&gt;2. Alex 的往年攀岩经历&lt;/h3&gt;
&lt;p&gt;影片中给我的一种误解是，Alex 在尝试攀登酋长岩之前，有很多尝试但是也没“那么”多。我觉得这一方面是我作为非美国人/圈内人对于这项运动的不了解，另一方面也和电影的暗示有关系。&lt;/p&gt;
&lt;p&gt;最明显的一段是“抱石难题”，电影中给人的感受是 Alex 在这一段失误率很高，视图将这一段作为电影中的高潮部分。事实上，虽然5.13a确实很难，但是 Alex 之前攀爬过近20次都没有失误，只有正式攀爬前前几天脱手了一次。这种不能说是事实错误，但无疑电影是给了观众一些错误引导的。&lt;/p&gt;
&lt;p&gt;类似的，Alex 在优胜美地攀爬了无数次的各种岩壁，其中徒手次数也数不胜数，挂绳攀岩酋长岩的次数也不少。如果说观众是熟悉攀岩的人会对这种壮举有某种“顺理成章”的感受（并不是说徒手攀岩不是奇迹，而是会认为 Alex 做了这么多的努力而做到是一种合理的结果）。同样，这也是一种程度问题，电影并没有过度神化这次攀岩，但神化的比例也并不少。&lt;/p&gt;
&lt;p&gt;这是他8年以来朝思暮想后的攀岩，而非一次狂妄自大的冒险之旅。&lt;/p&gt;
&lt;h3&gt;3. Alex 的女友&lt;/h3&gt;
&lt;p&gt;电影视图营造出一种不近人情的影视风格的天才形象，或许就是攀岩版的福尔摩斯吧。但是实际上 Alex 在情感方面也没那么奇怪。一方面，Alex 本身有过不少前女友，可以认为他对于爱是有明显渴求的。另一方面，他女友桑妮与他交往的过程也可以用浪漫来形容，影片中给人一种天才与凡人的隔阂感。&lt;/p&gt;
&lt;p&gt;在影片中拍摄到桑妮因为没拉紧绳子导致 Alex 摔伤时，B站弹幕立刻出现了一片指责声与“女人影响拔剑速度”等常见梗。当然，书中也记录了事后 Alex 想要分手这个情况，但是作者还额外补充了一个信息：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;亚历克斯没有揽下任何事故责任。作为有经验得多的攀岩者，离开地面之前“闭合系统”是他的工作。桑妮只爬了6个月，几乎没在野外爬过，因此将这类安全细节问题怪到她头上是不公平的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不论是书中的描述还是影片中的传达，我现在都认为桑妮可以算得上是一位“神仙女友”了。我作为情绪比较稳定的人，我都很难想象如果对方是一位随时可能在极限运动中丧命的人，我会是一个怎样的心态。&lt;/p&gt;
&lt;p&gt;因此指摘桑妮的人，我认为一方面是太自以为是把自己带入 Alex了，另一方面又有一种对女友极其严苛的标准，简而言之就是高度自我中心，甚至比 Alex 还如此，因为他早在20年就已经结婚了。&lt;/p&gt;
&lt;h3&gt;4. Alex 攀酋长岩的过程&lt;/h3&gt;
&lt;p&gt;这部分倒没太多可说的，因为文字肯定远不如影视资料精彩。但是书籍可以提供镜头之外的信息，比如 Alex 每次徒手攀岩前都会紧张地上厕所，甚至在徒手攀岩酋长岩时也分别有一次大号与小号。&lt;/p&gt;
&lt;p&gt;另外，影片中非常幽默的一处镜头是 Alex 攀岩图中遇到了一位在睡床中的“独角兽”。书中也补充了更多信息，例如这位攀岩者希望达成各个岩壁的“独角兽首攀”，更有一种风趣感。&lt;/p&gt;
&lt;h2&gt;读后感&lt;/h2&gt;
&lt;p&gt;感受主要分为两部分，一部分是对于一些书中事实（以及知识的看法），另一部分是对攀岩这项运动和每个时代的传奇攀岩者的宏观见解。&lt;/p&gt;
&lt;h3&gt;一些事实和我的看法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Alex 的父亲。书中提到了他父亲塑造了他攀岩的爱好，同时每一年都开车带他去攀岩以及比赛。但是他们之间话题很少，以至于他父亲去世了他也没有什么情感波动。&lt;/li&gt;
&lt;li&gt;Alex 的母亲。本身也是门萨的，并且后来自己还成为攀登酋长岩的最大年龄者。我对她非常钦佩，一方面她对于很多事情看得很透，另一方面自己也一直在追求个人的梦想。比如，“听到儿子用‘可憎’来描述大学生活，知道他如此痛苦，迪尔德丽同意了退学。”，以及后面对于他徒手攀岩的担忧与支持，都算是一位母亲的楷模了。虽然确实她的教育中有不少缺陷，但是在一些方面上她有一种非同寻常的通透感。&lt;/li&gt;
&lt;li&gt;攀岩者们有时候既想塑造一副低调的形象，又想让别人知道自己完成了一些壮举。有种矛盾有有趣的心理。&lt;/li&gt;
&lt;li&gt;Alex 为了“教训”波特，在自己完攀波特没做到的岩壁后开小号在论坛里面炫耀。感觉心态很小孩子哈哈。&lt;/li&gt;
&lt;li&gt;Alex 擅长的是长距离攀岩/徒手攀岩，形式上对标的是田径中的长跑。而他在运动攀岩中并不是世界最顶尖的存在，正如同100m和马拉松完全不是一个赛道。不可否认他是徒手领域的绝对第一，但在难度攀岩领域高手如云。认识到攀岩界的不同方面，才能更有一种对这项运动的敬畏。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;感悟&lt;/h3&gt;
&lt;p&gt;这本书严格来说不算是 Alex 的传记，而是串起整个一百年攀岩史的半科普半纪实类书籍。脉络上大致有三个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;攀岩知识分享。例如线路难度评判，攀岩工具、攀岩历史发展，这对于我这种刚涉足一点攀岩的人来说很有帮助。&lt;/li&gt;
&lt;li&gt;攀岩历史考据。介绍了不同时代的攀岩风格，以及对于时期的传奇人物们。从岩石大师，到岩猴，再到书籍中后部分着重笔墨介绍的汤米和 Alex，当然还有作者自己，从不同视角勾勒了一副攀岩群英绘卷。&lt;/li&gt;
&lt;li&gt;Alex 的攀岩历程。书中对于 Alex 2017年的攀岩经历是有着非常详实的记录的，并且作为一个旁观者，对 Alex 的诸多行为给出了自己角度的看法，可以对于了解 Alex 其人有更丰富的认知。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;《就要付出一切》这个中文标题有点不好评价，于是我找了下原版书名：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Impossible Climb: Alex Honnold, El Capitan, and the Climbing Life&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;客观来说，译者在翻译书中内容的专业度非常不错，有很多地道的中文攀岩术语。但是这个完全和原书名没有关联的译名我有点不能理解。&lt;/p&gt;
&lt;p&gt;但是退一步来说，“就要付出一切”可以算是攀岩者的一个写照，尽管这个说法感觉不够优雅，甚至有些鲁莽。攀岩者为攀岩付出了自己的一切，但似乎并不包含生命——他们看重生命的可贵。即便看起来无比疯狂的波特也是谨慎之人，Alex 也是极力提高自己成功率到他心中的“99%”才开始徒手攀岩。&lt;/p&gt;
&lt;p&gt;但另一方面，这种已知之外的不确定性也正是徒手攀岩的乐趣之所在。书中非常详实地描绘了徒手的“爽感”，而作者又从自己的角度给出了他望而却步的理由。我感觉我性格上也是有点寻刺激的成分的，但是我始终希望自己是在一种绝对安全的情况下（比如过山车、蹦极）。&lt;/p&gt;
&lt;p&gt;去年在瑞士 first 山的卡丁车游乐项目让我有了些新的感触，那是要在游玩前签一个生死状的项目。游客会在山坡上自己驾驶着卡丁车俯冲，自己能做的只有刹车调节速度和控制方向。因为自己如果稍有不慎真的会冲出没有围栏的车道，在2000米的高空自由落体，每一个人都极其谨慎与专注。这种生死仿佛只在一念之间的专注，有点像书中描述的完攀徒手攀岩后的心态：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一种增强的感知力。你需要踩的小棱看上去很大——一切都变得清晰起来。当你高度关注环境给你的反馈，不受干扰时，你的身心就会出现这样的变化。你变成一个依靠本能攀爬的动物，而不是一个努力完成困难线路的人，登顶之后，那种感知力不会立即消失。它会随着时间的流逝慢慢消退，但是有那么一段时间，你会觉得自己几乎拥有超级感官。一切都变得更强烈——雨燕在周围飞过的声音，或者太阳落山的颜色。很多次我都不想下去，不想失去那种感觉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当然，游乐项目般的卡丁车，与世界上几乎最危险都极限运动的差距实在有些过大了。但是二者的共同点都是一种更高的风险感。并且这种风险感也不是直接与死亡率挂钩，毕竟徒手攀岩的死亡率感觉还比不上某些其它运动或者疾病。我认为这里面的核心在于：&lt;strong&gt;即便没有保护，人的能动性也可以避免自己发生危险。&lt;/strong&gt; 这是一种对“人”的自觉的更高程度信任，而不是一种消极家长主义式的过度保护。想到我的小学现在因为担心小孩受伤，把所有窗户都有栏杆封起来，并禁止小孩课件跑动。这是家长与学校的共同利益权衡后的结果，但显然是一种无比荒诞的对自由的人的否定。&lt;/p&gt;
&lt;p&gt;希望未来我可以去优胜美地看看。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>书评 | 《异邦骑士》</title><link>https://www.ender-wiggin.com/posts/review-the-knight-stranger/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-the-knight-stranger/</guid><description> </description><pubDate>Tue, 21 May 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今天同事买了辆小电驴，载着我一起去健身。我突然发现我已经好久没有过这种在后座搂着前座的体验了，脑子也瞬间浮现出《异邦骑士》中那副经典意象，于是决定补一下书评。&lt;/p&gt;
&lt;p&gt;首先，这本书的最佳封面无疑是新星出版社的版本——这绚烂的夕阳直接为读者头脑中的应该浮现的那副画面染上了官方指定颜色。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20240521235909.png&quot; alt=&quot;image.png&quot; /&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><category>文学影视</category><author>Ender</author></item><item><title>书评 | 《强风吹拂》</title><link>https://www.ender-wiggin.com/posts/review-run-with-the-wind/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-run-with-the-wind/</guid><description> </description><pubDate>Sun, 19 May 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我想着动漫看得有些慢，小说可以在更加碎片化的时间里阅读，最终决定直接读原著，最后读了快5个小时才读完。如果考虑动漫23集每集23分钟再快进个1.5倍，大约是 6 个小时，确实看书还是稍微快一点点。&lt;/p&gt;
&lt;p&gt;跑步的一个有趣的地方就是可以延长体感的时间，有时候5分钟看起来都是这么漫长，尤其是对于我这种还达不到新手门槛的超级门外汉来说。这本小说一开始最让我震惊的不是箱根驿传的高门槛，因为这对我来说遥不可及，而是尼古对于这个配速的反应：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“三分半！我记得我跑三公里就花了15分钟啊。&amp;lt;br&amp;gt;
”“那真是……慢到人神共愤啊。”尼古吞云吐雾没停过，嘴里一边咕哝着。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我很喜欢“人神共愤”这个说法，这种激励对我来说比鸡汤式成功学读物有效多了。&lt;/p&gt;
&lt;p&gt;当我觉得人神共愤还能理解时，这几位的五公里首跑更是让我感觉像是步入时间尺度和我们不一致的架空平行宇宙，除了超过 30 分钟的正常人王子，第二慢的尼古总用时也小于 19 分钟。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“我竟然跑17分多……一定是姿势有问题，影响了速度。”脑袋清楚的他，马上开始分析问题所在。&lt;/p&gt;
&lt;/blockquote&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><category>文学影视</category><author>Ender</author></item><item><title>技术 | 开源低代码库 Puck 的架构与使用用例分析</title><link>https://www.ender-wiggin.com/posts/note-puck/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-puck/</guid><description> </description><pubDate>Wed, 08 May 2024 21:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/measuredco/puck&quot;&gt;Puck&lt;/a&gt; 是23年下旬才开始开发的一块开源富文本产品，截止到写下文章时收获了4.5k stars，有着不错的技术文档和使用体验，未来可期。&lt;/p&gt;
&lt;p&gt;本调研会分为如下部分：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;h2&gt;样例运行&lt;/h2&gt;
&lt;p&gt;使用官网模版运行 &lt;a href=&quot;https://github.com/measuredco/puck/tree/main/recipes/next&quot;&gt;puck/recipes/next at main · measuredco/puck · GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;可以看到，这个样例实现了一个JSON database作为数据交互模拟。&lt;/p&gt;
&lt;h3&gt;组件配置&lt;/h3&gt;
&lt;p&gt;Puck 的核心行为是通过 Config 配置的，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Puck 可以使用哪些组件&lt;/li&gt;
&lt;li&gt;如何渲染每个组件&lt;/li&gt;
&lt;li&gt;当用户选择组件时显示哪些字段&lt;/li&gt;
&lt;li&gt;额外信息，例如&lt;strong&gt;合并组&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Puck&lt;/code&gt; 组件中传入配置字段，根据配置渲染出&lt;strong&gt;编辑器UI&lt;/strong&gt;。用户与编辑器交互，触发&lt;a href=&quot;https://puckeditor.com/docs/api-reference/data&quot;&gt;data payload&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;Render&amp;gt;&lt;/code&gt; 访问 &lt;a href=&quot;https://puckeditor.com/docs/api-reference/data&quot;&gt;data payload&lt;/a&gt; 并渲染。这一块我还没看到，之后补充。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;补充：data payload 数据格式&lt;/h4&gt;
&lt;p&gt;data payload 其实就是 Puck 的数据交互格式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;content&quot;: [
    {
      &quot;type&quot;: &quot;HeadingBlock&quot;,
      &quot;props&quot;: {
        &quot;id&quot;: &quot;HeadingBlock-1234&quot;,
        &quot;title&quot;: &quot;Hello, world&quot;
      }
    }
  ],
  &quot;root&quot;: { &quot;props&quot;: { &quot;title&quot;: &quot;Puck Example&quot; } },
  &quot;zones&quot;: {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;zones&lt;/code&gt; 好像是用于拖拽的放置的，这我之后再确认一下。&lt;/p&gt;
&lt;h4&gt;补充：config数据结构&lt;/h4&gt;
&lt;p&gt;定义了每个类型的渲染方式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const config = {
  components: {
    HeadingBlock: {
      fields: {
        title: { type: &quot;text&quot; },
      },
      defaultProps: {
        title: &quot;Heading&quot;,
      },
      render: ({ title }) =&amp;gt; (
        &amp;lt;div style={{ padding: 64 }}&amp;gt;
          &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;
        &amp;lt;/div&amp;gt;
      ),
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拖拽放置后，会更新data payload。
这里的核心就是 &lt;code&gt;field&lt;/code&gt; 参数，它提供了组件的输入（例如 &lt;code&gt;title&lt;/code&gt;），是靠右侧的表单实现的。因此，对于我们项目而言，&lt;strong&gt;核心问题是：能不能把这个表单的处理移交到编辑器内部？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;目前这个结构，按我的理解是有可行性的。即：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初次生成时，预留好参数，直接填入&lt;/li&gt;
&lt;li&gt;后续通过内置编辑器交互数据，而非表单本身&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;难点：只支持一个字段，不支持富文本的多种字段形式。&lt;/p&gt;
&lt;p&gt;有一个富文本插件，具体分析可以参考下面的&lt;a href=&quot;#%E6%8F%92%E4%BB%B6%E6%89%A9%E5%B1%95%E6%80%A7&quot;&gt;插件扩展性&lt;/a&gt;。 &lt;a href=&quot;https://www.npmjs.com/package/@tohuhono/puck-rich-text&quot;&gt;@tohuhono/puck-rich-text - npm&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;布局方式&lt;/h3&gt;
&lt;p&gt;通过 &lt;code&gt;DropZone&lt;/code&gt; 实现拖放，其中有几个有趣的地方：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;布局本身是靠css实现的，很灵活&lt;/li&gt;
&lt;li&gt;布局组件是slot的形式，也挺灵活&lt;/li&gt;
&lt;li&gt;可以封装插槽到一个新的组件中，从而实现&lt;strong&gt;模版的复用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Zone可以设定允许接收的类型（通过type和组），符合dnd的设计范式&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import { DropZone } from &quot;@measured/puck&quot;;

const config = {
  components: {
    Example: {
      render: () =&amp;gt; {
        return (
          &amp;lt;div
            style={{ display: &quot;grid&quot;, gridTemplateColumns: &quot;1fr 1fr&quot;, gap: 16 }}
          &amp;gt;
            &amp;lt;DropZone zone=&quot;left-column&quot; /&amp;gt;
            &amp;lt;DropZone zone=&quot;right-column&quot; /&amp;gt;
          &amp;lt;/div&amp;gt;
        );
      },
    },
    HeadingBlock: {
      render: ({ text }) =&amp;gt; &amp;lt;p&amp;gt;{text}&amp;lt;/p&amp;gt;,
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;合并组&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const config = {
  categories: {
    typography: {
      components: [&quot;HeadingBlock&quot;, &quot;ParagraphBlock&quot;],
      title: &quot;Text&quot;,
      defaultExpanded: false, // Collapse this category by default
    },
    foundational: {
      components: [&quot;HeadingBlock&quot;],
      visible: false, // Mark this category as hidden
    },
  },
  // ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;插件扩展性&lt;/h2&gt;
&lt;p&gt;由于这个项目还在初期阶段，因此插件API的稳定性是很难保证的。在这种情况下，想要按照现有的插件API实现具体需要的功能风险会很大。&lt;/p&gt;
&lt;h3&gt;富文本插件&lt;/h3&gt;
&lt;p&gt;其中有一个富文本插件，可以参考一下它的具体实现。这个插件基于facebook开源的 &lt;a href=&quot;https://github.com/facebook/lexical&quot;&gt;Lexical&lt;/a&gt; 二次开发。原则上来说市面上几个主流的开源富文本编辑器差异不大，基本都是经过抽象处理的，可以配合内置的插件系统实现灵活需求。（注：要区分Puck的插件生态，和富文本库插件生态的区别）。&lt;/p&gt;
&lt;p&gt;这个插件目前是有明显bug的，和next.js的渲染有关。&lt;/p&gt;
&lt;h2&gt;编辑功能兼容性&lt;/h2&gt;
&lt;p&gt;综上，这个库：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;很适配前后端数据储存交互，有比较清晰实现（data payload）&lt;/li&gt;
&lt;li&gt;适配主题参数，因为本质上是直接写html+css（config）&lt;/li&gt;
&lt;li&gt;适配合并组功能，通过&lt;code&gt;DropZone&lt;/code&gt;实现。但是合并组的交互可能需要额外开发的地方。&lt;/li&gt;
&lt;li&gt;富文本。有插件，但不可靠。属于目前的痛点。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第27章-工作者线程</title><link>https://www.ender-wiggin.com/posts/note-js-27/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-27/</guid><description> </description><pubDate>Wed, 01 May 2024 22:01:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;工作者线程允许把主线程的工作转嫁给独立的实体，而不会改变现有的单线程模型。&lt;/p&gt;
&lt;p&gt;使用工作者线程，浏览器可以在原始页面环境之外再分配一个完全独立的二级子环境。这个子环境不能与依赖单线程交互的API（如DOM）互操作，但可以与父环境并行执行代码。&lt;/p&gt;
&lt;p&gt;工作者线程与线程有很多类似之处，但也有重要的区别：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;工作者线程不共享全部内存&lt;/strong&gt;。在传统线程模型中，多线程有能力读写共享内存空间。除了SharedArrayBuffer外，从工作者线程进出的数据需要复制或转移&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作者线程不一定在同一个进程里&lt;/strong&gt;。通常，一个进程可以在内部产生多个线程。根据浏览器引擎的实现，工作者线程可能与页面属于同一进程，也可能不属于。例如，Chrome的Blink引擎对共享工作者线程和服务工作者线程使用独立的进程&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建工作者线程的开销更大&lt;/strong&gt;。工作者线程有自己独立的事件循环、全局对象、事件处理程序和其他JavaScript环境必需的特性。创建这些结构的代价不容忽视&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;工作者线程相对比较重，不建议大量使用。例如，对一张400万像素的图片，为每个像素都启动一个工作者线程是不合适的。通常，工作者线程应该是长期运行的，启动成本比较高，每个实例占用的内存也比较大。&lt;/p&gt;
&lt;h3&gt;工作者线程的类型&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;专用工作者线程，只能被创建它的页面使用&lt;/li&gt;
&lt;li&gt;共享工作者线程，可以被多个不同的上下文使用，包括不同的页面。任何与创建共享工作者线程的脚本同源的脚本，都可以向共享工作者线程发送消息或从中接收消息&lt;/li&gt;
&lt;li&gt;服务工作者线程，要用途是拦截、重定向和修改页面发出的请求&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;WorkerGlobalScope&lt;/h3&gt;
&lt;p&gt;全局对象是&lt;code&gt;WorkerGlobalScope&lt;/code&gt;的实例，通过&lt;code&gt;self&lt;/code&gt;关键字暴露出来。&lt;/p&gt;
&lt;p&gt;self上可用的属性是window对象上属性的严格子集。其中有些属性会返回特定于工作者线程的版本。&lt;/p&gt;
&lt;h3&gt;专用工作者线程&lt;/h3&gt;
&lt;p&gt;创建专用工作者线程最常见的方式是加载JavaScript文件。把文件路径提供给Worker构造函数，然后构造函数再在后台异步加载脚本并实例化工作者线程。传给构造函数的文件路径可以是多种形式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // emptyWorker.js
    // 空的JS工作者线程文件
    // main.js
    console.log(location.href); // &quot;https://example.com/&quot;
    const worker = new Worker(location.href+&apos;emptyWorker.js&apos;);
    console.log(worker);          // Worker {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;工作者线程的脚本文件只能从与父页面相同的源加载。从其他源加载工作者线程的脚本文件会导致错误。&lt;/p&gt;
&lt;h4&gt;与专用工作者线程通信&lt;/h4&gt;
&lt;p&gt;与工作者线程的通信都是通过异步消息完成的，但这些消息可以有多种形式。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用postMessage()，最简单也最常用的形式是使用postMessage()传递序列化的消息。&lt;/li&gt;
&lt;li&gt;使用MessageChannel，基于该API可以在两个上下文间明确建立通信渠道（很复杂，不推荐，主要意义是两个工作者线程直接的通信）。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    // worker.js
    // 在监听器中存储全局messagePort
    let messagePort = null;
    function factorial(n) {
      let result = 1;
      while(n) { result ＊= n--; }
      return result;
    }
    // 在全局对象上添加消息处理程序
    self.onmessage = ({ports}) =&amp;gt; {
      // 只设置一次端口
      if (! messagePort) {
        // 初始化消息发送端口，
        // 给变量赋值并重置监听器
        messagePort=ports[0];
        self.onmessage = null;
        // 在全局对象上设置消息处理程序
        messagePort.onmessage = ({data}) =&amp;gt; {
          // 收到消息后发送数据
          messagePort.postMessage(`${data}! = ${factorial(data)}`);
        };
      }
    };
    // main.js
    const channel=new MessageChannel();
    const factorialWorker = new Worker(&apos;./worker.js&apos;);
    // 把`MessagePort`对象发送到工作者线程
    // 工作者线程负责处理初始化信道
    factorialWorker.postMessage(null, [channel.port1]);
    // 通过信道实际发送数据
    channel.port2.onmessage=({data})=&amp;gt;console.log(data);
    // 工作者线程通过信道响应
    channel.port2.postMessage(5);
    // 5! = 120
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;使用BroadcastChannel，同源脚本能够通过BroadcastChannel相互之间发送和接收消息，比较简单。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;工作者线程数据传输&lt;/h3&gt;
&lt;p&gt;使用工作者线程时，经常需要为它们提供某种形式的数据负载。工作者线程是独立的上下文，因此在上下文之间传输数据就会产生消耗。&lt;/p&gt;
&lt;p&gt;在支持传统多线程模型的语言中，可以使用锁、互斥量，以及volatile变量。&lt;/p&gt;
&lt;p&gt;在JavaScript中，有三种在上下文间转移信息的方式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;结构化克隆算法（structuredclone algorithm）&lt;/li&gt;
&lt;li&gt;可转移对象（transferableobjects）&lt;/li&gt;
&lt;li&gt;共享数组缓冲区（shared arraybuffers，之前有漏洞，现在逐步重启，传递的是缓冲区中的引用）&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;结构化克隆算法&lt;/h4&gt;
&lt;p&gt;结构化克隆算法可用于在两个独立上下文间共享数据。该算法由浏览器在后台实现，不能直接调用。在通过postMessage()传递对象时，浏览器会遍历该对象，并在目标上下文中生成它的一个&lt;strong&gt;副本&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;可转移对象&lt;/h4&gt;
&lt;p&gt;使用可转移对象（transferable objects）可以把所有权从一个上下文转移到另一个上下文。在不太可能在上下文间复制大量数据的情况下，这个功能特别有用。只有如下几种对象是可转移对象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ArrayBuffer&lt;/li&gt;
&lt;li&gt;MessagePort&lt;/li&gt;
&lt;li&gt;ImageBitmap&lt;/li&gt;
&lt;li&gt;OffscreenCanvas&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;postMessage()方法的第二个可选参数是数组，它指定应该将哪些对象转移到目标上下文。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;共享工作者线程&lt;/h3&gt;
&lt;p&gt;共享线程适合开发者希望通过在多个上下文间共享线程减少计算性消耗的情形。比如，可以用一个共享线程管理&lt;strong&gt;多个同源页面WebSocket消息的发送与接收&lt;/strong&gt;。共享线程也可以用在同源上下文希望通过一个线程通信的情形。&lt;/p&gt;
&lt;h3&gt;服务工作者线程&lt;/h3&gt;
&lt;p&gt;服务工作者线程在两个主要任务上最有用：充当网络请求的缓存层和启用推送通知。在这个意义上，服务工作者线程就是用于把网页变成像原生应用程序一样的工具。&lt;/p&gt;
&lt;h4&gt;ServiceWorkerContainer&lt;/h4&gt;
&lt;p&gt;服务工作者线程与专用工作者线程或共享工作者线程的一个区别是没有全局构造函数。服务工作者线程是通过ServiceWorkerContainer来管理的，它的实例保存在navigator.serviceWorker属性中。&lt;/p&gt;
&lt;h4&gt;创建服务工作者线程&lt;/h4&gt;
&lt;p&gt;与共享工作者线程类似，服务工作者线程同样是在还不存在时创建新实例，在存在时连接到已有实例。ServiceWorkerContainer没有通过全局构造函数创建，而是暴露了register()方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    navigator.serviceWorker.register(&apos;./emptyServiceWorker.js&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;register()方法返回一个期约，该期约解决为ServiceWorkerRegistration对象，或在注册失败时拒绝。&lt;/p&gt;
&lt;p&gt;由于服务工作者线程几乎可以任意修改和重定向网络请求，以及加载静态资源，服务工作者线程API只能在安全上下文（HTTPS）下使用。&lt;/p&gt;
&lt;h4&gt;缓存&lt;/h4&gt;
&lt;ul&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;li&gt;唯一的浏览器强制逐出策略基于服务工作者线程缓存占用的空间&lt;/li&gt;
&lt;li&gt;服务工作者线程负责管理自己缓存占用的空间&lt;/li&gt;
&lt;li&gt;缓存超过浏览器限制时，浏览器会基于最近最少使用（LRU, LeastRecently Used）原则为新缓存腾出空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;推送通知&lt;/h4&gt;
&lt;p&gt;对于模拟原生应用程序的Web应用程序而言（PWA, Progressive Web Apps），必须支持推送消息。这意味着网页必须能够接收服务器的推送事件，然后在设备上显示通知（即使应用程序没有运行）。当然，这在常规网页中肯定是不可能的。不过，有了服务工作者线程就可以实现该行为。&lt;/p&gt;
&lt;p&gt;服务工作者线程可以通过它们的注册对象使用Notification API。这样做有很好的理由：与服务工作者线程关联的通知也会触发服务工作者线程内部的交互事件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    navigator.serviceWorker.register(&apos;./serviceWorker.js&apos;)
    .then((registration) =&amp;gt; {
      Notification.requestPermission()
      .then((status)=&amp;gt;{
        if(status===&apos;granted&apos;){
          registration.showNotification(&apos;foo&apos;);
        }
      });
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    self.onactivate = () =&amp;gt; self.registration.showNotification(&apos;bar&apos;);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第26章-模块</title><link>https://www.ender-wiggin.com/posts/note-js-26/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-26/</guid><description> </description><pubDate>Wed, 01 May 2024 22:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;这一章内容太细了，我只记录一下我可能会使用到的几个模块的原理。&lt;/p&gt;
&lt;h2&gt;CommonJS&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;exports 记录当前模块导出的变量。&lt;/li&gt;
&lt;li&gt;module 记录当前模块的详细信息。&lt;/li&gt;
&lt;li&gt;require 进行模块的导入。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;exports&lt;/h3&gt;
&lt;p&gt;首先来看exports导出，面试经常会问的一个题目是exports和module.exports区别是什么。两者指向同一块内存，但是使用并不是完全等价的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当绑定一个属性时，两者相同。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;exports.propA = &apos;A&apos;;
module.exports.propB = &apos;B&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;不能直接赋值给exports，也就是不能直接使用exports={}这种语法。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 失败
exports = {propA:&apos;A&apos;};
// 成功
module.exports = {propB:&apos;B&apos;};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然两者指向同一块内存，但最后被导出的是module.exports，所以不能直接赋值给exports。
同样的道理，只要最后直接给module.exports赋值了，之前绑定的属性都会被覆盖掉。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exports.propA = &apos;A&apos;;
module.exports.propB = &apos;B&apos;;
module.exports = {propC:&apos;C&apos;};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用上面的例子所示，先是绑定了两个属性propA和propB，接着给module.exports赋值，最后能成功导出的只有propC。&lt;/p&gt;
&lt;h3&gt;require&lt;/h3&gt;
&lt;p&gt;CommonJS的引入特点是值的拷贝，简单来说就是把导出值复制一份，放到一块新的内存中。&lt;/p&gt;
&lt;h3&gt;循环引入&lt;/h3&gt;
&lt;p&gt;AB模块间的互相引用，本应是个死循环，但是实际并没有，因为CommonJS做了特殊处理——&lt;strong&gt;模块缓存&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//index.js
var a = require(&apos;./a&apos;)
console.log(&apos;入口模块引用a模块：&apos;,a)
// a.js
exports.a = &apos;原始值-a模块内变量&apos;
var b = require(&apos;./b&apos;)
console.log(&apos;a模块引用b模块：&apos;,b)
exports.a = &apos;修改值-a模块内变量&apos;
// b.js
exports.b =&apos;原始值-b模块内变量&apos;
var a = require(&apos;./a&apos;)
console.log(&apos;b模块引用a模块&apos;,a)
exports.b = &apos;修改值-b模块内变量&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;【入口模块】开始执行，把入口模块加入缓存。&lt;/li&gt;
&lt;li&gt;var a = require(&apos;./a&apos;) 执行 将a模块加入缓存，进入a模块。&lt;/li&gt;
&lt;li&gt;【a模块】exports.a = &apos;原始值-a模块内变量&apos;执行，a模块的缓存中给变量a初始化，为原始值。&lt;/li&gt;
&lt;li&gt;执行var b = require(&apos;./b&apos;)，将b模块加入缓存，进入b模块。&lt;/li&gt;
&lt;li&gt;【b模块】exports.b =&apos;原始值-b模块内变量&apos;，b模块的缓存中给变量b初始化，为原始值。&lt;/li&gt;
&lt;li&gt;var a = require(&apos;./a&apos;)，尝试导入a模块，发现已有a模块的缓存，所以不会进入执行，而是直接取a模块的缓存，此时打印{ a: &apos;原始值-a模块内变量&apos; }。&lt;/li&gt;
&lt;li&gt;exports.b = &apos;修改值-b模块内变量 执行，将b模块的缓存中变量b替换成修改值。&lt;/li&gt;
&lt;li&gt;【a模块】console.log(&apos;a模块引用b模块：&apos;,b) 执行，取缓存中的值，打印{ b: &apos;修改值-b模块内变量&apos; }。&lt;/li&gt;
&lt;li&gt;exports.a = &apos;修改值-a模块内变量&apos; 执行，将a模块缓存中的变量a替换成修改值。&lt;/li&gt;
&lt;li&gt;【入口模块】console.log(&apos;入口模块引用a模块：&apos;,a) 执行，取缓存中的值，打印{ a: &apos;修改值-a模块内变量&apos; }。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;上面就是对循环引用的处理过程，循环引用无非是要解决两个问题，怎么避免死循环以及输出的值是什么。CommonJS通过模块缓存来解决：每一个模块都先加入缓存再执行，每次遇到require都先检查缓存，这样就不会出现死循环；借助缓存，输出的值也很简单就能找到了。&lt;/p&gt;
&lt;h3&gt;路径解析规则&lt;/h3&gt;
&lt;p&gt;比如，为什么我们导入时直接简单写一个&apos;react&apos;就正确找到包的位置。
原因是，module这个对象中，还包括了paths这个路径。&lt;/p&gt;
&lt;p&gt;首先把路径作一个简单分类：内置的核心模块、本地的文件模块和第三方模块。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对于核心模块，node将其已经编译成二进制代码，直接书写标识符fs、http就可以。&lt;/li&gt;
&lt;li&gt;对于自己写的文件模块，需要用‘./’&apos;../&apos;开头，require会将这种相对路径转化为真实路径，找到模块。&lt;/li&gt;
&lt;li&gt;对于第三方模块，也就是使用npm下载的包，就会用到paths这个变量，会依次查找当前路径下的node_modules文件夹，如果没有，则在父级目录查找no_modules，一直到根目录下，找到为止。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在node_modules下找到对应包后，会以package.json文件下的main字段为准，找到包的入口，如果没有main字段，则查找index.js/index.json/index.node。&lt;/p&gt;
&lt;h2&gt;ES Module&lt;/h2&gt;
&lt;p&gt;ECMAScript 6模块是作为一整块JavaScript代码而存在的。带有&lt;code&gt;type=&quot;module&quot;&lt;/code&gt;属性的&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;标签会告诉浏览器相关代码应该作为模块执行，而不是作为传统的脚本执行（行为类似defer）。&lt;/p&gt;
&lt;h3&gt;export&lt;/h3&gt;
&lt;p&gt;ES Module导出的是一份&lt;strong&gt;值的引用&lt;/strong&gt;，CommonJS则是一份值的拷贝。也就是说，CommonJS是把暴露的对象拷贝一份，放在新的一块内存中，每次直接在新的内存中取值，所以对变量修改没有办法同步；而ES Module则是指向同一块内存，模块实际导出的是这块内存的地址，每当用到时根据地址找到对应的内存空间，这样就实现了所谓的“动态绑定”。&lt;/p&gt;
&lt;p&gt;两种导出模式可以共用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const foo = &apos;foo&apos;;
    const bar = &apos;bar&apos;;
    export { foo as default, bar };
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;import&lt;/h3&gt;
&lt;p&gt;ES module会根据import关系构建一棵依赖树，遍历到树的叶子模块后，然后根据依赖关系，反向找到父模块，将export/import指向同一地址。&lt;/p&gt;
&lt;p&gt;如果在浏览器中通过标识符原生加载模块，则文件必须带有&lt;code&gt;.js&lt;/code&gt;扩展名，不然可能无法正确解析。不过，如果是通过构建工具或第三方模块加载器打包或解析的ES6模块，则可能不需要包含文件扩展名。&lt;/p&gt;
&lt;p&gt;导入对模块而言是&lt;strong&gt;只读&lt;/strong&gt;的，实际上相当于&lt;code&gt;const&lt;/code&gt;声明的变量。在使用&lt;code&gt;*&lt;/code&gt;执行批量导入时，赋值给别名的命名导出就好像使用&lt;code&gt;Object.freeze()&lt;/code&gt;冻结过一样。直接修改导出的值是不可能的，但可以修改导出对象的属性。同样，也不能给导出的集合添加或删除导出的属性。要修改导出的值，必须使用有内部变量和属性访问权限的导出方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    import foo, * as Foo &apos;./foo.js&apos;;
    foo = &apos;foo&apos;;      // 错误
    Foo.foo = &apos;foo&apos;; // 错误
    foo.bar = &apos;bar&apos;; // 允许
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    // 默认导出的等效方式
    import { default as foo } from &apos;./foo.js&apos;;
    import foo from &apos;./foo.js&apos;;

    import foo, { bar, baz } from &apos;./foo.js&apos;;
    import { default as foo, bar, baz } from &apos;./foo.js&apos;;
    import foo, * as Foo from &apos;./foo.js&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;向下兼容&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    // 支持模块的浏览器会执行这段脚本
    // 不支持模块的浏览器不会执行这段脚本
    &amp;lt;script type=&quot;module&quot;src=&quot;module.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    // 支持模块的浏览器不会执行这段脚本
    // 不支持模块的浏览器会执行这段脚本
    &amp;lt;script nomodule src=&quot;script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;循环引入&lt;/h3&gt;
&lt;p&gt;和CommonJS一样，发生循环引用时并不会导致死循环，但两者的处理方式大有不同。CommonJS对循环引用的处理基于他的缓存，即：将导出值拷贝一份，放在一块新的内存，用到的时候直接读取这块内存。&lt;/p&gt;
&lt;p&gt;但ES module导出的是一个索引——内存地址，没有办法这样处理。&lt;strong&gt;它依赖的是“模块地图”和“模块记录”&lt;/strong&gt;，模块地图在下面会解释，而模块记录是好比每个模块的“身份证”，记录着一些关键信息——这个模块导出值的的内存地址，加载状态，在其他模块导入时，会做一个“连接”——根据模块记录，把导入的变量指向同一块内存，这样就是实现了动态绑定。&lt;/p&gt;
&lt;p&gt;下面这个例子，和之前的demo逻辑一样：入口模块引用a模块，a模块引用b模块，b模块又引用a模块，这种ab模块相互引用就形成了循环。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// index.mjs
import * as a from &apos;./a.mjs&apos;
console.log(&apos;入口模块引用a模块：&apos;,a)
// a.mjs
let a = &quot;原始值-a模块内变量&quot;
export { a }
import * as b from &quot;./b.mjs&quot;
console.log(&quot;a模块引用b模块：&quot;, b)
a = &quot;修改值-a模块内变量&quot;
// b.mjs
let b = &quot;原始值-b模块内变量&quot;
export { b }
import * as a from &quot;./a.mjs&quot;
console.log(&quot;b模块引用a模块：&quot;, a)
b = &quot;修改值-b模块内变量&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，在b模块中引用a模块时，得到的值是uninitialized，接下来一步步分析代码的执行。&lt;/p&gt;
&lt;p&gt;在代码执行前，首先要进行预处理，这一步会根据import和export来构建模块地图（Module Map），它类似于一颗树，树中的每一个“节点”就是一个模块记录，这个记录上会标注导出变量的内存地址，将导入的变量和导出的变量连接，即把他们指向同一块内存地址。不过此时这些内存都是空的，也就是看到的uninitialized。&lt;/p&gt;
&lt;p&gt;接下来就是代码的一行行执行，import和export语句都是只能放在代码的顶层，也就是说不能写在函数或者if代码块中。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;【入口模块】首先进入入口模块，在模块地图中把入口模块的模块记录标记为“获取中”（Fetching），表示已经进入，但没执行完毕。&lt;/li&gt;
&lt;li&gt;import * as a from &apos;./a.mjs&apos; 执行，进入a模块，此时模块地图中a的模块记录标记为“获取中”。&lt;/li&gt;
&lt;li&gt;【a模块】import * as b from &apos;./b.mjs&apos; 执行，进入b模块，此时模块地图中b的模块记录标记为“获取中”。&lt;/li&gt;
&lt;li&gt;【b模块】import * as a from &apos;./a.mjs&apos; 执行，检查模块地图，模块a已经是Fetching态，不再进去。&lt;/li&gt;
&lt;li&gt;let b = &apos;原始值-b模块内变量&apos; 模块记录中，存储b的内存块初始化。&lt;/li&gt;
&lt;li&gt;console.log(&apos;b模块引用a模块：&apos;, a) 根据模块记录到指向的内存中取值，是{ a:}。&lt;/li&gt;
&lt;li&gt;b = &apos;修改值-b模块内变量&apos; 模块记录中，存储b的内存块值修改。&lt;/li&gt;
&lt;li&gt;【a模块】let a = &apos;原始值-a模块内变量&apos; 模块记录中，存储a的内存块初始化。&lt;/li&gt;
&lt;li&gt;console.log(&apos;a模块引用b模块：&apos;, b) 根据模块记录到指向的内存中取值，是{ b: &apos;修改值-b模块内变量&apos; }。&lt;/li&gt;
&lt;li&gt;a = &apos;修改值-a模块内变量&apos; 模块记录中，存储a的内存块值修改。&lt;/li&gt;
&lt;li&gt;【入口模块】console.log(&apos;入口模块引用a模块：&apos;,a) 根据模块记录，到指向的内存中取值，是{ a: &apos;修改值-a模块内变量&apos; }。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总结一下：和上面一样，循环引用要解决的无非是两个问题，保证不进入死循环以及输出什么值。ES Module来处理循环使用一张模块间的依赖地图来解决死循环问题，标记进入过的模块为“获取中”，所以循环引用时不会再次进入；使用模块记录，标注要去哪块内存中取值，将导入导出做连接，解决了要输出什么值。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>书评 | 《足利女童连续失踪事件》</title><link>https://www.ender-wiggin.com/posts/review-ashikaga-murder-case/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-ashikaga-murder-case/</guid><description> </description><pubDate>Mon, 29 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;p&gt;上一本《桶川》后劲挺大的，所以当看到作者还有一本书后就光速加入书单了。首先，不可否认，也毫无争议的是，清水洁是一位伟大的记者以及作家。这本书总的来说也是还不错的作品，但个人感觉不如上一作。&lt;/p&gt;
&lt;p&gt;从叙事视角上来说，《桶川》的叙述主体是学生和作者，而《足利》更侧重受害人家长与被冤枉的凶手。从事件发生时间上来看，《桶川》是实时跟进以及作者亲历的事件，而《足利》是被时间尘封的冤案，缺少了一种直接的紧张感。另外，从纪实目的上来说，《桶川》是一次为被害人鸣不平的完整记录，中期笔锋直指当地警察局但是视角上没有上升到抽象的执法机关，而《足利》的核心关注点就是司法体系的现有弊端，不论是死刑执行还是司法程序，并且考虑到事件悬而未决的固有属性，对嫌疑人以及司法的叙述都有一种克制。&lt;/p&gt;
&lt;p&gt;这本书的叙述很容易让人联想到另外一本虚构的社会派推理《消失的13级台阶》，其中对于死刑、冤案、判决的探讨异曲同工。尽管作者在书中说了“不反对死刑”，但还是能看出他认为冤案代价太大而坚决的废死立场，而《消失的13级台阶》有一种贯穿始终的报应主义立场，作为对比阅读也还饶有趣味。&lt;/p&gt;
&lt;p&gt;阅读到书籍中部，看到作者描述他与“鲁邦”的接触，我一度以为事件就快要像《桶川》一样收尾了。我相信戛然而止的结局让很多读者也为之失望，也能看到作者在面对庞大机关的无力感。这种令人不适的不完整，或许也正是纪实所传达的这种真实的沉重感——《桶川》更像是一次机缘巧合下的奇迹。&lt;/p&gt;
&lt;p&gt;但是我在阅读中感受到了一定的突兀感，这是我在《桶川》中完全没有察觉甚至觉得是优点的，即比例有点太大的“文学性”。我觉得这直接的区别在于，《桶川》全书都是一种弱者向世界抗争的声声呐喊，这种文学性有一种恰到好处的推波助澜感。但是《足利》更像是在真空中的呐喊，看得出来作者很卖力——但是却又无法掀起波澜。
另外，这种文学性，在某种意义上增添了剧情的波折程度，但是对于纪实这个严肃文体本身并没有什么帮助。比如：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我离开座位，朝电梯走去。按下电梯键的瞬间，如同按下了引爆开关。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这句话读起来像是赴汤蹈火的超级英雄电影画面，但是放在一个压抑的叙事中，感觉就有些违和。当然这种形式也可以解读为“鸟鸣山更幽”，只是我个人从阅读偏好上会更喜欢一种连贯的克制。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>学习笔记 | 《一天理解 JavaScript Promise》</title><link>https://www.ender-wiggin.com/posts/note-js-promise/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-promise/</guid><description>这个书名有点浮夸，但毕竟作者鼎鼎有名，看肯定还是要看一下的。</description><pubDate>Sat, 27 Apr 2024 21:01:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;因为这本书对 &lt;code&gt;Promise&lt;/code&gt; 有很多细节上的介绍，因此这篇笔记主要也以我个人的查漏补缺为主，一些基本的语法可以参考之前写过的 &lt;code&gt;学习笔记 | JS 高级程序设计-第11章-异步&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;一、Promise 基础&lt;/h2&gt;
&lt;h2&gt;1. Promise 生命周期&lt;/h2&gt;
&lt;p&gt;状态：pending/fulfilled/rejected&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一道面试题：如何获取一个 Promise 实例的状态？&lt;/strong&gt;
Promise有一个非公开的内部属性&lt;code&gt;[[PromiseState]]&lt;/code&gt; 反映状态&lt;/p&gt;
&lt;h2&gt;起源&lt;/h2&gt;
&lt;p&gt;早期 JS 实现需要通过计时器和回调实现异步，会导致多层嵌套的“回调地狱”。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function double(value, success, failure) {
  setTimeout(() =&amp;gt; {
    try {
      if (typeof value !== &quot;number&quot;) {
        throw &quot;Must provide number as first argument&quot;;
      }
      success(2 * value);
    } catch (e) {
      failure(e);
    }
  }, 1000);
}
const successCallback = (x) =&amp;gt; {
  double(x, (y) =&amp;gt; console.log(`Success: ${y}`));
};
const failureCallback = (e) =&amp;gt; console.log(`Failure: ${e}`);
double(3, successCallback, failureCallback); // Success: 12（大约1000 毫秒之后）
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;期约 Promise&lt;/h2&gt;
&lt;p&gt;2010年，CommonJS项目实现的Promises/A规范日益流行起来。后续出现了多种期约实现，2012年Promises/A+组织 fork 了CommonJS的Promises/A建议，并以相同的名字制定了Promises/A+规范。这个规范最终成为了ECMAScript6规范实现的范本。&lt;/p&gt;
&lt;h3&gt;状态机&lt;/h3&gt;
&lt;p&gt;期约是一个有状态的对象，可能处于如下3种状态之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;待定（pending）&lt;/li&gt;
&lt;li&gt;兑现（fulfilled，或resolved）-&amp;gt; 产生一个私有内部值 value&lt;/li&gt;
&lt;li&gt;拒绝（rejected）-&amp;gt; 产生一个私有内部理由 reason&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;期约的状态是私有的，不能直接通过JavaScript检测到。这主要是为了避免根据读取到的期约状态，以同步方式处理期约对象。另外，期约的状态也不能被外部JavaScript代码修改。这与不能读取该状态的原因是一样的：期约故意将异步行为封装起来，从而隔离外部的同步代码。&lt;/p&gt;
&lt;h3&gt;执行函数&lt;/h3&gt;
&lt;p&gt;由于期约的状态是私有的，所以只能在内部进行操作。内部操作在期约的执行器函数中完成（resolve 和 reject）。&lt;/p&gt;
&lt;p&gt;执行器函数是&lt;strong&gt;同步&lt;/strong&gt;执行的。这是因为执行器函数是期约的初始化程序。&lt;/p&gt;
&lt;p&gt;无论resolve()和reject()中的哪个被调用，状态转换都不可撤销了。于是继续修改状态会静默失败。&lt;/p&gt;
&lt;h4&gt;Promise.resolve()&lt;/h4&gt;
&lt;p&gt;通过调用Promise.resolve()静态方法，可以实例化一个解决的期约。这个解决的期约的值对应着传给Promise.resolve()的&lt;strong&gt;第一个&lt;/strong&gt;参数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setTimeout(console.log, 0, Promise.resolve());
// Promise &amp;lt;resolved&amp;gt;: undefined
setTimeout(console.log, 0, Promise.resolve(3));
// Promise &amp;lt;resolved&amp;gt;: 3
// 多余的参数会忽略
setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise &amp;lt;resolved&amp;gt;: 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Promise.resolve()是一个幂等方法，如果传入的参数本身是一个期约，那它的行为就类似于一个空包装。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p));
// true
setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));
// true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，这个静态方法能够包装任何非期约值，包括错误对象，并将其转换为解决的期约。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let p = Promise.resolve(new Error(&apos;foo&apos;)); setTimeout(console.log, 0, p);
//Promise&amp;lt;resolved&amp;gt;: Error: foo
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Promise.reject()&lt;/h4&gt;
&lt;p&gt;Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误（这个错误不能通过try/catch捕获，而只能通过拒绝处理程序捕获）。&lt;/p&gt;
&lt;p&gt;关键在于，&lt;code&gt;Promise.reject()&lt;/code&gt;并没有照搬&lt;code&gt;Promise.resolve()&lt;/code&gt;的幂等逻辑。如果给它传一个期约对象，则这个期约会成为它返回的拒绝期约的理由。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setTimeout(console.log, 0, Promise.reject(Promise.resolve()));
// Promise &amp;lt;rejected&amp;gt;: Promise &amp;lt;resolved&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;期约真正的异步特性：它们是同步对象（在同步执行模式中使用），但也是异步执行模式的媒介。比如下面的例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    thrownewError(&apos;foo&apos;);
  } catch(e) {
    console.log(e); // Error: foo
  }
  try {
    Promise.reject(newError(&apos;bar&apos;));
  } catch(e) {
    console.log(e);
  }
  //Uncaught(inpromise)Error: bar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拒绝期约的错误并没有抛到执行同步代码的线程里，而是通过浏览器异步消息队列来处理的。因此，try/catch块并不能捕获该错误。代码一旦开始以异步模式执行，则唯一与之交互的方式就是使用异步结构——更具体地说，就是期约的方法。&lt;/p&gt;
&lt;p&gt;期约可以以任何理由拒绝，包括undefined，但最好统一使用错误对象。这样做主要是因为创建错误对象可以让浏览器捕获错误对象中的&lt;strong&gt;栈追踪信息&lt;/strong&gt;，而这些信息对调试是非常关键的。&lt;/p&gt;
&lt;h3&gt;期约的实例方法&lt;/h3&gt;
&lt;p&gt;期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据，处理期约成功和失败的结果，连续对期约求值，或者添加只有期约进入终止状态时才会执行的代码。&lt;/p&gt;
&lt;h4&gt;Thenable接口&lt;/h4&gt;
&lt;p&gt;在ECMAScript暴露的异步结构中，任何对象都有一个&lt;code&gt;then()&lt;/code&gt;方法。这个方法被认为实现了Thenable接口。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyThenable {
	then() {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Promise.prototype.then()&lt;/h4&gt;
&lt;p&gt;这个&lt;code&gt;then()&lt;/code&gt;方法接收最多两个参数：&lt;code&gt;onResolved&lt;/code&gt; 处理程序和&lt;code&gt;onRejected&lt;/code&gt;处理程序。这两个参数都是可选的。传给then()的任何&lt;strong&gt;非函数类型&lt;/strong&gt;的参数都会被静默忽略。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Promise.prototype.then()&lt;/code&gt;方法返回一个新的期约实例。
这个新期约实例基于&lt;code&gt;onResovled&lt;/code&gt;处理程序的返回值构建。换句话说，该处理程序的返回值会通过&lt;code&gt;Promise.resolve()&lt;/code&gt;包装来生成新期约。如果没有提供这个处理程序，则&lt;code&gt;Promise.resolve()&lt;/code&gt;就会包装上一个期约解决之后的值。如果没有显式的返回语句，则&lt;code&gt;Promise.resolve()&lt;/code&gt;会包装默认的返回值&lt;code&gt;undefined&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;onRejected&lt;/code&gt;处理程序也与之类似：&lt;code&gt;onRejected&lt;/code&gt;处理程序返回的值也会被&lt;code&gt;Promise.resolve()&lt;/code&gt;包装。&lt;/p&gt;
&lt;h4&gt;Promise.prototype.catch()&lt;/h4&gt;
&lt;p&gt;这个方法就是一个语法糖，调用它就相当于调用&lt;code&gt;Promise.prototype. then(null, onRejected)&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;Promise.prototype.finally()&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Promise.prototype.finally()&lt;/code&gt;方法用于给期约添加&lt;code&gt;onFinally&lt;/code&gt;处理程序，这个处理程序在期约转换为解决或拒绝状态时都会执行。这个方法可以避免&lt;code&gt;onResolved&lt;/code&gt;和o&lt;code&gt;nRejected&lt;/code&gt;处理程序中出现冗余代码。但&lt;code&gt;onFinally&lt;/code&gt;处理程序没有办法知道期约的状态是解决还是拒绝，所以这个方法主要用于添加清理代码。&lt;/p&gt;
&lt;p&gt;这个新期约实例不同于&lt;code&gt;then()&lt;/code&gt;或&lt;code&gt;catch()&lt;/code&gt;方式返回的实例。因为&lt;code&gt;onFinally&lt;/code&gt;被设计为一个&lt;strong&gt;状态无关&lt;/strong&gt;的方法，所以在大多数情况下它将表现为父期约的传递（父期约什么值，它就是什么值）。对于已解决状态和被拒绝状态都是如此。&lt;/p&gt;
&lt;p&gt;特殊情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回待定期约&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onFinally&lt;/code&gt;处理程序抛出了错误（显式抛出或返回了一个拒绝期约）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;非重入期约方法&lt;/h4&gt;
&lt;p&gt;“非重入”（non-reentrancy）特性保证了当期约进入落定状态时，与该状态相关的处理程序仅仅会被排期，而非立即执行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let synchronousResolve;
    // 创建一个期约并将解决函数保存在一个局部变量中
    let p = new Promise((resolve) =&amp;gt; {
      synchronousResolve = function() {
        console.log(&apos;1: invoking resolve()&apos;);
        resolve();
        console.log(&apos;2: resolve() returns&apos;);
      };
    });
    p.then(() =&amp;gt; console.log(&apos;4: then() handler executes&apos;));
    synchronousResolve();
    console.log(&apos;3: synchronousResolve() returns&apos;);
    // 实际的输出：
    //1: invokingresolve()
    // 2: resolve()returns
    // 3: synchronousResolve() returns
    // 4: then() handler executes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;期约连锁与期约合成&lt;/h3&gt;
&lt;p&gt;把期约逐个地串联起来称为期约连锁。
要真正执行异步任务，可以让每个执行器都返回一个期约实例。这样就可以让每个后续期约都等待之前的期约，也就是&lt;strong&gt;串行化异步任务&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let p1 = new Promise((resolve, reject) =&amp;gt; {
  console.log(&apos;p1 executor&apos;);
  setTimeout(resolve, 1000);
});
p1.then(() =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
    console.log(&apos;p2 executor&apos;);
    setTimeout(resolve, 1000);
  }))
  .then(() =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
    console.log(&apos;p3 executor&apos;);
    setTimeout(resolve, 1000);
  }))
  .then(() =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
    console.log(&apos;p4 executor&apos;);
    setTimeout(resolve, 1000);
  }));
// p1 executor（1 秒后）
// p2 executor（2 秒后）
// p3 executor（3 秒后）
// p4 executor（4 秒后）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把生成期约的代码提取到一个工厂函数中，就可以写成这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function delayedResolve(str) {
  return new Promise((resolve, reject) =&amp;gt; {
    console.log(str);
    setTimeout(resolve, 1000);
  });
}
delayedResolve(&apos;p1 executor&apos;)
  .then(() =&amp;gt; delayedResolve(&apos;p2 executor&apos;))
  .then(() =&amp;gt; delayedResolve(&apos;p3 executor&apos;))
  .then(() =&amp;gt; delayedResolve(&apos;p4 executor&apos;))
// p1 executor（1 秒后）
// p2 executor（2 秒后）
// p3 executor（3 秒后）
// p4 executor（4 秒后）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种写法优化了之前的“回调地狱”问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function delayedExecute(str, callback = null) {
  setTimeout(() =&amp;gt; {
	console.log(str);
	callback &amp;amp;&amp;amp; callback();
  }, 1000)
}
delayedExecute(&apos;p1 callback&apos;, () =&amp;gt; {
  delayedExecute(&apos;p2 callback&apos;, () =&amp;gt; {
	delayedExecute(&apos;p3 callback&apos;, () =&amp;gt; {
	  delayedExecute(&apos;p4 callback&apos;);
	});
  });
});
// p1 callback（1 秒后）
// p2 callback（2 秒后）
// p3 callback（3 秒后）
// p4 callback（4 秒后）
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Promise.all()和Promise.race()&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Promise.all()&lt;/code&gt;静态方法创建的期约会在一组期约全部解决之后再解决。这个静态方法接收一个可迭代对象，返回一个新期约。&lt;/p&gt;
&lt;p&gt;合成的期约只会在每个包含的期约都解决之后才解决。如果至少有一个包含的期约待定，则合成的期约也会待定。&lt;/p&gt;
&lt;p&gt;如果有期约拒绝，则第一个拒绝的期约会将自己的理由作为合成期约的拒绝理由。之后再拒绝的期约不会影响最终期约的拒绝理由。不过，这并不影响所有包含期约正常的拒绝操作。合成的期约会静默处理所有包含期约的拒绝操作。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Promise.race()&lt;/code&gt;静态方法返回一个包装期约，是一组集合中最先解决或拒绝的期约的镜像。这个方法接收一个可迭代对象，返回一个新期约。无论是解决还是拒绝，只要是第一个落定的期约，&lt;code&gt;Promise.race()&lt;/code&gt;就会包装其解决值或拒绝理由并返回新期约。&lt;/p&gt;
&lt;h4&gt;串行期约合成&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function addTwo(x) {return x + 2; }
function addThree(x) {return x + 3; }
function addFive(x) {return x + 5; }
functioncompose(...fns){
  return(x)=&amp;gt;fns.reduce((promise, fn)=&amp;gt;promise.then(fn), Promise.resolve(x))
}
let addTen = compose(addTwo, addThree, addFive);
addTen(8).then(console.log); // 18
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;期约扩展&lt;/h2&gt;
&lt;h3&gt;期约取消&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class CancelToken {
  constructor(cancelFn) {
	this.promise = new Promise((resolve, reject) =&amp;gt; {
	  cancelFn(resolve);
	});
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button id=&quot;start&quot;&amp;gt;Start&amp;lt;/button&amp;gt;
&amp;lt;button id=&quot;cancel&quot;&amp;gt;Cancel&amp;lt;/button&amp;gt;
&amp;lt;script&amp;gt;
class CancelToken {
  constructor(cancelFn) {
	this.promise = new Promise((resolve, reject) =&amp;gt; {
	  cancelFn(() =&amp;gt; {
		setTimeout(console.log, 0, &quot;delay cancelled&quot;);
		resolve();
	  });
	});
  }
}
const startButton = document.querySelector(&apos;#start&apos;);
const cancelButton = document.querySelector(&apos;#cancel&apos;);
function cancellableDelayedResolve(delay) {
  setTimeout(console.log, 0, &quot;set delay&quot;);
  return new Promise((resolve, reject) =&amp;gt; {
	const id = setTimeout((() =&amp;gt; {
	  setTimeout(console.log, 0, &quot;delayed resolve&quot;);
	  resolve();
	}), delay);
	const cancelToken = new CancelToken((cancelCallback) =&amp;gt;
	  cancelButton.addEventListener(&quot;click&quot;, cancelCallback));
	cancelToken.promise.then(() =&amp;gt; clearTimeout(id));
  });
}
startButton.addEventListener(&quot;click&quot;, () =&amp;gt; cancellableDelayedResolve(1000));
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;期约进度通知&lt;/h3&gt;
&lt;p&gt;扩展原有的 &lt;code&gt;Promise&lt;/code&gt; 类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TrackablePromise extends Promise {
  constructor(executor) {
    const notifyHandlers = []; // 储存通知回调函数
    super((resolve, reject) =&amp;gt; { // 传递新的执行器函数
      return executor(resolve, reject, (status) =&amp;gt; {
        notifyHandlers.map((handler) =&amp;gt; handler(status));
      });
    });
    this.notifyHandlers = notifyHandlers;
  }
  notify(notifyHandler) {
    // 允许外部代码注册一个通知处理函数，该函数会在 executor 函数中的 notify 被调用时执行。
    console.log(&quot;previous&quot;, this.notifyHandlers);
    this.notifyHandlers.push(notifyHandler);
    console.log(&quot;after&quot;, this.notifyHandlers);
    return this; // 链式调用
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let p = new TrackablePromise((resolve, reject, fn) =&amp;gt; {
  function countdown(x) {
    if (x &amp;gt; 0) {
      fn(`${20 * x}% remaining`);
      setTimeout(() =&amp;gt; countdown(x - 1), 1000);
    } else {
      resolve();
    }
  }
  countdown(5);
});
// 注册 notify handler 函数
p.notify((x) =&amp;gt; setTimeout(console.log, 0, &quot;progress:&quot;, x));
p.then(() =&amp;gt; setTimeout(console.log, 0, &quot;completed&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的例子中只有 &lt;code&gt;notifyHandlers&lt;/code&gt; 只注册了一个通知函数，但是因为 &lt;code&gt;notify&lt;/code&gt; 支持链式调用，所以可以注册多个。&lt;/p&gt;
&lt;h2&gt;异步函数 async/await&lt;/h2&gt;
&lt;p&gt;使用&lt;code&gt;async&lt;/code&gt;关键字可以让函数具有异步特征，但总体上其代码仍然是同步求值的。而在参数或闭包方面，异步函数仍然具有普通函数的正常行为。&lt;/p&gt;
&lt;p&gt;不过，异步函数如果使用&lt;code&gt;return&lt;/code&gt;关键字返回了值（如果没有&lt;code&gt;return&lt;/code&gt;则会返回&lt;code&gt;undefined&lt;/code&gt;），这个值会被&lt;code&gt;Promise.resolve()&lt;/code&gt;包装成一个期约对象。异步函数始终返回期约对象。&lt;/p&gt;
&lt;p&gt;异步函数的返回值期待（但实际上并不要求）一个实现&lt;code&gt;thenable&lt;/code&gt;接口的对象，但常规的值也可以。如果返回的是实现&lt;code&gt;thenable&lt;/code&gt;接口的对象，则这个对象可以由提供给&lt;code&gt;then()&lt;/code&gt;的处理程序“解包”。如果不是，则返回值就被当作已经解决的期约。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 返回一个原始值
    async function foo() {
      return &apos;foo&apos;;
    }
    foo().then(console.log);
    // foo
    // 返回一个没有实现thenable接口的对象
    async function bar() {
      return [&apos;bar&apos;];
    }
    bar().then(console.log);
    // [&apos;bar&apos;]
    // 返回一个实现了thenable接口的非期约对象
    async function baz() {
      const thenable={
        then(callback){callback(&apos;baz&apos;);}
      };
      return thenable;
    }
    baz().then(console.log);
    // baz
    // 返回一个期约
    async function qux() {
      return Promise.resolve(&apos;qux&apos;);
    }
    qux().then(console.log); // 返回期约等效于返回期约的值
    // qux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与在期约处理程序中一样，在异步函数中抛出错误会返回拒绝的期约：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    async function foo() {
      console.log(1);
      throw 3;
    }
    // 给返回的期约添加一个拒绝处理程序
    foo().catch(console.log);
    console.log(2);
    // 1
    // 2
    // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt;关键字会暂停执行异步函数后面的代码，让出JavaScript运行时的执行线程。这个行为与生成器函数中的yield关键字是一样的。&lt;code&gt;await&lt;/code&gt;关键字同样是尝试“解包”对象的值，然后将这个值传给表达式，再异步恢复异步函数的执行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt;关键同样字期待一个实现&lt;code&gt;thenable&lt;/code&gt;接口的对象，则这个对象可以由&lt;code&gt;await&lt;/code&gt;来“解包”。&lt;/p&gt;
&lt;p&gt;注意：对拒绝的期约使用&lt;code&gt;await&lt;/code&gt;则会释放（unwrap）错误值（&lt;strong&gt;将拒绝期约返回&lt;/strong&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    async function foo() {
      console.log(1);
      await Promise.reject(3);
      console.log(4); // 这行代码不会执行！
    }
    // 给返回的期约添加一个拒绝处理程序
    foo().catch(console.log);
    console.log(2);
    // 1
    // 2
    // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JavaScript运行时在碰到&lt;code&gt;await&lt;/code&gt;关键字时，会记录在哪里暂停执行。等到&lt;code&gt;await&lt;/code&gt;右边的值可用了，JavaScript运行时会向&lt;strong&gt;消息队列&lt;/strong&gt;中推送一个任务，这个任务会恢复异步函数的执行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    async function foo() {
      console.log(await Promise.resolve(&apos;foo&apos;));
    }
    async function bar() {
      console.log(await &apos;bar&apos;); // 等价于await Promise.resolve(&apos;bar&apos;)
    }
    async function baz() {
      console.log(&apos;baz&apos;);
    }
    foo();
    bar();
    baz();
    // baz
    // foo
	// bar
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    async function foo() {
      console.log(2);
      await null;
      console.log(4);
    }
    console.log(1);
    foo();
    console.log(3);
    // 1
    // 2
    // 3
    // 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;栈追踪与内存管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    function fooPromiseExecutor(resolve, reject) {
      setTimeout(reject, 1000, &apos;bar&apos;);
    }
    function foo() {
      new Promise(fooPromiseExecutor);
    }
    foo();
    // Uncaught (in promise) bar
    //    setTimeout
    //    setTimeout (async)
    //   fooPromiseExecutor
    //   foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;栈追踪信息应该相当直接地表现JavaScript引擎当前栈内存中函数调用之间的嵌套关系。在超时处理程序执行时和拒绝期约时，我们看到的错误信息包含嵌套函数的标识符，那是被调用以创建最初期约实例的函数。
可是，我们知道这些函数已经返回了，因此栈追踪信息中不应该看到它们。但是因为JavaScript引擎会在创建期约时尽可能保留完整的调用栈。当然，这意味着栈追踪信息会占用内存，从而带来一些计算和存储成本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function fooPromiseExecutor(resolve, reject) {
      setTimeout(reject, 1000, &apos;bar&apos;);
    }
    asyncfunction foo() {
      awaitnew Promise(fooPromiseExecutor);
    }
    foo();
    // Uncaught (in promise) bar
    //   foo
    //   asyncfunction(async)
    //   foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是异步的话，&lt;code&gt;fooPromiseExecutor()&lt;/code&gt;已经返回，所以它不在错误信息中。但&lt;code&gt;foo()&lt;/code&gt;此时被挂起了，并没有退出。JavaScript运行时可以简单地在嵌套函数中存储指向包含函数的指针，就跟对待同步函数调用栈一样。这个指针实际上存储在内存中，可用于在出错时生成栈追踪信息。这样就不会像之前的例子那样带来额外的消耗，因此在重视性能的应用中是可以优先考虑的。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>书评 | 《桶川跟踪狂杀人事件》</title><link>https://www.ender-wiggin.com/posts/review-murder-of-shiori-ino/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-murder-of-shiori-ino/</guid><description> </description><pubDate>Wed, 17 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;阅读纪实文学总会比阅读纯虚构多了些什么“东西”，即便是近乎完全还原事实的纪实小说也依然弥补不了这种“东西”——即一种实感。这种实感一方面是历史层面的严谨与非虚构，另一方面则是与读者在空间或时间线命运红线般的亲密链接。&lt;/p&gt;
&lt;p&gt;《桶川跟踪狂杀人事件》从纪实的角度来看，主要记录了三个核心事实：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;跟踪狂以及相关团伙的恶劣行径。也正如书中所说，对于警察而言，凶手逮捕归案便是案件的终结。因此这个案件本身虽说是警方的主体，但是在作者笔下仅仅是冰山一角。&lt;/li&gt;
&lt;li&gt;贯穿全文的“普通”。这种普通也可以分为三个角度：
&lt;ol&gt;
&lt;li&gt;诗织与她父母的普通。作者通过严谨的报道、诗织朋友的话语以及最后诗织房间的描绘，极力营造出了一种高度生活化的普通，一种似乎我们人人皆是如此的普通感。诗织被迫陷入胁迫的洪流，到生命最后一刻惦记的依然是父母，这种普通的善被另一种并不罕见的恶无情吞噬。而她父母的普通是一种理想化父母的普通，从某种角度上来说甚至已经高于了很多原生家庭那些不配为父母的人。在诗织坦言后父母倾力支持，即便遭受诽谤也同仇敌忾，这对父母虽然在身份上普通，但是在精神上格外伟岸。&lt;/li&gt;
&lt;li&gt;作者的普通。这里也是一种身份上的普通，作为“三流记者”只能在茫茫人海中搜寻一些虚无缥缈的线索，并且还始终要为内容上的噱头和案件本身的严谨性而左右为难。此外，作者以第一人称让我们更加真切地感受到了第二次诗织的遭遇——周遭风吹草动，草木皆兵。整个案件的推动，远非传统推理小说式的隔岸观火，也非硬汉推理或者一些犯罪小说风格的激情澎湃，这其中作者就像是刀殂下的鱼肉，作为事件的亲历者之一在各种外部势力的风起云涌中夹缝求生。&lt;/li&gt;
&lt;li&gt;跟踪狂的普通。这是一种隐性的普通，但是在书中作者也有类似的表达。且不论所谓的“小川的脆弱与敏感”，这种对凶手心理的叙述我认为只是出于作者记者身份的严谨职业态度。背后更让人不寒而栗的是小川身份的相对普通——他虽然有钱，但也只有钱。警察不是被他收买的黑警、黑道也只是兄弟二人的故弄玄虚、最终的逃亡也难逃自杀的宿命......试想，如果是一位更有权势的人，也许这一切的破案链路在一开始就中断了。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;警署的无能。但另一方面，与无能警署相对应的是，那些散落在腐败幕布上的零零星星闪亮高光的个体们。在碌碌无为的媒体群体中，有作者这样的良知记者，也有通情达理的杂志老板，也有那些“因为阅读到 FOCUS 杂志后义愤填膺”的媒体从业人员。要求一个体制滴水不漏至臻至美确实有些过于严苛了，但是图论和信息学的经验告诉我们，只要有那么一个连通点，或许就可以带动起一整片的死气沉沉。书中的描述是有点“个人英雄式”的，但是或许也正是这一个个英雄般的个体，才可以实现这种不可能的记录。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在纪实性之外，这部作品还有着如同小说般阅读体验。
一方面是作者优异的文笔，用一种极其生动真实又感伤的笔触记录下了这一切。例如前言这段：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;被害人诗织看不到今年春天的樱花，也听不到夏季的蝉鸣。往后都再也看不到、听不到了。同龄女性应该会在往后经历恋爱、结婚、生子等充满各种喜悦的人生，她的人生却在那个秋天结束了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以及一些重复出现的意象：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;打开挂着Kitty猫牌子的门，映入眼帘的是诗织从小就十分珍惜的玩具、摆满各处的Kitty猫周边商品、小学领到的奖状。书桌上、衣柜里也有Kitty猫。完全得不出半点“迷恋名牌”的印象。这个房间刻画着受到父母全心关爱的少女，正准备要蜕变成女人的历史与证明。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;另一方面是案件本身以及作者经历的不可思议性，说是虚构小说都不为过。尤其是最后的那封信：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“你写了什么？”母亲问。诗织微笑说：“收到就知道了。”就是她充满少女气息、稚拙但强而有力的笔迹。&amp;lt;br&amp;gt;我现在七岁。二〇〇一年的我，变成了什么样子？我变成一个很棒的女生了吗？我有男朋友了吗？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;作者在后记中也提到了自己的女儿意外去世的经历，一想到在正文部分作者几乎只字未提家庭，更让我感受到一种悲凉感。并且，即便在当时媒体行业比较自由的日本，依然有警察、法院、立法上的诸多弊病。而20年后的中国，连媒体的自主性都难以寻觅，又有多少阳光下的罪恶被历史悄然抹去？&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>学习笔记 | 《置身事内：中国政府与经济发展》</title><link>https://www.ender-wiggin.com/posts/note-zssn/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-zssn/</guid><description>这篇笔记为阅读后的二次整理，因此就不按书中的结构次序了，我以第八章的总结作为主体，学习记忆为主，顺便夹杂一些个人的理解和想法。</description><pubDate>Tue, 16 Apr 2024 21:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;这篇笔记为阅读后的二次整理，因此就不按书中的结构次序了，我以第八章的总结作为主要脉络，学习记忆为主，顺便夹杂一些个人的理解和想法。&lt;/p&gt;
&lt;h2&gt;脉络&lt;/h2&gt;
&lt;p&gt;书的副标题是“中国政府与经济发展”，那么显然包含了两个主体。&lt;/p&gt;
&lt;p&gt;其中，中国政府分为了&lt;strong&gt;中央政府&lt;/strong&gt;与&lt;strong&gt;地方政府&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而经济发展的核心原则是优化资源配置，提高使用效率。&lt;/p&gt;
&lt;p&gt;由于我国是以计划经济为起点，因此要在这个基础上提高经济效率，方案有二：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;以中央政府为主&lt;/strong&gt;，按功能划分许多部委，以部委为基本单位在全国范围内调动资源。竞争主要体现在中央设定目标和规划过程中部委之间的博弈（计划经济时期方案，沿袭苏联）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;以地方政府为主&lt;/strong&gt;，在设定经济发展目标之后，放权给地方政府，让它们发挥积极性，因地制宜，在实际工作中去竞争资源。这是一种自下而上的“块块”式的竞争模式&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在改革开放以后，地方政府权力扩大，“属地管理”和“地方竞争”就构成了政府间竞争的基本模式（第一到四章）&lt;/p&gt;
&lt;p&gt;地方政府竞争中的关键一环，是“以经济建设为中心”来评价地方主官，并将这种评价纳入升迁考核。各地政府不仅要在市场上竞争，还要在官场上竞争。这种 &lt;strong&gt;“官场+市场”&lt;/strong&gt; 体制，有三个特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一，将官员晋升的政治激励和地区经济表现挂钩。&lt;/li&gt;
&lt;li&gt;第二，以市场竞争约束官员行为。&lt;/li&gt;
&lt;li&gt;第三，当地的经济表现能为地方官员和政府工作提供及时的反馈。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是，这种体制的竞争，与市场竞争区别很大：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一，缺乏真正的淘汰机制。&lt;/li&gt;
&lt;li&gt;第二，绝大多数市场竞争是“正和博弈”，而官员升迁则是“零和博弈”。&lt;/li&gt;
&lt;li&gt;第三，市场和公司间的竞争一般是长期竞争，延续性很强。但地方官员任期有限，必须在任期内干出政绩，且新官往往不理旧账，因此会刺激大干快上的投资冲动，拉动地区GDP数字快速上涨，不惜忽视长期风险和债务负担。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;作者在描述了这些困境之后，解释了政府的发展与转型。&lt;/p&gt;
&lt;p&gt;“&lt;strong&gt;瓦格纳法则&lt;/strong&gt;”，指国家越富裕，政府在国民经济中所占的比重也往往越大。&lt;/p&gt;
&lt;p&gt;而要调整经济结构失衡，关键是将更多资源从政府和国企转到居民手中，在降低政府投资支出的同时加大其民生支出，也就是“&lt;strong&gt;生产投资型政府&lt;/strong&gt;”向“&lt;strong&gt;服务型政府&lt;/strong&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;ol&gt;
&lt;li&gt;&lt;strong&gt;中央与地方&lt;/strong&gt;的集权与分权（央地关系）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;党和政府&lt;/strong&gt;在经济层面上的重叠性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;条块分割，多重领导&lt;/strong&gt;。“层层复制”（中央的主要政治架构，即党委、政府、人大、政协等，省、市、县三级都完全复制，即所谓“四套班子”）、条条（垂直部门关系）块块（横向区域政府）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上级领导与协调&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;官僚体系&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;权力运作都受到两种约束：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;做事的能力，取决于掌握的资源。&lt;/li&gt;
&lt;li&gt;做事的意愿，取决于各方的积极性和主动性。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;事权划分三原则&lt;/h3&gt;
&lt;p&gt;目的：处理不同群体的利益差别与冲突。&lt;/p&gt;
&lt;h4&gt;1. 外部性和受益范围原则&lt;/h4&gt;
&lt;p&gt;“外部性”即人的行为影响到了别人。这个原则关注的是&lt;strong&gt;公共服务覆盖范围&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;按照经典经济学的看法，政府的核心职能是提供公共物品和公共服务，比如国防和公园。这类物品一旦生产出来，大家都能用，用的人越多就越划算——因为建造和维护成本也分摊得越薄，这就是“规模经济”。但绝大部分公共物品只能服务有限人群。&lt;/p&gt;
&lt;p&gt;以公共物品的规模经济和边界为切入点，也可以帮助理解中央和地方政府在分工上的一些差异。&lt;/p&gt;
&lt;p&gt;影响公共服务效果的因素：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;人口密度&lt;/li&gt;
&lt;li&gt;地理条件&lt;/li&gt;
&lt;li&gt;语言文化差异&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;处在行政交界（尤其是省交界处）的地区，经济发展普遍比较落后。&lt;/p&gt;
&lt;p&gt;行政边界影响经济发展，地方保护主义和市场分割现象今天依然存在，尤其在生产要素市场上，用地指标和户籍制度对土地和人口流动影响很大。&lt;/p&gt;
&lt;p&gt;区域性整合由于城乡土地制度的差异导致了诸多矛盾。&lt;/p&gt;
&lt;h4&gt;2. 信息复杂性原则&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;信息与权利&lt;/strong&gt;密切相关：所以上级虽然名义上有最终决定权，拥有“形式权威”，但由于信息复杂、不易处理，下级实际上自主性很大，拥有“实际权威”。&lt;/p&gt;
&lt;p&gt;信息复杂性和权力分配是个普遍性的问题。&lt;/p&gt;
&lt;p&gt;从信息角度来看，所谓权力，实质就是在说不清楚的情况下由谁来拍板决策的问题。如果这种说不清的情况很多，权力就一定会向个人集中，这也是各地区、各部门“一把手负责制”的根源之一，这种权力的自然集中可能会造成专权和腐败。&lt;/p&gt;
&lt;p&gt;因为信息复杂，不可信的信息比比皆是，而权力和责任又高度个人化，所以体制内的规章制度无法完全取代个人信任。&lt;/p&gt;
&lt;h4&gt;激励相容原则&lt;/h4&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;h3&gt;招商引资&lt;/h3&gt;
&lt;p&gt;招商引资不仅是招商局的部门职能，也是以经济建设为中心的地方政府的核心任务，是需要调动所有资源和手段去实现的目标。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;招商 -&amp;gt; 需要工业/产业园区 -&amp;gt; 需要土地开发、产业规划、项目运作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这种“&lt;strong&gt;混合经济&lt;/strong&gt;”体系是政府及其各类附属机构（国企、事业单位、大银行等）&lt;strong&gt;深度参与大多数生产和分配&lt;/strong&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;1. 分税制改革&lt;/h3&gt;
&lt;p&gt;介绍这次改革的背景和过程，加深我们对央地关系的理解&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1985—1993年 &lt;strong&gt;财政包干&lt;/strong&gt;：国有向承包过渡，造成了“两个比重”不断降低：中央财政预算收入占全国财政预算总收入的比重越来越低，而全国财政预算总收入占GDP的比重也越来越低（避免“鞭打快牛”、预算外收入避税）&lt;/li&gt;
&lt;li&gt;1994年 &lt;strong&gt;分税制&lt;/strong&gt;： 把税收分为三类：中央税（如关税）、地方税（如营业税）、共享税（如增值税）。同时分设国税、地税两套机构，与地方财政部门脱钩，省以下税务机关以垂直管理为主，由上级税务机构负责管理人员和工资。&lt;/li&gt;
&lt;li&gt;2002年 所得税改革：将几乎所有企业的所得税都以中央地方六四分成&lt;/li&gt;
&lt;li&gt;2018年 &lt;strong&gt;国税地税再次合并&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分税制改革中最重要的税种是&lt;strong&gt;增值税&lt;/strong&gt;，占全国税收收入的1/4。改革之前，增值税（即产品税）是最大的地方税，改革后变成共享税，中央拿走75%，留给地方25%。
中央采用了&lt;strong&gt;税收返还&lt;/strong&gt;的方式（保证改革后地方增值税收入与改革前一样，新增部分才和中央分）渐进收取。但也应运而生了&lt;strong&gt;突击征税&lt;/strong&gt;这种有趣的现象。&lt;/p&gt;
&lt;p&gt;分税制也从根本上改变了地方政府发展经济的模式。&lt;/p&gt;
&lt;h3&gt;2. 土地财政&lt;/h3&gt;
&lt;p&gt;分析改革对地方经济发展方式的影响，介绍地方政府为了应对财政压力而发展出的“土地财政”，这是理解城市化和债务问题的基础。&lt;/p&gt;
&lt;h4&gt;背景&lt;/h4&gt;
&lt;p&gt;给定税率的情况下，想要增加税收收入，要么靠扩大税源，要么靠加强征管。&lt;/p&gt;
&lt;p&gt;地方政府在财政支出上向招商引资倾斜（如基础设施建设、企业补贴等），而民生支出（教育、医疗、环保等）相对不足。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2002年，中央提出“科学发展观”&lt;/strong&gt;，要求“统筹经济社会发展、统筹人与自然和谐发展”，要求更加重视民生支出。&lt;/p&gt;
&lt;p&gt;税改又抽取了大量收入，地方开始土地财政。&lt;/p&gt;
&lt;h4&gt;介绍&lt;/h4&gt;
&lt;p&gt;我国实行土地公有制，城市土地归国家所有，农村土地归集体所有。农地要转为建设用地，必须先经过征地变成国有土地，然后才可以用于发展工商业或建造住宅。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1998年，单位停止福利分房，逐步实行住房分配货币化，商品房和房地产时代的大幕拉开。同时，修订后的《中华人民共和国土地管理法》开始实施，基本上锁死了农村集体土地的非农建设通道，规定了农地要想转为建设用地，必须经过征地后变成国有土地，这也就确立了城市政府对土地建设的垄断权力。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2001年，招标拍卖大力推行之后，土地财政开始膨胀。&lt;/p&gt;
&lt;p&gt;其中大部分税收的&lt;strong&gt;税基是土地的价值&lt;/strong&gt;而非面积，所以税收随着土地升值而猛增。&lt;/p&gt;
&lt;h4&gt;问题&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;地方政府的债务问题。土地的资本化运作，本质是把未来的收益抵押到今天去借钱。&lt;/li&gt;
&lt;li&gt;资源的使用效率仍然不高。例如，土地资源和建设用地分配却很难优化，用地指标不能跨省流动到效率更高的地区。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;早年问题不大，是因为工业化和城市化初期，传统农业生产率低，只要把农地变成工商业用地，农业变成工商业，效率就会大大提升。&lt;/p&gt;
&lt;p&gt;但如今经济发展阶段变化，随着工业化的发展，市场竞争越来越激烈，&lt;strong&gt;技术要求越来越高&lt;/strong&gt;，徒有大量建设用地指标作用不大。&lt;/p&gt;
&lt;h3&gt;3. 纵向不平衡与横向不平衡&lt;/h3&gt;
&lt;p&gt;分税制改革之后，造成了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基层财政困难：财权层层上收，事权层层下压；三农问题；农业税；“乡财县管”和“省直管县”改革拉平多级行政管理体制&lt;/li&gt;
&lt;li&gt;地区间不平等：中央转移支付（一般性与专款专用两种）；&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第三章 政府投融资与债务&lt;/h2&gt;
&lt;p&gt;本章分析如何从土地财政变成“土地金融”。&lt;/p&gt;
&lt;h3&gt;1. 城投公司与土地金融&lt;/h3&gt;
&lt;p&gt;例如文旅、工业园区。&lt;/p&gt;
&lt;p&gt;政府付费使用私营企业开发建设的基础设施（如产业园区），不算什么新鲜事。这种模式叫“政府和社会资本合作”（Public-Private Partnership, PPP）&lt;/p&gt;
&lt;h3&gt;2. 地方政府债务&lt;/h3&gt;
&lt;p&gt;地方盈利能力取决于土地价格，如果价格增长放缓，就会积累更多债务。&lt;/p&gt;
&lt;h3&gt;3. 腐败与反腐败&lt;/h3&gt;
&lt;p&gt;作者认为腐败特点有二：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;腐败与经济增长并存。&lt;/li&gt;
&lt;li&gt;腐败形式变化（双轨制和投机倒把 -&amp;gt; 国企改革 -&amp;gt; 土地开发）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而腐败分为掠夺式和“官商勾连共同发财式”腐败。&lt;/p&gt;
&lt;h2&gt;第四章 工业化中的政府角色&lt;/h2&gt;
&lt;p&gt;理想的产业政策退出机制：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;政策本身退出&lt;/li&gt;
&lt;li&gt;低效企业破产退出&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是：&lt;/p&gt;
&lt;p&gt;债权银行不愿走破产程序，因为会暴露不良贷款，无法再掩盖风险；
地方政府也不愿企业（尤其是大企业）走破产程序，否则职工安置和民间借贷等一系列矛盾会公开化；&lt;/p&gt;
&lt;h3&gt;政府产业引导基金&lt;/h3&gt;
&lt;p&gt;私募基金：限制了投资资格、募集方式和推出方式的基金。
公募基金：公众可以购买的基金。&lt;/p&gt;
&lt;h2&gt;第五章 城市化与不平衡&lt;/h2&gt;
&lt;p&gt;城市化带来了更多的不平衡，这一章将分析：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;房价和土地供需间的关系，讨论高房价带来的日益沉重的居民债务负担&lt;/li&gt;
&lt;li&gt;地区间发展不平衡，其根源之一在于土地和人口等生产要素流动受限，所以近年来在土地流转和户籍制度等方面的改革非常重要&lt;/li&gt;
&lt;li&gt;我国经济发展过程中出现的贫富差距，这一现象也和房价以及要素市场改革有关&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1. 房价与居民债务&lt;/h3&gt;
&lt;p&gt;欧美也有过自有住房率不断上升的阶段，并且由于得益于房价上涨的人越来越多，政府为了讨好选民也不愿让房价降低。&lt;/p&gt;
&lt;p&gt;房地产常被称作“经济周期之母”，根源就在于其内在的供需矛盾：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一方面，银行可以通过按揭创造几乎无限的新购买力；&lt;/li&gt;
&lt;li&gt;另一方面，不可再生的城市土地供给却有限。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这对矛盾常常会导致资产泡沫与破裂的周期循环，是金融和房地产不稳定的核心矛盾。而房地产不仅连接着银行，还连接着千家万户的财富和消费，因此影响很大。&lt;/p&gt;
&lt;p&gt;房价上涨不仅会增加按揭债务负担，还会拉大贫富差距，进而刺激低收入人群举债消费，这一现象被称为“&lt;strong&gt;消费下渗&lt;/strong&gt;”（trickle-down consumption）。&lt;/p&gt;
&lt;p&gt;作者认为中国房价不会像美国房价金融危机那样突发，原因如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;中国住房按揭首付比例30%，而美国0%，造成了银行的压力&lt;/li&gt;
&lt;li&gt;住房按揭形成的信贷资产，没有被层层嵌套金融衍生品，在金融体系中来回翻滚，规模和风险被放大几十倍&lt;/li&gt;
&lt;li&gt;由于资本账户管制，外国资金很少参与我国的住房市场&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2. 不平衡与要素市场改革&lt;/h3&gt;
&lt;p&gt;2017年党的十九大报告指出：我国社会主要矛盾已经转化为人民日益增长的美好生活需要和不平衡不充分的发展之间的矛盾。这是自1981年党的十一届六中全会提出“我国所要解决的主要矛盾”（即人民日益增长的物质文化需要同落后的社会生产之间的矛盾）以来，中央首次重新定义“主要矛盾”，说明经济政策的根本导向发生了变化。&lt;/p&gt;
&lt;p&gt;这一部分强调了人口自由流动的重要性。&lt;/p&gt;
&lt;h3&gt;3. 经济发展与贫富差距&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;我国十几亿人在40年间摆脱了贫困，大大缩小了全世界70亿人之间的不平等&lt;/li&gt;
&lt;li&gt;在经济快速增长过程中，虽然收入差距在拉大，但低收入人群的收入水平也在快速上升，社会对贫富差距的敏感度在一段时间之内没有那么高&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在经济增长减速时，社会对不平等的容忍度会减弱，贫富差距更容易触发社会矛盾&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;“&lt;strong&gt;隧道效应&lt;/strong&gt;”（tunnel effect），形容隧道中两条车道一动一静时，静的那条的焦虑和难耐。主要指代经济放缓时穷人比富人更加焦虑的心态。&lt;/p&gt;
&lt;p&gt;另一个影响不平等容忍度的因素是人群的相似性。中国多数人群身份上是相似的，不存在人种、肤色、种姓等因素。&lt;/p&gt;
&lt;p&gt;家庭观念也会影响对不平等的容忍度。&lt;/p&gt;
&lt;p&gt;总的来说，贫富差距是一个波浪线的曲线，而非所谓的倒U型曲线（库兹涅茨曲线），因此未来的贫富差距变化不容易预测。&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;我国的城市化大概可以分为三个阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第一阶段是1994年之前，乡镇企业崛起，农民离土不离乡，城市化速度不快&lt;/li&gt;
&lt;li&gt;第二阶段是1994年分税制改革后，乡镇企业式微，农民工进城大潮形成。这个阶段的主要特征是土地的城市化速度远远快于人的城市化速度，土地撬动的资金支撑了大规模城市建设，但并没有为大多数城市新移民提供应有的公共服务&lt;/li&gt;
&lt;li&gt;第三个阶段是党的十八大以后，随着一系列改革的陆续推行，城市化的重心开始逐步从“土地”向“人”转移。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;城市化和工业化&lt;/strong&gt;互相作用。上述三个阶段背后的共同动力之一就是工业化。&lt;/p&gt;
&lt;p&gt;在大规模城市化过程中，地方政府背上了沉重的债务。地价和房价飞涨，也让居民背上了沉重的债务。这些累积的债务为宏观经济和金融体系增加了很大&lt;strong&gt;风险&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;第六章 债务与风险&lt;/h2&gt;
&lt;p&gt;债务负担重的家庭也面临至少三大风险：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;债务缺乏弹性&lt;/li&gt;
&lt;li&gt;收入变化弹性很大&lt;/li&gt;
&lt;li&gt;第三个风险来自家庭支出的变动&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2018年末，我国的债务总量达到了GDP的258%。近几年供给侧结构性改革中的诸多举措，尤其是“去产能”“去库存”“去杠杆”，都与债务问题和风险有关。&lt;/p&gt;
&lt;p&gt;08年后，为应对巨大的外部冲击，我国迅速出台了“4万亿”计划，稳定了我国和世界经济，但同时也加剧了债务负担和产能过剩。&lt;/p&gt;
&lt;p&gt;产能过剩可以从三个角度去理解：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;生产效率下降。宏观上表现为GDP增速放缓，低于债务增速，所以宏观债务负担加重。&lt;/li&gt;
&lt;li&gt;国际失衡。地方政府重视投资、生产和企业税收，相对忽视消费、民生和居民收入，造成经济结构失衡，分配体制偏向资本，劳动收入偏低，所以消费不足，必须向国外输出剩余产能。&lt;/li&gt;
&lt;li&gt;产业升级。因为产能过剩，我国制造业竞争激烈，价格和成本不断降低，不仅冲击了外国的中低端制造业，也冲击了本国同行。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1. 债务与经济衰退&lt;/h3&gt;
&lt;h3&gt;2. 债台为何高筑：欧美的教训&lt;/h3&gt;
&lt;p&gt;所以要理解债务的增长，首先要理解银行为什么会大量放贷。&lt;/p&gt;
&lt;p&gt;全球金融自由化浪潮始于20世纪70年代&lt;strong&gt;布雷顿森林体系&lt;/strong&gt;的解体。在布雷顿森林体系下，各国货币以固定比例与美元挂钩，美元则以固定比例与黄金挂钩。&lt;/p&gt;
&lt;p&gt;而解体之后，银行危机出现的频率大幅增加。原因如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;银行规模大、杠杆高。&lt;strong&gt;总资产 = 负债 + 自有资本&lt;/strong&gt;，银行有着极高的杠杆率。&lt;/li&gt;
&lt;li&gt;银行借钱短期流转，而贷款例如企业贷款往往长期，期限不匹配。&lt;/li&gt;
&lt;li&gt;银行信贷大都和房地产有关，常常与土地和房产价值一同起落，放大经济波动。&lt;/li&gt;
&lt;li&gt;银行风险会传导到其他金融部门。比如银行可以把各种按揭贷款打包成一个证券组合，卖给其他金融机构。如果银行借出去的钱转手就能打包卖给下家，那银行就不会在乎借钱的人是不是真的有能力还钱。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;特里芬悖论的核心&lt;/h4&gt;
&lt;p&gt;特里芬悖论的核心在于，当一种货币被广泛地作为国际储备货币使用时，其发行国必须提供足够的货币供应以满足其他国家的储备需求。然而，这种持续的货币提供，尤其是通过运行贸易逆差来实现时，最终会削弱其他国家对该货币的信心，因为过度的货币提供和贸易逆差可能导致发行国的债务水平不断上升，进而引发对其货币价值的担忧。&lt;/p&gt;
&lt;h5&gt;对美元的影响&lt;/h5&gt;
&lt;p&gt;在布雷顿森林体系下，美元与黄金挂钩，并成为主要的国际储备货币。美国需要通过贸易逆差，向世界其他国家提供足够的美元，以满足它们对美元的需求。然而，这也意味着美国的黄金储备与外国持有的美元之间的比例会不断下降，从而削弱其他国家对美元兑换成黄金承诺的信心。这一矛盾最终导致了1971年美国结束了美元兑黄金的直接兑换，布雷顿森林体系的崩溃。&lt;/p&gt;
&lt;h5&gt;长期影响&lt;/h5&gt;
&lt;p&gt;特里芬悖论揭示了作为国际储备货币的货币所面临的固有矛盾，即需要不断提供足够的货币供应以支持全球贸易和金融体系，而这种供应又可能损害货币的价值和信誉。这一悖论至今对国际金融体系，特别是对美元的国际地位，仍有深远的影响。在当前的国际货币体系中，虽然美元仍然是主要的国际储备货币，但特里芬悖论引发的问题，如美国持续的贸易逆差、债务积累以及对其他货币可能崛起挑战美元霸权的关注，仍然是国际经济讨论的热点。&lt;/p&gt;
&lt;h3&gt;3. 中国的债务与风险&lt;/h3&gt;
&lt;p&gt;企业债务的几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;地方政府融资平台企业的债务，约占GDP的40%，资金主要投向基础设施，项目回报率很低&lt;/li&gt;
&lt;li&gt;“国进民退”现象。2008年以后，国有企业规模快速扩张，但效率比私营企业低，多占用的资金没有转化为同比例的新增收入，推升了整体债务负担（也包含了破产重组问题）。&lt;/li&gt;
&lt;li&gt;房地产企业的债务问题（在购置土地环节，发达国家一般要求企业使用自有资本金，而我国允许房企借钱“买地”，这就刺激了房企竞相抬高地价和储备土地。储备的土地又可以作为抵押去撬动更多借贷资金，进而储备更多土地，所以房企规模和债务都越滚越大）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总体看来，我国企业债务负担较重，应对风险的能力受限。若遭遇重大外部冲击，就可能面临债务违约风险。而企业裁员甚至倒闭，会降低居民收入，加大居民的风险，也加大其债主银行的风险。&lt;/p&gt;
&lt;p&gt;中国的债权人主要是银行，不仅发放贷款，也持有大多数债券。上文讨论的欧美银行业的很多风险点，同样适用于我国。
银行偏爱以土地和房产为抵押物的贷款。&lt;/p&gt;
&lt;h3&gt;4. 化解债务风险&lt;/h3&gt;
&lt;p&gt;任何国家的债务问题，解决方案都可以分成两个部分：一是偿还已有债务；二是遏制新增债务，改革滋生债务的政治、经济环境。&lt;/p&gt;
&lt;h4&gt;偿还已有债务&lt;/h4&gt;
&lt;p&gt;所以偿债过程很痛苦，还有可能陷入经济衰退。相比之下，增发货币也能缓解债务负担，似乎还不那么痛苦，因为没有明显的利益受损方，实施起来阻力也小。&lt;/p&gt;
&lt;p&gt;增发货币的方式大概有三类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;增发货币来降低利率&lt;/strong&gt;。08年之前的常见做法。低利率既能减少利息支出，也能刺激投资和消费，提振经济。若经济增长、实际收入增加，就可以减轻债务负担。就算实际收入不增加，增发货币也能维持稳定温和的通货膨胀，随着物价上涨和时间推移，债务负担也会减轻，因为欠的债慢慢也就不值钱了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;量化宽松&lt;/strong&gt;，即央行增发货币来买入各类资产，把货币注入经济，这是金融危机后发达国家的主流做法。等于将其它部门的负债转移到了央行，但是因为央行可以无限印钱，而其他部门只在还债没有增加支出，因此物价不会有压力，也不容易出现通货膨胀。主要问题是，资产泡沫化，以及增发的货币到不了穷人手中无法刺激消费，会拉大贫富差距。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;债务货币化&lt;/strong&gt;。政府加大财政支出去刺激经济，由财政部发债融资，央行直接印钱买过来，无需其他金融机构参与也无需支付利息，这便是所谓“&lt;strong&gt;赤字货币化&lt;/strong&gt;”（用无利率的货币替代有利率的债务，以政府预算收支的数量代替金融市场的价格即利率来调节经济资源配置）。08年之后前两种方式基本到了尽头，从而催生出的新方案。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;遏制新增债务&lt;/h4&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;h2&gt;第七章 国内国际失衡&lt;/h2&gt;
&lt;p&gt;“中国制造”下的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;内部经济结构失衡：重生产、重投资，相对轻民生、轻消费，导致与巨大的产能相比，国内消费不足，而消化不了的产品只能对外输出。&lt;/li&gt;
&lt;li&gt;这就带来了第二个问题：国外需求的不稳定和贸易冲突。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本书介绍了我国地方政府推动经济发展的模式，从微观机制开始，到宏观现象结束。总结一下，这一模式有三大特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;城市化过程中“重土地、轻人”（第五章）&lt;/li&gt;
&lt;li&gt;招商引资竞争中“重规模、重扩张”（第五章）&lt;/li&gt;
&lt;li&gt;经济结构上“重投资、重生产、轻消费”（本章）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种模式，其优点是能快速扩大投资和对外贸易，利用全球化的契机拉动经济快速增长。
但缺点是经济结构失衡：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对内，资源向企业和政府转移，居民收入和消费占比偏低，不利于经济长期发展&lt;/li&gt;
&lt;li&gt;对外，国内无法消纳的产能向国外输出，加剧了贸易冲突&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>学习笔记</category><category>文学影视</category><author>Ender</author></item><item><title>所思所想 | Apple Watch 往事</title><link>https://www.ender-wiggin.com/posts/thought-apple-watch-s7/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/thought-apple-watch-s7/</guid><description>记录一次冲动消费的故事。</description><pubDate>Tue, 16 Apr 2024 01:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;缘起&lt;/h2&gt;
&lt;p&gt;最近比较喜欢运动，也研究了下运动手表，发现我家中那块积灰已久的 Apple Watch S7 可以从原有的 WatchOS8 更新到 OS10 从而获得丰富的新功能，甚是欣喜，总算在今晚成功更新。&lt;/p&gt;
&lt;p&gt;看着全新表盘设计与运动睡眠功能双升级的这块手表，感觉就如同一块崭新的手表令我耳目一新。虽然严格意义上来说也确实如此：这块表在我购买当时很快就被束之高阁了，可谓是不折不扣的一次冲动消费。&lt;/p&gt;
&lt;p&gt;在浏览 OS 版本更新时，我也注意到了这个时间：2022 年 6 月 7 号苹果宣布了具有重大更新的 OS9，而我恰好是在这个时间点前几天购买的，可以说是与这个新版本擦肩而过。而这个没有赶上新时代列车的手表，也就静静地躺在收纳盒中，沉睡到了今年年初。&lt;/p&gt;
&lt;p&gt;想到这里，我决定流水账式的回顾一下我冲动消费的全过程。&lt;/p&gt;
&lt;h2&gt;6月1号&lt;/h2&gt;
&lt;p&gt;2022年6月1日对所有上海人民来说都算是值得铭记的一天。当时的上海人民刚收到了封控取消的消息，在一片萧条的城市中突然响起了放纵的前奏。&lt;/p&gt;
&lt;p&gt;我在收到解封消息后，迫不及待地跨出合租房还残留着半截封锁贴条的铁门，步入曾经那个油烟遍地的典型城中村商圈。过去充满烟火气的繁华街区染上了一层苍白的疲惫，昔日五光十色的店铺如今百废待兴：灯牌摇摇欲坠的小餐馆中老板正忙碌地放下杂乱摆放在桌上的塑料板凳，空空荡荡的便利店的员工正无奈地清扫残留商品上灰蒙的烟尘。&lt;/p&gt;
&lt;p&gt;而我作为受困三个月没有理发的“野人”，迫不及待地前往转角处的平价理发店。理发店门口聚集了一群人，多数是和我一样的“野人”大哥们，穿着略显肮脏的白背心皱着眉头盯着昏暗店面中奔忙的员工们。很快，一位黄毛几乎完全褪去的小伙遗憾地告诉我们今天暂时不能接客了，大家改天再来。门口的一群人在小声叹气后，都开始漫无目的的在街上行走了起来。&lt;/p&gt;
&lt;p&gt;那时的我生活了22年，但也从没想过有那么一个时刻，会觉得在破败不堪的街上无所事事地行走是这么的愉悦。突然脑子浮现出宇航员回到地球后热泪盈眶亲吻海岸边红土的画面，我当时也差不多是怀着同样的心情漫步着，只是一种原始的理性让我没有做出一些社死的举动。&lt;/p&gt;
&lt;p&gt;比较可惜的是，走了几公里也没有看到可以营业的餐饮店，当然从卫生的角度来说几个月没开张的店面，在重新营业第一天去吃风险太高了。但是当时的我对在&lt;strong&gt;外面&lt;/strong&gt;吃饭有一种莫名的渴望，这种情感在后来我阅读陀氏《死屋笔记》时似乎又重温了一遍。&lt;/p&gt;
&lt;h2&gt;6月2号&lt;/h2&gt;
&lt;p&gt;在6月1号的吃饭计划泡汤后，&lt;strong&gt;6月2号&lt;/strong&gt;我又突然萌生出了打篮球的想法，约着同事下午去了公园野球场。我实际上已经几年没打过篮球了，并且平时对于篮球也没什么热情。但是脑中就是想进行一些体育运动，进行那种与户外空气、与土地、与人互相接触的运动。不出所料，我被公园大爷重拳出击，又一次击碎了我几乎还没有发芽的篮球梦。&lt;/p&gt;
&lt;h2&gt;6月3号&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;6月3号&lt;/strong&gt;，我决定出门走走，一个人走向了外滩。早些时候，下面这张上海外滩长草的照片在许多老上海人心中都埋下了忧郁的种子：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20240416004645.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我也认为这张照片恰如其分地体现了当时上海的环境面貌，配合上《四月之声》简直就是多模态立体版上海纪录。但是当我下了地铁站的时候，不由得感慨上海人民的快速恢复能力。&lt;/p&gt;
&lt;p&gt;我脑中浮现出法租界载歌载舞的影片，浮现出“孤岛繁荣”的社会学分析，那上世纪40年代的“歌舞升平”似乎和这一刻完美重叠了。在六月温暖的橙黄灯光中，在熙熙攘攘的欢腾人群中，我挤进了外滩的 Apple Store：落地玻璃交融着门店内外的灯光令人眼花缭乱，店中的热情洋溢的人群带来了一种躁动的气流......在推销员“循循善诱”下，我很快就恍恍惚惚拎着 Apple Watch 的白色纸袋走出了店面，重返同样绚烂却又冷漠的夜色。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;事后回想起来这就是一次莫名其妙的冲动消费，但是考虑我之前的那些经历，我又似乎觉得这是一种必然的结果。“报复性消费”恰如其分，我仿佛主动又全乎被动，跟随着时代的漩涡随波逐流。&lt;/p&gt;
&lt;p&gt;邓小平评价伤痕文学“哭哭啼啼，没有出息”，但退一步来说，至少人家将自己的记忆印刷在了薄纸上。我现在回望上海的这些日子，觉得记忆越来越缥缈了，也就像许多人所有的压抑都在6月1号消息公布的那一刻顿然烟消云散了一样。&lt;/p&gt;
&lt;p&gt;我看着曾经被淡忘的手表，记忆突然闪回到了购买它的那个夜晚。我想，或许还是记录一下吧，记录一下那段似乎已经忘却的记念。&lt;/p&gt;
</content:encoded><category>所思所想</category><author>Ender</author></item><item><title>技术 | Vercel AI SDK 架构与简要源码分析</title><link>https://www.ender-wiggin.com/posts/note-vercel-ai-sdk/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-vercel-ai-sdk/</guid><description> </description><pubDate>Fri, 12 Apr 2024 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://sdk.vercel.ai/docs&quot;&gt;Vercel AI SDK&lt;/a&gt; 是 Vercel 于24年3月推出的一个方便开发 AI 应用的 SDK，可以让开发者快速搭建诸如聊天机器人等应用。&lt;/p&gt;
&lt;p&gt;这篇文章会先简单分析一下这个库的优点和使用方式，然后关注一下最核心的流式UI部分的源码实现。&lt;/p&gt;
&lt;h2&gt;亮点&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SDK 的便携集成&lt;/strong&gt;。但是对于这一部分，并没有明显感觉出 Vercel AI SDK 和 &lt;a href=&quot;https://github.com/langchain-ai/langchainjs&quot;&gt;LangChain.js&lt;/a&gt; 以及 &lt;a href=&quot;https://github.com/langgenius/dify&quot;&gt;Dify&lt;/a&gt; 的明显区别。通过官网文档可以看出来，Vercel AI SDK 可以兼容 LangChain，因此可以理解为更上层的封装，而 Dify 有着相对独立的路径和更加工程化的庞大，配合刚出的 Pipeline 功能表现尤为亮眼。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;封装后的流式UI&lt;/strong&gt;。在我看来，这个 SDK 主要的优点就是与 React Server Component 高度绑定的流式UI。可以看出来 Vercel 官方也一直试图强推这个功能。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;基础用例&lt;/h2&gt;
&lt;p&gt;后端部分，可以直接连 Vercel edge function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import OpenAI from &apos;openai&apos;;
import { OpenAIStream, StreamingTextResponse } from &apos;ai&apos;;

// Create an OpenAI API client (that&apos;s edge friendly!)
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

// Set the runtime to edge for best performance
export const runtime = &apos;edge&apos;;

export async function POST(req: Request) {
  const { messages } = await req.json();

  // Ask OpenAI for a streaming chat completion given the prompt
  const response = await openai.chat.completions.create({
    model: &apos;gpt-3.5-turbo&apos;,
    stream: true,
    messages,
  });

  // Convert the response into a friendly text-stream
  const stream = OpenAIStream(response);
  // Respond with the stream
  return new StreamingTextResponse(stream);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;显然，&lt;code&gt;OpenAIStream&lt;/code&gt; 和 &lt;code&gt;StreamingTextResponse&lt;/code&gt; 是两个封装后的流式输出器，我们下个章节会一探究竟。&lt;/p&gt;
&lt;p&gt;前端部分，通过 &lt;code&gt;useChat&lt;/code&gt; hook 实现快速开发：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;;

import { useChat } from &apos;ai/react&apos;;

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();
  return (
    &amp;lt;div className=&quot;flex flex-col w-full max-w-md py-24 mx-auto stretch&quot;&amp;gt;
      {messages.map(m =&amp;gt; (
        &amp;lt;div key={m.id} className=&quot;whitespace-pre-wrap&quot;&amp;gt;
          {m.role === &apos;user&apos; ? &apos;User: &apos; : &apos;AI: &apos;}
          {m.content}
        &amp;lt;/div&amp;gt;
      ))}

      &amp;lt;form onSubmit={handleSubmit}&amp;gt;
        &amp;lt;input
          className=&quot;fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl&quot;
          value={input}
          placeholder=&quot;Say something...&quot;
          onChange={handleInputChange}
        /&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;流式&lt;/h2&gt;
&lt;p&gt;流式输出的优点就不赘述了，我们直接看源码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function OpenAIStream(
  res: Response | AsyncIterableOpenAIStreamReturnTypes,
  callbacks?: OpenAIStreamCallbacks,
): ReadableStream
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;函数的输入输出比较清晰，就是输入请求后返回所有流式的基础数据结构 &lt;code&gt;ReadableStream&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;其中使用了一个自定函数，将异步可迭代对象变成流式输出。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function readableFromAsyncIterable&amp;lt;T&amp;gt;(iterable: AsyncIterable&amp;lt;T&amp;gt;) {
  let it = iterable[Symbol.asyncIterator]();
  return new ReadableStream&amp;lt;T&amp;gt;({
    async pull(controller) { // 内置API
      const { done, value } = await it.next();
      if (done) controller.close();
      else controller.enqueue(value);
    },

    async cancel(reason) {
      await it.return?.(reason);
    },
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;优化：back-pressure 和取消&lt;/h3&gt;
&lt;p&gt;上面优化后的代码，和下面的代码相比有两个特点:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过 &lt;code&gt;pull&lt;/code&gt; + 修改 &lt;code&gt;for await&lt;/code&gt; 实现数据生成的动态调整&lt;/li&gt;
&lt;li&gt;增加取消&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function createStream(iterator) {
  return new ReadableStream({
    async start(controller) {
      for await (const v of iterator) {
        controller.enqueue(v);
      }
      controller.close();
    },
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Generative UI 生成式UI&lt;/h2&gt;
&lt;p&gt;AI SDK 利用 React Server Components 和 Server Actions，通过 &lt;code&gt;ai/rsc&lt;/code&gt; 包集成界面渲染功能。&lt;/p&gt;
&lt;p&gt;AI SDK 引入了两个新概念： &lt;code&gt;AIState&lt;/code&gt; 和 &lt;code&gt;UIState&lt;/code&gt; 。这些状态在服务器端 AI 操作和应用程序中呈现的客户端 UI 之间引入了明确的关注点分离。这种分离使开发人员能够安全地维护 AI 状态，其中可能包括系统提示或其他元数据等内容。同时，UI 状态旨在允许 React 服务器组件有效地传输到客户端。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AIState&lt;/code&gt; 是 LLM 需要读取的所有上下文的 JSON 表示形式。对于聊天应用程序， &lt;code&gt;AIState&lt;/code&gt; 通常存储用户和助手之间的文本对话历史记录。实际上，它还可以用于存储其他值和元信息，例如每条消息的 &lt;code&gt;createdAt&lt;/code&gt; 。 &lt;code&gt;AIState&lt;/code&gt; 默认情况下，可以在服务器和客户端上访问/修改。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UIState&lt;/code&gt; 是应用程序用来显示 UI 的内容。它是完全客户端状态（与 &lt;code&gt;useState&lt;/code&gt; 非常相似），可以保留 LLM 返回的数据和 UI 元素。该状态可以是任何状态，但无法在服务器上访问。&lt;/p&gt;
&lt;p&gt;注意，这里返回的是服务器组件，通过 &lt;code&gt;zod&lt;/code&gt; 实现了 agent 数据结构的生成与验证：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// An example of a flight card component.
function FlightCard({ flightInfo }) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Flight Information&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;Flight Number: {flightInfo.flightNumber}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;Departure: {flightInfo.departure}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;Arrival: {flightInfo.arrival}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

// An example of a function that fetches flight information from an external API.
async function getFlightInfo(flightNumber: string) {
  return {
    flightNumber,
    departure: &apos;New York&apos;,
    arrival: &apos;San Francisco&apos;,
  };
}

async function submitUserMessage(userInput: string) {
  &apos;use server&apos;;

  const aiState = getMutableAIState&amp;lt;typeof AI&amp;gt;();

  // Update the AI state with the new user message.
  aiState.update([
    ...aiState.get(),
    {
      role: &apos;user&apos;,
      content: userInput,
    },
  ]);

  // The `render()` creates a generated, streamable UI.
  const ui = render({
    model: &apos;gpt-4-0125-preview&apos;,
    provider: openai,
    messages: [
      { role: &apos;system&apos;, content: &apos;You are a flight assistant&apos; },
      ...aiState.get()
    ],
    // `text` is called when an AI returns a text response (as opposed to a tool call).
    // Its content is streamed from the LLM, so this function will be called
    // multiple times with `content` being incremental.
    text: ({ content, done }) =&amp;gt; {
      // When it&apos;s the final content, mark the state as done and ready for the client to access.
      if (done) {
        aiState.done([
          ...aiState.get(),
          {
            role: &quot;assistant&quot;,
            content
          }
        ]);
      }

      return &amp;lt;p&amp;gt;{content}&amp;lt;/p&amp;gt;
    },
    tools: {
      get_flight_info: {
        description: &apos;Get the information for a flight&apos;,
        parameters: z.object({
          flightNumber: z.string().describe(&apos;the number of the flight&apos;)
        }).required(),
        render: async function* ({ flightNumber }) {
          // Show a spinner on the client while we wait for the response.
          yield &amp;lt;Spinner/&amp;gt;

          // Fetch the flight information from an external API.
          const flightInfo = await getFlightInfo(flightNumber)

          // Update the final AI state.
          aiState.done([
            ...aiState.get(),
            {
              role: &quot;function&quot;,
              name: &quot;get_flight_info&quot;,
              // Content can be any string to provide context to the LLM in the rest of the conversation.
              content: JSON.stringify(flightInfo),
            }
          ]);

          // Return the flight card to the client.
          return &amp;lt;FlightCard flightInfo={flightInfo} /&amp;gt;
        }
      }
    }
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;源码探究&lt;/h3&gt;
&lt;p&gt;这一块的源码就比较复杂了，大致的逻辑是在后端调用 LLM 服务并渲染出流式的 react server component 并传给前端。&lt;/p&gt;
&lt;p&gt;首先封装了一个可以从外部触发 resolve 和 reject 的期约：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function createResolvablePromise&amp;lt;T = any&amp;gt;() {
  let resolve: (value: T) =&amp;gt; void, reject: (error: unknown) =&amp;gt; void;
  const promise = new Promise&amp;lt;T&amp;gt;((res, rej) =&amp;gt; {
    resolve = res;
    reject = rej;
  });
  return {
    promise,
    resolve: resolve!,
    reject: reject!,
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来定义了一个递归渲染的组件，异步获取chunk并包裹在 Suspense 里面：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const R = [
  (async ({
    c, // current
    n, // next
  }: {
    c: React.ReactNode;
    n: Promise&amp;lt;any&amp;gt;;
  }) =&amp;gt; {
    const chunk = await n;
    if (chunk.done) {
      return chunk.value;
    }

    if (chunk.append) {
      return (
        &amp;lt;&amp;gt;
          {c}
          &amp;lt;Suspense fallback={chunk.value}&amp;gt;
            &amp;lt;R c={chunk.value} n={chunk.next} /&amp;gt;
          &amp;lt;/Suspense&amp;gt;
        &amp;lt;/&amp;gt;
      );
    }

    return (
      &amp;lt;Suspense fallback={chunk.value}&amp;gt;
        &amp;lt;R c={chunk.value} n={chunk.next} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    );
  }) as unknown as React.FC&amp;lt;{
    c: React.ReactNode;
    n: Promise&amp;lt;any&amp;gt;;
  }&amp;gt;,
][0];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过前面的函数，实现了初始化组件后的异步更新，并通过暴露的 resolve 和 reject 方法来实现对原有promise的控制。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function createSuspensedChunk(initialValue: React.ReactNode) {
  const { promise, resolve, reject } = createResolvablePromise();

  return {
    row: (
      &amp;lt;Suspense fallback={initialValue}&amp;gt;
        &amp;lt;R c={initialValue} n={promise} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    ),
    resolve,
    reject,
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终封装出的函数就是 &lt;code&gt;createStreamableUI&lt;/code&gt;，返回值是一个包含了 React 节点，以及更新/增加方法，和 &lt;code&gt;done&lt;/code&gt; 方法的一个 stream-like 结构。其中 &lt;code&gt;update&lt;/code&gt;/&lt;code&gt;append&lt;/code&gt; 方法每次都会再创建一个新的 &lt;code&gt;resolvable&lt;/code&gt;，从而实现持续的流传输。&lt;/p&gt;
&lt;p&gt;源码不长，但是设计思路很值得学习：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function createStreamableUI(initialValue?: React.ReactNode) {
  let currentValue = initialValue;
  let closed = false;
  let { row, resolve, reject } = createSuspensedChunk(initialValue);

  function assertStream(method: string) {
    if (closed) {
      throw new Error(method + &apos;: UI stream is already closed.&apos;);
    }
  }

  let warningTimeout: NodeJS.Timeout | undefined;
  function warnUnclosedStream() {
    if (process.env.NODE_ENV === &apos;development&apos;) {
      if (warningTimeout) {
        clearTimeout(warningTimeout);
      }
      warningTimeout = setTimeout(() =&amp;gt; {
        console.warn(
          &apos;The streamable UI has been slow to update. This may be a bug or a performance issue or you forgot to call `.done()`.&apos;,
        );
      }, DEV_DEFAULT_STREAMABLE_WARNING_TIME);
    }
  }
  warnUnclosedStream();

  return {
    /**
     * The value of the streamable UI. This can be returned from a Server Action and received by the client.
     */
    value: row,
    /**
     * This method updates the current UI node. It takes a new UI node and replaces the old one.
     */
    update(value: React.ReactNode) {
      assertStream(&apos;.update()&apos;);

      // There is no need to update the value if it&apos;s referentially equal.
      if (value === currentValue) {
        warnUnclosedStream();
        return;
      }

      const resolvable = createResolvablePromise();
      currentValue = value;

      resolve({ value: currentValue, done: false, next: resolvable.promise });
      resolve = resolvable.resolve;
      reject = resolvable.reject;

      warnUnclosedStream();
    },
    /**
     * This method is used to append a new UI node to the end of the old one.
     * Once appended a new UI node, the previous UI node cannot be updated anymore.
     *
     * @example
     * ```jsx
     * const ui = createStreamableUI(&amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;)
     * ui.append(&amp;lt;div&amp;gt;world&amp;lt;/div&amp;gt;)
     *
     * // The UI node will be:
     * // &amp;lt;&amp;gt;
     * //   &amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;
     * //   &amp;lt;div&amp;gt;world&amp;lt;/div&amp;gt;
     * // &amp;lt;/&amp;gt;
     * ```
     */
    append(value: React.ReactNode) {
      assertStream(&apos;.append()&apos;);

      const resolvable = createResolvablePromise();
      currentValue = value;

      resolve({ value, done: false, append: true, next: resolvable.promise });
      resolve = resolvable.resolve;
      reject = resolvable.reject;

      warnUnclosedStream();
    },
    /**
     * This method is used to signal that there is an error in the UI stream.
     * It will be thrown on the client side and caught by the nearest error boundary component.
     */
    error(error: any) {
      assertStream(&apos;.error()&apos;);

      if (warningTimeout) {
        clearTimeout(warningTimeout);
      }
      closed = true;
      reject(error);
    },
    /**
     * This method marks the UI node as finalized. You can either call it without any parameters or with a new UI node as the final state.
     * Once called, the UI node cannot be updated or appended anymore.
     *
     * This method is always **required** to be called, otherwise the response will be stuck in a loading state.
     */
    done(...args: [] | [React.ReactNode]) {
      assertStream(&apos;.done()&apos;);

      if (warningTimeout) {
        clearTimeout(warningTimeout);
      }
      closed = true;
      if (args.length) {
        resolve({ value: args[0], done: true });
        return;
      }
      resolve({ value: currentValue, done: true });
    },
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;嵌套UI流&lt;/h3&gt;
&lt;p&gt;可以在StockCard中套一层HistoryChart，同时异步更新:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Create a streaming UI node. You can make as many as you need.
async function getStockHistoryChart() {
  &apos;use server&apos;;

  const ui = createStreamableUI(&amp;lt;Spinner /&amp;gt;);

  // We need to wrap this in an async IIFE to avoid blocking. Without it, the UI wouldn&apos;t render
  // while the fetch or LLM call are in progress.
  (async () =&amp;gt; {
    const price = await callLLM(&apos;What is the current stock price of AAPL?&apos;);

    // Show a spinner as the history chart for now.
    // We won&apos;t be updating this again so we use `ui.done()` instead of `ui.update()`.
    const historyChart = createStreamableUI(&amp;lt;Spinner /&amp;gt;);
    ui.done(&amp;lt;StockCard historyChart={historyChart.value} price={price} /&amp;gt;);

    // Getting the history data and then update that part of the UI.
    const historyData = await fetch(&apos;https://my-stock-data-api.com&apos;);
    historyChart.done(&amp;lt;HistoryChart data={historyData} /&amp;gt;);
  })();

  return ui;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>技术 | 无服务器数据库迁移的感想</title><link>https://www.ender-wiggin.com/posts/note-serverless/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-serverless/</guid><description> </description><pubDate>Wed, 10 Apr 2024 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;3月的时候收到 &lt;a href=&quot;https://planetscale.com/&quot;&gt;PlanetScale&lt;/a&gt; 的短信说他们家的免费数据库会在今年4月8号开始收费，互联网上突然冒出了一大批“数据库难民”，四处寻觅免费的替代品。很多人都推荐了 &lt;a href=&quot;https://neon.tech/&quot;&gt;Neon&lt;/a&gt;，但我一想到迁移数据库这种非常无趣的运维工作就毫无做的欲望，直到今天数据库被停用了之后才开始搬家之旅。&lt;/p&gt;
&lt;p&gt;这件事情，以及关于 “serverless” 的各种新闻，让我对于这个带不少炒作性质的概念有了一些新的想法：&lt;/p&gt;
&lt;h2&gt;从用户的角度&lt;/h2&gt;
&lt;p&gt;去年我第一次知道 Vercel 令人匪夷所思的云部署福利后欣喜若狂，也看到网上很多人和我一样对 Vercel, Cloudflare 这些公司感恩戴德，尊称为赛博菩萨。而当人们一开始期待于 Vercel 的kv数据库集成时，定价的公布让用户直接望而却步，转向了 Supabase 和 Planetscale “新一代” 业界良心。可是不到一年过去了，曾经的良心也在经济压力面前倒下了，而新扶持的这位又能撑多久呢？&lt;/p&gt;
&lt;p&gt;无服务器这个概念天生就存在陷阱，因为它显然是有服务器的，而真正&lt;strong&gt;无&lt;/strong&gt;服务器的只是用户。过去的 aws，阿里云这种infra层面的云服务，对于整个云平台的可用性与稳定性是有着极高要求的，而稳定性也不只是系统层面的稳定，也包含了持续定价上的一致性。在这种稳定性的加持下，购买者是可以真真切切的感受到自己是&lt;strong&gt;有&lt;/strong&gt;这个服务器的。而现在的无服务器，就像是一种商人的施舍，说不准哪天就收回了。&lt;/p&gt;
&lt;p&gt;从商业决策上来说，这种带有一定背刺意味的行为无可厚非，但是无疑会让用户对于这一类公司都有一种不安全感。并且，它们所精心营造的极佳 DX 体验，从比较“阴暗”的角度来看也是一种让人习惯后难以舍弃的糖衣炮弹。&lt;/p&gt;
&lt;p&gt;我上面的观点不是对于 Vercel 这些公司的质疑，毕竟大家都是白嫖，心里面显然只有感恩。但是公司背后的商业性不可忽视，尤其是这种几乎全方面黑盒的运营模式。&lt;/p&gt;
&lt;h2&gt;从商业的角度&lt;/h2&gt;
&lt;p&gt;商业价值是无法回避的问题，而我一方面作为常年白嫖的用户，另一方面现在也在一家AIGC公司里面做类似的服务开发，对于定价问题时常有些忧虑。&lt;/p&gt;
&lt;p&gt;作为广大普通用户，最核心的观念就是：不选对的，只选最便宜的。&lt;/p&gt;
&lt;p&gt;这么多年来，我们已经见过了数个本来如日中天的新起之秀，因为定价问题导致用户纷纷离去。Planetscale 就是典型的一例，曾经是行业标杆，如今似乎成为了背信弃义的“无良公司”，令人唏嘘。而反观去年的 Midjournay 和 ChatGPT，尤其是后者离谱的 GPT-4 定价和出尔反尔的宣传加上时不时的网络崩溃，简直就是软件界的灾难。但偏偏这20刀的定价可以屹立不倒，这其中的核心是不可替代性：只有他能被选。&lt;/p&gt;
&lt;p&gt;但是去年也传出了 GitHub Copilot 亏损的消息，让这种商业定价的规律更加难以捉摸：即便用户愿意购买，是否盈利依然是一个疑问。&lt;/p&gt;
&lt;p&gt;所以从多方面来看，我对于 Vercel 都有着十足的担忧，因为它几乎把所有可能的坑都踩了一边：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;以免费服务吸引用户&lt;/li&gt;
&lt;li&gt;重infra的运营模式&lt;/li&gt;
&lt;li&gt;可替代性。既有同风格的 netify 和 Cloudflare Page，也可以直接购买服务器的正常部署。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vercel 口碑很好的一点是出色的 DX，但一个很遗憾的事实是，作为以B端为核心盈利模式的产品，DX 根本不值钱。aws等一众不是人用的导航界面，似乎就是在嘲笑这种锦上添花的努力。&lt;/p&gt;
&lt;p&gt;因此，我认为当我们选择一个服务时，即便它有再多的精美包装，两个核心问题都是无法回避的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;公司的定价逻辑。这决定了公司靠什么盈利，从 Vercel 计费部分的高额收费，可以看出来他们很想赚钱，或者说只能靠这种高额收费的方式赚钱。&lt;/li&gt;
&lt;li&gt;公司的盈利能力。这决定了公司能不能持续提供免费服务，以及会不会突然背刺。强如 Google 也是砍产品风驰电掣，其它公司如何运营更是未知。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;从技术的角度&lt;/h2&gt;
&lt;p&gt;前几年我对&lt;strong&gt;新&lt;/strong&gt;技术有一种狂热的痴迷，但现在静下来想，这个“新”里面有好几种不同的成分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新且好。GPT 算是一例，Astro 这种框架也可以算。我觉得作为技术爱好者，显然不能固步自封。但是选择技术的核心在于它“好”而不是它“新”，如果说它解决了以前从没有人解决的问题那么自然就是又新又好。&lt;/li&gt;
&lt;li&gt;好，但是不新。某种意义上来说，就是旧瓶装新酒，底层逻辑已经延续了很多年了，但是因为某些新的包装，让它看起来焕然一新。而这层包装，在当今往往就是“&lt;strong&gt;黑盒换便携性&lt;/strong&gt;”。Next.js 在新手刚上手时会非常好用，但是随着使用深度的增加，一方面会被迫学习很多纯 hardcode 的 magic，另一方面又对于黑盒有一种观摩金字塔般的困惑。这种“好”，我认为就是 false positive，让人只能理解器的层面而不是道，同时习惯后带来的依赖性让新手开发者可能进退两难。&lt;/li&gt;
&lt;li&gt;新，但不好。这个程度可能是渐进的，比如 Zed，现在看起来不具备什么实用性但是说不定几年之后就独当一面了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于上面的第二点，也说明真正核心的“基础知识”的重要性。比如这次的服务器迁移，用 prisma orm做了一层工程上的封装确实就方便了很多。&lt;/p&gt;
&lt;p&gt;最后写一下步骤吧，没想到前面扯了这么多废话233。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安装 pgloader 用于迁移数据库。这里我用 Ubuntu 22.04 报错了，可以自己从&lt;strong&gt;头 build 后运行&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;更新 prisma 配置。用 drizzle 也差不多。然后 push 一下。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;datasource db {
  provider     = &quot;postgresql&quot;
  url          = env(&quot;DATABASE_URL&quot;)
  relationMode = &quot;prisma&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;按照文档随便配一下参数。注意 &lt;code&gt;127.0.0.1:3306&lt;/code&gt; 是需要靠本地 &lt;code&gt;pscale cli&lt;/code&gt; 起一个服务的。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;pgloader --with &quot;quote identifiers&quot; --with &quot;data only&quot; mysql://username:planet_scale_password@127.0.0.1:3306/database_name &quot;postgres://alex:endpoint=ep-cool-darkness-123456;AbC123dEf@ep-cool-darkness-123456.us-east-2.aws.neon.tech/dbname?sslmode=require&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后提一下，用下划线命名确实是好文明，数据库的大小写坑虽然说可以解决，但总还是有点麻烦。下划线轻松而省力，何乐而不为呢。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>书评 | 关于乙一短篇小说的碎碎念</title><link>https://www.ender-wiggin.com/posts/review-otsuichi/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-otsuichi/</guid><description>剧透预警。乙一的短篇小说总让我又爱又恨，有时候看完很无语，但是又有点停不下来。</description><pubDate>Wed, 03 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;h2&gt;I 暗黑系&lt;/h2&gt;
&lt;p&gt;咖啡馆这意象挺有意思，同时冷静又模糊的“我”这个代称暗示了全书的叙诡基调。&lt;/p&gt;
&lt;h2&gt;II 断掌事件&lt;/h2&gt;
&lt;p&gt;初看的时候感觉非常阴暗，但看完更多乙一的作品后这篇相较而言还算正常，对“我”这个角色的复杂动机有更深入的描写。&lt;/p&gt;
&lt;h2&gt;III 犬&lt;/h2&gt;
&lt;p&gt;个人最不喜欢的作品之一，明明有很多方式可以达成目的却偏偏选择了最意外的一种，有点强行动物叙诡的感觉，这个反转让我有点无法接受甚至有点生理上的不适。&lt;/p&gt;
&lt;h2&gt;IV 记忆&lt;/h2&gt;
&lt;p&gt;作为比较明显的双胞胎诡计，故事本身还是不错的。&lt;/p&gt;
&lt;h2&gt;V 土&lt;/h2&gt;
&lt;p&gt;这篇的凶手真的太变态了，开头杀纯真的小孩就看得很难受了。除开剧情上的不合理性，这篇作品创意还是挺有意思的。&lt;/p&gt;
&lt;h2&gt;VI 声&lt;/h2&gt;
&lt;p&gt;可以说整本书靠这一篇出色的剧情与叙诡，让读者勉强忽略了前面几篇中的各种缺陷，黑暗中伴随温情。&lt;/p&gt;
&lt;h2&gt;森野去拍纪念照&lt;/h2&gt;
&lt;p&gt;没什么名堂的作品。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;小饰与阳子&lt;/h2&gt;
&lt;p&gt;阅读的时候总会觉得这种离谱的家庭关系应该不会存在。我可以接受一个完全脱离人性的反社会杀人狂，但无法想象一个形同杀人狂的母亲形象。理论上这种形象也是有概率存在的，但还是让我有点太震撼了。
还是经典双胞胎叙事，但书中关于老奶奶那一段实在是太有温度了，如同正午的阳光洒在榻榻米上。&lt;/p&gt;
&lt;h2&gt;七个房间&lt;/h2&gt;
&lt;p&gt;个人非常喜欢的一篇。对于凶手主体的描述几乎没有，甚至连具体的长相都几乎没有提及。而杀意却充斥全篇，更像是精妙绝伦的建筑犯罪。
与之相应的是，对房间中受害者们的并不浓墨重彩但恰到好处的心理描写，姐弟组合的最终结局更是让人久久不能忘怀。&lt;/p&gt;
&lt;h2&gt;远离的夫妇&lt;/h2&gt;
&lt;p&gt;最有意思的一点是结局的“去超自然化”处理，虽然会引申出很多前面的逻辑矛盾点，但不妨作为一个有趣的思想实验，也确实反应出了孩童心理与家长关系直接的重要联系。&lt;/p&gt;
&lt;h2&gt;向阳之诗&lt;/h2&gt;
&lt;p&gt;很不乙一，但也挺乙一的一篇作品。有些《最后的生还者》的感觉，末世设定具有永恒的魅力，因为它的主题永远是探究人生的终极问题。&lt;/p&gt;
&lt;h2&gt;动物园&lt;/h2&gt;
&lt;p&gt;可以理解这种仿佛精神分裂但自始至终“似乎很冷静”的叙事风格很出色地描绘了杀人之后的多种情绪堆叠的纠结。&lt;/p&gt;
&lt;h2&gt;把血液找出来&lt;/h2&gt;
&lt;p&gt;在合理设定上构建的一个幽默荒诞的故事，很有小品剧的画面感。&lt;/p&gt;
&lt;h2&gt;寒冷森林中的小白屋&lt;/h2&gt;
&lt;p&gt;不是很喜欢。作者视图描写一下再压抑生活下的似乎无思考的杀人犯，但“小白屋”这个设定实在是太奇怪了。不过不得不说，这篇作品的逻辑性挺强，也有两个亮点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;前文对马厩臭味与石头人脸的多次强调，为后文看似突兀的“小白屋”带来了合理性。&lt;/li&gt;
&lt;li&gt;红衣女孩这个看似救赎的角色，最终又走向了宿命的轮回，为这个黯淡的故事更增添了一丝绝望。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;衣柜&lt;/h2&gt;
&lt;p&gt;叙诡的结构挺合理，但是故事有点无趣，动机更是抽象。&lt;/p&gt;
&lt;h2&gt;神的咒语&lt;/h2&gt;
&lt;p&gt;暗黑风格的设定系作品，言灵的概念还是可以玩出花来的，这篇作品中的主角有点操作但不多，如果让白井智之来写说不定就是《象首》2.0了。
最终的结局虽说也不错，但我总觉得这个设定可以有一个更有趣的结局，无限流还是稍微有点循规蹈矩。&lt;/p&gt;
&lt;h2&gt;在即将坠落的飞机里&lt;/h2&gt;
&lt;p&gt;极端场景下的冷静思考+黑色幽默确实很有阅读乐趣，和前面的《血液》异曲同工。算是这本书里我第三喜欢的作品吧，有些值得深思的地方，又名《小镇做题家之死》。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>技术 | React-DnD 预览层实现的两种方案</title><link>https://www.ender-wiggin.com/posts/note-react-dnd/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-react-dnd/</guid><description>React-DnD 中如果想要修改拖拽中的预览元素会非常麻烦，这篇文章将总结一下通用性的实现方案。</description><pubDate>Sun, 31 Mar 2024 22:03:00 GMT</pubDate><content:encoded>&lt;p&gt;React-DnD 作为 React 中功能最全面的拖拽库，几乎可以满足所有业务需求。但是当用户拖拽对应 &lt;code&gt;item&lt;/code&gt; 时，如果想要修改拖拽中的预览元素会非常麻烦，官方虽然提供了API 但是使用起来并不方便。这篇文章将总结一下通用性的实现方案。&lt;/p&gt;
&lt;h2&gt;图片预览&lt;/h2&gt;
&lt;p&gt;显然，如果只需要预览图片，那么直接使用自带的 API &lt;code&gt;DragPreviewImage&lt;/code&gt; 即可，使用起来非常简单：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { DragSource, DragPreviewImage } from &apos;React-DnD&apos;

function DraggableHouse({ connectDragSource, connectDragPreview }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;DragPreviewImage src=&quot;house_dragged.png&quot; connect={connectDragPreview} /&amp;gt;
      &amp;lt;div ref={connectDragSource}&amp;gt;🏠&amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，很多场景下，我们需要拖拽的是一个 &lt;code&gt;div&lt;/code&gt; 而不是简单的图片，因此就需要使用另一个 API：&lt;code&gt;useDragLayer&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;元素预览&lt;/h2&gt;
&lt;p&gt;Stackoverflow 中有一篇很好的回答解释了具体的&lt;a href=&quot;https://stackoverflow.com/a/70320222&quot;&gt;实现方案&lt;/a&gt;。我将对其中的实现展开说明一下。&lt;/p&gt;
&lt;p&gt;从架构上来说，大致分为以下几步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;定义全局的 &lt;code&gt;&amp;lt;CustomDragLayer /&amp;gt;&lt;/code&gt; 组件。这个组件将会覆盖&lt;strong&gt;所有&lt;/strong&gt; Dnd 上下文中的预览效果。因此需要谨慎设计这个组件的架构。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在需要定制化预览效果的组件中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;禁用默认预览效果，避免样式冲突&lt;/li&gt;
&lt;li&gt;传入需要的参数(&lt;code&gt;item&lt;/code&gt;)，用于让全局的layer根据对应参数渲染出需要的组件。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;接下来，我会逐一介绍具体实现。&lt;/p&gt;
&lt;h3&gt;全局配置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;export const CustomDragLayer = (props: {}) =&amp;gt; {
  const {
    itemType, // item类型，一般作为工厂函数入口
    item,  // 具体item参数，可以在uesDrag的item()中自己配置，用于渲染具体组件
    isDragging,
    initialCursorOffset, // 偏移量计算，不需要关注具体的实现，只需要知道它们实现了元素的拖拽跟随即可
    initialFileOffset,
    currentFileOffset,
  } = useDragLayer((monitor) =&amp;gt; ({
    item: monitor.getItem(),
    itemType: monitor.getItemType(),
    initialCursorOffset: monitor.getInitialClientOffset(),
    initialFileOffset: monitor.getInitialSourceClientOffset(),
    currentFileOffset: monitor.getSourceClientOffset(),
    isDragging: monitor.isDragging(),
  }));

  if (!isDragging) {
    return null;
  }

// 这个返回值就是具体组件的渲染
  return (
    &amp;lt;div style={layerStyles}&amp;gt;
      &amp;lt;div
        style={getItemStyles(
          initialCursorOffset,
          initialFileOffset,
          currentFileOffset
        )}
      &amp;gt;
        &amp;lt;div&amp;gt;Your custom drag preview component logic here&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const layerStyles: CSSProperties = {
  position: &quot;fixed&quot;,
  pointerEvents: &quot;none&quot;,
  zIndex: 100,
  left: 0,
  top: 0,
  width: &quot;100%&quot;,
  height: &quot;100%&quot;,
  border: &quot;10px solid red&quot;, // 这是拖拽时的全局效果，按个人需要增加或删除
};

function getItemStyles(
  initialCursorOffset: XYCoord | null,
  initialOffset: XYCoord | null,
  currentOffset: XYCoord | null
) {
  if (!initialOffset || !currentOffset || !initialCursorOffset) {
    return {
      display: &quot;none&quot;,
    };
  }

  const x = initialCursorOffset?.x + (currentOffset.x - initialOffset.x);
  const y = initialCursorOffset?.y + (currentOffset.y - initialOffset.y);
  const transform = `translate(${x}px, ${y}px)`;

  return {
    transform,
    WebkitTransform: transform,
    background: &quot;red&quot;,
    width: &quot;200px&quot;,
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在实践中，我认为比较好的方式是实现一个工厂函数，用于根据不同的 &lt;code&gt;itemType&lt;/code&gt; 渲染出不同的工厂模版。当然，&lt;code&gt;itemType&lt;/code&gt; 在 React-DnD 中属于重要参数，开发人员在设计时本身就需要遵守一些常量命名的最佳实践。&lt;/p&gt;
&lt;p&gt;而另外的一个 &lt;code&gt;item&lt;/code&gt; 参数，也是这个库的最核心数据交互手段，可以根据业务需求非常灵活的加入各种属性进行数据传输。在下一部分也会额外介绍一下。&lt;/p&gt;
&lt;h3&gt;具体组件配置&lt;/h3&gt;
&lt;p&gt;目前我的个人感受时，在实现一个拖拽组件时，往往需要封装3层组件：
最细粒度的纯函数组件（不需要hook，直接传入参数） -&amp;gt; 进行状态管理封装的最小组件（需要调用&lt;code&gt;useDrag&lt;/code&gt; hook）-&amp;gt; 组件容器 （进行上游的状态管理）。&lt;/p&gt;
&lt;p&gt;其中纯函数组件是为了在预览层和拖拽层进行复用，基本的参数如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface PureItemProps {
  dragStatus: &apos;dragging&apos; | &apos;idle&apos; | &apos;preview&apos;; // 拖拽状态，一共有三种：未拖拽的静止态、拖拽中原先位置的状态、拖拽中跟随鼠标的元素状态
  name: string; // 可以根据输入渲染出具体组件的最细参数
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而对纯函数的封装层，就需要调用最核心的 &lt;code&gt;usedrag&lt;/code&gt; hook了，大致设计如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const DragItem: React.FC&amp;lt;DragItemProps&amp;gt; = ({ item }) =&amp;gt; {
  const [{ isDragging }, dragRef, dragPreview] = useDrag(() =&amp;gt; ({
    type: DRAG_ITEM_BLOCK,
    item: () =&amp;gt; {
      // 可以在拖拽开始时触发一些操作
      console.log(&apos;开始拖拽&apos;);
      return {
        id: item.id,
        name: item.name,
        otherData: &apos;some data&apos;,
      };
    },
    end: (item, monitor) =&amp;gt; {
      // 可以在拖拽结束后触发一些操作
      console.log(&apos;结束拖拽&apos;);
    },
    collect: (monitor) =&amp;gt; {
      return {
        isDragging: monitor.isDragging(),
      };
    },
  }));

  // 清除默认拖拽效果
  useEffect(() =&amp;gt; {
    dragPreview(getEmptyImage(), { captureDraggingState: true });
  }, [dragPreview]); // 配置依赖性，避免一开始的时候没有去除默认效果

  return (
    &amp;lt;div
      ref={dragRef}
    &amp;gt;
      &amp;lt;PureDragItem name={item.name} isDragging={isDragging} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，在dnd上下文的下一层引入这个layer即可实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      &amp;lt;DndProvider backend={MyBackend}&amp;gt;
        &amp;lt;ItemList /&amp;gt;
        &amp;lt;CustomDragLayer /&amp;gt;
      &amp;lt;/DndProvider&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;fixed定位问题&lt;/h3&gt;
&lt;p&gt;在后续的开发中，还遇到一个比较奇怪的问题，即拖拽之后定位并没有严格跟随鼠标，而是有一段偏移。这是由于 &lt;code&gt;fixed&lt;/code&gt; 定位虽然是相对于浏览器视口的，但是会被 &lt;code&gt;transform&lt;/code&gt; 影响导致。&lt;code&gt;fixed&lt;/code&gt; 定位的元素将相对于最近的已转换祖先元素进行定位，而不是整个视口。这是因为 &lt;code&gt;transform&lt;/code&gt; 属性创建了一个新的包含块（containing block），对于 &lt;code&gt;fixed&lt;/code&gt; 定位的元素而言，这个新的包含块就像是它的视口。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;因此按照最佳实践，只需要将这个预览层放在根节点就可以了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是在项目实操中，由于一些组件的解耦不到位，可能会导致组件受到上下文影响必须放置在某个更深的树节点中，这种情况下就会被 &lt;code&gt;transform&lt;/code&gt; 影响定位了。解决方案是使用 React Portal 将节点挂载到 &lt;code&gt;body&lt;/code&gt; 中。用法即：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ReactDOM.createPortal(child, container)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第9章-代理与反射</title><link>https://www.ender-wiggin.com/posts/note-js-9/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-9/</guid><description>整理一下之前的笔记，当做复习。</description><pubDate>Mon, 18 Mar 2024 22:04:00 GMT</pubDate><content:encoded>&lt;h2&gt;代理&lt;/h2&gt;
&lt;p&gt;代理是目标对象的抽象。从很多方面看，代理类似C++指针，因为它可以用作目标对象的替身，但又完全独立于目标对象。目标对象既可以直接被操作，也可以通过代理来操作。但直接操作会绕过代理施予的行为。&lt;/p&gt;
&lt;p&gt;代理是使用Proxy构造函数创建的。&lt;strong&gt;这个构造函数接收两个参数：目标对象和处理程序对象&lt;/strong&gt;。缺少其中任何一个参数都会抛出TypeError。要创建空代理，可以传一个简单的对象字面量作为处理程序对象，从而让所有操作畅通无阻地抵达目标对象。&lt;/p&gt;
&lt;h3&gt;定义捕获器&lt;/h3&gt;
&lt;p&gt;使用代理的主要目的是可以定义捕获器。&lt;/p&gt;
&lt;p&gt;例如当执行get()时（&lt;code&gt;proxy[property]&lt;/code&gt;、&lt;code&gt;proxy.property&lt;/code&gt;或&lt;code&gt;Object.create(proxy)[property]&lt;/code&gt;），就会触发捕获：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const target = {
      foo: &apos;bar&apos;
    };
    const handler = {
      get(trapTarget, property, receiver) {
        console.log(trapTarget === target);
        console.log(property);
        console.log(receiver === proxy);
      }
    };
    const proxy = new Proxy(target, handler);
    proxy.foo;
    // true
    // foo
    // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有捕获器都可以基于自己的参数重建原始操作，但并非所有捕获器行为都像get()那么简单。因此，通过手动写码如法炮制的想法是不现实的。实际上，开发者并不需要手动重建原始行为，而是可以通过调用全局&lt;code&gt;Reflect&lt;/code&gt;对象上（封装了原始行为）的同名方法来轻松重建。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const target = {
      foo: &apos;bar&apos;
    };
    const handler = {
      get() {
        return Reflect.get(...arguments);
      }
    };
    const proxy = new Proxy(target, handler);
    console.log(proxy.foo);    // bar
    console.log(target.foo);   // bar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以更加简化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const handler = {
      get: Reflect.get
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事实上，如果真想创建一个可以捕获所有方法，然后将每个方法转发给对应反射API的空代理，那么甚至不需要定义处理程序对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const target = {
      foo: &apos;bar&apos;
    };
    const proxy = new Proxy(target, Reflect);
    console.log(proxy.foo);    // bar
    console.log(target.foo);   // bar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;捕获器不变式&lt;/h3&gt;
&lt;p&gt;如果目标对象有一个不可配置且不可写的数据属性，那么在捕获器返回一个与该属性不同的值时，会抛出TypeError：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const target = {};
    Object.defineProperty(target, &apos;foo&apos;, {
      configurable: false,
      writable: false,
      value: &apos;bar&apos;
    });
    const handler = {
      get() {
        return &apos;qux&apos;;
      }
    };
    const proxy = new Proxy(target, handler);
    console.log(proxy.foo);
    // TypeError
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;可撤销代理&lt;/h3&gt;
&lt;p&gt;Proxy也暴露了revocable()方法，这个方法支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的。而且，撤销函数（revoke()）是幂等的，调用多少次的结果都一样。撤销代理之后再调用代理会抛出TypeError。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const target = {
      foo: &apos;bar&apos;
    };
    const handler = {
      get() {
        return &apos;intercepted&apos;;
      }
    };
    const { proxy, revoke } = Proxy.revocable(target, handler);
    console.log(proxy.foo);    // intercepted
    console.log(target.foo);   // bar
    revoke();
    console.log(proxy.foo);    // TypeError
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实用反射API&lt;/h3&gt;
&lt;p&gt;在使用反射API时，要记住：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;反射API并不限于捕获处理程序&lt;/li&gt;
&lt;li&gt;大多数反射API方法在Object类型上有对应的方法。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通常，Object上的方法适用于通用程序，而反射方法适用于细粒度的对象控制与操作。
例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Function.prototype.apply.call(myFunc, thisVal, argumentList);

    // 可以使用Reflect变为：
    Reflect.apply(myFunc, thisVal, argumentsList);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;代理反射API&lt;/h2&gt;
&lt;p&gt;ownKeys()捕获器会在Object.keys()及类似方法中被调用。对应的反射API方法为Reflect. ownKeys()。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getPrototypeOf()&lt;/code&gt;捕获器会在&lt;code&gt;Object.getPrototypeOf()&lt;/code&gt;中被调用。对应的反射API方法为&lt;code&gt;Reflect.getPrototypeOf()&lt;/code&gt;。拦截的操作包括了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object.getPrototypeOf(proxy)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Reflect.getPrototypeOf(proxy)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy.__proto__&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object.prototype.isPrototypeOf(proxy)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy instanceof Object&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;construct()捕获器会在new操作符中被调用。对应的反射API方法为Reflect.construct()。&lt;/p&gt;
&lt;h2&gt;用途&lt;/h2&gt;
&lt;p&gt;比如（但远远不限于）跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定，以及可观察对象。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第8章-对象、类与面向对象编程</title><link>https://www.ender-wiggin.com/posts/note-js-8/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-8/</guid><description>整理一下之前的笔记，当做复习。</description><pubDate>Mon, 18 Mar 2024 22:03:00 GMT</pubDate><content:encoded>&lt;h2&gt;对象&lt;/h2&gt;
&lt;h3&gt;属性&lt;/h3&gt;
&lt;p&gt;ECMA-262使用一些内部特性来描述属性的特征。
属性分两种：数据属性和访问器属性。&lt;/p&gt;
&lt;h4&gt;数据属性&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[[Configurable]]&lt;/code&gt;：表示属性是否可以通过delete删除并重新定义，是否可以修改它的特性，以及是否可以把它改为访问器属性。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[[Enumerable]]&lt;/code&gt;：表示属性是否可以通过&lt;code&gt;for-in&lt;/code&gt;循环返回。默认情况下，所有直接定义在对象上的属性的这个特性都是&lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[[Writable]]&lt;/code&gt;：表示属性的值是否可以被修改。默认情况下，所有直接定义在对象上的属性的这个特性都是&lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[[Value]]&lt;/code&gt;：包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为&lt;code&gt;undefined&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要修改属性的默认特性，就必须使用&lt;code&gt;Object.defineProperty()&lt;/code&gt;方法。
这个方法接收3个参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;要给其添加属性的对象&lt;/li&gt;
&lt;li&gt;属性的名称&lt;/li&gt;
&lt;li&gt;一个描述符对象。描述符对象上的属性可以包含：configurable、enumerable、writable和value&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    let person = {};
    Object.defineProperty(person, &quot;name&quot;, {
      configurable: false, // 不能删除这个属性
      value: &quot;Nicholas&quot;
    });
    console.log(person.name); // &quot;Nicholas&quot;
    delete person.name; // 严格模式报错
    console.log(person.name); // &quot;Nicholas&quot;
    let person = {};

    // 抛出错误
    Object.defineProperty(person, &quot;name&quot;, {
      configurable: true,
      value: &quot;Nicholas&quot;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;访问器属性&lt;/h4&gt;
&lt;p&gt;有与前面类似的&lt;code&gt;Configurable&lt;/code&gt;和&lt;code&gt;Enumerable&lt;/code&gt;属性，以及&lt;code&gt;Get&lt;/code&gt;和&lt;code&gt;Set&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 定义一个对象，包含伪私有成员year_和公共成员edition
    let book = {
      year_: 2017,
      edition: 1
    };
    Object.defineProperty(book, &quot;year&quot;, {
      get() {
        return this.year_;
      },
      set(newValue) {
        if (newValue &amp;gt; 2017) {
          this.year_ = newValue;
          this.edition += newValue -2017;
        }
      }
    });
    book.year = 2018;
    console.log(book.edition); // 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;定义多个属性&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    let book = {};
    Object.defineProperties(book, {
      year_: {
        value: 2017
      },
      edition: {
        value: 1
      },
      year: {
        get() {
          return this.year_;
        },
        set(newValue) {
          if (newValue &amp;gt; 2017) {
            this.year_ = newValue;
            this.edition += newValue -2017;
          }
        }
      }
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;读取属性的特性&lt;/h3&gt;
&lt;p&gt;使用&lt;code&gt;Object.getOwnPropertyDescriptor()&lt;/code&gt;方法可以取得指定属性的属性描述符。&lt;/p&gt;
&lt;p&gt;ECMAScript 2017新增了&lt;code&gt;Object.getOwnPropertyDescriptors()&lt;/code&gt;静态方法。这个方法实际上会在每个自有属性上调用&lt;code&gt;Object.getOwnPropertyDescriptor()&lt;/code&gt;并在一个&lt;strong&gt;新对象&lt;/strong&gt;中返回它们。&lt;/p&gt;
&lt;h3&gt;合并对象&lt;/h3&gt;
&lt;p&gt;ES6提供了Object.assign()方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;接收一个目标对象和一个或多个源对象作为参数&lt;/li&gt;
&lt;li&gt;后将每个源对象中可枚举（&lt;code&gt;Object.propertyIsEnumerable()&lt;/code&gt;返回true）和&lt;strong&gt;自有属性&lt;/strong&gt;（&lt;code&gt;Object.hasOwnProperty()&lt;/code&gt;返回true）复制到目标对象&lt;/li&gt;
&lt;li&gt;以字符串和符号为键的属性会被复制&lt;/li&gt;
&lt;li&gt;对每个符合条件的属性，这个方法会使用源对象上的&lt;code&gt;[[Get]]&lt;/code&gt;取得属性的值，然后使用目标对象上的&lt;code&gt;[[Set]]&lt;/code&gt;设置属性的值。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;Object.assign()&lt;/code&gt;实际上对每个源对象执行的是&lt;strong&gt;浅复制&lt;/strong&gt;。如果多个源对象都有相同的属性，则使用最后一个复制的值。
此外，从源对象访问器属性取得的值，比如获取函数，会作为一个静态值赋给目标对象。换句话说，不能在两个对象间转移获取函数和设置函数。&lt;/p&gt;
&lt;p&gt;如果赋值期间出错，则操作会中止并退出，同时抛出错误。&lt;code&gt;Object.assign()&lt;/code&gt;没有“回滚”之前赋值的概念，因此它是一个尽力而为、&lt;strong&gt;可能只会完成部分复制&lt;/strong&gt;的方法。&lt;/p&gt;
&lt;h3&gt;对象标识及相等判定&lt;/h3&gt;
&lt;p&gt;ES6新增了&lt;code&gt;Object.is()&lt;/code&gt;，这个方法与&lt;code&gt;===&lt;/code&gt;很像&lt;/p&gt;
&lt;p&gt;递归比较多个：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function recursivelyCheckEqual(x, ...rest) {
      return Object.is(x, rest[0]) &amp;amp;&amp;amp;
              (rest.length &amp;lt; 2 || recursivelyCheckEqual(...rest));
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;增强的对象语法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;属性值简写：变量名与值相同时可以省略冒号及后面的内容&lt;/li&gt;
&lt;li&gt;可计算属性：&lt;strong&gt;中括号包围的对象属性键&lt;/strong&gt;告诉运行时将其作为JS表达式而不是字符串来求值&lt;/li&gt;
&lt;li&gt;简写方法名：方法名即函数名 `let person = { sayName(name){ ... ) }&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;对象解构&lt;/h3&gt;
&lt;p&gt;解构在内部使用函数&lt;code&gt;toObject()&lt;/code&gt;（不能在运行时环境中直接访问）把源数据结构转换为对象。这意味着在对象解构的上下文中，原始值会被当成对象。这也意味着（根据ToObject()的定义）, null和undefined不能被解构，否则会抛出错误。&lt;/p&gt;
&lt;p&gt;解构并不要求变量必须在解构表达式中声明。不过，如果是给事先声明的变量赋值，则赋值表达式必须包含在一对括号中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let personName, personAge;
    let person = {
      name: &apos;Matt&apos;,
      age: 27
    };
    ({name: personName, age: personAge} = person);
    console.log(personName, personAge); // Matt, 27
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;创建对象&lt;/h2&gt;
&lt;h3&gt;ES5 构造函数写法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    function Person(name, age, job){
      this.name = name;
      this.age = age;
      this.job = job;
      this.sayName = function() {
        console.log(this.name);
      };
    }
    let person1 = new Person(&quot;Nicholas&quot;, 29, &quot;Software Engineer&quot;);
    let person2 = new Person(&quot;Greg&quot;, 27, &quot;Doctor&quot;);
    person1.sayName();   // Nicholas
    person2.sayName();   // Greg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要创建&lt;code&gt;Person&lt;/code&gt;的实例，应使用&lt;code&gt;new&lt;/code&gt;操作符。以这种方式调用构造函数会执行如下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在内存中创建一个新对象。&lt;/li&gt;
&lt;li&gt;这个新对象内部的&lt;code&gt;[[Prototype]]&lt;/code&gt;特性被赋值为构造函数的&lt;code&gt;prototype&lt;/code&gt;属性&lt;/li&gt;
&lt;li&gt;构造函数内部的&lt;code&gt;this&lt;/code&gt;被赋值为这个新对象（即&lt;code&gt;this&lt;/code&gt;指向新对象）&lt;/li&gt;
&lt;li&gt;执行构造函数内部的代码（给新对象添加属性）&lt;/li&gt;
&lt;li&gt;如果构造函数返回非空对象，则返回该对象；否则，返回刚创建的新对象&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;person1和person2分别保存着Person的不同实例。这两个对象都有一个constructor属性指向Person，如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    console.log(person1.constructor == Person);   // true
    console.log(person2.constructor == Person);   // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;构造函数与普通函数唯一的区别就是调用方式不同。除此之外，构造函数也是函数。并没有把某个函数定义为构造函数的特殊语法。&lt;strong&gt;任何函数只要使用&lt;code&gt;new&lt;/code&gt;操作符调用就是构造函数&lt;/strong&gt;，而不使用&lt;code&gt;new&lt;/code&gt;操作符调用的函数就是普通函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 作为构造函数
    let person = new Person(&quot;Nicholas&quot;, 29, &quot;Software Engineer&quot;);
    person.sayName();     // &quot;Nicholas&quot;
    // 作为函数调用
    Person(&quot;Greg&quot;, 27, &quot;Doctor&quot;);    // 添加到window对象
    window.sayName();     // &quot;Greg&quot;
    // 在另一个对象的作用域中调用
    let o = new Object();
    Person.call(o, &quot;Kristen&quot;, 25, &quot;Nurse&quot;);
    o.sayName();    // &quot;Kristen&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二个情况中没有使用&lt;code&gt;new&lt;/code&gt;，因此会将this指向global。&lt;/p&gt;
&lt;h4&gt;构造函数的问题&lt;/h4&gt;
&lt;p&gt;其定义的方法会在每个实例上都创建一遍。不同实例上的函数虽然同名却不相等：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    console.log(person1.sayName == person2.sayName); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解决方案是在外边先定义好函数，在构造函数内使用该函数方法的引用。但是依然会造成作用域的问题。
这个新问题可以通过原型模式来解决。&lt;/p&gt;
&lt;h3&gt;原型模式&lt;/h3&gt;
&lt;p&gt;每个函数都会创建一个prototype属性，这个属性是一个对象，包含应该由特定引用类型的实例共享的属性和方法。实际上，这个对象就是通过调用构造函数创建的对象的原型。
使用原型对象的好处是，在它上面定义的属性和方法可以被对象实例共享。原来在构造函数中直接赋给对象实例的值，可以直接赋值给它们的原型，如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Person() {}
    Person.prototype.name = &quot;Nicholas&quot;;
    Person.prototype.age = 29;
    Person.prototype.job = &quot;Software Engineer&quot;;
    Person.prototype.sayName = function() {
      console.log(this.name);
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;理解原型&lt;/h4&gt;
&lt;p&gt;无论何时，只要创建一个函数，就会按照特定的规则为这个函数创建一个prototype属性（指向原型对象）。默认情况下，所有原型对象自动获得一个名为constructor的属性，指回与之关联的构造函数。对前面的例子而言，&lt;strong&gt;&lt;code&gt;Person.prototype.constructor&lt;/code&gt;指向&lt;code&gt;Person&lt;/code&gt;&lt;/strong&gt;。然后，因构造函数而异，可能会给原型对象添加其他属性和方法。&lt;/p&gt;
&lt;p&gt;在自定义构造函数时，原型对象默认只会获得constructor属性，其他的所有方法都继承自Object。每次调用构造函数创建一个新实例，这个实例的内部&lt;code&gt;[[Prototype]]&lt;/code&gt;指针就会被赋值为构造函数的原型对象。
脚本中没有访问这个&lt;code&gt;[[Prototype]]&lt;/code&gt;特性的标准方式，但Firefox、Safari和Chrome会在每个对象上暴露&lt;code&gt;__proto__&lt;/code&gt;属性，通过这个属性可以访问对象的原型。在其他实现中，这个特性完全被隐藏了。关键在于理解这一点：实例与构造函数原型之间有直接的联系，但实例与构造函数之间没有。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /＊＊
      ＊ 构造函数可以是函数表达式
      ＊ 也可以是函数声明，因此以下两种形式都可以：
      ＊    function Person() {}
      ＊    let Person = function() {}
      ＊/
    function Person() {}
    /＊＊
      ＊ 声明之后，构造函数就有了一个
      ＊ 与之关联的原型对象：
      ＊/
    console.log(typeof Person.prototype);
    console.log(Person.prototype);
    // {
    //    constructor: f Person(),
    //    __proto__: Object
    // }
    /＊＊
      ＊ 如前所述，构造函数有一个prototype属性
      ＊ 引用其原型对象，而这个原型对象也有一个
      ＊ constructor属性，引用这个构造函数
      ＊ 换句话说，两者循环引用：
      ＊/
    console.log(Person.prototype.constructor === Person); // true
    /＊＊
      ＊ 正常的原型链都会终止于Object的原型对象
      ＊ Object原型的原型是null
      ＊/
    console.log(Person.prototype.__proto__ === Object.prototype);    // true
    console.log(Person.prototype.__proto__.constructor === Object); // true
    console.log(Person.prototype.__proto__.__proto__ === null);      // true
    console.log(Person.prototype.__proto__);
    // {
    //    constructor: f Object(),
    //    toString: ...
    //    hasOwnProperty: ...
    //    isPrototypeOf: ...
    //    ...
    // }
    let person1 = new Person(),
        person2 = new Person();
    /＊＊
      ＊ 构造函数、原型对象和实例
      ＊ 是3 个完全不同的对象：
      ＊/
    console.log(person1 ! == Person);              // true
    console.log(person1 ! == Person.prototype); // true
    console.log(Person.prototype ! == Person);   // true
    /＊＊
      ＊ 实例通过__proto__链接到原型对象，
      ＊ 它实际上指向隐藏特性[[Prototype]]
      ＊
      ＊ 构造函数通过prototype属性链接到原型对象
      ＊
      ＊ 实例与构造函数没有直接联系，与原型对象有直接联系
      ＊/
    console.log(person1.__proto__ === Person.prototype);    // true
    conosle.log(person1.__proto__.constructor === Person); // true
    /＊＊
      ＊ 同一个构造函数创建的两个实例
      ＊ 共享同一个原型对象：
      ＊/
    console.log(person1.__proto__ === person2.__proto__); // true
    /＊＊
      ＊ instanceof检查实例的原型链中
      ＊ 是否包含指定构造函数的原型：
      ＊/
    console.log(person1 instanceof Person);              // true
    console.log(person1 instanceof Object);              // true
    console.log(Person.prototype instanceof Object);   // true
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isPrototypeOf()&lt;/code&gt;会在传入参数的&lt;code&gt;[[Prototype]]&lt;/code&gt;指向调用它的对象时返回&lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object.getPrototypeOf()&lt;/code&gt;，返回参数的内部特性&lt;code&gt;[[Prototype]]&lt;/code&gt;的值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object.setPrototypeOf()&lt;/code&gt;方法，可以向实例的私有特性&lt;code&gt;[[Prototype]]&lt;/code&gt;写入一个新值。不建议使用这个方法，建议使用&lt;code&gt;Object.create()&lt;/code&gt;来创建一个新对象，同时为其指定原型&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    let biped = {
      numLegs: 2
    };
    let person = Object.create(biped);
    person.name = &apos;Matt&apos;;
    console.log(Object.getPrototypeOf(person) === biped);   // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;原型层级&lt;/h4&gt;
&lt;p&gt;在通过对象访问属性时，会按照这个属性的名称开始搜索。搜索开始于对象实例本身。&lt;/p&gt;
&lt;p&gt;注意，虽然可以通过实例读取原型对象上的值，但不可能通过实例重写这些值。如果在实例上添加了一个与原型对象中同名的属性，那就会在实例上创建这个属性，这个属性会&lt;strong&gt;遮住原型对象上的属性&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Person() {}
    Person.prototype.name = &quot;Nicholas&quot;;
    Person.prototype.age = 29;
    Person.prototype.job = &quot;Software Engineer&quot;;
    Person.prototype.sayName = function() {
      console.log(this.name);
    };
    let person1 = new Person();
    let person2 = new Person();
    person1.name = &quot;Greg&quot;;
    console.log(person1.name);   // &quot;Greg&quot;，来自实例
    console.log(person2.name);   // &quot;Nicholas&quot;，来自原型
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;hasOwnProperty()&lt;/code&gt;方法用于确定某个属性是在实例上还是在原型对象上。\&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Object.getOwnPropertyDescriptor()&lt;/code&gt;方法只对实例属性有效。要取得原型属性的描述符，就必须直接在原型对象上调用&lt;code&gt;Object.getOwnPropertyDescriptor()&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;in&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在单独使用时，in操作符会在可以通过对象访问指定属性时返回true，无论该属性是在实例上还是在原型上（&lt;code&gt;hasOwnProperty&lt;/code&gt;只有实例上才能返回true）。&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;for-in&lt;/code&gt;循环中使用&lt;code&gt;in&lt;/code&gt;操作符时，可以通过对象访问且可以被枚举的属性都会返回，包括实例属性和原型属性。&lt;strong&gt;遮蔽&lt;/strong&gt;原型中&lt;strong&gt;不可枚举属性的实例属性也会在&lt;code&gt;for-in&lt;/code&gt;循环中返回&lt;/strong&gt;，因为默认情况下开发者定义的属性都是可枚举的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要获得对象上所有可枚举的实例属性，可以使用&lt;code&gt;Object.keys()&lt;/code&gt;方法。&lt;/p&gt;
&lt;p&gt;如果想列出所有实例属性，无论是否可以枚举，都可以使用&lt;code&gt;Object.getOwnPropertyNames()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let keys = Object.getOwnPropertyNames(Person.prototype);
    console.log(keys);    // &quot;[constructor,name,age,job,sayName]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;	// 类似的符号版本
    let k1 = Symbol(&apos;k1&apos;),
        k2 = Symbol(&apos;k2&apos;);
    let o = {
      [k1]: &apos;k1&apos;,
      [k2]: &apos;k2&apos;
    };
    console.log(Object.getOwnPropertySymbols(o));
    // [Symbol(k1), Symbol(k2)]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;属性枚举顺序&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;for-in&lt;/code&gt;循环和&lt;code&gt;Object.keys()&lt;/code&gt;的枚举顺序是不确定的&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object.getOwnPropertyNames()&lt;/code&gt;、&lt;code&gt;Object.getOwnPropertySymbols()&lt;/code&gt;和&lt;code&gt;Object.assign()&lt;/code&gt;的枚举顺序是确定性的。&lt;strong&gt;先以升序枚举数值键，然后以插入顺序枚举字符串和符号键。在对象字面量中定义的键以它们逗号分隔的顺序插入。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    let k1 = Symbol(&apos;k1&apos;),
        k2 = Symbol(&apos;k2&apos;);
    let o = {
      1: 1,
      first: &apos;first&apos;,
      [k1]: &apos;sym2&apos;,
      second: &apos;second&apos;,
      0: 0
    };
    o[k2] = &apos;sym2&apos;;
    o[3] = 3;
    o.third = &apos;third&apos;;
    o[2] = 2;
    console.log(Object.getOwnPropertyNames(o));
    // [&quot;0&quot;, &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;first&quot;, &quot;second&quot;, &quot;third&quot;]
    console.log(Object.getOwnPropertySymbols(o));
    // [Symbol(k1), Symbol(k2)]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;对象迭代&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Object.values()返回对象值的数组&lt;/li&gt;
&lt;li&gt;Object.entries()返回键/值对的数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;符号属性均会被忽略。&lt;/p&gt;
&lt;h4&gt;一些注意事项&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;原型重写&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    function Person() {}
    Person.prototype = {
	  // constructor: Person, // 需要显式定义，同时需要设置为不可枚举值
      name: &quot;Nicholas&quot;,
      age: 29,
      job: &quot;Software Engineer&quot;,
      sayName() {
        console.log(this.name);
      }
    };

    // 恢复constructor属性，推荐写法
    Object.defineProperty(Person.prototype, &quot;constructor&quot;, {
      enumerable: false,
      value: Person
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种写法等于覆盖了原有的原型，也就是说&lt;code&gt;Person&lt;/code&gt;原型对象的&lt;code&gt;constructor&lt;/code&gt;并不会指向&lt;code&gt;Person&lt;/code&gt;了。但是，&lt;code&gt;instanceof&lt;/code&gt;依然可以正确捕获（因为只单纯检查是否在原型链上）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let friend = new Person();
    console.log(friend instanceof Object);        // true
    console.log(friend instanceof Person);        // true
    console.log(friend.constructor == Person);   // false
    console.log(friend.constructor == Object);   // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，实例只有指向原型的指针，没有指向构造函数的指针：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Person() {}
    let friend = new Person();
    Person.prototype = {
      constructor: Person,
      name: &quot;Nicholas&quot;,
      age: 29,
      job: &quot;Software Engineer&quot;,
      sayName() {
        console.log(this.name);
      }
    };
    friend.sayName();   // 错误
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是因为firend指向的原型还是最初的原型，而这个原型上并没有sayName属性。&lt;/p&gt;
&lt;h4&gt;原型的问题&lt;/h4&gt;
&lt;p&gt;原型模式也不是没有问题。首先，它弱化了向构造函数传递初始化参数的能力，会导致所有实例默认都取得相同的属性值。虽然这会带来不便，但还不是原型的最大问题。原型的最主要问题源自它的共享特性，即不同实例共享同样的浅复制属性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Person() {}
    Person.prototype = {
      constructor: Person,
      name: &quot;Nicholas&quot;,
      age: 29,
      job: &quot;Software Engineer&quot;,
      friends: [&quot;Shelby&quot;, &quot;Court&quot;],
      sayName() {
        console.log(this.name);
      }
    };
    let person1 = new Person();
    let person2 = new Person();
    person1.friends.push(&quot;Van&quot;);
    console.log(person1.friends);   // &quot;Shelby,Court,Van&quot;
    console.log(person2.friends);   // &quot;Shelby,Court,Van&quot;
    console.log(person1.friends === person2.friends);   // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;继承&lt;/h2&gt;
&lt;p&gt;很多OOP分为接口继承和实现继承，而JS只有实现继承。&lt;/p&gt;
&lt;h3&gt;原型链&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    function SuperType() {
      this.property = true;
    }
    SuperType.prototype.getSuperValue = function() {
      return this.property;
    };
    function SubType() {
      this.subproperty = false;
    }
    // 继承SuperType
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function () {
      return this.subproperty;
    };
    let instance = new SubType();
    console.log(instance.getSuperValue()); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原型链虽然是实现继承的强大工具，但它也有问题。主要问题出现在原型中包含引用值的时候。前面在谈到原型的问题时也提到过，原型中包含的引用值会在所有实例间共享，这也是为什么属性通常会在构造函数中定义而不会定义在原型上的原因。在使用原型实现继承时，原型实际上变成了另一个类型的实例。这意味着原先的实例属性摇身一变成为了原型属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function SuperType() {
      this.colors = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;];
    }
    function SubType() {}
    // 继承SuperType
    SubType.prototype = new SuperType();
    let instance1 = new SubType();
    instance1.colors.push(&quot;black&quot;);
    console.log(instance1.colors); // &quot;red, blue, green, black&quot;
    let instance2 = new SubType();
    console.log(instance2.colors); // &quot;red, blue, green, black&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;盗用构造函数&lt;/h3&gt;
&lt;p&gt;为了解决原型包含引用值导致的继承问题，一种叫作“盗用构造函数”（constructor stealing）的技术在开发社区流行起来（这种技术有时也称作“对象伪装”或“经典继承”）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function SuperType() {
      this.colors = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;];
    }
    function SubType() {
      //继承SuperType
      SuperType.call(this);
    }
    let instance1 = new SubType();
    instance1.colors.push(&quot;black&quot;);
    console.log(instance1.colors); // &quot;red, blue, green, black&quot;
    let instance2 = new SubType();
    console.log(instance2.colors); // &quot;red, blue, green&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;盗用构造函数的主要缺点，也是使用构造函数模式自定义类型的问题：必须在构造函数中定义方法，因此函数不能重用。此外，子类也不能访问父类原型上定义的方法，因此所有类型只能使用构造函数模式。由于存在这些问题，盗用构造函数基本上也不能单独使用。&lt;/p&gt;
&lt;h3&gt;组合继承&lt;/h3&gt;
&lt;p&gt;组合继承（有时候也叫伪经典继承）综合了原型链和盗用构造函数，将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法，而通过盗用构造函数继承实例属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function SuperType(name){
      this.name = name;
      this.colors = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;];
    }
    SuperType.prototype.sayName = function() {
      console.log(this.name);
    };
    function SubType(name, age){
      // 继承属性
      SuperType.call(this, name);
      this.age = age;
    }
    // 继承方法
    SubType.prototype = new SuperType();
    SubType.prototype.sayAge = function() {
      console.log(this.age);
    };
    let instance1 = new SubType(&quot;Nicholas&quot;, 29);
    instance1.colors.push(&quot;black&quot;);
    console.log(instance1.colors);   // &quot;red, blue, green, black&quot;
    instance1.sayName();               // &quot;Nicholas&quot;;
    instance1.sayAge();                // 29
    let instance2 = new SubType(&quot;Greg&quot;, 27);
    console.log(instance2.colors);   // &quot;red, blue, green&quot;
    instance2.sayName();               // &quot;Greg&quot;;
    instance2.sayAge();                // 27
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;类&lt;/h2&gt;
&lt;p&gt;与函数定义不同的是，虽然函数声明可以提升，但类定义不能。&lt;/p&gt;
&lt;h3&gt;类构造函数&lt;/h3&gt;
&lt;p&gt;方法名constructor会告诉解释器在使用new操作符创建类的新实例时，应该调用这个函数。构造函数的定义不是必需的，不定义构造函数相当于将构造函数定义为空函数。&lt;/p&gt;
&lt;p&gt;使用new操作符实例化的操作等于使用new调用其构造函数。唯一可感知的不同之处就是，JavaScript解释器知道使用new和类意味着应该使用constructor函数进行实例化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;重要，使用new调用类的构造函数会执行如下操作&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在内存中创建一个新对象&lt;/li&gt;
&lt;li&gt;这个新对象内部的&lt;code&gt;[[Prototype]]&lt;/code&gt;指针被赋值为构造函数的&lt;code&gt;prototype&lt;/code&gt;属性&lt;/li&gt;
&lt;li&gt;构造函数内部的&lt;code&gt;this&lt;/code&gt;被赋值为这个新对象（即&lt;code&gt;this&lt;/code&gt;指向新对象）&lt;/li&gt;
&lt;li&gt;执行构造函数内部的代码（给新对象添加属性）&lt;/li&gt;
&lt;li&gt;如果构造函数返回非空对象，则返回该对象；否则，返回刚创建的新对象。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;类实例化时传入的参数会用作构造函数的参数。如果不需要参数，则类名后面的括号也是可选的。&lt;/p&gt;
&lt;p&gt;类构造函数与构造函数的主要区别是，调用类构造函数必须使用new操作符。而普通构造函数如果不使用new调用，那么就会以全局的this（通常是window）作为内部对象。调用类构造函数时如果忘了使用new则会抛出错误。&lt;/p&gt;
&lt;p&gt;类构造函数实例化之后依然可以通过 new 调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    class Person {}
    // 使用类创建一个新实例
    let p1 = new Person();
    p1.constructor();
    // TypeError: Class constructor Person cannot be invoked without &apos;new&apos;
    // 使用对类构造函数的引用创建一个新实例
    let p2 = new p1.constructor();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;类本身在使用new调用时就会被当成构造函数&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;类中定义的constructor方法不会被当成构造函数，在对它使用instanceof操作符时会返回false。&lt;/li&gt;
&lt;li&gt;但是，如果在创建实例时直接将类构造函数当成普通构造函数来使用，那么instanceof操作符的返回值会反转：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    class Person {}
    let p1 = new Person();
    console.log(p1.constructor === Person);           // true
    console.log(p1 instanceof Person);                  // true
    console.log(p1 instanceof Person.constructor);   // false
    let p2 = new Person.constructor();
    console.log(p2.constructor === Person);           // false
    console.log(p2 instanceof Person);                  // false
    console.log(p2 instanceof Person.constructor);   // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实例、原型和类成员&lt;/h3&gt;
&lt;p&gt;静态类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    class Person {
      constructor() {
        // 添加到this的所有内容都会存在于不同的实例上
        this.locate = () =&amp;gt; console.log(&apos;instance&apos;, this);
      }
      // 定义在类的原型对象上
      locate() {
        console.log(&apos;prototype&apos;, this);
      }
      //定义在类本身上
      static locate(){
        console.log(&apos;class&apos;, this);
      }
    }
    let p = new Person();
    p.locate();                     // instance, Person {}
    Person.prototype.locate();   // prototype, {constructor: ... }
    Person.locate();               // class, class Person {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可迭代实例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    class Person {
      constructor() {
        this.nicknames = [&apos;Jack&apos;, &apos;Jake&apos;, &apos;J-Dog&apos;];
      }
      ＊[Symbol.iterator](){
        yield ＊ this.nicknames.entries();
      }
    }
    let p = new Person();
    for (let [idx, nickname] of p) {
      console.log(nickname);
    }
    // Jack
    // Jake
    // J-Dog
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;继承&lt;/h3&gt;
&lt;p&gt;派生类的方法可以通过super关键字引用它们的原型。这个关键字只能在派生类中使用，而且仅限于类构造函数、实例方法和静态方法内部。在类构造函数中使用super可以调用父类构造函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    class Vehicle {
      constructor() {
        this.hasEngine = true;
      }
    }
    class Bus extends Vehicle {
      constructor() {
        // 不要在调用super()之前引用this，否则会抛出ReferenceError
        super(); // 相当于super.constructor()
        console.log(this instanceof Vehicle);   // true
        console.log(this);                          // Bus { hasEngine: true }
      }
    }
    new Bus();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;super 传参同理：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    class Vehicle {
      constructor(licensePlate) {
        this.licensePlate = licensePlate;
      }
    }
    class Bus extends Vehicle {
      constructor(licensePlate) {
        super(licensePlate);
      }
    }
    console.log(new Bus(&apos;1337H4X&apos;)); // Bus { licensePlate: &apos;1337H4X&apos; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果没有定义类构造函数，在实例化派生类时会调用super()，而且会传入所有传给派生类的参数。&lt;/p&gt;
&lt;p&gt;在类构造函数中，不能在调用super()之前引用this：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    class Vehicle {}
    class Bus extends Vehicle {
      constructor() {
        console.log(this);
      }
    }
    new Bus();
    // ReferenceError: Must call super constructor in derived class
    // before accessing &apos;this&apos; or returning from derived constructor
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第7章-迭代器与生成器</title><link>https://www.ender-wiggin.com/posts/note-js-7/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-7/</guid><description>整理一下之前的笔记，当做复习。</description><pubDate>Sun, 03 Mar 2024 22:03:00 GMT</pubDate><content:encoded>&lt;h2&gt;迭代器&lt;/h2&gt;
&lt;p&gt;ES6 之后支持。&lt;/p&gt;
&lt;p&gt;任何实现Iterable接口的数据结构都可以被实现Iterator接口的结构“消费”（consume）。迭代器（iterator）是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象，而迭代器会暴露迭代其关联可迭代对象的API。迭代器无须了解与其关联的可迭代对象的结构，只需要知道如何取得连续的值。这种概念上的分离正是Iterable和Iterator的强大之处。&lt;/p&gt;
&lt;h3&gt;可迭代协议&lt;/h3&gt;
&lt;p&gt;同时具备两种能力：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持迭代的自我识别能力&lt;/li&gt;
&lt;li&gt;创建实现Iterator接口的对象的能力&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这会暴露一个属性作为“默认迭代器”，而且这个属性必须使用特殊的Symbol.iterator作为键。这个默认迭代器属性必须引用一个迭代器工厂函数，调用这个工厂函数必须返回一个新迭代器。&lt;/p&gt;
&lt;h3&gt;迭代器协议&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;next()&lt;/code&gt;方法返回的迭代器对象&lt;code&gt;IteratorResult&lt;/code&gt;包含两个属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;done&lt;/code&gt;是一个布尔值，表示是否还可以再次调用&lt;code&gt;next()&lt;/code&gt;取得下一个值。&lt;code&gt;done: true&lt;/code&gt;状态称为“耗尽”。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt;包含可迭代对象的下一个值（&lt;code&gt;done&lt;/code&gt;为&lt;code&gt;false&lt;/code&gt;），或者undefined（done为true）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意，迭代器维护着一个指向可迭代对象的引用，因此迭代器会阻止垃圾回收程序回收可迭代对象。&lt;/p&gt;
&lt;p&gt;显式的迭代器实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 这个类实现了可迭代接口（Iterable）
    // 调用默认的迭代器工厂函数会返回
    // 一个实现迭代器接口（Iterator）的迭代器对象
    class Foo {
      [Symbol.iterator]() {
        return {
          next() {
            return { done: false, value: &apos;foo&apos; };
          }
        }
      }
    }
    let f = new Foo();
    // 打印出实现了迭代器接口的对象
    console.log(f[Symbol.iterator]()); // { next: f() {} }
    // Array类型实现了可迭代接口（Iterable）
    // 调用Array类型的默认迭代器工厂函数
    // 会创建一个ArrayIterator的实例
    let a = new Array();
    // 打印出ArrayIterator的实例
    console.log(a[Symbol.iterator]()); // Array Iterator {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;自定义迭代器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    class Counter {
      // Counter的实例应该迭代limit次
      constructor(limit) {
        this.count = 1;
        this.limit = limit;
      }
      next() {
        if (this.count &amp;lt;= this.limit) {
          return { done: false, value: this.count++ };
        } else {
          return { done: true, value: undefined };
        }
      }
      [Symbol.iterator]() {
        return this;
      }
    }
    let counter = new Counter(3);
    for (let i of counter) {
      console.log(i);
    }
    // 1
    // 2
    // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，上面的构造中，每个实例只能被迭代一次。因此需要使用闭包：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    class Counter {
      constructor(limit) {
        this.limit = limit;
      }
      [Symbol.iterator]() {
        let count=1,
            limit=this.limit;
        return{
          next(){
            if(count&amp;lt;=limit){
              return{done: false, value: count++};
            }else{
              return{done: true, value: undefined};
            }
          }
        };
      }
    }
    let counter = new Counter(3);
    for (let i of counter) { console.log(i); }
    // 1
    // 2
    // 3
    for (let i of counter) { console.log(i); }
    // 1
    // 2
    // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为每个迭代器也实现了Iterable接口，所以它们可以用在任何期待可迭代对象的地方，比如for-of循环：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let arr = [3, 1, 4];
    let iter = arr[Symbol.iterator]();
    for (let item of arr) { console.log(item); }
    // 3
    // 1
    // 4
    for (let item of iter) { console.log(item); }
    // 3
    // 1
    // 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;提前终止迭代器&lt;/h3&gt;
&lt;p&gt;可选的return()方法用于指定在迭代器提前关闭时执行的逻辑，必须返回一个有效的IteratorResult对象。简单情况下，可以只返回{ done: true }。&lt;/p&gt;
&lt;p&gt;例如在上面的例子中，可以加入如下函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//...
return() {
	return {done: true};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为return()方法是可选的，所以并非所有迭代器都是可关闭的。要知道某个迭代器是否可关闭，可以测试这个迭代器实例的return属性是不是函数对象。不过，仅仅给一个不可关闭的迭代器增加这个方法并不能让它变成可关闭的。这是因为调用return()不会强制迭代器进入关闭状态。即便如此，return()方法还是会被调用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let a = [1, 2, 3, 4, 5];
    let iter = a[Symbol.iterator]();
    iter.return=function(){
      console.log(&apos;Exitingearly&apos;);
      return{done: true};
    };
    for (let i of iter) {
      console.log(i);
      if (i &amp;gt; 2) {
        break
      }
    }
    // 1
    // 2
    // 3
    //提前退出
    for (let i of iter) {
      console.log(i);
    }
    // 4
    // 5
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;生成器&lt;/h2&gt;
&lt;p&gt;生成器是ECMAScript 6新增的一个极为灵活的结构，拥有在一个函数块内暂停和恢复代码执行的能力。&lt;/p&gt;
&lt;p&gt;生成器的形式是一个函数，函数名称前面加一个星号（＊）表示它是一个生成器。只要是可以定义函数的地方，就可以定义生成器。但是&lt;strong&gt;箭头函数不能用来定义生成器函数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行（suspended）的状态。与迭代器相似，生成器对象也实现了Iterator接口，因此具有next()方法。调用这个方法会让生成器开始或恢复执行。&lt;/p&gt;
&lt;p&gt;生成器对象实现了Iterable接口，它们默认的迭代器是自引用的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ generatorFn() {}
    console.log(generatorFn);
    // f＊ generatorFn() {}
    console.log(generatorFn()[Symbol.iterator]);
    // f [Symbol.iterator]() {native code}
    console.log(generatorFn());
    // generatorFn {&amp;lt;suspended&amp;gt;}
    console.log(generatorFn()[Symbol.iterator]());
    // generatorFn {&amp;lt;suspended&amp;gt;}
    const g = generatorFn();
    console.log(g === g[Symbol.iterator]());
    // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;通过yield中断执行&lt;/h3&gt;
&lt;p&gt;yield关键字可以让生成器停止和开始执行，也是生成器最有用的地方。生成器函数在遇到yield关键字之前会正常执行。遇到这个关键字后，执行会停止，函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行。&lt;/p&gt;
&lt;p&gt;通过yield关键字退出的生成器函数会处在done: false状态；通过return关键字退出的生成器函数会处于done: true状态。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ generatorFn() {
      yield 1;
      yield 2;
      yield 3;
    }
    for (const x of generatorFn()) {
      console.log(x);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除了可以作为函数的中间返回语句使用，yield关键字还可以作为函数的中间参数使用。上一次让生成器函数暂停的yield关键字会接收到传给next()方法的第一个值。&lt;/p&gt;
&lt;p&gt;注意，第一次调用next()传入的值不会被使用，因为这一次调用是为了开始执行生成器函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ generatorFn(initial) {
      console.log(initial);
      console.log(yield);
      console.log(yield);
    }
    let generatorObject = generatorFn(&apos;foo&apos;);
    generatorObject.next(&apos;bar&apos;);  //foo
    generatorObject.next(&apos;baz&apos;);   // baz
    generatorObject.next(&apos;qux&apos;);   // qux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用&lt;code&gt;generatorObject.next(&apos;bar&apos;)&lt;/code&gt;时，由于这是第一次调用&lt;code&gt;next&lt;/code&gt;方法，传入的参数并不会发送给Generator函数。这是因为Generator函数的执行上下文尚未建立，所以传给第一次&lt;code&gt;next&lt;/code&gt;方法的参数会被忽略。此调用会执行到第一个&lt;code&gt;yield&lt;/code&gt;处，打印出&lt;code&gt;&apos;foo&apos;&lt;/code&gt;（因为这是Generator函数接收到的初始参数），然后函数暂停执行。&lt;/p&gt;
&lt;p&gt;可以通过生成器快速实现&lt;code&gt;range&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ range(start, end) {
      while(end &amp;gt; start) {
        yield start++;
      }
    }
    for (const x of range(4, 7)) {
      console.log(x);
    }
    // 4
    // 5
    // 6
    function＊ zeroes(n) {
      while(n--) {
        yield 0;
      }
    }
    console.log(Array.from(zeroes(8))); // [0, 0, 0, 0, 0, 0, 0, 0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以使用星号增强yield的行为，让它能够迭代一个可迭代对象，从而一次产出一个值：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ generatorFn() {
      yield＊[1, 2];
      yield＊[3, 4];
      yield＊[5, 6];
    }
    for (const x of generatorFn()) {
      console.log(x);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;，&lt;code&gt;yield＊&lt;/code&gt;的值是关联迭代器返回&lt;code&gt;done: true&lt;/code&gt;时的&lt;code&gt;value&lt;/code&gt;属性。对于普通迭代器来说，这个值是&lt;code&gt;undefined&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ generatorFn() {
      console.log(&apos;iter value:&apos;, yield＊ [1, 2, 3]);
    }
    for (const x of generatorFn()) {
      console.log(&apos;value:&apos;, x);
    }
    // value: 1
    // value: 2
    // value: 3
    //itervalue: undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于生成器函数产生的迭代器来说，这个值就是&lt;strong&gt;生成器函数返回的值&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;yield＊最有用的地方是实现递归操作：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ nTimes(n) {
      if (n &amp;gt; 0) {
        yield ＊ nTimes(n-1);
        yield n-1;
      }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;图数据结构非常适合递归遍历，而递归生成器恰好非常合用。为此，生成器函数必须接收一个可迭代对象，产出该对象中的每一个值，并且对每个值进行递归。这个实现可以用来测试某个图是否连通，即是否没有不可到达的节点。只要从一个节点开始，然后尽力访问每个节点就可以了。&lt;/p&gt;
&lt;h3&gt;提前终止生成器&lt;/h3&gt;
&lt;p&gt;与迭代器类似，生成器也支持“可关闭”的概念。一个实现Iterator接口的对象一定有next()方法，还有一个可选的return()方法用于提前终止迭代器。生成器对象除了有这两个方法，还有第三个方法：throw()。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ generatorFn() {
      for (const x of [1, 2, 3]) {
        yield x;
      }
    }
    const g = generatorFn();
    console.log(g.next());      // { done: false, value: 1 }
    console.log(g.return(4));   // { done: true, value: 4 }
    console.log(g.next());      // { done: true, value: undefined }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与迭代器不同，所有生成器对象都有return()方法，只要通过它进入关闭状态，就无法恢复了。后续调用next()会显示done: true状态，而提供的任何返回值都不会被存储或传播。&lt;/p&gt;
&lt;p&gt;throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理，生成器就会关闭。
假如生成器函数内部处理了这个错误，那么生成器就不会关闭，而且还可以恢复执行。错误处理会跳过对应的yield，因此在这个例子中会跳过一个值。比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function＊ generatorFn() {
      for (const x of [1, 2, 3]) {
        try {
          yield x;
        } catch(e) {}
      }
    }
    const g = generatorFn();
    console.log(g.next()); // { done: false, value: 1}
    g.throw(&apos;foo&apos;);
    console.log(g.next()); // { done: false, value: 3}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意,如果生成器对象还没有开始执行，那么调用throw()抛出的错误不会在函数内部被捕获，因为这&lt;strong&gt;相当于在函数块外部抛出了错误&lt;/strong&gt;。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | 函数式伪类</title><link>https://www.ender-wiggin.com/posts/note-pseudo-class/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-pseudo-class/</guid><description> </description><pubDate>Sat, 02 Mar 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CSS &lt;strong&gt;伪类&lt;/strong&gt;是添加到选择器的关键字，用于指定所选元素的特殊状态。&lt;/p&gt;
&lt;h2&gt;函数式伪类&lt;/h2&gt;
&lt;p&gt;函数式伪类的选择器都是可容错的。&lt;/p&gt;
&lt;p&gt;在 CSS 中，当使用选择器列表时，如果任何一个选择器无效，则&lt;strong&gt;整个列表将被视为无效&lt;/strong&gt;。使用这些伪类时，如果一个选择器无法解析，整个选择器列表不会被视为无效，而是会忽略不正确或不支持的选择器，并使用其他的选择器。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;:is()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;:is()&lt;/code&gt;&lt;/strong&gt; 以选择器列表作为参数，并选择该列表中任意一个选择器可以选择的元素。这对于以更紧凑的形式编写大型选择器非常有用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
ul &amp;gt; li &amp;gt; a,
ol &amp;gt; li &amp;gt; a,
nav &amp;gt; ul &amp;gt; li &amp;gt; a,
nav &amp;gt; ol &amp;gt; li &amp;gt; a {
 color: blue;
}
/* 使用 :is() */
:is(ul, ol, nav &amp;gt; ul, nav &amp;gt; ol) &amp;gt; li &amp;gt; a {
 color: blue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 三层或更深的无序列表使用方形符号。 */
:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) :is(ul, menu, dir) {
  list-style-type: square;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/* 处理不同层级的 h1 元素 */
/* 0 级 */
h1 {
  font-size: 30px;
}
/* 1 级 */
:is(section, article, aside, nav) h1 {
  font-size: 25px;
}
/* 2 级 */
:is(section, article, aside, nav) :is(section, article, aside, nav) h1 {
  font-size: 20px;
}
/* 3 级 */
:is(section, article, aside, nav)
  :is(section, article, aside, nav)
  :is(section, article, aside, nav)
  h1 {
  font-size: 15px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;:is()&lt;/code&gt;&lt;/strong&gt; 优先级依然遵循CSS选择器的优先级规则，即 &lt;code&gt;ID -&amp;gt; 类 -&amp;gt; 元素&lt;/code&gt; 的顺序。由它的选择器列表中优先级最高的选择器决定的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;:is()&lt;/code&gt;&lt;/strong&gt; 的参数也可以传一个匹配规则:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 匹配所有 `class` 开头是 `is-styling` 的选择器 */
:is([class^=&quot;is-styling&quot;]) a {
 color: yellow;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，伪元素在 &lt;code&gt;:is()&lt;/code&gt; 的选择器列表中无效。不匹配伪元素。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;:not()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;用来匹配不符合一组选择器的元素。由于它的作用是防止特定的元素被选中，它也被称为 &lt;em&gt;反选伪类&lt;/em&gt;（&lt;em&gt;negation pseudo-class&lt;/em&gt;）。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;:where()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;:where()&lt;/code&gt;&lt;/strong&gt; 和 &lt;strong&gt;:is()&lt;/strong&gt; 相似，都可以传入选择器或者匹配规则来简化你的CSS代码。&lt;/p&gt;
&lt;p&gt;但和 &lt;strong&gt;:is()&lt;/strong&gt; 不同的是，&lt;code&gt;:where()&lt;/code&gt; 拥有最低优先级，这样的好处是它定义的样式规则不会影响其他样式规则，避免了样式冲突。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* &amp;lt;footer class=&quot;where-styling&quot;&amp;gt;……&amp;lt;/footer&amp;gt; */
footer a {
 color: green;
}
:where([class^=&quot;where-styling&quot;]) a {
 color: red
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当有其他规则和 &lt;strong&gt;&lt;code&gt;:where()&lt;/code&gt;&lt;/strong&gt; 同时被命中时，&lt;code&gt;:where()&lt;/code&gt; 一定是失效的。所以上面这个例子实际效果是链接显示绿色。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;:has()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;:has()&lt;/code&gt;&lt;/strong&gt; 表示一个元素，如果作为参数传递的任何相对选择器]在锚定到该元素时，至少匹配一个元素。这个伪类通过把可容错相对选择器列表作为参数，提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 选择直接包含 p 元素的 div */
div:has(&amp;gt; p) {
 border: 1px solid black;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/* 选择后面紧跟着 p 元素的 div */
div:has(+ p) {
 border: 1px solid black;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;h1,
h2,
h3 {
  margin: 0 0 1rem 0;
}

:is(h1, h2, h3):has(+ :is(h2, h3, h4)) {
/* 等价于
:is(h1, h2, h3):has(+ h2, + h3, + h4) {
*/
  margin: 0 0 0.25rem 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第6章-集合引用类型</title><link>https://www.ender-wiggin.com/posts/note-js-6/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-6/</guid><description>整理一下之前的笔记，当做复习。</description><pubDate>Fri, 01 Mar 2024 22:03:00 GMT</pubDate><content:encoded>&lt;h2&gt;Object&lt;/h2&gt;
&lt;p&gt;在使用对象字面量表示法定义对象时，并不会实际调用Object构造函数。&lt;/p&gt;
&lt;h2&gt;Array&lt;/h2&gt;
&lt;p&gt;数组中每个槽位可以存储任意类型的数据。这意味着可以创建一个数组，它的第一个元素是字符串，第二个元素是数值，第三个是对象。ECMAScript数组也是动态大小的，会随着数据添加而自动增长。&lt;/p&gt;
&lt;p&gt;在使用Array构造函数时，也可以省略new操作符。&lt;/p&gt;
&lt;p&gt;Array构造函数还有两个ES6新增的用于创建数组的静态方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;from()用于将类数组结构转换为数组实例&lt;/li&gt;
&lt;li&gt;of()用于将一组参数转换为数组实例。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Array.from()还接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值，而无须像调用Array.from().map()那样先创建一个中间数组。
还可以接收第三个可选参数，用于指定映射函数中this的值。但这个重写的this值在箭头函数中不适用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const a1 = [1, 2, 3, 4];
    const a2 = Array.from(a1, x =&amp;gt; x ＊＊ 2);
    const a3 = Array.from(a1, function(x) {return x ＊＊ this.exponent}, {exponent: 2});
    console.log(a2);   // [1, 4, 9, 16]
    console.log(a3);   // [1, 4, 9, 16]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;数组length属性的独特之处在于，它不是只读的。&lt;strong&gt;通过修改length属性&lt;/strong&gt;，可以从数组末尾删除或添加元素。&lt;/li&gt;
&lt;li&gt;Array.isArray()判断是否是数组&lt;/li&gt;
&lt;li&gt;在ES6中，Array的原型上暴露了3个用于检索数组内容的方法：keys()、values()和entries()。keys()返回数组索引的迭代器，values()返回数组元素的迭代器，而entries()返回索引/值对的迭代器&lt;/li&gt;
&lt;li&gt;ES6新增了两个方法：
&lt;ul&gt;
&lt;li&gt;批量复制方法copyWithin() 会按照指定范围浅复制数组中的部分内容，然后将它们插入到指定索引开始的位置。开始索引和结束索引则与fill()使用同样的计算方法：&lt;/li&gt;
&lt;li&gt;填充数组方法fill()，负值索引从数组末尾开始计算，静默忽略超出数组边界、零长度及方向相反的索引范围&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;reverse()和sort()都返回调用它们的数组的引用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;补充：fill的运行逻辑&lt;/h3&gt;
&lt;p&gt;在算法中，比如需要创建一个长度为n的二维数组作为初始化，如果是如下写法会导致意外情况：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const res: string[][] = new Array(numRows).fill([]);
// 或者
const res: string[][] = new Array(numRows).fill(new Array&amp;lt;string&amp;gt;());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是因为fill实际上会先创建一个实例，然后再把这个实例赋给每一个子项，也就是说每一项都共享同一个引用。
因此正规写法应该是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const res: string[][] = new Array(numRows).fill(0).map(_ =&amp;gt; new Array&amp;lt;string&amp;gt;());
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;concat&lt;/h3&gt;
&lt;p&gt;打平数组参数的行为可以重写，方法是在参数数组上指定一个特殊的符号：&lt;code&gt;Symbol.isConcatSpreadable&lt;/code&gt;。这个符号能够阻止&lt;code&gt;concat()&lt;/code&gt;打平参数数组。相反，把这个值设置为true可以强制打平类数组对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let colors = [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;];
    let newColors = [&quot;black&quot;, &quot;brown&quot;];
    let moreNewColors = {
      [Symbol.isConcatSpreadable]: true,
      length: 2,
      0: &quot;pink&quot;,
      1: &quot;cyan&quot;
    };
    newColors[Symbol.isConcatSpreadable] = false;
    // 强制不打平数组
    let colors2 = colors.concat(&quot;yellow&quot;, newColors);
    // 强制打平类数组对象
    let colors3 = colors.concat(moreNewColors);
    console.log(colors);    // [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;]
    console.log(colors2);   // [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;, &quot;yellow&quot;, [&quot;black&quot;, &quot;brown&quot;]]
    console.log(colors3);   // [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;, &quot;pink&quot;, &quot;cyan&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;splice&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;删除。需要给splice()传2个参数：要删除的第一个元素的位置和要删除的元素数量。可以从数组中删除任意多个元素，比如splice(0,2)会删除前两个元素。&lt;/li&gt;
&lt;li&gt;插入。需要给splice()传3个参数：开始位置、0（要删除的元素数量）和要插入的元素，可以在数组中指定的位置插入元素。第三个参数之后还可以传第四个、第五个参数，乃至任意多个要插入的元素。比如，splice(2,0, &quot;red&quot;, &quot;green&quot;)会从数组位置2开始插入字符串&quot;red&quot;和&quot;green&quot;。&lt;/li&gt;
&lt;li&gt;替换。splice()在删除元素的同时可以在指定位置插入新元素，同样要传入3个参数：开始位置、要删除元素的数量和要插入的任意多个元素。要插入的元素数量不一定跟删除的元素数量一致。比如，splice(2, 1, &quot;red&quot;,&quot;green&quot;)会在位置2删除一个元素，然后从该位置开始向数组中插入&quot;red&quot;和&quot;green&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;splice()方法始终返回这样一个数组，它包含从数组中被删除的元素（如果没有删除元素，则返回空数组）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let colors = [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;];
    let removed = colors.splice(0,1);   // 删除第一项
    alert(colors);                          // green, blue
    alert(removed);                         // red，只有一个元素的数组
    removed = colors.splice(1, 0, &quot;yellow&quot;, &quot;orange&quot;);    // 在位置1 插入两个元素
    alert(colors);                                                 // green, yellow, orange, blue
    alert(removed);                                                // 空数组
    removed = colors.splice(1, 1, &quot;red&quot;, &quot;purple&quot;);   // 插入两个值，删除一个元素
    alert(colors);                                            // green,red,purple,orange,blue
    alert(removed);                                           // yellow，只有一个元素的数组
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;reduce()方法从数组第一项开始遍历到最后一项。而reduceRight()从最后一项开始遍历至第一项。
这两个方法都接收两个参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对每一项都会运行的归并函数&lt;/li&gt;
&lt;li&gt;可选的以之为归并起点的初始值。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;传给reduce()和reduceRight()的函数接收4个参数：上一个归并值、当前项、当前项的索引和数组本身。这个函数返回的任何值都会作为下一次调用同一个函数的第一个参数。如果没有给这两个方法传入可选的第二个参数（作为归并起点值），则第一次迭代将从数组的第二项开始，因此传给归并函数的第一个参数是数组的第一项，第二个参数是数组的第二项。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let values = [1, 2, 3, 4, 5];
    let sum = values.reduce((prev, cur, index, array) =&amp;gt; prev + cur);
    alert(sum);   // 15
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Map&lt;/h2&gt;
&lt;p&gt;set()方法返回映射实例，因此可以把多个操作连缀起来&lt;/p&gt;
&lt;h3&gt;Object与Map对比&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;内存占用，Map更加节约空间&lt;/li&gt;
&lt;li&gt;插入性能，差不多，Map略微快一点&lt;/li&gt;
&lt;li&gt;查找性能，几乎一致。如果键值对数量少或者使用连续整数，Object快一点。&lt;/li&gt;
&lt;li&gt;删除性能，完爆Object&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;WeakMap&lt;/h3&gt;
&lt;p&gt;弱映射中的键只能是Object或者继承自Object的类型，尝试使用非对象设置键会抛出TypeError。值的类型没有限制。&lt;/p&gt;
&lt;p&gt;WeakMap中“weak”表示弱映射的键不属于正式的引用，不会阻止垃圾回收。
但要注意的是，值并不weak，只要键存在，键/值对就会存在于映射中，并被当作对值的引用，因此就不会被当作垃圾回收。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const wm = new WeakMap();
    const container = {
      key: {}
    };
    wm.set(container.key, &quot;val&quot;);
    function removeReference() {
      container.key = null;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WeakMap实例之所以限制只能用对象作为键，是为了保证只有通过键对象的引用才能取得值。如果允许原始值，那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。&lt;/p&gt;
&lt;p&gt;一些应用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;私有变量。私有变量会存储在弱映射中，以对象实例为键，以私有成员的字典为值。&lt;/li&gt;
&lt;li&gt;DOM节点元数据。因为WeakMap实例不会妨碍垃圾回收，所以非常适合保存关联元数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Set&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;add()和delete()操作是幂等的。delete()返回一个布尔值，表示集合中是否存在要删除的值&lt;/li&gt;
&lt;li&gt;Set会维护值插入时的顺序，因此支持按顺序迭代&lt;/li&gt;
&lt;li&gt;因为values()是默认迭代器，所以可以直接对集合实例使用扩展操作，把集合转换为数组&lt;/li&gt;
&lt;li&gt;集合的entries()方法返回一个迭代器，可以按照插入顺序产生包含两个元素的数组，这两个元素是集合中每个值的重复出现&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;WeakSet&lt;/h3&gt;
&lt;p&gt;类似WeakMap，存的是弱值&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第5章-基本引用类型</title><link>https://www.ender-wiggin.com/posts/note-js-5/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-5/</guid><description>整理一下之前的笔记，当做复习。</description><pubDate>Fri, 01 Mar 2024 22:02:00 GMT</pubDate><content:encoded>&lt;h2&gt;RegExp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    let expression = /pattern/flags;
    // 也可以
    let pattern = new RegExp(&quot;[bc]at&quot;, &quot;i&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个正则表达式可以带零个或多个flags（标记），用于控制正则表达式的行为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;g：全局模式，表示查找字符串的全部内容，而不是找到第一个匹配的内容就结束&lt;/li&gt;
&lt;li&gt;i：不区分大小写，表示在查找匹配时忽略pattern和字符串的大小写&lt;/li&gt;
&lt;li&gt;m：多行模式，表示查找到一行文本末尾时会继续查找&lt;/li&gt;
&lt;li&gt;y：粘附模式，表示只查找从lastIndex开始及之后的字符串&lt;/li&gt;
&lt;li&gt;u: Unicode模式，启用Unicode匹配&lt;/li&gt;
&lt;li&gt;s:dotAll模式，表示元字符．匹配任何字符（包括&lt;code&gt;\n&lt;/code&gt;或&lt;code&gt;\r&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实例方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;exec()&lt;/code&gt;，返回的是包含匹配项的array，以及额外属性&lt;code&gt;index&lt;/code&gt;和&lt;code&gt;input&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test()&lt;/code&gt;，返回布尔值&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;原始值包装类型&lt;/h2&gt;
&lt;p&gt;每当用到某个原始值的方法或属性时，后台都会创建一个相应原始包装类型的对象，从而暴露出操作原始值的各种方法。大致流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建一个String类型的实例&lt;/li&gt;
&lt;li&gt;调用实例上的特定方法&lt;/li&gt;
&lt;li&gt;销毁实例（执行完当前行代码后立刻销毁）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;另外，Object构造函数作为一个工厂方法，能够根据传入值的类型返回相应原始值包装类型的实例。比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let obj = new Object(&quot;some text&quot;);
    console.log(obj instanceof String);   // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用new调用原始值包装类型的构造函数，与调用同名的转型函数并不一样。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let value = &quot;25&quot;;
    let number = Number(value);     // 转型函数
    console.log(typeof number);     // &quot;number&quot;
    let obj = new Number(value);    // 构造函数
    console.log(typeof obj);         // &quot;object&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想得到数值最适当的形式，那么可以使用toPrecision()。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let num = 99;
    console.log(num.toPrecision(1));//&quot;1e+2&quot;
    console.log(num.toPrecision(2));//&quot;99&quot;
    console.log(num.toPrecision(3));//&quot;99.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Number&lt;/h4&gt;
&lt;p&gt;Number.isInteger()方法，用于辨别一个数值是否保存为整数。
Number.isSafeInteger()可以判断数字是否在 IEEE754 安全范围之内。&lt;/p&gt;
&lt;h4&gt;String&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    let message = &quot;abcde&quot;;
    console.log(message.charAt(2)); // &quot;c&quot;

    // Unicode &quot;Latin small letter C&quot;的编码是U+0063
    console.log(message.charCodeAt(2));   // 99
    // 十进制99 等于十六进制63
    console.log(99 === 0x63);               // true
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fromCharCode()&lt;/code&gt;方法用于根据给定的UTF-16码元创建字符串中的字符。&lt;/li&gt;
&lt;li&gt;改成&lt;code&gt;CodePoint&lt;/code&gt;码点可以适配多字符情况。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;normalize()&lt;/code&gt;加上对应方法可以比对字符是否实质相同&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;字符串操作方法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cancat&lt;/code&gt; 或者 &lt;code&gt;+&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;slice/substring(start, end)&lt;/code&gt; 不包括end
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;slice&lt;/code&gt; 负数将所有负值参数都当成字符串长度加上负参数值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;substring&lt;/code&gt; 所有负数均为0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;substr(start, len)&lt;/code&gt;(第二个参数负数时视为0)&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;字符串位置方法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;indexOf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lastIndexOf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;都包含可选的第二个参数，表示开始搜索的位置&lt;/p&gt;
&lt;h5&gt;字符串包含方法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;startsWith&lt;/code&gt; 包含可选的第二个参数，表示开始搜索的位置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;endsWith&lt;/code&gt; 包含可选的第二个参数，表示应该当作字符串末尾的位置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;includes&lt;/code&gt; 包含可选的第二个参数，表示开始搜索的位置&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;一些其它方法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;trim()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trimLeft()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trimRight()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;返回的均为副本&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;repeat(times)&lt;/code&gt; 重复&lt;/li&gt;
&lt;li&gt;&lt;code&gt;padStart()&lt;/code&gt;和&lt;code&gt;padEnd()&lt;/code&gt;方法会复制字符串，如果小于指定长度，则在相应一边填充字符，直至满足长度条件。这两个方法的第一个参数是长度，第二个参数是可选的填充字符串，默认为空格。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;match(pattern)&lt;/code&gt; 类似RegExp的exec&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search(pattern)&lt;/code&gt; 返回找到的第一个位置或者-1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replace(str, repaceStrOrFunc)&lt;/code&gt; 第一个参数可以是正则，第二个参数是函数时可以执行额外操作&lt;/li&gt;
&lt;li&gt;&lt;code&gt;split&lt;/code&gt;，可选第二个参数是数组大小，确保返回的数组不会超过指定大小&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;字符串迭代与解构&lt;/h5&gt;
&lt;p&gt;字符串的原型上暴露了一个&lt;code&gt;@@iterator&lt;/code&gt;方法，表示可以迭代字符串的每个字符。可以像下面这样手动使用迭代器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let message = &quot;abc&quot;;
    let stringIterator = message[Symbol.iterator]();
    console.log(stringIterator.next());   // {value: &quot;a&quot;, done: false}
    console.log(stringIterator.next());   // {value: &quot;b&quot;, done: false}
    console.log(stringIterator.next());   // {value: &quot;c&quot;, done: false}
    console.log(stringIterator.next());   // {value: undefined, done: true}

	// 或者 for...of
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;单例内置对象&lt;/h2&gt;
&lt;h3&gt;Global&lt;/h3&gt;
&lt;p&gt;Global对象是ECMAScript中最特别的对象，因为代码不会显式地访问它。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;encodeURI()和encodeURIComponent()方法用于编码统一资源标识符（URI）&lt;/li&gt;
&lt;li&gt;eval()调用时，会将参数解释为实际的ECMAScript语句，然后将其插入到该位置。通过eval()定义的任何变量和函数都不会被提升，这是因为在解析代码的时候，它们是被包含在一个字符串中的。它们只是在eval()执行的时候才会被创建。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;浏览器将window对象实现为Global对象的代理。因此，所有全局作用域中声明的变量和函数都变成了window的属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let global = function() {
      return this;
    }();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码创建一个立即调用的函数表达式，返回了this的值。当一个函数在没有明确（通过成为某个对象的方法，或者通过call()/apply()）指定this值的情况下执行时，this值等于Global对象。因此，调用一个简单返回this的函数是在任何执行上下文中获取Global对象的通用方式。&lt;/p&gt;
&lt;h3&gt;Math&lt;/h3&gt;
&lt;p&gt;注意 Math对象上提供的计算要比直接在JavaScript实现的快得多，因为Math对象上的计算使用了JavaScript引擎中更高效的实现和处理器指令。但使用Math计算的问题是精度会因浏览器、操作系统、指令集和硬件而异。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Math.fround()&lt;/code&gt;方法返回数值最接近的单精度（32位）浮点值表示。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.random()&lt;/code&gt;方法返回一个0~1范围内的随机数，其中包含0但不包含1&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    function selectFrom(lowerValue, upperValue) {
      let choices = upperValue - lowerValue + 1;
      return Math.floor(Math.random() ＊ choices + lowerValue);
    }
    let num = selectFrom(2,10);
    console.log(num);   // 2~10 范围内的值，其中包含2 和10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;window.crypto.getRandomValues()&lt;/code&gt; 具有更高的加密随机性&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第4章-变量、作用域与内存</title><link>https://www.ender-wiggin.com/posts/note-js-4/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-4/</guid><description>整理一下之前的笔记，当做复习。</description><pubDate>Fri, 01 Mar 2024 22:01:00 GMT</pubDate><content:encoded>&lt;h2&gt;原始值与引用值&lt;/h2&gt;
&lt;ul&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;
&lt;p&gt;原始值不能有属性，尽管尝试给原始值添加属性不会报错。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let name = &quot;Nicholas&quot;;
    name.age = 27;
    console.log(name.age);   // undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原始类型的初始化可以只使用原始字面量形式。如果使用的是new关键字，则JavaScript会创建一个Object类型的实例，但其行为类似原始值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let name1 = &quot;Nicholas&quot;;
    let name2 = new String(&quot;Matt&quot;);
    name1.age = 27;
    name2.age = 26;
    console.log(name1.age);     // undefined
    console.log(name2.age);     // 26
    console.log(typeof name1); // string
    console.log(typeof name2); // object
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有函数参数都是按值传递，也就是复制一份值。如果值是引用则复制一份引用值，两个变量指向同一个引用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function setName(obj) {
      obj.name = &quot;Nicholas&quot;;
      obj=newObject();
      obj.name=&quot;Greg&quot;;
    }
    let person = new Object();
    setName(person);
    console.log(person.name);   // &quot;Nicholas&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;typeof&lt;/code&gt; 判断 &lt;code&gt;null&lt;/code&gt; 会判断为 &lt;code&gt;object&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let s = &quot;Nicholas&quot;;
    let b = true;
    let i = 22;
    let u;
    let n = null;
    let o = new Object();
    console.log(typeof s); // string
    console.log(typeof i); // number
    console.log(typeof b); // boolean
    console.log(typeof u); // undefined
    console.log(typeof n); // object
    console.log(typeof o); // object
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;执行上下文与作用域&lt;/h2&gt;
&lt;p&gt;每个函数调用都有自己的上下文。当代码执行流进入函数时，函数的上下文被推到一个上下文栈上。在函数执行完之后，上下文栈会弹出该函数上下文，将控制权返还给之前的执行上下文。ECMAScript程序的执行流就是通过这个上下文栈进行控制的。&lt;/p&gt;
&lt;p&gt;代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。搜索过程始终从作用域链的最前端开始，然后逐级往后，直到找到标识符。&lt;/p&gt;
&lt;p&gt;执行上下文主要有全局上下文和函数上下文两种（eval()调用内部存在第三种上下文），但有其他方式来增强作用域链：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;try/catch语句的catch块（包含要抛出的错误对象的声明的变量对象）&lt;/li&gt;
&lt;li&gt;with语句（向作用域链前端添加指定的对象）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;变量声明&lt;/h3&gt;
&lt;p&gt;在使用var声明变量时，变量会被自动添加到最接近的上下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在函数中，最接近的上下文就是函数的局部上下文&lt;/li&gt;
&lt;li&gt;在with语句中，最接近的上下文也是函数上下文&lt;/li&gt;
&lt;li&gt;如果变量未经声明就被初始化了，那么它就会自动被添加到全局上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;let&lt;/code&gt; 是块级作用域，由最近的一对 &lt;code&gt;{}&lt;/code&gt; 界定。同一作用域重复的块会报错。&lt;/p&gt;
&lt;p&gt;对于&lt;code&gt;const&lt;/code&gt;，如果想让整个对象都不能修改，可以使用&lt;code&gt;Object.freeze()&lt;/code&gt;，这样再给属性赋值时虽然不会报错，但会静默失败。&lt;/p&gt;
&lt;h2&gt;垃圾回收&lt;/h2&gt;
&lt;h3&gt;标记清理&lt;/h3&gt;
&lt;p&gt;mark-and-sweep：当变量进入上下文，比如在函数内部声明一个变量时，这个变量会被加上存在于上下文中的标记。&lt;/p&gt;
&lt;p&gt;给变量加标记的方式有很多种。比如，当变量进入上下文时，反转某一位；或者可以维护“在上下文中”和“不在上下文中”两个变量列表，可以把变量从一个列表转移到另一个列表。&lt;/p&gt;
&lt;p&gt;垃圾回收程序运行的时候，会标记内存中存储的所有变量。然后，它会将所有在上下文中的变量，以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了，原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理，销毁带标记的所有值并收回它们的内存。&lt;/p&gt;
&lt;h3&gt;引用计数&lt;/h3&gt;
&lt;p&gt;其思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时，这个值的引用数为1。如果同一个值又被赋给另一个变量，那么引用数加1。类似地，如果保存对该值引用的变量被其他值给覆盖了，那么引用数减1。当一个值的引用数为0时，就说明没办法再访问到这个值了，因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为0的值的内存。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function problem() {
      let objectA = new Object();
      let objectB = new Object();
      objectA.someOtherObject = objectB;
      objectB.anotherObject = objectA;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;循环引用问题会导致内存无法释放。&lt;/p&gt;
&lt;h3&gt;性能&lt;/h3&gt;
&lt;p&gt;现代垃圾回收程序会基于对JavaScript运行时环境的探测来决定何时运行。探测机制因引擎而异，但基本上都是根据已分配对象的大小和数量来判断的。比如，根据V8团队2016年的一篇博文的说法：“在一次完整的垃圾回收之后，V8的堆增长策略会根据活跃对象的数量外加一些余量来确定何时再次垃圾回收。”&lt;/p&gt;
&lt;p&gt;将内存占用量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要，那么把它设置为null，从而释放其引用。这也可以叫作解除引用。这个建议最适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用。&lt;/p&gt;
&lt;h4&gt;隐藏类和删除操作&lt;/h4&gt;
&lt;p&gt;运行期间，V8会将创建的对象与隐藏类关联起来，以跟踪它们的属性特征。能够共享相同隐藏类的对象性能会更好，V8会针对这种情况进行优化，但不一定总能够做到。比如下面的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Article() {
      this.title = &apos;Inauguration Ceremony Features Kazoo Band&apos;;
    }
    let a1 = new Article();
    let a2 = new Article();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;V8会在后台配置，让这两个类实例&lt;strong&gt;共享相同的隐藏类&lt;/strong&gt;，因为这两个实例共享同一个构造函数和原型。但是如果对其中一个实例进行了修改，那么他们就会对应不同的隐藏类。&lt;/p&gt;
&lt;p&gt;解决方案就是避免JavaScript的“先创建再补充”（ready-fire-aim）式的动态属性赋值，并在构造函数中一次性声明所有属性:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Article(opt_author) {
      this.title = &apos;Inauguration Ceremony Features Kazoo Band&apos;;
      this.author = opt_author;
    }
    let a1 = new Article();
    let a2 = new Article(&apos;Jake&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;删除和增加类似，因此最佳实践是把不想要的属性设置为null。这样可以保持隐藏类不变和继续共享，同时也能达到删除引用值供垃圾回收程序回收的效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Article() {
      this.title = &apos;Inauguration Ceremony Features Kazoo Band&apos;;
      this.author = &apos;Jake&apos;;
    }
    let a1 = new Article();
    let a2 = new Article();
    a1.author = null;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;内存泄漏&lt;/h3&gt;
&lt;p&gt;调用outer()会导致分配给name的内存被泄漏。以上代码执行后创建了一个内部闭包，只要返回的函数存在就不能清理name，因为闭包一直在引用着它。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let outer = function() {
      let name = &apos;Jake&apos;;
      return function() {
        return name;
      };
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;静态分配与对象池&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    function addVector(a, b) {
      let resultant = new Vector();
      resultant.x = a.x + b.x;
      resultant.y = a.y + b.y;
      return resultant;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用这个函数时，会在堆上创建一个新对象，然后修改它，最后再把它返回给调用者。如果这个矢量对象的生命周期很短，那么它会很快失去所有对它的引用，成为可以被回收的值。假如这个矢量加法函数频繁被调用，那么垃圾回收调度程序会发现这里对象更替的速度很快，从而会更频繁地安排垃圾回收。&lt;/p&gt;
&lt;p&gt;该问题的解决方案是不要动态创建矢量对象，比如可以修改上面的函数，让它使用一个已有的矢量对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function addVector(a, b, resultant) {
      resultant.x = a.x + b.x;
      resultant.y = a.y + b.y;
      return resultant;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是这需要在其他地方实例化矢量参数resultant，但这个函数的行为没有变。那么在哪里创建矢量可以不让垃圾回收调度程序盯上呢？&lt;/p&gt;
&lt;p&gt;一个策略是使用对象池（注意，可能是过早优化）。在初始化的某一时刻，可以创建一个对象池，用来管理一组可回收的对象。应用程序可以向这个对象池请求一个对象、设置其属性、使用它，然后在操作完成后再把它还给对象池。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // vectorPool是已有的对象池
    let v1 = vectorPool.allocate();
    let v2 = vectorPool.allocate();
    let v3 = vectorPool.allocate();
    v1.x = 10;
    v1.y = 5;
    v2.x = -3;
    v2.y = -6;
    addVector(v1, v2, v3);
    console.log([v3.x, v3.y]); // [7, -1]
    vectorPool.free(v1);
    vectorPool.free(v2);
    vectorPool.free(v3);
    // 如果对象有属性引用了其他对象
    // 则这里也需要把这些属性设置为null
    v1 = null;
    v2 = null;
    v3 = null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果对象池只按需分配矢量（在对象不存在时创建新的，在对象存在时则复用存在的），那么这个实现本质上是一种贪婪算法，有单调增长但为静态的内存。这个对象池必须使用某种结构维护所有对象，数组是比较好的选择。&lt;/p&gt;
&lt;p&gt;不过，使用数组来实现，必须留意不要招致额外的垃圾回收。比如下面这个例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let vectorList = new Array(100);
    let vector = new Vector();
    vectorList.push(vector);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数组大小可变，会先移除一个100的数组，在新建一个200的数组，很容易导致GC。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第19章-表单</title><link>https://www.ender-wiggin.com/posts/note-js-19/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-19/</guid><description> </description><pubDate>Wed, 28 Feb 2024 22:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;表单基础&lt;/h2&gt;
&lt;p&gt;Web表单在HTML中以&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;元素表示，在JavaScript中则以HTMLFormElement类型表示。HTMLFormElement类型继承自HTMLElement类型，因此拥有与其他HTML元素一样的默认属性。不过，HTMLFormElement也有自己的属性和方法。&lt;/p&gt;
&lt;h3&gt;提交表单&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;! -- 通用提交按钮 --&amp;gt;
    &amp;lt;input type=&quot;submit&quot; value=&quot;Submit Form&quot;&amp;gt;
    &amp;lt;! -- 自定义提交按钮 --&amp;gt;
    &amp;lt;button type=&quot;submit&quot;&amp;gt;Submit Form&amp;lt;/button&amp;gt;
    &amp;lt;! -- 图片按钮 --&amp;gt;
    &amp;lt;input type=&quot;image&quot; src=&quot;graphic.gif&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果表单中有上述任何一个按钮，且焦点在表单中某个控件上，则按回车键也可以提交表单。&lt;/p&gt;
&lt;p&gt;以这种方式提交表单会在向服务器发送请求之前触发submit事件。这样就提供了一个&lt;strong&gt;验证表单数据&lt;/strong&gt;的机会，可以根据验证结果决定是否真的要提交。阻止这个事件的默认行为可以取消提交表单。例如，下面的代码会阻止表单提交：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let form = document.getElementById(&quot;myForm&quot;);
    form.addEventListener(&quot;submit&quot;, (event) =&amp;gt; {
      // 阻止表单提交
      event.preventDefault();
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以通过js直接提交：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let form = document.getElementById(&quot;myForm&quot;);
    //提交表单
    form.submit();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了避免多次重复提交，一般都处理方案是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在表单提交后禁用提交按钮&lt;/li&gt;
&lt;li&gt;通过onsubmit事件处理程序取消之后的表单提交&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    // 避免多次提交表单的代码
    let form = document.getElementById(&quot;myForm&quot;);
    form.addEventListener(&quot;submit&quot;, (event) =&amp;gt; {
      let target = event.target;
      // 取得提交按钮
      let btn = target.elements[&quot;submit-btn&quot;];
      // 禁用提交按钮
      btn.disabled = true;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，这里的事件监听必须是&lt;code&gt;submit&lt;/code&gt;，因为不同浏览器的事件触发时机不同，有的浏览器click先于submit触发，如果使用了&lt;code&gt;onclick&lt;/code&gt;就会导致提交前被禁用。&lt;/p&gt;
&lt;h3&gt;重置表单&lt;/h3&gt;
&lt;p&gt;用户单击重置按钮（&lt;code&gt;type === &apos;reset&apos;&lt;/code&gt;）可以重置表单。一般不使用，影响用户体验。&lt;/p&gt;
&lt;h3&gt;表单字段&lt;/h3&gt;
&lt;p&gt;表单元素可以像页面中的其他元素一样使用原生DOM方法来访问。此外，所有表单元素都是表单elements属性（元素集合）中包含的一个值。这个elements集合是一个有序列表，包含对表单中所有字段的引用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let form = document.getElementById(&quot;form1&quot;);
    // 取得表单中的第一个字段
    let field1 = form.elements[0];
    // 取得表单中名为&quot;textbox1&quot;的字段
    let field2 = form.elements[&quot;textbox1&quot;];
    // 取得字段的数量
    let fieldCount = form.elements.length;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个HTML中的表单有3个单选按钮的&lt;code&gt;name&lt;/code&gt;是&lt;code&gt;&quot;color&quot;&lt;/code&gt;，这个名字把它们联系在了一起。在访问&lt;code&gt;elements[&quot;color&quot;]&lt;/code&gt;时，返回的&lt;code&gt;NodeList&lt;/code&gt;就包含这3个元素。而在访问&lt;code&gt;elements[0]&lt;/code&gt;时，只会返回第一个元素。&lt;/p&gt;
&lt;h3&gt;公共属性与方法&lt;/h3&gt;
&lt;p&gt;表单存在公共属性，除了form属性（只读），其余属性均可以修改：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let form = document.getElementById(&quot;myForm&quot;);
    let field = form.elements[0];
    // 修改字段的值
    field.value = &quot;Another value&quot;;
    // 检查字段所属的表单
    console.log(field.form === form);    // true
    // 给字段设置焦点
    field.focus();
    // 禁用字段
    field.disabled = true;
    // 改变字段的类型（不推荐，但对&amp;lt;input&amp;gt;来说是可能的）
    field.type = &quot;checkbox&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个表单字段都有两个公共方法：&lt;code&gt;focus()&lt;/code&gt;和&lt;code&gt;blur()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;focus()方法把浏览器焦点设置到表单字段，这意味着该字段会变成活动字段并可以响应键盘事件。比如，在页面加载后把焦点定位到表单中第一个字段就是很常见的做法。实现方法是监听load事件，然后在第一个字段上调用focus()：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    window.addEventListener(&quot;load&quot;, (event) =&amp;gt; {
      document.forms[0].elements[0].focus();
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但现在实际上可以直接自动对焦： &lt;code&gt;&amp;lt;input type=&quot;text&quot; autofocus&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;focus()&lt;/code&gt;的反向操作是&lt;code&gt;blur()&lt;/code&gt;，其用于从元素上移除焦点。调用blur()时，焦点不会转移到任何特定元素，仅仅只是从调用这个方法的元素上移除了。&lt;/p&gt;
&lt;p&gt;公共事件有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;blur&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;change&lt;/code&gt; (在&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;和&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;元素的value发生变化&lt;strong&gt;且失去焦点&lt;/strong&gt;时触发，或者在&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;元素中选中项发生变化时触发。)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;focus&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;文本框&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;元素显示为文本框，省略type属性会以&quot;text&quot;作为默认值。然后可以通过size属性指定文本框的宽度，这个宽度是以字符数来计量的。而value属性用于指定文本框的初始值，maxLength属性用于指定文本框允许的最多字符数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;元素总是会创建多行文本框。可以使用rows属性指定这个文本框的高度，以字符数计量；以cols属性指定以字符数计量的文本框宽度，类似于&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;元素的size属性。与&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;不同的是，&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;的初始值必须包含在&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;和&lt;code&gt;&amp;lt;/textarea&amp;gt;&lt;/code&gt;之间。&lt;/p&gt;
&lt;h4&gt;选择文本&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 获得焦点后自动选中所有文本
    textbox.addEventListener(&quot;focus&quot;, (event) =&amp;gt; {
      event.target.select();
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而当选中后，还会触发&lt;code&gt;select&lt;/code&gt; 事件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 如下参数可以获取到选中的起点和终点
    function getSelectedText(textbox){
      return textbox.value.substring(textbox.selectionStart,
                                      textbox.selectionEnd);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;输入过滤&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 屏蔽非数字的输入，charCode &amp;gt; 9 是为了避免屏蔽低版本浏览器对于退格删除这些键的keypress触发（高版本都不会）
    textbox.addEventListener(&quot;keypress&quot;, (event) =&amp;gt; {
      if(!/\d/.test(String.fromCharCode(event.charCode))&amp;amp;&amp;amp;
          event.charCode&amp;gt;9&amp;amp;&amp;amp;
          !event.ctrlKey){
        event.preventDefault();
      }
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;剪贴版事件&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;beforecopy：复制操作发生前触发&lt;/li&gt;
&lt;li&gt;copy：复制操作发生时触发&lt;/li&gt;
&lt;li&gt;beforecut：剪切操作发生前触发&lt;/li&gt;
&lt;li&gt;cut：剪切操作发生时触发&lt;/li&gt;
&lt;li&gt;beforepaste：粘贴操作发生前触发&lt;/li&gt;
&lt;li&gt;paste：粘贴操作发生时触发&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般浏览器只在剪贴板事件期间暴露clipboardData对象，包括3个方法：getData()、setData()和clearData()。数据除了IE均为MIME类型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 只允许数字粘贴的实现
    textbox.addEventListener(&quot;paste&quot;, (event) =&amp;gt; {
      let text = getClipboardText(event);
      if (! /^\d＊$/.test(text)){
        event.preventDefault();
      }
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;自动切换&lt;/h4&gt;
&lt;p&gt;多个输入框时，当一个长度到达最大值，自动切换到下一个&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;script&amp;gt;
      function tabForward(event){
        let target = event.target;
        if (target.value.length == target.maxLength){
          let form = target.form; // 指向form的引用
          for (let i = 0, len = form.elements.length; i &amp;lt; len; i++) {
            if (form.elements[i] == target) {
              if (form.elements[i+1]) {
                form.elements[i+1].focus();
              }
              return;
            }
          }
        }
      }
      let inputIds = [&quot;txtTel1&quot;, &quot;txtTel2&quot;, &quot;txtTel3&quot;];
      for (let id of inputIds) {
        let textbox = document.getElementById(id);
        textbox.addEventListener(&quot;keyup&quot;, tabForward);
      }
      let textbox1 = document.getElementById(&quot;txtTel1&quot;);
      let textbox2 = document.getElementById(&quot;txtTel2&quot;);
      let textbox3 = document.getElementById(&quot;txtTel3&quot;);
    &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HTML5约束验证API&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;必填：&lt;code&gt;&amp;lt;input type=&quot;text&quot; name=&quot;username&quot; required&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type=&apos;email&apos;或&apos;url&apos;&lt;/code&gt;的内置验证，但局限性较大&lt;/li&gt;
&lt;li&gt;输入模式：&lt;code&gt;&amp;lt;input type=&quot;text&quot; pattern=&quot;\d+&quot; name=&quot;count&quot;&amp;gt;&lt;/code&gt; 自带输入检测&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkValidity()&lt;/code&gt;检测表单中任意给定字段或整个表单是否有效&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validity&lt;/code&gt; 返回一系列对象，说明哪些值有效或无效&lt;/li&gt;
&lt;li&gt;&lt;code&gt; novalidate&lt;/code&gt;属性可以禁止对表单进行任何验证&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;选择框&lt;/h2&gt;
&lt;p&gt;选择框是使用&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;和&lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;元素创建的。为方便交互，HTMLSelectElement类型在所有表单字段的公共能力之外又提供了以下属性和方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add(newOption, relOption)：在relOption之前向控件中添加新的&lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;multiple：布尔值，表示是否允许多选，等价于HTML的multiple属性&lt;/li&gt;
&lt;li&gt;options：控件中所有&lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;元素的HTMLCollection&lt;/li&gt;
&lt;li&gt;remove(index)：移除给定位置的选项&lt;/li&gt;
&lt;li&gt;selectedIndex：选中项基于0的索引值，如果没有选中项则为-1。对于允许多选的列表，始终是第一个选项的索引&lt;/li&gt;
&lt;li&gt;size：选择框中可见的行数，等价于HTML的size属性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于只允许选择一项的选择框，获取选项最简单的方式是使用选择框的selectedIndex属性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let selectedOption = selectbox.options[selectbox.selectedIndex];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;多选如果使用上述属性只能获取第一个选项，因此需要遍历&lt;code&gt;selected&lt;/code&gt;属性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function getSelectedOptions(selectbox){
      let result = new Array();
      for (let option of selectbox.options) {
        if (option.selected) {
          result.push(option);
        }
      }
      return result;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;表单序列化&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    function serialize(form) {
      let parts = [];
      let optValue;
      for (let field of form.elements) {
        switch(field.type) {
          case &quot;select-one&quot;:
          case &quot;select-multiple&quot;:
            if (field.name.length) {
            for (let option of field.options) {
                if (option.selected) {
                  if (option.hasAttribute){
                    optValue = (option.hasAttribute(&quot;value&quot;) ?
                                  option.value : option.text);
                  } else {
                    optValue = (option.attributes[&quot;value&quot;].specified ?
                                  option.value : option.text);
                  }
                  parts.push(encodeURIComponent(field.name)} + &quot;=&quot; +
                              encodeURIComponent(optValue));
                }
              }
            }
            break;
          case undefined:      // 字段集
          case &quot;file&quot;:          // 文件输入
          case &quot;submit&quot;:        // 提交按钮
          case &quot;reset&quot;:         // 重置按钮
          case &quot;button&quot;:        // 自定义按钮
            break;
          case &quot;radio&quot;:         // 单选按钮
          case &quot;checkbox&quot;:     // 复选框
            if (! field.checked) {
              break;
            }
          default:
            // 不包含没有名字的表单字段
            if (field.name.length) {
              parts.push(&apos;${encodeURIComponent(field.name)}=&apos; +
                          &apos;${encodeURIComponent(field.value)}&apos;);
            }
      }
      return parts.join(&quot;&amp;amp;&quot;);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;富文本&lt;/h2&gt;
&lt;p&gt;为了可以编辑，必须将文档的&lt;strong&gt;designMode&lt;/strong&gt;属性设置为&quot;on&quot;。不过，只有在文档完全加载之后才可以设置。在这个包含页面内，需要使用onload事件处理程序在适当时机设置designMode：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;iframe name=&quot;richedit&quot; style=&quot;height: 100px; width: 100px&quot;&amp;gt;&amp;lt;/iframe&amp;gt;
    &amp;lt;script&amp;gt;
      window.addEventListener(&quot;load&quot;, () =&amp;gt; {
        frames[&quot;richedit&quot;].document.designMode = &quot;on&quot;;
      });
    &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与富文本编辑器交互的主要方法是使用&lt;code&gt;document.execCommand()&lt;/code&gt;。这个方法在文档上执行既定的命令，可以实现大多数格式化任务，例如加粗、插入元素。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 在内嵌窗格中切换粗体文本样式
    frames[&quot;richedit&quot;].document.execCommand(&quot;bold&quot;, false, null);
    // 在内嵌窗格中切换斜体文本样式
    frames[&quot;richedit&quot;].document.execCommand(&quot;italic&quot;, false, null);
    // 在内嵌窗格中创建指向www.wrox.com的链接
    frames[&quot;richedit&quot;].document.execCommand(&quot;createlink&quot;, false,
                                              &quot;http://www.wrox.com&quot;);
    // 在内嵌窗格中为内容添加&amp;lt;h1&amp;gt;标签
    frames[&quot;richedit&quot;].document.execCommand(&quot;formatblock&quot;, false, &quot;&amp;lt;h1&amp;gt;&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在内嵌窗格中使用getSelection()方法，可以获得富文本编辑器的选区。这个方法暴露在document和window对象上，返回表示当前选中文本的Selection对象。&lt;/p&gt;
&lt;p&gt;对于这种传统js的形式，如果要在表单中提交富文本，则需要将iframe的innerHTML插入一个隐藏字段中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    form.addEventListener(&quot;submit&quot;, (event) =&amp;gt; {
      let target = event.target;
      target.elements[&quot;comments&quot;].value =
          frames[&quot;richedit&quot;].document.body.innerHTML;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;数据传输&lt;/h2&gt;
&lt;p&gt;在HTML和JavaScript中，表单提交时数据的传输结构主要取决于表单的&lt;code&gt;enctype&lt;/code&gt;属性和提交方法（通常是GET或POST）。&lt;/p&gt;
&lt;h3&gt;1. 提交方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GET&lt;/strong&gt;：通过URL传输数据，数据附加在URL之后，以&lt;code&gt;?&lt;/code&gt;分隔URL和传输数据，多个参数之间以&lt;code&gt;&amp;amp;&lt;/code&gt;分隔。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POST&lt;/strong&gt;：通过HTTP消息的主体传输数据，不在URL中显示。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. &lt;code&gt;enctype&lt;/code&gt;属性&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;enctype&lt;/code&gt;属性定义了表单数据在发送到服务器时浏览器使用的编码类型。它有以下几种类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;application/x-www-form-urlencoded&lt;/strong&gt;（默认值）：在发送前，所有字符都会进行编码（空格转换为&quot;+&quot;加号，特殊符号转换为ASCII HEX值）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;multipart/form-data&lt;/strong&gt;：用于文件上传。不对字符编码。在发送表单数据之前，会将其分为多个部分，每部分包含一个表单控件的数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;text/plain&lt;/strong&gt;：空格转换为&quot;+&quot;加号，但不对特殊字符编码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 数据结构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;application/x-www-form-urlencoded&lt;/strong&gt;：数据以&lt;code&gt;key=value&lt;/code&gt;对形式发送，多个键值对之间以&lt;code&gt;&amp;amp;&lt;/code&gt;分隔。例如：&lt;code&gt;name=John&amp;amp;Doe&amp;amp;age=23&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;multipart/form-data&lt;/strong&gt;：每个表单域（文件或其他数据）作为消息中的一个部分发送。每部分都被一个唯一分隔符分隔，分隔符在整个消息中是唯一的。每个部分都包含了表单域的内容类型和表单域本身的数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--boundary123
Content-Disposition: form-data; name=&quot;fieldName&quot;

fieldValue
--boundary123
Content-Disposition: form-data; name=&quot;file&quot;; filename=&quot;filename.jpg&quot;
Content-Type: image/jpeg

[Binary image data]
--boundary123--
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;text/plain&lt;/strong&gt;：数据以纯文本形式发送，键值对以&lt;code&gt;=&lt;/code&gt;连接，对之间以换行符&lt;code&gt;\n&lt;/code&gt;分隔。这种格式不常用，因为它不支持文件上传，且数据没有经过URL编码，可能会导致服务器解析困难。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. JavaScript中的数据提交&lt;/h3&gt;
&lt;p&gt;在JavaScript中，你可以使用&lt;code&gt;XMLHttpRequest&lt;/code&gt;或&lt;code&gt;Fetch API&lt;/code&gt;来提交表单数据。你可以设置请求的&lt;code&gt;Content-Type&lt;/code&gt;头部来控制数据的格式。例如，使用&lt;code&gt;Fetch API&lt;/code&gt;提交JSON数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(&apos;your-endpoint&apos;, {
  method: &apos;POST&apos;,
  headers: {
    &apos;Content-Type&apos;: &apos;application/json&apos;,
  },
  body: JSON.stringify({
    key: &apos;value&apos;,
    anotherKey: &apos;anotherValue&apos;
  })
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，数据以JSON格式发送，这是一个常见的API交互格式，但并非直接通过HTML表单提交的格式。&lt;/p&gt;
&lt;p&gt;总的来说，HTML和JavaScript中表单提交的数据结构主要由表单的提交方法（GET或POST）和&lt;code&gt;enctype&lt;/code&gt;属性决定，而在JavaScript中，你还可以通过AJAX技术（如&lt;code&gt;XMLHttpRequest&lt;/code&gt;或&lt;code&gt;Fetch API&lt;/code&gt;）以更灵活的方式发送数据。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>书评 | 《斜屋犯罪》</title><link>https://www.ender-wiggin.com/posts/review-xie-wu/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-xie-wu/</guid><description> </description><pubDate>Wed, 28 Feb 2024 22:00:00 GMT</pubDate><content:encoded>&lt;hr /&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;ol&gt;
&lt;li&gt;出场人物过于繁多。上来14个房间加上形形色色的人物，信息量堪比俄国小说开篇的人物介绍，这对于需要准确掌握人物信息（以及额外建筑信息）的推理小说而言极大程度的增加了读者的负担。&lt;/li&gt;
&lt;li&gt;剧情缺少明显冲突。书中大部分篇幅都是大家闲庭信步的聊家常，在最终揭秘前也没有读者能看出最终的冲突与动机，剧情发展有些寡淡。此外，配合岛田庄司本就不优秀的文笔，进一步削减了阅读体验。&lt;/li&gt;
&lt;li&gt;最终动机莫名其妙。这我觉得也是这本书最大的缺点，“为了杀人造房子”在新本格建筑推理里面也算默认的事实了，即便读者可以心安理得的接受凶手辛辛苦苦建造房子就为了杀一个人，但这个被杀的人至少也得需要有一些血海深仇，营造出一部与恢弘建筑体量相当的壮阔史诗吧。凶手的动机完全就像一部架空小说，在公布谜底前完全不知道有这一出，即便公布了动机依然让我感觉“就这”，很难令人满意。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;相比之下《钟表馆事件》的建筑和动机是这么的完美，加上恐怖感的叙述风格让读者也不觉得乏味，确实在我心中是建筑推理（尽管我没看过几本，估计未来也不会怎么再看这个类别了）中的佳作了。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>书评 | 《平原上的摩西》</title><link>https://www.ender-wiggin.com/posts/review-pysdmx/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-pysdmx/</guid><description> </description><pubDate>Wed, 28 Feb 2024 22:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;p&gt;看到一篇书评谈到一种套路式写作，感觉还挺有意思：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这部小说符合一个当代的流行创作公式：东北＋下岗潮＋浪漫爱＋凶杀案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一方面联想到了去年热门的《漫长的季节》，二者的叙事背景与意象非常相似，也感慨文学总是能先影视很多。虽然我阅读过的中国文学不多，但是这些在中国文学史上有足够地位的优秀作品总能让我感受到文字的力度，也庆幸文学这个阵地还没有完全沦陷于审查。&lt;/p&gt;
&lt;p&gt;另一方面也在思考，为什么这种创作公式可以流行，并且作为一部15年的小说显然也不能单纯用“疫情衰退论”做一个粗浅的解释。我的看法如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;东北与下岗潮。时代是有阵痛的，并且往往有滞后性：正如民国时期乡土小说盛行，如《死水微澜》；改革开放后伤痕文学大行其道，而二十一世纪一零到二零年代也正好应该是中年人缅怀上世纪重工业的峥嵘岁月之时。人们面对时代的无情车轮总会有一种无力感，而这在运动型治理之风魂牵梦绕的中国似乎更加具有时代的烙印。&lt;/li&gt;
&lt;li&gt;浪漫爱。这种浪漫爱也很有90年代的风采，是哪个《恋曲1990》和《光阴的故事》响彻大江南北的岁月。这种朦朦胧胧的校园恋爱情愫似乎在那一代人心中一直留存，毕竟现在在KTV看他们点歌也是这个风格。一种时代性的束缚似乎像是思想上的万有引力，也正如自诩千禧一代的孩童门如今也是循环播放着周杰伦或者林俊杰等老歌。&lt;/li&gt;
&lt;li&gt;凶杀案。我始终觉得中国人对于刑侦与犯罪作品有一种执着，尤其是在23年冬季连续上映3部犯罪题材作品之后愈发感受到这种很大范围的狂热感。并且，大家对于悬疑犯罪小说的热情比正统推理小说的热情更大，即便是推理范畴也是社会派作为主导，这似乎也是东野圭吾可以在国内一直有极高热度的原因。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;回到这篇中短篇小说本身，多线叙事加上如炭笔速写一般短促的白描手法给小说的背景蒙上了一层如白雪覆盖般的时代滤镜。人物群像的情感不论浓厚与否，在作者笔下都有一种压抑着的克制。开放性结尾似乎两种解答都可以自洽，但我还是倾向于一种乐观的结局，毕竟作品本身的基调也不是那么灰暗。&lt;/p&gt;
&lt;p&gt;另外，当小树轻描淡写地说“我忘了”的时候我感觉悲从中来，或许这也是作品最打动我的地方吧。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第17章-动画与Canvas</title><link>https://www.ender-wiggin.com/posts/note-js-18/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-18/</guid><description> </description><pubDate>Wed, 28 Feb 2024 21:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;requestAnimationFrame&lt;/h2&gt;
&lt;p&gt;早期在JavaScript中创建动画基本上就是使用&lt;code&gt;setInterval()&lt;/code&gt;来控制动画的执行。下面的例子展示了使用&lt;code&gt;setInterval()&lt;/code&gt;的基本模式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    (function() {
      function updateAnimations() {
        doAnimation1();
        doAnimation2();
        // 其他任务
      }
      setInterval(updateAnimations, 100);
    })();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是毫秒延时并不是说何时这些代码会执行，而只是说到时候会把回调加到任务队列。如果添加到队列后，主线程还被其他任务占用，比如正在处理用户操作，那么回调就不会马上执行。&lt;/p&gt;
&lt;p&gt;浏览器自身计时器的精度让这个问题雪上加霜，比如chrome的精度是4毫秒，那么任何0-4直接的值只能是0或者4。&lt;/p&gt;
&lt;p&gt;而&lt;code&gt;requestAnimationFrame()&lt;/code&gt;方法接收一个参数，此参数是一个要在重绘屏幕前调用的函数。这个函数就是修改DOM样式以反映下一次重绘有什么变化的地方。为了实现动画循环，可以把多个&lt;code&gt;requestAnimationFrame()&lt;/code&gt;调用串联起来，就像以前使用&lt;code&gt;setTimeout()&lt;/code&gt;时一样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function updateProgress() {
      var div = document.getElementById(&quot;status&quot;);
      div.style.width = (parseInt(div.style.width, 10) + 5) + &quot;%&quot;;
      if (div.style.left ! = &quot;100%&quot;) {
      requestAnimationFrame(updateProgress);
      }
    }
    requestAnimationFrame(updateProgress);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外，传给&lt;code&gt;requestAnimationFrame()&lt;/code&gt;的函数实际上可以接收一个参数，此参数是一个DOMHighRes-TimeStamp的实例（比如&lt;code&gt;performance.now()&lt;/code&gt;返回的值），表示&lt;strong&gt;下次重绘的时间&lt;/strong&gt;。这一点非常重要：&lt;code&gt;requestAnimationFrame()&lt;/code&gt;实际上把重绘任务安排在了未来一个已知的时间点上，而且通过这个参数告诉了开发者。基于这个参数，就可以更好地决定如何调优动画了。&lt;/p&gt;
&lt;h2&gt;cancelAnimationFrame&lt;/h2&gt;
&lt;p&gt;与setTimeout()类似，requestAnimationFrame()也返回一个请求ID，可以用于通过另一个方法cancelAnimationFrame()来取消重绘任务。下面的例子展示了刚把一个任务加入队列又立即将其取消：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let requestID = window.requestAnimationFrame(() =&amp;gt; {
      console.log(&apos;Repaint! &apos;);
    });
    window.cancelAnimationFrame(requestID);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;通过requestAnimationFrame节流&lt;/h2&gt;
&lt;p&gt;requestAnimationFrame这个名字有时候会让人误解，因为看不出来它跟排期任务有关。支持这个方法的浏览器实际上会暴露出作为钩子的回调队列。所谓钩子（hook），就是浏览器在执行下一次重绘之前的一个点。这个回调队列是一个可修改的函数列表，包含应该在重绘之前调用的函数。每次调用requestAnimationFrame()都会在队列上推入一个回调函数，队列的长度没有限制。&lt;/p&gt;
&lt;p&gt;这个回调队列的行为不一定跟动画有关。不过，通过requestAnimationFrame()递归地向队列中加入回调函数，可以保证每次重绘最多只调用一次回调函数。这是一个非常好的节流工具。在频繁执行影响页面外观的代码时（比如滚动事件监听器），可以利用这个回调队列进行节流。&lt;/p&gt;
&lt;p&gt;先来看一个原生实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function expensiveOperation() {
      console.log(&apos;Invoked at&apos;, Date.now());
    }
    window.addEventListener(&apos;scroll&apos;, () =&amp;gt; {
      expensiveOperation();
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想把事件处理程序的调用限制在每次重绘前发生，那么可以像这样下面把它封装到request-AnimationFrame()调用中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function expensiveOperation() {
      console.log(&apos;Invoked at&apos;, Date.now());
    }
    window.addEventListener(&apos;scroll&apos;, () =&amp;gt; {
      window.requestAnimationFrame(expensiveOperation);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样会把所有回调的执行集中在重绘钩子，但不会过滤掉每次重绘的多余调用。此时，定义一个标志变量，由回调设置其开关状态，就可以将多余的调用屏蔽：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    letenqueued=false;
    function expensiveOperation() {
      console.log(&apos;Invoked at&apos;, Date.now());
      enqueued=false;
    }
    window.addEventListener(&apos;scroll&apos;, () =&amp;gt; {
      if(!enqueued){
        enqueued=true;
        window.requestAnimationFrame(expensiveOperation);
      }
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以通过闭包实现。&lt;/p&gt;
&lt;h2&gt;Canvas&lt;/h2&gt;
&lt;p&gt;至少需要设置宽高，中间的内容是无法显示时的fallback：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;canvas id=&quot;drawing&quot; width=&quot;200&quot; height=&quot;200&quot;&amp;gt;A drawing of something.&amp;lt;/canvas&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要在画布上绘制图形，首先要取得绘图上下文。使用&lt;code&gt;getContext()&lt;/code&gt;方法可以获取对绘图上下文的引用。对于平面图形，需要给这个方法传入参数&quot;2d&quot;，表示要获取2D上下文对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let drawing = document.getElementById(&quot;drawing&quot;);
    // 确保浏览器支持&amp;lt;canvas&amp;gt;
    if (drawing.getContext) {
      let context = drawing.getContext(&quot;2d&quot;);
      // 其他代码
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以使用toDataURL()方法导出&lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;元素上的图像。这个方法接收一个参数：要生成图像的MIME类型（与用来创建图形的上下文无关）。例如，要从画布上导出一张PNG格式的图片，可以这样做：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let drawing = document.getElementById(&quot;drawing&quot;);
    // 确保浏览器支持&amp;lt;canvas&amp;gt;
    if (drawing.getContext) {
      // 取得图像的数据URI
      let imgURI = drawing.toDataURL(&quot;image/png&quot;);
      // 显示图片
      let image = document.createElement(&quot;img&quot;);
      image.src = imgURI;
      document.body.appendChild(image);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;填充和描边&lt;/h3&gt;
&lt;p&gt;填充以指定样式（颜色、渐变或图像）自动填充形状，而描边只为图形边界着色。大多数2D上下文操作有填充和描边的变体，显示效果取决于两个属性：&lt;code&gt;fillStyle&lt;/code&gt;和&lt;code&gt;strokeStyle&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;绘制矩形&lt;/h3&gt;
&lt;p&gt;矩形是唯一一个可以直接在2D绘图上下文中绘制的形状。与绘制矩形相关的方法有3个：&lt;code&gt;fillRect()&lt;/code&gt;、&lt;code&gt;strokeRect()&lt;/code&gt;和&lt;code&gt;clearRect()&lt;/code&gt;。这些方法都接收4个参数：矩形x坐标、矩形y坐标、矩形宽度和矩形高度。这几个参数的单位都是像素。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let drawing = document.getElementById(&quot;drawing&quot;);
    // 确保浏览器支持&amp;lt;canvas&amp;gt;
    if (drawing.getContext) {
      let context = drawing.getContext(&quot;2d&quot;);
      /＊
        ＊ 引自MDN文档
        ＊/
      // 绘制红色矩形
      context.fillStyle = &quot;#ff0000&quot;;
      context.fillRect(10, 10, 50, 50);
      // 绘制半透明蓝色矩形
      context.fillStyle = &quot;rgba(0,0,255,0.5)&quot;;
      context.fillRect(30, 30, 50, 50);
      //在前两个矩形重叠的区域擦除一个矩形区域
      context.clearRect(40, 40, 10, 10);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;绘制路径&lt;/h3&gt;
&lt;p&gt;2D绘图上下文支持很多在画布上绘制路径的方法。通过路径可以创建复杂的形状和线条。要绘制路径，必须首先调用&lt;code&gt;beginPath()&lt;/code&gt;方法以表示要开始绘制新路径。然后，再调用下列方法来绘制路径:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;arc()&lt;/code&gt; 和 &lt;code&gt;arcTo()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bezierCurveTo()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lineTo()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;moveTo()&lt;/code&gt; 单纯移动光标到指定位置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;quadraticCurveTo()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rect()&lt;/code&gt; 建立的是矩形的路径而不是图形&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;创建路径之后，可以使用&lt;code&gt;closePath()&lt;/code&gt;方法绘制一条返回起点的线。如果路径已经完成，则既可以指定&lt;code&gt;fillStyle&lt;/code&gt;属性并调用&lt;code&gt;fill()&lt;/code&gt;方法来填充路径，也可以指定&lt;code&gt;strokeStyle&lt;/code&gt;属性并调用&lt;code&gt;stroke()&lt;/code&gt;方法来描画路径，还可以调用&lt;code&gt;clip()&lt;/code&gt;方法基于已有路径创建一个新剪切区域。&lt;/p&gt;
&lt;h3&gt;绘制文本&lt;/h3&gt;
&lt;p&gt;2D绘图上下文还提供了绘制文本的方法，即fillText()和strokeText()。这两个方法都接收4个参数：要绘制的字符串、x坐标、y坐标和可选的最大像素宽度（如果超出会水平压缩）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    context.font = &quot;bold 14px Arial&quot;;
    context.textAlign = &quot;center&quot;;
    context.textBaseline = &quot;middle&quot;;
    context.fillText(&quot;12&quot;, 100, 20);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于绘制文本很复杂，特别是想把文本绘制到特定区域的时候，因此2D上下文提供了用于&lt;strong&gt;辅助确定文本大小&lt;/strong&gt;的&lt;code&gt;measureText()&lt;/code&gt;方法。这个方法接收一个参数，即要绘制的文本，然后返回一个&lt;code&gt;TextMetrics&lt;/code&gt;对象。这个返回的对象目前只有一个属性&lt;code&gt;width&lt;/code&gt;，不过将来应该会增加更多度量指标。&lt;/p&gt;
&lt;p&gt;示例：字体大小自适应宽度：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let fontSize = 100;
    context.font = fontSize + &quot;px Arial&quot;;
    while(context.measureText(&quot;Hello world! &quot;).width &amp;gt; 140) {
      fontSize--;
      context.font = fontSize + &quot;px Arial&quot;;
    }
    context.fillText(&quot;Hello world! &quot;, 10, 10);
    context.fillText(&quot;Font size is &quot; + fontSize + &quot;px&quot;, 10, 50);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;变换&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;rotate(angle)：围绕原点把图像旋转angle弧度。&lt;/li&gt;
&lt;li&gt;scale(scaleX, scaleY)：通过在x轴乘以scaleX、在y轴乘以scaleY来缩放图像。scaleX和scaleY的默认值都是1.0。&lt;/li&gt;
&lt;li&gt;translate(x, y)：把原点移动到(x, y)。执行这个操作后，坐标(0, 0)就会变成(x, y)。&lt;/li&gt;
&lt;li&gt;transform(m1_1, m1_2, m2_1, m2_2, dx, dy)：像下面这样通过矩阵乘法直接修改矩阵。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果想着什么时候再回到当前的属性和变换状态，可以调用&lt;code&gt;save()&lt;/code&gt;方法。调用这个方法后，所有这一时刻的&lt;strong&gt;设置和变换&lt;/strong&gt;会被放到一个暂存栈中（内容不会保存）。保存之后，可以继续修改上下文。&lt;/p&gt;
&lt;p&gt;而在需要恢复之前的上下文时，可以调用&lt;code&gt;restore()&lt;/code&gt;方法。这个方法会从暂存栈中取出并恢复之前保存的设置。多次调用&lt;code&gt;save()&lt;/code&gt;方法可以在暂存栈中存储多套设置，然后通过&lt;code&gt;restore()&lt;/code&gt;可以系统地恢复。&lt;/p&gt;
&lt;h3&gt;绘制图像&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// HTML img 元素
    let image = document.images[0];
    context.drawImage(image, 10, 10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结合其他一些方法，drawImage()方法可以方便地实现常见的图像操作。操作的结果可以使用toDataURL()方法获取。不过有一种情况例外：如果绘制的图像来自其他域而非当前页面，则不能获取其数据。此时，调用toDataURL()将抛出错误。比如，如果来自www.example.com的页面上绘制的是来自www.wrox.com的图像，则上下文就是“脏的”，获取数据时会抛出错误。&lt;/p&gt;
&lt;h3&gt;渐变&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    let gradient = context.createLinearGradient(30, 30, 70, 70);
    gradient.addColorStop(0, &quot;white&quot;);
    gradient.addColorStop(1, &quot;black&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从(30, 30)到(70, 70)绘制一个渐变。渐变的起点颜色为白色，终点颜色为黑色。可以把这个对象赋给fillStyle或strokeStyle属性，从而以渐变填充或描画绘制的图形。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 绘制红色矩形
    context.fillStyle = &quot;#ff0000&quot;;
    context.fillRect(10, 10, 50, 50);
    // 绘制渐变矩形
    context.fillStyle=gradient;
    context.fillRect(30, 30, 50, 50);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，因为上面的渐变是固定的，底下使用渐变时不会根据矩形的大小进行自适应，因此可以封装函数让渐变跟随矩形大小：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function createRectLinearGradient(context, x, y, width, height) {
      return context.createLinearGradient(x, y, x+width, y+height);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;图案&lt;/h3&gt;
&lt;p&gt;图案是用于填充和描画图形的重复图像。要创建新图案，可以调用createPattern()方法并传入两个参数：一个HTML &amp;lt;img&amp;gt;元素和一个表示该如何重复图像的字符串。第二个参数的值与CSS的background-repeat属性是一样的，包括&quot;repeat&quot;、&quot;repeat-x&quot;、&quot;repeat-y&quot;和&quot;no-repeat&quot;。&lt;/p&gt;
&lt;h2&gt;WebGL&lt;/h2&gt;
&lt;p&gt;第二种是3D上下文，也就是WebGL。WebGL是浏览器对OpenGL ES 2.0的实现。OpenGL ES 2.0是游戏图形开发常用的一个标准。WebGL支持比2D上下文更强大的绘图能力，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用OpenGL着色器语言（GLSL）编写顶点和片段着色器；&lt;/li&gt;
&lt;li&gt;支持定型数组，限定数组中包含数值的类型；&lt;/li&gt;
&lt;li&gt;创建和操作纹理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;暂时略过具体细节。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | 记录一个自己没有修改代码却出现bug的场景</title><link>https://www.ender-wiggin.com/posts/note-plate-bug/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-plate-bug/</guid><description> </description><pubDate>Mon, 26 Feb 2024 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;bug的问题定位在开发中是尤为重要的一环，尤其是当项目庞大架构复杂之后，能否剪枝快速定位问题场景并针对性调试与修复会很大程度地影响开发效率与个人心情。之前在twitter上看过一位微软开发者如何使用条理清晰的方法论快速定位并修复问题，令我颇为佩服。另外也不时能看到有博主提出bug的场景，然后底下评论区的用户猜测是什么原因导致的bug，饶有海龟汤和本格推理的意味，也深感自己在这方面的不足。这篇文章仅记录一个&lt;strong&gt;非常愚蠢&lt;/strong&gt;的bug，但由于个人的经验不足花费了大量时间，也算自我检讨与反思，共勉。&lt;/p&gt;
&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;周四下班前，一位同事告诉我，春节前我负责开发的一个功能出现了问题，无法使用。这个功能基于&lt;code&gt;plate.js&lt;/code&gt;的 drag-and-drop 库(&lt;code&gt;plate-dnd&lt;/code&gt;)进行二次开发，实现了跨编辑器的组件拖拽功能。而&lt;code&gt;plate.js&lt;/code&gt; 这个库是基于&lt;code&gt;slate&lt;/code&gt;和&lt;code&gt;React-DnD&lt;/code&gt;的封装库，同时提供了大量插件与频繁的版本更新，以至于不容易定位源码中的具体问题。&lt;/p&gt;
&lt;p&gt;我开发的这个功能属于一个独立的package，是monorepo架构的一部分，通过yarn workspaces实现。另外，由于项目由多人同时开发，并不清楚其它同事是否有什么部分影响了我现有的代码。同时由于项目没有单元测试，并不能获知是什么时候导致的bug。&lt;/p&gt;
&lt;p&gt;目前能明确确认的信息是：&lt;strong&gt;在春节前我的代码正常运行，春节后在我的代码没有做任何修改的情况下，程序出现了意外的bug。不但我二次修改的代码无法正常运行，这个包原有的dnd功能也无法使用。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;排查流程&lt;/h2&gt;
&lt;h3&gt;步骤1&lt;/h3&gt;
&lt;p&gt;在遇到上面的场景后，我的第一反应就是检查近几天的代码提交，看看是否有其他人的代码修改影响了我原有功能。在检查过每个提交修改的文件和依赖后，没有什么特殊发现。&lt;/p&gt;
&lt;h3&gt;步骤2&lt;/h3&gt;
&lt;p&gt;在第一步无果后，我在本地运行了春节前我个人本地开发的环境，发现&lt;strong&gt;代码可以正常运行&lt;/strong&gt;，并没有出现bug，说明bug是出现在春节之后。&lt;/p&gt;
&lt;h3&gt;步骤3&lt;/h3&gt;
&lt;p&gt;为了明确到底是什么原因导致了bug，我fork了最新的分支，运行后通过控制台发现其中一个本应该有值的变量现在变成了&lt;code&gt;undefined&lt;/code&gt;。至此，我认为问题是出在外部的变量传递有问题，导致了我封装的包无法获取正确的值。因此将关注点转向了调用这个包的父级文件的代码。&lt;/p&gt;
&lt;p&gt;花费大量时间排查后没有发现问题。&lt;/p&gt;
&lt;h3&gt;步骤4&lt;/h3&gt;
&lt;p&gt;因为现在这个包原有的功能无法使用，我尝试将这个包迁移回原有的版本而非我二次开发的版本，发现可以正常使用。&lt;/p&gt;
&lt;p&gt;至此，问题似乎又定位回了&lt;code&gt;plate-dnd&lt;/code&gt;自身，因此我尝试直接使用&lt;code&gt;plate-dnd&lt;/code&gt; 源码，将源码移动到&lt;code&gt;/src&lt;/code&gt;目录中并转译，发现居然也无法正常工作。&lt;/p&gt;
&lt;p&gt;也就是说，直接通过&lt;code&gt;yarn&lt;/code&gt;下载的编译后版本可以正常运行，而从源码自行build的版本无法运行。&lt;/p&gt;
&lt;h3&gt;步骤5&lt;/h3&gt;
&lt;p&gt;至此，我人已经晕了。问题基本定位为工程上导致的bug，而非我个人代码的问题。因此关注点放在了yarn, node_modules, cache, package.json, workspace 这些工程化细节上。&lt;/p&gt;
&lt;p&gt;另外，我尝试回滚到2周前的代码并重新build，发现&lt;strong&gt;也出现了bug&lt;/strong&gt;（和步骤2不同的地方在于我这一步重新build了）。&lt;/p&gt;
&lt;h2&gt;结论&lt;/h2&gt;
&lt;p&gt;现在信息已经全部给出，我从事后来看如果有经验或者敏锐的人可能在一开始看到问题描述就能知道是什么原因，而自己在步骤5之后都想了比较长时间才明白，实属惭愧。&lt;/p&gt;
&lt;p&gt;结论就是：
没有锁版本。&lt;/p&gt;
&lt;p&gt;直到很晚我才发现这个项目居然不知道什么时候把&lt;code&gt;yarn.lock&lt;/code&gt; 放到了&lt;code&gt;.gitignore&lt;/code&gt;（因为之前很长一段时间内都有，所以我完全没有考虑版本变动的问题）。而其他同事移除lock的原因是，一个需要付费的动画库&lt;code&gt;gsap&lt;/code&gt;需要没有yarn.lock的时候才能正常安装。&lt;/p&gt;
&lt;p&gt;另外，在导入&lt;code&gt;plate&lt;/code&gt;相关的几十个包的时候，都是统一使用了&lt;code&gt;&quot;plate&quot;: &quot;^30.1.2&quot;&lt;/code&gt; 这种锁定主要版本的形式。原则上来说不是主要版本也不会出现破坏性更新，但是因为plate是一个代码高频更新的比较新的开源库，出现不兼容的情况也在所难免。&lt;/p&gt;
&lt;p&gt;在修复bug后我光速提交代码并开始写这篇总结性质的文章，因此尚未定位到具体是哪个依赖的什么代码导致了这个奇怪的异常问题。虽然说知其所以然很重要，但是我还是觉得先记录下“知其然”的过程，确保以后不要在大方向上失之千里。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;在发现这个本不应该出现，并且也应该快速想到的问题时，我脑子突然浮现出了去年底OpenAI Python SDK的破坏性更新以及LangChain版本不兼容导致的诸多程序崩溃，也愈发感受到了锁版本的重要性，尤其是对于社区不够活跃的新项目来说。&lt;/p&gt;
&lt;p&gt;并且正如同windows的“重启大法”一样，对于前端项目就应该从最开始就&lt;code&gt;cache clean&lt;/code&gt; + &lt;code&gt;npm install&lt;/code&gt; 二连，从源头排查经典的“我本地可以运行的问题”。同时，对于依赖的版本控制需要有更敏锐的认知，避免毫无意义的心智负担。&lt;/p&gt;
&lt;p&gt;而在回顾我自己的排查路径中，我能明显感觉到我思维上的滑坡，也造成了大量时间的浪费。当然，也不得不吐槽万恶的node_modules，也许使用&lt;code&gt;bun&lt;/code&gt;可以提升很多效率，但是既然项目从初期就是&lt;code&gt;yarn&lt;/code&gt;那也没什么办法，只能每次安装时苦苦等待漫长的几分钟。&lt;/p&gt;
&lt;p&gt;如下是我对于“自己代码没变动但是导致程序bug”的后验方法论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;依赖检查（版本检查），即需要确保当前的bug不是由于版本导致的，也要确认回滚后的代码和过去那个时刻是完全一致的。&lt;/li&gt;
&lt;li&gt;回滚至没有bug与有bug的临界版本。在确定不是外部依赖导致的问题后，才需要关注项目自身的代码实现。&lt;/li&gt;
&lt;li&gt;定位是自己的问题还是其它人代码修改导致的问题。&lt;/li&gt;
&lt;li&gt;如果是自己的问题或者问题本身很容易解决可以尝试解决，否则就先甩锅嘿嘿，除非自己时间精力非常充足。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>技术 | TypeCell源码实现简要分析</title><link>https://www.ender-wiggin.com/posts/note-typecell/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-typecell/</guid><description> </description><pubDate>Wed, 21 Feb 2024 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TypeCell是一个类似Notion+Jupyter的TypeScript在线编辑文档，可以实时运行ts和react代码。本文主要分析渲染部分的源码实现。&lt;/p&gt;
&lt;h2&gt;核心技术栈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.blocknotejs.org&quot;&gt;BlockNote&lt;/a&gt;：富文本编辑器&lt;/li&gt;
&lt;li&gt;&lt;code&gt;monaco-editor&lt;/code&gt;：类似vs code的代码编辑器和&lt;strong&gt;浏览器端的ts解析&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mobx&lt;/code&gt;：状态管理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vscode-lib&lt;/code&gt;：复用了部分vscode的生命周期管理，相应代码待继续调研&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;项目架构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;typecell
├── packages
│   ├── editor        - 主页面，WYSIWYG富文本编辑器
│   ├── engine        - 解析器和运行时，核心功能，后续会介绍
│   ├── frame         - 基于iframe的沙盒版本运行时，可以理解为codesandbox
│   ├── packager      - 打包器
│   ├── parsers       - 解析器，用于解析/转化Markdown和TypeCell格式
│   ├── server        - HocusPocus + Supabase后端
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后续主要介绍&lt;code&gt;engine&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;渲染引擎&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;ReactiveEngine.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;自动运行注册模型的引擎，主要进行资源管理，例如注册和注销事件监听器和其他可释放资源。引擎会处理模型&lt;code&gt;Model&lt;/code&gt;的代码，并为其提供一个上下文（&lt;code&gt;$&lt;/code&gt;），使得不同模型的代码可以交互。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;生命周期管理：&lt;code&gt;vscode-lib&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;代码执行：&lt;code&gt;createCellEvaluator&lt;/code&gt; （见下个section）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;CellEvaluator.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;用于评估并运行在TypeCell环境中执行的代码，分为两步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;assignExecutionExports&lt;/code&gt; 将模块的导出经过处理后映射到TypeCell context&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createCellEvaluator&lt;/code&gt; 创建了评估器对象，能够&lt;strong&gt;执行编译后的代码&lt;/strong&gt;，并管理与TypeCell上下文相关的状态和回调。
&lt;ol&gt;
&lt;li&gt;通过&lt;code&gt;getPatchedTypeCellCode&lt;/code&gt;将编译后的代码包在独立的函数之中&lt;/li&gt;
&lt;li&gt;通过&lt;code&gt;getModulesFromPatchedTypeCellCode&lt;/code&gt; 将上一步的代码与当前作用域结合，生成一个&lt;code&gt;module&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;调用&lt;code&gt;runModule&lt;/code&gt;传入&lt;code&gt;module&lt;/code&gt;和回调，并执行代码&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;export function createCellEvaluator(
  typecellContext: TypeCellContext&amp;lt;any&amp;gt;, // TypeCell上下文
  resolveImport: (module: string) =&amp;gt; Promise&amp;lt;any&amp;gt;, // 解析导入的函数
  setAndWatchOutput = true, // 是否设置和监控输出
  onOutputChanged: (output: any) =&amp;gt; void, // 输出变化时的回调
  beforeExecuting: () =&amp;gt; void // 执行前的回调
) {
  // ...
  const executionScope = createExecutionScope(typecellContext); // 创建执行作用域
  let moduleExecution: ModuleExecution | undefined; // 模块执行实例

  async function evaluate(compiledCode: string) {
    // 评估函数，用于执行编译后的代码
    if (moduleExecution) {
      moduleExecution.dispose(); // 如果已存在模块执行实例，先销毁它
    }

    try {
      // 将编译后的代码转换为可执行的模块
      const patchedCode = getPatchedTypeCellCode(compiledCode, executionScope);
      const modules = getModulesFromPatchedTypeCellCode(
        patchedCode,
        executionScope
      );

      if (modules.length !== 1) {
        throw new Error(&quot;expected exactly 1 module&quot;); // 期望有且只有一个模块
      }

      // 执行模块并处理结果
      moduleExecution = await runModule(
        modules[0],
        typecellContext,
        resolveImport,
        beforeExecuting,
        onExecuted,
        onError,
        moduleExecution?.disposeVariables
      );
      await moduleExecution.initialRun;
    } catch (e) {
      console.error(e);
      onOutputChanged(e); // 发生错误时，通过回调通知
    }
  }

  return {
    evaluate, // 返回的评估器对象包含evaluate函数
    dispose: () =&amp;gt; {
      // 评估器的销毁函数
      if (moduleExecution) {
        moduleExecution.dispose();
        moduleExecution.disposeVariables();
      }
    },
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;补充：&lt;code&gt;getPatchedTypeCellCode&lt;/code&gt; 实现&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;getPatchedTypeCellCode&lt;/code&gt; 将传入的编译后的代码(&lt;code&gt;compiledCode&lt;/code&gt;)转换为一个可以在特定作用域(&lt;code&gt;scope&lt;/code&gt;)下执行的模块化代码，通过添加额外的代码来确保编译后的代码可以作为模块运行。这个函数主要操作如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查&lt;code&gt;compiledCode&lt;/code&gt;是否已经包含了AMD（Asynchronous Module Definition）格式的模块定义代码。AMD格式的模块定义通常以&lt;code&gt;define([], function() { /* module code */ });&lt;/code&gt;的形式出现。如果不包含，那么它会将整段代码包裹在一个&lt;code&gt;define&lt;/code&gt;函数调用中，从而手动创建一个模块。&lt;/li&gt;
&lt;li&gt;验证&lt;code&gt;scope&lt;/code&gt;对象中的键是否都是有效的JavaScript变量名。如果发现无效的键名，则抛出错误。&lt;/li&gt;
&lt;li&gt;构建一个字符串(&lt;code&gt;variableImportCode&lt;/code&gt;)，该字符串包含一系列的&lt;code&gt;let&lt;/code&gt;声明，用于将&lt;code&gt;scope&lt;/code&gt;对象中的每个键映射为局部变量。这样做的目的是让编译后的代码能够访问到这些作用域变量。&lt;/li&gt;
&lt;li&gt;将&lt;code&gt;define&lt;/code&gt;函数和&lt;code&gt;variableImportCode&lt;/code&gt;添加到&lt;code&gt;compiledCode&lt;/code&gt;前面，从而构建出完整的可执行代码(&lt;code&gt;totalCode&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;使用正则表达式替换，将原有的同步&lt;code&gt;define&lt;/code&gt;函数调用替换为异步的&lt;code&gt;define&lt;/code&gt;函数调用。这样做可能是为了支持异步模块加载。&lt;/li&gt;
&lt;li&gt;返回修改后的代码(&lt;code&gt;totalCode&lt;/code&gt;)，以便它可以作为一个模块在相应的执行上下文中运行。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;export function getPatchedTypeCellCode(compiledCode: string, scope: any) {
  // Checks if define([], function) like code is already present
  if (!compiledCode.match(/(define\((&quot;.*&quot;, )?\[.*\], )function/gm)) {
    // file is not a module (no exports). Create module-like code manually
    compiledCode = `define([], function() { ${compiledCode}; });`;
  }

  if (Object.keys(scope).find((key) =&amp;gt; !/^[a-zA-Z0-9_$]+$/.test(key))) {
    throw new Error(&quot;invalid key on scope!&quot;);
  }

  const variableImportCode = Object.keys(scope)
    .map((key) =&amp;gt; `let ${key} = this.${key};`)
    .join(&quot;\n&quot;);

  let totalCode = `;
  let define = this.define;
  ${variableImportCode}
  ${compiledCode}
  `;

  totalCode = totalCode.replace(
    /^\s*(define\((&quot;.*&quot;, )?\[.*\], )function/gm,
    &quot;$1async function&quot;
  ); // TODO: remove await?

  return totalCode;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;runModele()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;在评估器内部的最终执行逻辑。&lt;/p&gt;
&lt;p&gt;这个执行器&lt;code&gt;runModule&lt;/code&gt;函数是一个异步函数，它的目的是在TypeCell环境中执行一个模块(&lt;code&gt;mod&lt;/code&gt;)。它处理模块的依赖项，执行模块的工厂函数，并管理模块执行过程中的资源清理和错误处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心运行步骤&lt;/strong&gt;发生在：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    executionPromise = mod.factoryFunction.apply(
        undefined,
        argsToCallFunctionWith,
    );
} finally {
    // ... 其他代码
}
await executionPromise;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里，&lt;code&gt;mod.factoryFunction&lt;/code&gt;是编译后代码的一个函数，通常这个函数是由模块系统（比如Webpack或RequireJS）创建的。它封装了实际的代码，并且在执行时，可以通过&lt;code&gt;.apply()&lt;/code&gt;方法被调用。&lt;code&gt;.apply()&lt;/code&gt;方法允许你调用函数并显式设置函数内部的&lt;code&gt;this&lt;/code&gt;值（在这个例子中设置为&lt;code&gt;undefined&lt;/code&gt;），以及传入一个参数数组&lt;code&gt;argsToCallFunctionWith&lt;/code&gt;，这些参数包括模块的依赖项，如&lt;code&gt;exports&lt;/code&gt;对象和其他模块。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const f = new Function(code);

f.apply({ ...scope, define });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;argsToCallFunctionWith&lt;/code&gt;数组是通过&lt;code&gt;resolveDependencyArray&lt;/code&gt;函数解析得到的，它基于模块的依赖列表（&lt;code&gt;mod.dependencyArray&lt;/code&gt;），并且通过&lt;code&gt;resolveImport&lt;/code&gt;函数异步获取这些依赖。&lt;/p&gt;
&lt;p&gt;在调用&lt;code&gt;mod.factoryFunction.apply()&lt;/code&gt;之后，如果返回的是一个&lt;code&gt;Promise&lt;/code&gt;，那么代码会等待此&lt;code&gt;Promise&lt;/code&gt;完成。这意味着如果模块中的代码是异步的，它会被正确地等待和处理。&lt;/p&gt;
&lt;p&gt;因此，具体的代码执行是在调用&lt;code&gt;mod.factoryFunction.apply()&lt;/code&gt;时发生的，而不是通过&lt;code&gt;eval()&lt;/code&gt;。这种方法遵循了现代JavaScript模块化实践，允许代码以安全、可控的方式在浏览器环境中运行。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;module.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这段代码定义了一个处理JavaScript模块的机制。&lt;code&gt;Module&lt;/code&gt;类型用于表示一个模块，其中包含模块名、依赖项数组和工厂函数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getModulesFromWrappedPatchedTypeCellFunction&lt;/code&gt;和&lt;code&gt;getModulesFromPatchedTypeCellCode&lt;/code&gt;两个函数的目的是从不同的上下文中提取模块定义。前者接受一个函数&lt;code&gt;caller&lt;/code&gt;，后者接受一个字符串&lt;code&gt;code&lt;/code&gt;。两者都使用&lt;code&gt;createDefine&lt;/code&gt;函数创建的&lt;code&gt;define&lt;/code&gt;函数来注册模块。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createDefine&lt;/code&gt;函数返回一个&lt;code&gt;typeCellDefine&lt;/code&gt;函数，该函数用于定义模块。当&lt;code&gt;typeCellDefine&lt;/code&gt;被调用时，它会将模块信息（模块名、依赖项、工厂函数）推入到&lt;code&gt;modules&lt;/code&gt;数组中，这个数组随后可以用于执行模块。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createExecutionScope&lt;/code&gt;函数创建一个包含TypeCell环境所需的作用域对象，通常包括一些MobX的函数（如&lt;code&gt;autorun&lt;/code&gt;、&lt;code&gt;untracked&lt;/code&gt;、&lt;code&gt;computed&lt;/code&gt;、&lt;code&gt;observable&lt;/code&gt;），以及&lt;code&gt;$&lt;/code&gt;和&lt;code&gt;$views&lt;/code&gt;对象，分别表示TypeCell上下文和视图上下文。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getPatchedTypeCellCode&lt;/code&gt;函数将编译后的代码转换成可以在特定作用域下执行的模块化代码。如果代码不是模块形式，它会手动包装代码以创建一个模块。同时，它会将作用域中的变量导入到执行环境中，并确保所有的&lt;code&gt;define&lt;/code&gt;函数调用都转换为异步函数调用。&lt;/p&gt;
&lt;h4&gt;mod.factoryFunction.apply的含义&lt;/h4&gt;
&lt;p&gt;在上下文中，&lt;code&gt;mod.factoryFunction&lt;/code&gt;是一个模块的工厂函数，它包含了模块的实际代码。&lt;code&gt;apply&lt;/code&gt;方法是JavaScript中一个函数对象的方法，允许你调用这个函数，并指定函数执行时&lt;code&gt;this&lt;/code&gt;的值以及传入的参数列表。&lt;/p&gt;
&lt;p&gt;当&lt;code&gt;mod.factoryFunction.apply(undefined, argsToCallFunctionWith)&lt;/code&gt;被调用时，它执行以下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;undefined&lt;/code&gt;作为&lt;code&gt;this&lt;/code&gt;的值传入，这意味着在工厂函数内部，&lt;code&gt;this&lt;/code&gt;将不指向任何对象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;argsToCallFunctionWith&lt;/code&gt;是一个数组，包含了工厂函数需要的参数，如&lt;code&gt;exports&lt;/code&gt;对象和其他模块依赖。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;ts解析&lt;/h2&gt;
&lt;p&gt;使用了&lt;code&gt;monaco-editor&lt;/code&gt;，即Visual Studio Code的编辑器组件，是在浏览器环境中运行并解析TypeScript代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：这个库也实现了通过请求 &lt;code&gt;code.typescriptrepl.com&lt;/code&gt; 获取编译结果的方式。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getCompiledCodeInternal&lt;/code&gt; 函数通过调用&lt;code&gt;monaco-editor&lt;/code&gt;的&lt;code&gt;TypeScriptWorker&lt;/code&gt;获取TypeScript的编译输出，包括&lt;code&gt;.js&lt;/code&gt;和&lt;code&gt;.d.ts&lt;/code&gt;文件。它返回一个包含JavaScript代码和TypeScript声明文件内容的对象。
整个流程主要是在浏览器中通过&lt;code&gt;monaco-editor&lt;/code&gt;的API和TypeScript的工作线程（&lt;code&gt;Worker&lt;/code&gt;）来完成TypeScript代码到JavaScript代码的转换。&lt;/p&gt;
&lt;h2&gt;未来工作&lt;/h2&gt;
&lt;p&gt;一般主流的react浏览器端编译是靠 &lt;code&gt;babel/standlone&lt;/code&gt; 实现的（例如&lt;a href=&quot;https://github.com/elias-sundqvist/obsidian-react-components&quot;&gt;obsidian-react插件&lt;/a&gt;），但是在项目源码中没有看到相关操作，待后续调研。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第17章-事件</title><link>https://www.ender-wiggin.com/posts/note-js-17/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-17/</guid><description> </description><pubDate>Thu, 01 Feb 2024 22:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;事件&lt;/h1&gt;
&lt;p&gt;JavaScript与HTML的交互是通过事件实现的，事件代表文档或浏览器窗口中某个有意义的时刻。可以使用仅在事件发生时执行的监听器（也叫处理程序）订阅事件。即“观察者模式”，使JS中定义的页面行为与HTML的页面展示分离。&lt;/p&gt;
&lt;h2&gt;事件流&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;事件冒泡&lt;/strong&gt;从最具体的元素（文档树中最深的节点）开始触发，然后向上传播至没有那么具体的元素（文档）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事件捕获&lt;/strong&gt;中最不具体的节点最先收到事件，而最具体的节点最后收到事件。事件捕获实际上是为了在事件到达最终目标前拦截事件。&lt;/p&gt;
&lt;p&gt;DOM事件流分为3个阶段：事件捕获、到达目标和事件冒泡。
注意，虽然DOM2 Events规范明确捕获阶段不命中事件目标，但现代浏览器都会在捕获阶段在事件目标上触发事件。最终结果是在事件目标上有两个机会来处理事件。&lt;/p&gt;
&lt;h2&gt;事件处理程序&lt;/h2&gt;
&lt;p&gt;事件是某种动作，而为响应事件而调用的函数被称为事件处理程序（或事件监听器）。&lt;/p&gt;
&lt;p&gt;这个程序首先会创建一个函数来封装属性的值。这个函数有一个特殊的局部变量event，其中保存的就是event对象（&lt;code&gt;this&lt;/code&gt; 即为目标元素）。
另外，这个动态创建的包装函数的作用链扩展了，document和元素自身的成员都可以被当成局部变量来访问（通过包了&lt;code&gt;with&lt;/code&gt;实现）:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function() {
      with(document) {
        with(this) {
            // 属性值
        }
      }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，如果是表单输入框，那么with还会包一层 &lt;code&gt;this.form&lt;/code&gt; ，这样事件处理程序可以直接访问同一表单中的其它元素了。&lt;/p&gt;
&lt;p&gt;大多数HTML事件处理程序会封装在try/catch块中，以便在特殊情况下（如调用还没有定义的函数时）&lt;strong&gt;静默失败&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;DOM0事件处理程序&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    let btn = document.getElementById(&quot;myBtn&quot;);
    btn.onclick = function() {
      console.log(&quot;Clicked&quot;);
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;像这样使用DOM0方式为事件处理程序赋值时，所赋函数被视为元素的方法。因此，事件处理程序会在元素的作用域中运行，即this等于元素。
事件处理程序里通过this可以访问元素的任何属性和方法。以这种方式添加事件处理程序是注册在事件流的&lt;strong&gt;冒泡阶段&lt;/strong&gt;的。&lt;/p&gt;
&lt;h3&gt;DOM2事件处理程序&lt;/h3&gt;
&lt;p&gt;DOM2 Events为事件处理程序的赋值和移除定义了两个方法：&lt;code&gt;addEventListener()&lt;/code&gt;和&lt;code&gt;removeEventListener()&lt;/code&gt;。这两个方法暴露在所有DOM节点上，它们接收3个参数：事件名、事件处理函数和一个布尔值，true表示在&lt;strong&gt;捕获阶段&lt;/strong&gt;调用，false（默认值）表示在&lt;strong&gt;冒泡阶段&lt;/strong&gt;调用。
使用DOM2方式的主要优势是可以为同一个事件添加多个事件处理程序。&lt;/p&gt;
&lt;p&gt;一般为了兼容性考虑，都是在冒泡时添加事件。如果一定要捕获，则主要目的是为了在事件到达目标前拦截事件。&lt;/p&gt;
&lt;h3&gt;兼容性写法&lt;/h3&gt;
&lt;p&gt;自己封装一个函数，处理多种情况：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var EventUtil = {
      addHandler: function(element, type, handler) {
        if (element.addEventListener) {
          element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
          element.attachEvent(&quot;on&quot; + type, handler);
        } else {
          element[&quot;on&quot; + type] = handler;
        }
      },
      removeHandler: function(element, type, handler) {
        if (element.removeEventListener) {
          element.removeEventListener(type, handler, false);
        } else if (element.detachEvent) {
          element.detachEvent(&quot;on&quot; + type, handler);
        } else {
          element[&quot;on&quot; + type] = null;
        }
      }
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    let btn = document.getElementById(&quot;myBtn&quot;)
    let handler = function() {
      console.log(&quot;Clicked&quot;);
    };
    EventUtil.addHandler(btn, &quot;click&quot;, handler);
    //其他代码
    EventUtil.removeHandler(btn, &quot;click&quot;, handler);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;事件对象&lt;/h2&gt;
&lt;h3&gt;DOM事件对象&lt;/h3&gt;
&lt;p&gt;在事件处理程序内部，&lt;code&gt;this&lt;/code&gt;对象始终等于&lt;code&gt;currentTarget&lt;/code&gt;的值，而&lt;code&gt;target&lt;/code&gt;只包含事件的实际目标。
如果事件处理程序在父节点上：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    document.body.onclick = function(event) {
      console.log(event.currentTarget === document.body);                  // true
      console.log(this === document.body);                                     // true
      console.log(event.target === document.getElementById(&quot;myBtn&quot;));   // true
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;preventDefault()&lt;/code&gt;方法用于阻止特定事件的默认动作。比如，链接的默认行为就是在被单击时导航到href属性指定的URL。
任何可以通过&lt;code&gt;preventDefault()&lt;/code&gt;取消默认行为的事件，其事件对象的&lt;code&gt;cancelable&lt;/code&gt;属性都会设置为true。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;stopPropagation()&lt;/code&gt;方法用于立即阻止事件流在DOM结构中传播，取消后续的事件捕获或冒泡。例如，直接添加到按钮的事件处理程序中调用&lt;code&gt;stopPropagation()&lt;/code&gt;，可以阻止&lt;code&gt;document.body&lt;/code&gt;上注册的事件处理程序执行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;eventPhase&lt;/code&gt;属性可用于确定事件流当前所处的阶段。事件处理程序:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在捕获阶段被调用&lt;/li&gt;
&lt;li&gt;在目标上被调用&lt;/li&gt;
&lt;li&gt;在冒泡阶段被调用（虽然“到达目标”是在冒泡阶段发生的）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;event&lt;/code&gt;对象只在事件处理程序执行期间存在，一旦执行完毕，就会被销毁。&lt;/p&gt;
&lt;h3&gt;兼容性实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    var EventUtil = {
      addHandler: function(element, type, handler) {
          // 为节省版面，删除了之前的代码
      },
      getEvent: function(event){
        return event?event: window.event;
      },
      getTarget: function(event){
        return event.target||event.srcElement;
      },
      preventDefault: function(event){
        if(event.preventDefault){
          event.preventDefault();
        }else{
          event.returnValue=false;
        }
      },
      removeHandler: function(element, type, handler) {
        // 为节省版面，删除了之前的代码
      },
      stopPropagation: function(event){
        if(event.stopPropagation){
          event.stopPropagation();
        }else{
          event.cancelBubble=true;
        }
      }
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;事件类型&lt;/h2&gt;
&lt;p&gt;DOM3 Events定义了如下事件类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户界面事件（UIEvent）：涉及与BOM交互的通用浏览器事件。&lt;/li&gt;
&lt;li&gt;焦点事件（FocusEvent）：在元素获得和失去焦点时触发。&lt;/li&gt;
&lt;li&gt;鼠标事件（MouseEvent）：使用鼠标在页面上执行某些操作时触发。&lt;/li&gt;
&lt;li&gt;滚轮事件（WheelEvent）：使用鼠标滚轮（或类似设备）时触发。&lt;/li&gt;
&lt;li&gt;输入事件（InputEvent）：向文档中输入文本时触发。&lt;/li&gt;
&lt;li&gt;键盘事件（KeyboardEvent）：使用键盘在页面上执行某些操作时触发。&lt;/li&gt;
&lt;li&gt;合成事件（CompositionEvent）：在使用某种IME（Input Method Editor，输入法编辑器）输入字符时触发。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;用户界面事件&lt;/h3&gt;
&lt;p&gt;用户界面事件或UI事件不一定跟用户操作有关。这类事件在DOM规范出现之前就已经以某种形式存在了，保留它们是为了向后兼容。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;load&lt;/code&gt;: 在window上当页面加载完成后触发，在窗套（&lt;code&gt;&amp;lt;frameset&amp;gt;&lt;/code&gt;）上当所有窗格（&lt;code&gt;&amp;lt;frame&amp;gt;&lt;/code&gt;）都加载完成后触发，在&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;元素上当图片加载完成后触发，在&lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;元素上当相应对象加载完成后触发。(理论上应该在&lt;code&gt;document&lt;/code&gt;中触发，但是&lt;code&gt;window&lt;/code&gt;有向下兼容)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图片预加载（注意需要先绑定事件再设置src，因为一旦有src了就会自动下载）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    window.addEventListener(&quot;load&quot;, () =&amp;gt; {
      let image=new Image();
      image.addEventListener(&quot;load&quot;, (event) =&amp;gt; {
        console.log(&quot;Image loaded! &quot;);
      });
      image.src = &quot;smile.gif&quot;;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unload&lt;/code&gt;: 类似前者&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;unload事件会在文档卸载完成后触发。unload事件一般是在从一个页面导航到另一个页面时触发，最常用于清理引用，以避免内存泄漏。&lt;/p&gt;
&lt;p&gt;因为unload事件是在页面卸载完成后触发的，所以不能使用页面加载后才有的对象。此时要访问DOM或修改页面外观都会导致错误。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;abort&lt;/code&gt;：在&lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;元素上当相应对象加载完成前被用户提前终止下载时触发。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;error&lt;/code&gt;：在&lt;code&gt;window&lt;/code&gt;上当JavaScript报错时触发，在&amp;lt;img&amp;gt;元素上当无法加载指定图片时触发，在&lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;元素上当无法加载相应对象时触发，在窗套上当一个或多个窗格无法完成加载时触发。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select&lt;/code&gt;：在文本框（&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;或textarea）上当用户选择了一个或多个字符时触发。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resize&lt;/code&gt;：在&lt;code&gt;window&lt;/code&gt;或窗格上当窗口或窗格被缩放时触发。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scroll&lt;/code&gt;：当用户滚动包含滚动条的元素时在元素上触发。&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;元素包含已加载页面的滚动条。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;焦点事件&lt;/h3&gt;
&lt;p&gt;焦点事件在页面元素获得或失去焦点时触发。这些事件可以与document.hasFocus()和document.activeElement一起为开发者提供用户在页面中导航的信息。焦点事件有以下6种:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;blur：当元素失去焦点时触发。这个事件&lt;strong&gt;不冒泡&lt;/strong&gt;，所有浏览器都支持。&lt;/li&gt;
&lt;li&gt;&lt;s&gt;DOMFocusIn：当元素获得焦点时触发。这个事件是focus的冒泡版。Opera是唯一支持这个事件的主流浏览器。DOM3 Events废弃了DOMFocusIn，推荐focusin。&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;DOMFocusOut：当元素失去焦点时触发。这个事件是blur的通用版。Opera是唯一支持这个事件的主流浏览器。&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;focus：当元素获得焦点时触发。这个事件&lt;strong&gt;不冒泡&lt;/strong&gt;，所有浏览器都支持。&lt;/li&gt;
&lt;li&gt;focusin：当元素获得焦点时触发。这个事件是focus的冒泡版。&lt;/li&gt;
&lt;li&gt;focusout：当元素失去焦点时触发。这个事件是blur的通用版。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;鼠标和滚轮事件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;click&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dbclick&lt;/code&gt; 双击&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mousedown&lt;/code&gt; 按下鼠标后&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseenter&lt;/code&gt; 鼠标进入元素内部后，&lt;strong&gt;不冒泡&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseleave&lt;/code&gt; 从内部移动到外部， &lt;strong&gt;不冒泡&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mousemove&lt;/code&gt; 鼠标在元素上移动&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseout&lt;/code&gt; 鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元素的外部元素，也可以是原始元素的子元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseover&lt;/code&gt; 光标从元素外部移到元素内部时&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseup&lt;/code&gt; 用户释放鼠标键时触发&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mousewheel&lt;/code&gt; 反映的是鼠标滚轮或带滚轮的类似设备上滚轮的交互。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一些事件是依赖与前面的事件的，比如必须先触发了&lt;code&gt;mousedown&lt;/code&gt;再紧接着触发了&lt;code&gt;mouseup&lt;/code&gt; 才会执行 &lt;code&gt;click&lt;/code&gt; 事件，如果有操作被取消默认了，那么 &lt;code&gt;click&lt;/code&gt; 就不会发生。&lt;/p&gt;
&lt;h4&gt;客户端坐标&lt;/h4&gt;
&lt;p&gt;鼠标事件都是在浏览器视口中的某个位置上发生的。这些信息被保存在&lt;code&gt;event&lt;/code&gt;对象的&lt;code&gt;clientX&lt;/code&gt;和&lt;code&gt;clientY&lt;/code&gt;属性中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let div = document.getElementById(&quot;myDiv&quot;);
    div.addEventListener(&quot;click&quot;, (event) =&amp;gt; {
      console.log(`Client coordinates: ${event.clientX}, ${event.clientY}`);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;页面坐标&lt;/h4&gt;
&lt;p&gt;页面坐标是事件发生时鼠标光标在页面上的坐标，通过&lt;code&gt;event&lt;/code&gt;对象的&lt;code&gt;pageX&lt;/code&gt;和&lt;code&gt;pageY&lt;/code&gt;可以获取。&lt;/p&gt;
&lt;h4&gt;屏幕坐标&lt;/h4&gt;
&lt;p&gt;可以通过event对象的screenX和screenY属性获取鼠标光标在屏幕上的坐标，指的是整个显示器屏幕中鼠标的位置。&lt;/p&gt;
&lt;h4&gt;修饰键&lt;/h4&gt;
&lt;p&gt;如下属性会在各自对应的修饰键被按下时包含布尔值true，没有被按下时包含false。在鼠标事件发生的，可以通过这几个属性来检测修饰键是否被按下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let div = document.getElementById(&quot;myDiv&quot;);
    div.addEventListener(&quot;click&quot;, (event) =&amp;gt; {
      let keys = new Array();
      if (event.shiftKey) {
        keys.push(&quot;shift&quot;);
      }
      if (event.ctrlKey) {
        keys.push(&quot;ctrl&quot;);
      }
      if (event.altKey) {
        keys.push(&quot;alt&quot;);
      }
      if (event.metaKey) {
        keys.push(&quot;meta&quot;);
      }
      console.log(&quot;Keys: &quot; + keys.join(&quot;, &quot;));
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;相关元素&lt;/h4&gt;
&lt;p&gt;对mouseover和mouseout事件而言，还存在与事件相关的其他元素。这两个事件都涉及从一个元素的边界之内把光标移到另一个元素的边界之内。对mouseover事件来说，事件的主要目标是获得光标的元素(&lt;code&gt;target&lt;/code&gt;)，&lt;strong&gt;相关元素是失去光标的元素&lt;/strong&gt;（&lt;code&gt;relatedTarget&lt;/code&gt;）。&lt;/p&gt;
&lt;h4&gt;鼠标按键&lt;/h4&gt;
&lt;p&gt;event对象上会有一个button属性，表示按下或释放的是哪个按键。DOM为这个button属性定义了3个值：0表示鼠标主键、1表示鼠标中键（通常也是滚轮键）、2表示鼠标副键。&lt;/p&gt;
&lt;h4&gt;额外事件信息&lt;/h4&gt;
&lt;p&gt;DOM2 Events规范在event对象上提供了detail属性，以给出关于事件的更多信息。对鼠标事件来说，detail包含一个数值，表示在给定位置上发生了多少次单击。单击相当于在同一个像素上发生一次mousedown紧跟一次mouseup。detail的值从1开始，每次单击会加1。如果鼠标在mousedown和mouseup之间移动了，则detail会重置为0。&lt;/p&gt;
&lt;h4&gt;mousewheel事件&lt;/h4&gt;
&lt;p&gt;mousewheel事件会在用户使用鼠标滚轮时触发，包括在垂直方向上任意滚动。这个事件会在任何元素上触发，并（在IE8中）冒泡到document和（在所有现代浏览器中）window。mousewheel事件的event对象包含鼠标事件的所有标准信息，此外还有一个名为wheelDelta的新属性。当鼠标滚轮向前滚动时，wheelDelta每次都是+120；而当鼠标滚轮向后滚动时，wheelDelta每次都是-120。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    document.addEventListener(&quot;mousewheel&quot;, (event) =&amp;gt; {
      console.log(event.wheelDelta);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;键盘与输入事件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;keydown，用户按下键盘上某个键时触发，而且持续按住会重复触发。&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;keyup，用户释放键盘上某个键时触发。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;输入事件只有一个，即textInput。这个事件是对keypress事件的扩展，用于在文本显示给用户之前更方便地截获文本输入。textInput会在文本被插入到文本框之前触发。&lt;/p&gt;
&lt;h4&gt;键码&lt;/h4&gt;
&lt;p&gt;event对象的keyCode属性中会保存一个键码，对应键盘上特定的一个键。对于字母和数字键，keyCode的值与小写字母和数字的ASCII编码一致。比如数字7键的keyCode为55，而字母A键的keyCode为65，而且跟是否按了Shift键无关。&lt;/p&gt;
&lt;p&gt;DOM3使用&lt;strong&gt;key属性&lt;/strong&gt;用于替代keyCode，且包含字符串。在按下字符键时，key的值等于文本字符（如“k”或“M”）；在按下非字符键时，key的值是键名（如“Shift”或“ArrowDown”）。char属性在按下字符键时与key类似，在按下非字符键时为null&lt;/p&gt;
&lt;h4&gt;字符编码&lt;/h4&gt;
&lt;p&gt;一旦有了字母编码，就可以使用&lt;code&gt;String.fromCharCode()&lt;/code&gt;方法将其转换为实际的字符了。&lt;/p&gt;
&lt;p&gt;最后一个变化是给event对象增加了&lt;code&gt;getModifierState()&lt;/code&gt;方法。这个方法接收一个参数，一个等于Shift、Control、Alt、AltGraph或Meta的字符串，表示要检测的修饰键。如果给定的修饰键处于激活状态（键被按住），则方法返回true，否则返回false：&lt;/p&gt;
&lt;h4&gt;textInput事件&lt;/h4&gt;
&lt;p&gt;DOM3 Events规范增加了一个名为textInput的事件，其在字符被输入到可编辑区域时触发。作为对keypress的替代，textInput事件的行为有些不一样。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个区别是keypress会在任何可以获得焦点的元素上触发，而textInput只在可编辑区域上触发。&lt;/li&gt;
&lt;li&gt;另一个区别是textInput只在有新字符被插入时才会触发，而keypress对任何可能影响文本的键都会触发（包括退格键）。因为textInput事件主要关注字符，所以在event对象上提供了一个data属性，包含要插入的字符（不是字符编码）。data的值&lt;strong&gt;始终是要被插入的字符&lt;/strong&gt;，因此如果在按S键时没有按Shift键，data的值就是&quot;s&quot;，但在按S键时同时按Shift键，data的值则是&quot;S&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    let textbox = document.getElementById(&quot;myText&quot;);
    textbox.addEventListener(&quot;textInput&quot;, (event)=&amp;gt;{
      console.log(event.data);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;event对象上还有一个名为&lt;strong&gt;inputMethod&lt;/strong&gt;的属性，该属性表示向控件中输入文本的手段，例如键盘、粘贴，拖放......&lt;/p&gt;
&lt;h2&gt;HTML5事件&lt;/h2&gt;
&lt;h3&gt;contextmenu事件&lt;/h3&gt;
&lt;p&gt;contextmenu事件专门用于表示何时该显示上下文菜单，从而允许开发者取消默认的上下文菜单并提供自定义菜单。&lt;/p&gt;
&lt;p&gt;contextmenu事件冒泡，因此只要给document指定一个事件处理程序就可以处理页面上的所有同类事件。事件目标是触发操作的元素。这个事件在所有浏览器中都可以取消，在DOM合规的浏览器中使用event.preventDefault()，在IE8及更早版本中将event.returnValue设置为false。contextmenu事件应该算一种鼠标事件，因此event对象上的很多属性都与光标位置有关。通常，自定义的上下文菜单都是通过oncontextmenu事件处理程序触发显示，并通过onclick事件处理程序触发隐藏的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;! DOCTYPE html&amp;gt;
    &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
      &amp;lt;title&amp;gt;ContextMenu Event Example&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
      &amp;lt;div id=&quot;myDiv&quot;&amp;gt;Right click or Ctrl+click me to get a custom context menu.
        Click anywhere else to get the default context menu.&amp;lt;/div&amp;gt;
      &amp;lt;ul id=&quot;myMenu&quot; style=&quot;position:absolute; visibility:hidden; background-color:
        silver&quot;&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;http://www.somewhere.com&quot;&amp;gt; somewhere&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;http://www.wrox.com&quot;&amp;gt;Wrox site&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;http://www.somewhere-else.com&quot;&amp;gt;somewhere-else&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    window.addEventListener(&quot;load&quot;, (event) =&amp;gt; {
      let div = document.getElementById(&quot;myDiv&quot;);
      div.addEventListener(&quot;contextmenu&quot;, (event) =&amp;gt; {
        event.preventDefault();
        let menu = document.getElementById(&quot;myMenu&quot;);
        menu.style.left = event.clientX + &quot;px&quot;;
        menu.style.top = event.clientY + &quot;px&quot;;
        menu.style.visibility = &quot;visible&quot;;
      });
      document.addEventListener(&quot;click&quot;, (event) =&amp;gt; {
        document.getElementById(&quot;myMenu&quot;).style.visibility = &quot;hidden&quot;;
      });
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;beforeunload事件&lt;/h3&gt;
&lt;p&gt;在卸载界面前弹出提示框。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    window.addEventListener(&quot;beforeunload&quot;, (event) =&amp;gt; {
      let message = &quot;I&apos;m really going to miss you if you go.&quot;;
      event.returnValue = message;
      return message;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;DOMContentLoaded事件&lt;/h3&gt;
&lt;p&gt;window的load事件会在页面完全加载后触发，因为要等待很多外部资源加载完成，所以会花费较长时间。而DOMContentLoaded事件会在DOM树构建完成后立即触发，而不用等待图片、JavaScript文件、CSS文件或其他资源加载完成。相对于load事件，DOMContentLoaded可以让开发者在外部资源下载的同时就能指定事件处理程序，从而让用户能够更快地与页面交互。&lt;/p&gt;
&lt;h2&gt;设备事件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;orientationchange事件，判断设备水平还是垂直，需要通过window.orientation获得&lt;/li&gt;
&lt;li&gt;deviceorientation事件，获取加速计信息，返回左右、前后、扭转的度数。&lt;/li&gt;
&lt;li&gt;devicemotion事件，提示设备实际上在移动，而不仅仅是改变了朝向。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;触摸及手势事件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;touchstart：手指放到屏幕上时触发（即使有一个手指已经放在了屏幕上）。&lt;/li&gt;
&lt;li&gt;touchmove：手指在屏幕上滑动时连续触发。在这个事件中调用preventDefault()可以阻止滚动。&lt;/li&gt;
&lt;li&gt;touchend：手指从屏幕上移开时触发。&lt;/li&gt;
&lt;li&gt;touchcancel：系统停止跟踪触摸时触发。文档中并未明确什么情况下停止跟踪。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了这些公共的DOM属性，触摸事件还提供了以下3个属性用于跟踪触点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;touches: &lt;strong&gt;Touch对象&lt;/strong&gt;的数组，表示当前屏幕上的每个触点。&lt;/li&gt;
&lt;li&gt;targetTouches: Touch对象的数组，表示特定于事件目标的触点。&lt;/li&gt;
&lt;li&gt;changedTouches: Touch对象的数组，表示自上次用户动作之后变化的触点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每个Touch对象都包含下列属性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clientX：触点在视口中的x坐标。&lt;/li&gt;
&lt;li&gt;clientY：触点在视口中的y坐标。&lt;/li&gt;
&lt;li&gt;identifier：触点ID。&lt;/li&gt;
&lt;li&gt;pageX：触点在页面上的x坐标。&lt;/li&gt;
&lt;li&gt;pageY：触点在页面上的y坐标。&lt;/li&gt;
&lt;li&gt;screenX：触点在屏幕上的x坐标。&lt;/li&gt;
&lt;li&gt;screenY：触点在屏幕上的y坐标。&lt;/li&gt;
&lt;li&gt;target：触摸事件的事件目标。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;内存与性能&lt;/h2&gt;
&lt;p&gt;在JavaScript中，页面中事件处理程序的数量与页面整体性能直接相关。原因有很多。首先，每个函数都是对象，都占用内存空间，对象越多，性能越差。其次，为指定事件处理程序所需访问DOM的次数会先期造成整个页面交互的延迟。只要在使用事件处理程序时多注意一些方法，就可以改善页面性能。&lt;/p&gt;
&lt;h3&gt;事件委托&lt;/h3&gt;
&lt;p&gt;“过多事件处理程序”的解决方案是使用事件委托。事件委托利用事件冒泡，可以只使用一个事件处理程序来管理一种类型的事件。例如，click事件冒泡到document。这意味着可以为整个页面指定一个onclick事件处理程序，而不用为每个可点击元素分别指定事件处理程序。&lt;/p&gt;
&lt;p&gt;例如下面的例子中，&lt;code&gt;list&lt;/code&gt;是父级&lt;code&gt;ul&lt;/code&gt;元素，&lt;code&gt;event.target&lt;/code&gt;指向了实际的目标。因为所有列表项都是这个元素的后代，所以它们的事件会向上冒泡，最终都会由这个函数来处理。相对于前面不使用事件委托的代码，这里的代码不会导致先期延迟，因为&lt;strong&gt;只访问了一个DOM元素和添加了一个事件处理程序&lt;/strong&gt;。结果对用户来说没有区别，但这种方式占用内存更少。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let list = document.getElementById(&quot;myLinks&quot;);
    list.addEventListener(&quot;click&quot;, (event) =&amp;gt; {
      let target = event.target;
      switch(target.id) {
        case &quot;doSomething&quot;:
          document.title = &quot;I changed the document&apos;s title&quot;;
          break;
        case &quot;goSomewhere&quot;:
          location.href = &quot;http:// www.wrox.com&quot;;
          break;
        case &quot;sayHi&quot;:
          console.log(&quot;hi&quot;);
          break;
      }
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事件委托具有如下优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;document对象随时可用，任何时候都可以给它添加事件处理程序（不用等待DOMContentLoaded或load事件）。这意味着只要页面渲染出可点击的元素，就可以无延迟地起作用。&lt;/li&gt;
&lt;li&gt;节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省DOM引用，也可以节省时间。&lt;/li&gt;
&lt;li&gt;减少整个页面所需的内存，提升整体性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;删除事件处理程序&lt;/h3&gt;
&lt;p&gt;很多Web应用性能不佳都是由于无用的事件处理程序长驻内存导致的，原因主要有两个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;删除带有事件处理程序的元素。比如通过真正的DOM方法removeChild()或replaceChild()删除节点。最常见的还是使用innerHTML整体替换页面的某一部分。这时候，被innerHTML删除的元素上如果有事件处理程序，就&lt;strong&gt;不会被垃圾收集&lt;/strong&gt;程序正常清理。所以最好先把事件设为null，再整体替换。（但是后续浏览器都使用了标记清除和分代收集，理论上这种情况的内存泄漏应该不常见了）&lt;/li&gt;
&lt;li&gt;页面卸载。同样，IE8及更早版本在这种情况下有很多问题，不过好像所有浏览器都会受这个问题影响。如果在页面卸载后事件处理程序没有被清理，则它们仍然会残留在内存中。之后，浏览器每次加载和卸载页面（比如通过前进、后退或刷新），内存中残留对象的数量都会增加，这是因为事件处理程序不会被回收。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一般来说，最好在onunload事件处理程序中趁页面尚未卸载先删除所有事件处理程序。这时候也能体现使用事件委托的优势，因为事件处理程序很少，所以很容易记住要删除哪些。关于卸载页面时的清理，&lt;strong&gt;onload事件处理程序中做了什么，最好在onunload事件处理程序中恢复。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;模拟事件&lt;/h2&gt;
&lt;p&gt;任何时候，都可以使用document.createEvent()方法创建一个event对象。这个方法接收一个参数，此参数是一个表示要创建事件类型的字符串。&lt;/p&gt;
&lt;p&gt;创建event对象之后，需要使用事件相关的信息来初始化。每种类型的event对象都有特定的方法，可以使用相应数据来完成初始化。方法的名字并不相同，这取决于调用createEvent()时传入的参数。&lt;/p&gt;
&lt;p&gt;事件模拟的最后一步是触发事件。为此要使用&lt;code&gt;dispatchEvent()&lt;/code&gt;方法，这个方法存在于所有支持事件的DOM节点之上。&lt;code&gt;dispatchEvent()&lt;/code&gt;方法接收一个参数，即表示要触发事件的event对象。调用&lt;code&gt;dispatchEvent()&lt;/code&gt;方法之后，事件就“转正”了，接着便冒泡并触发事件处理程序执行。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let btn = document.getElementById(&quot;myBtn&quot;);
    // 创建event对象
    let event = document.createEvent(&quot;MouseEvents&quot;);
    // 初始化event对象
    event.initMouseEvent(&quot;click&quot;, true, true, document.defaultView,
                          0, 0, 0, 0, 0, false, false, false, false, 0, null);
    // 触发事件
    btn.dispatchEvent(event);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第15章-DOM扩展</title><link>https://www.ender-wiggin.com/posts/note-js-15/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-15/</guid><description> </description><pubDate>Thu, 01 Feb 2024 21:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;DOM 扩展&lt;/h1&gt;
&lt;h2&gt;Selectors API&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;querySelector()&lt;/code&gt;方法接收CSS选择符参数，返回匹配该模式的第一个后代元素，如果没有匹配项则返回&lt;code&gt;null&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 取得&amp;lt;body&amp;gt;元素
    let body = document.querySelector(&quot;body&quot;);
    // 取得ID为&quot;myDiv&quot;的元素
    let myDiv = document.querySelector(&quot;#myDiv&quot;);
    // 取得类名为&quot;selected&quot;的第一个元素
    let selected = document.querySelector(&quot;.selected&quot;);
    // 取得类名为&quot;button&quot;的图片
    let img = document.body.querySelector(&quot;img.button&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;querySelectorAll()&lt;/code&gt;方法也接收一个用于查询的参数，但它会返回所有匹配的节点的一个&lt;code&gt;NodeList&lt;/code&gt;的&lt;strong&gt;静态&lt;/strong&gt;实例（不是实时查询）。&lt;/p&gt;
&lt;p&gt;matches() 接收一个CSS选择符参数，如果&lt;strong&gt;当前元素&lt;/strong&gt;匹配则该选择符返回true，否则返回false。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (document.body.matches(&quot;body.page1&quot;)){
  // true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用这个方法可以方便地检测某个元素会不会被&lt;code&gt;querySelector()&lt;/code&gt;或&lt;code&gt;querySelectorAll(&lt;/code&gt;)方法返回。&lt;/p&gt;
&lt;h2&gt;元素遍历&lt;/h2&gt;
&lt;p&gt;Element Traversal API为DOM元素添加了5个属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;childElementCount&lt;/code&gt;，返回子元素数量（不包含文本节点和注释）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;firstElementChild&lt;/code&gt;，指向第一个Element类型的子元素（&lt;strong&gt;只返回元素节点，这是为了避免空格导致的空白文本节点问题&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lastElementChild&lt;/code&gt;，指向最后一个Element类型的子元素&lt;/li&gt;
&lt;li&gt;&lt;code&gt;previousElementSibling&lt;/code&gt;，指向前一个Element类型的同胞元素&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nextElementSibling&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;HTML5&lt;/h2&gt;
&lt;p&gt;H5 规范提供了以前从未出现过的 JS 接口描述，并定义了浏览器需要提供的DOM扩展。&lt;/p&gt;
&lt;h3&gt;getElementsByClassName()&lt;/h3&gt;
&lt;p&gt;对于查询类名非常方便，与 &lt;code&gt;querySelectAll()&lt;/code&gt; 主要的区别在于，前者返回动态的&lt;code&gt;HTMLCollection&lt;/code&gt;（动态&lt;code&gt;NodeList&lt;/code&gt;），而后者是静态&lt;code&gt;NodeList&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 类名顺序无关紧要，但语法上以空格分隔
let allCurrentUsernames = document.getElementsByClassName(&quot;username current&quot;);
    // 取得ID为&quot;myDiv&quot;的元素子树中所有包含&quot;selected&quot;类的元素
    let selected = document.getElementById(&quot;myDiv&quot;).getElementsByClassName(&quot;selected&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外，前者因为只需要查询类名，不需要CSS解析，性能会好一点点（可以忽略不计）。&lt;/p&gt;
&lt;h3&gt;classList&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;div.classList&lt;/code&gt; 等于 &lt;code&gt;className&lt;/code&gt; 的 &lt;code&gt;Array&lt;/code&gt; 版本，增&lt;code&gt;add&lt;/code&gt;删&lt;code&gt;remove&lt;/code&gt;查&lt;code&gt;contains&lt;/code&gt;，新增的&lt;code&gt;toggle&lt;/code&gt; 方法如果类名存在则删除否则增加。&lt;/p&gt;
&lt;h3&gt;焦点管理&lt;/h3&gt;
&lt;p&gt;HTML5增加了辅助DOM焦点管理的功能。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;document.activeElement&lt;/code&gt; 始终包含当前拥有焦点的DOM元素。默认情况下在页面刚加载完之后会设置为&lt;code&gt;document.body&lt;/code&gt;。而在页面完全加载之前为null。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;document.hasFocus()&lt;/code&gt; 返回布尔值，判断用户是否在操作页面。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;HTMLDocument扩展&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;document.readyState&lt;/code&gt;属性有两个可能的值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;loading&lt;/code&gt;，表示文档正在加载；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;complete&lt;/code&gt;，表示文档加载完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;document.head&lt;/code&gt;属性，指向文档的&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;元素。&lt;/p&gt;
&lt;h3&gt;自定义数据属性&lt;/h3&gt;
&lt;p&gt;HTML5允许给元素指定非标准的属性，但要使用前缀data-以便告诉浏览器，这些属性既不包含与渲染有关的信息，也不包含元素的语义信息。&lt;/p&gt;
&lt;p&gt;定义了自定义数据属性后，可以通过元素的&lt;code&gt;dataset&lt;/code&gt;属性来访问。&lt;code&gt;dataset&lt;/code&gt;属性是一个&lt;code&gt;DOMStringMap&lt;/code&gt;的实例，包含一组键（&lt;code&gt;data-&lt;/code&gt;后面的内容）/值对映射。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;div id=&quot;myDiv&quot; data-appId=&quot;12345&quot; data-myname=&quot;Nicholas&quot;&amp;gt;&amp;lt;/div&amp;gt;
// 本例中使用的方法仅用于示范
let div = document.getElementById(&quot;myDiv&quot;);
// 取得自定义数据属性的值
let appId = div.dataset.appId;
let myName = div.dataset.myname;
// 设置自定义数据属性的值
div.dataset.appId = 23456;
div.dataset.myname = &quot;Michael&quot;;
// 有&quot;myname&quot;吗？
if (div.dataset.myname){
  console.log(`Hello, ${div.dataset.myname}`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;插入标记&lt;/h3&gt;
&lt;h4&gt;&lt;code&gt;innerHTML&lt;/code&gt;和&lt;code&gt;outerHTML&lt;/code&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;innerHTML&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当用作获取值时，&lt;code&gt;innerHTML&lt;/code&gt;返回调用它的&lt;strong&gt;元素内部&lt;/strong&gt;的HTML内容，不包括该元素本身的标签。&lt;/li&gt;
&lt;li&gt;当用作设置值时，它会替换调用它的元素内部的所有内容，但不会影响元素本身的标签。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，有如下HTML元素：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;example&quot;&amp;gt;这是一些文本&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;code&gt;innerHTML&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var elem = document.getElementById(&quot;example&quot;);
console.log(elem.innerHTML); // 输出: 这是一些文本
elem.innerHTML = &apos;&amp;lt;p&amp;gt;新的内容&amp;lt;/p&amp;gt;&apos;; // 修改后，div内部将包含一个&amp;lt;p&amp;gt;标签
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改后的HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;example&quot;&amp;gt;&amp;lt;p&amp;gt;新的内容&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;outerHTML&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当用作获取值时，&lt;code&gt;outerHTML&lt;/code&gt;返回调用它的元素及其所有内部HTML内容的完整HTML。&lt;/li&gt;
&lt;li&gt;当用作设置值时，它会替换整个元素及其内部内容，也就是说，它会替换调用它的元素本身和所有子内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;继续上面的例子，使用&lt;code&gt;outerHTML&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var elem = document.getElementById(&quot;example&quot;);
console.log(elem.outerHTML); // 输出: &amp;lt;div id=&quot;example&quot;&amp;gt;这是一些文本&amp;lt;/div&amp;gt;
elem.outerHTML = &apos;&amp;lt;p&amp;gt;新的内容&amp;lt;/p&amp;gt;&apos;; // 修改后，原来的div被整个替换为了&amp;lt;p&amp;gt;标签
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改后的HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;p&amp;gt;新的内容&amp;lt;/p&amp;gt; &amp;lt;!-- 原来的&amp;lt;div id=&quot;example&quot;&amp;gt;...&amp;lt;/div&amp;gt;不再存在 --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;&lt;code&gt;insertAdjacentHTML()&lt;/code&gt; 和 &lt;code&gt;insertAdjacentText()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;二者都允许在指定元素的相对位置插入HTML或文本内容。两者的主要区别在于它们插入的内容类型和如何被解析。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;insertAdjacentHTML()&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;此方法接受两个参数：第一个参数是一个字符串，表示插入内容的位置；第二个参数是一个HTML字符串，表示要插入的HTML内容。&lt;/li&gt;
&lt;li&gt;插入的HTML字符串会被解析为HTML元素，并按照指定的位置插入到DOM中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可用的位置参数包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&apos;beforebegin&apos;&lt;/code&gt;: 在当前元素之前插入HTML内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&apos;afterbegin&apos;&lt;/code&gt;: 在当前元素内部的开始位置插入HTML内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&apos;beforeend&apos;&lt;/code&gt;: 在当前元素内部的结束位置插入HTML内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&apos;afterend&apos;&lt;/code&gt;: 在当前元素之后插入HTML内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var elem = document.getElementById(&quot;example&quot;);
elem.insertAdjacentHTML(&apos;beforebegin&apos;, &apos;&amp;lt;p&amp;gt;这是新的段落&amp;lt;/p&amp;gt;&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;insertAdjacentText()&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;此方法也接受两个参数：第一个参数同样是一个字符串，表示插入内容的位置；第二个参数是文本字符串，表示要插入的文本内容。&lt;/li&gt;
&lt;li&gt;插入的文本不会被解析为HTML，它将作为纯文本插入（文本节点），这意味着任何HTML标签将被视为普通文本，并不会创建相应的DOM元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;注意，&lt;code&gt;innerText&lt;/code&gt;(按照深度优先合并子树的&lt;strong&gt;所有文本&lt;/strong&gt;) 和 &lt;code&gt;outerText&lt;/code&gt;(写入时，替换整个元素和子树为文本节点) 没有纳入 H5 标准（Firefox不支持&lt;code&gt;outerText&lt;/code&gt;）。&lt;/p&gt;
&lt;h3&gt;内存与性能&lt;/h3&gt;
&lt;p&gt;在旧版本的浏览器中（特别是旧版本的Internet Explorer），如果你移除或替换了一个DOM节点，但是这个节点或其子节点上绑定的事件处理器没有被正确移除，或者节点上通过JavaScript引用了某些对象（例如通过属性），这些事件处理器和对象之间的引用可能不会被浏览器的垃圾回收机制所清除。这会导致即使DOM节点已经从文档中移除，相关的JavaScript对象仍然保留在内存中，从而引起内存泄漏。&lt;/p&gt;
&lt;p&gt;为了避免这种情况，建议在替换或移除DOM节点之前，手动解除这些节点上的事件绑定，并且清除可能通过JavaScript属性引用的对象。你可以通过以下步骤来减少内存泄漏的风险：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;移除事件监听器： 如果你使用&lt;code&gt;addEventListener()&lt;/code&gt;方法为元素添加了事件监听器，请确保在移除或替换元素之前，使用&lt;code&gt;removeEventListener()&lt;/code&gt;移除这些监听器。&lt;/li&gt;
&lt;li&gt;清除JavaScript引用： 如果你的元素通过属性（如&lt;code&gt;elem.data = myObject;&lt;/code&gt;）引用了JavaScript对象，请在移除元素之前将这些属性设置为&lt;code&gt;null&lt;/code&gt;或者&lt;code&gt;undefined&lt;/code&gt;，以确保引用被清除。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果使用React等框架，自带了事件监听管理和内存回收，所以不需要考虑这么多。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    for (let value of values){
	  // 每次循环前先读取，再写入，访问了2次
      ul.innerHTML += &apos;&amp;lt;li&amp;gt;${value}&amp;lt;/li&amp;gt;&apos;;   // 每次遍历都调用了innerHTML，性能很差
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尽管innerHTML不会执行自己创建的&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;标签，但仍然向恶意用户暴露了很大的攻击面，因为通过它可以毫不费力地创建元素并执行&lt;code&gt;onclick&lt;/code&gt;之类的属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;div.outerText = &quot;Hello world! &quot;;
// 等价于
let text = document.createTextNode(&quot;Hello world! &quot;);
div.parentNode.replaceChild(text, div);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;scrollIntoView()&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;scrollIntoView()&lt;/code&gt; 使元素滚动到浏览器窗口的可视区域内。&lt;/p&gt;
&lt;p&gt;该方法可以接受一个参数，这个参数可以是一个布尔值或者一个选项对象。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;当参数为布尔值时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果参数为 &lt;code&gt;true&lt;/code&gt;（或省略，因为默认值就是 &lt;code&gt;true&lt;/code&gt;），&lt;strong&gt;元素的顶端会和视窗的顶端对齐&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果参数为 &lt;code&gt;false&lt;/code&gt;，元素的底端会和视窗的底端对齐。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;element.scrollIntoView(); // 等同于 element.scrollIntoView(true);
element.scrollIntoView(false);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当参数为一个选项对象时，你可以指定更多的滚动行为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;behavior&lt;/code&gt;: 定义滚动动作，可能的值为 &lt;code&gt;&quot;auto&quot;&lt;/code&gt;（默认）或 &lt;code&gt;&quot;smooth&quot;&lt;/code&gt;，后者会平滑地滚动到元素位置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;block&lt;/code&gt;: 定义垂直方向的对齐，可能的值为 &lt;code&gt;&quot;start&quot;&lt;/code&gt;、&lt;code&gt;&quot;center&quot;&lt;/code&gt;、&lt;code&gt;&quot;end&quot;&lt;/code&gt; 或 &lt;code&gt;&quot;nearest&quot;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inline&lt;/code&gt;: 定义水平方向的对齐，可能的值也为 &lt;code&gt;&quot;start&quot;&lt;/code&gt;、&lt;code&gt;&quot;center&quot;&lt;/code&gt;、&lt;code&gt;&quot;end&quot;&lt;/code&gt; 或 &lt;code&gt;&quot;nearest&quot;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;element.scrollIntoView({
  behavior: &apos;smooth&apos;, // 平滑滚动
  block: &apos;start&apos;, // 元素顶部与视窗顶部对齐
  inline: &apos;nearest&apos; // 在水平方向上不进行滚动或滚动最小距离
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个方法对于创建锚点导航（即点击链接后滚动到页面特定部分）或者当用户进行某些操作后需要将特定的DOM元素展示在视图中时非常有用。&lt;/p&gt;
&lt;p&gt;需要注意的是，&lt;code&gt;scrollIntoView()&lt;/code&gt; 方法可能不会在所有的滚动情况下都能如预期工作，比如当元素被隐藏（&lt;code&gt;display: none&lt;/code&gt;）或者它或其祖先元素设置了不能滚动的样式（&lt;code&gt;overflow: hidden&lt;/code&gt;）时。&lt;/p&gt;
&lt;h3&gt;滚动&lt;/h3&gt;
&lt;p&gt;除了标准化的 &lt;code&gt;scrollIntoView()&lt;/code&gt;，还有一些专有方法。比&lt;code&gt;scrollIntoViewIfNeeded(alingCenter)&lt;/code&gt;会在元素不可见的情况下，将其滚动到窗口或包含窗口中，使其可见；如果已经在视口中可见，则这个方法什么也不做。如果将可选的参数alingCenter设置为true，则浏览器会尝试将其放在视口中央。Safari、Chrome和Opera实现了这个方法。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | Treemap算法实现</title><link>https://www.ender-wiggin.com/posts/note-treemap/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-treemap/</guid><description> </description><pubDate>Thu, 01 Feb 2024 21:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;树状图将分层数据显示为一组嵌套矩形。每个组都由一个矩形表示，该矩形的面积与其值成正比。本文将以 D3 作为代码实现对象，探讨现有 &lt;code&gt;tiling&lt;/code&gt; 函数的实现效果。&lt;/p&gt;
&lt;h2&gt;现有函数&lt;/h2&gt;
&lt;p&gt;D3 提供的函数如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;​&lt;/th&gt;
&lt;th&gt;平均长宽比&lt;/th&gt;
&lt;th&gt;节点有序性&lt;/th&gt;
&lt;th&gt;稳定性&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;treemapBinary&lt;/td&gt;
&lt;td&gt;良好&lt;/td&gt;
&lt;td&gt;部分有序&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;treemapSlice&lt;/td&gt;
&lt;td&gt;很差&lt;/td&gt;
&lt;td&gt;有序&lt;/td&gt;
&lt;td&gt;优秀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;treemapDice&lt;/td&gt;
&lt;td&gt;很差&lt;/td&gt;
&lt;td&gt;有序&lt;/td&gt;
&lt;td&gt;优秀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;treemapResquarify&lt;/td&gt;
&lt;td&gt;良好&lt;/td&gt;
&lt;td&gt;有序&lt;/td&gt;
&lt;td&gt;优秀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;treemapSquarify&lt;/td&gt;
&lt;td&gt;优秀&lt;/td&gt;
&lt;td&gt;部分有序&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以发现，treemapSquarify 拥有更优秀的平均长宽比。而treemapResquarify 首次布局采用 squarified 树图方式，保证具有较好的平均长宽比。后续即便是数据变化也只改变节点的大小，而不会改变节点的相对位置。这种布局方式在树图的&lt;strong&gt;动画&lt;/strong&gt;表现上效果将会更好。&lt;/p&gt;
&lt;h3&gt;treemapDice &amp;amp; treemapSlice&lt;/h3&gt;
&lt;p&gt;这是最简单的排布方式，将所有元素切片。
&lt;code&gt;x0&lt;/code&gt;和&lt;code&gt;y0&lt;/code&gt;：分别代表父节点矩形区域的左下角的 x 和 y 坐标（ &lt;code&gt;x1&lt;/code&gt;和&lt;code&gt;y1&lt;/code&gt; 右上角）。
父元素的children是所有子元素，value也就是所有子元素value总和。因此，源码意思是，将子元素总和按照长度均匀划分，每份大小为k，然后子元素 node 按照大小比例乘以k获得实际位置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default function(parent, x0, y0, x1, y1) {
  var nodes = parent.children,
      node,
      i = -1,
      n = nodes.length,
      k = parent.value &amp;amp;&amp;amp; (x1 - x0) / parent.value;

  while (++i &amp;lt; n) {
    node = nodes[i], node.y0 = y0, node.y1 = y1;
    node.x0 = x0, node.x1 = x0 += node.value * k;
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;treemapSliceDice&lt;/h3&gt;
&lt;p&gt;parent.depth 即树的深度，每次递归深度+1。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default function(parent, x0, y0, x1, y1) {
  (parent.depth &amp;amp; 1 ? slice : dice)(parent, x0, y0, x1, y1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;treemapBinary&lt;/h3&gt;
&lt;p&gt;核心思路是找到一条分界线，让左右两边值相近。
思考：对于多个并列的组件，可以用 n-ary 划分？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default function(parent, x0, y0, x1, y1) {
  var nodes = parent.children,
      i, n = nodes.length,
      sum, sums = new Array(n + 1);

  // 前缀和
  for (sums[0] = sum = i = 0; i &amp;lt; n; ++i) {
    sums[i + 1] = sum += nodes[i].value;
  }

  partition(0, n, parent.value, x0, y0, x1, y1);

  function partition(i, j, value, x0, y0, x1, y1) {
    // 如果只有一个元素，该node填充整片区域
    if (i &amp;gt;= j - 1) {
      var node = nodes[i];
      node.x0 = x0, node.y0 = y0;
      node.x1 = x1, node.y1 = y1;
      return;
    }

    // 此时sum的偏移量
    var valueOffset = sums[i],
        // 找到一个分割点k，使得在这个点左边的所有节点的值的总和接近父节点值的一半
        valueTarget = (value / 2) + valueOffset,
        k = i + 1,
        hi = j - 1;

    while (k &amp;lt; hi) {
      var mid = k + hi &amp;gt;&amp;gt;&amp;gt; 1;
      if (sums[mid] &amp;lt; valueTarget) k = mid + 1;
      else hi = mid;
    }

    if ((valueTarget - sums[k - 1]) &amp;lt; (sums[k] - valueTarget) &amp;amp;&amp;amp; i + 1 &amp;lt; k) --k;

    // 一旦找到分割点，函数会计算左侧和右侧子节点的值，并根据父节点矩形的方向（宽度大于高度或高度大于宽度），沿着较长的边将矩形分为两部分。
    var valueLeft = sums[k] - valueOffset,
        valueRight = value - valueLeft;

    // partition函数递归地对左侧和右侧的子区域进行相同的操作，直到所有的子节点都被分配了一个矩形区域。
    if ((x1 - x0) &amp;gt; (y1 - y0)) {
      var xk = value ? (x0 * valueRight + x1 * valueLeft) / value : x1;
      partition(i, k, valueLeft, x0, y0, xk, y1);
      partition(k, j, valueRight, xk, y0, x1, y1);
    } else {
      var yk = value ? (y0 * valueRight + y1 * valueLeft) / value : y1;
      partition(i, k, valueLeft, x0, y0, x1, yk);
      partition(k, j, valueRight, x0, yk, x1, y1);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;treemapSquarify&lt;/h3&gt;
&lt;p&gt;比较主流的分块算法，通过设置一个长宽比，使得每个矩形的长宽比尽可能接近。
核心在于，每次迭代都会计算当前行的最小宽高比，然后添加节点，直到宽高比不再改善。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function squarifyRatio(ratio, parent, x0, y0, x1, y1) {
  var rows = [], // 初始化行数组，用于存储处理结果
      nodes = parent.children, // 获取父节点的子节点数组
      row, // 定义一个变量用于存储当前行
      nodeValue, // 定义一个变量用于存储节点的值
      i0 = 0, // 初始化索引i0，表示当前行起始节点的索引
      i1 = 0, // 初始化索引i1，表示当前行结束节点的索引
      n = nodes.length, // 节点数组长度
      dx, dy, // 定义dx和dy，表示区域的宽度和高度
      value = parent.value, // 父节点的总值
      sumValue, // 行内节点值的总和
      minValue, // 行内节点值的最小值
      maxValue, // 行内节点值的最大值
      newRatio, // 新的宽高比
      minRatio, // 最小的宽高比
      alpha, // 临时变量，用于计算比例
      beta; // 临时变量，用于计算比例

  // 遍历所有的节点
  while (i0 &amp;lt; n) {
    dx = x1 - x0, dy = y1 - y0; // 计算当前区域的宽度和高度

    // 查找下一个非空节点
    do sumValue = nodes[i1++].value; while (!sumValue &amp;amp;&amp;amp; i1 &amp;lt; n);
    minValue = maxValue = sumValue; // 初始化最小值和最大值为第一个节点的值
    alpha = Math.max(dy / dx, dx / dy) / (value * ratio); // 计算alpha值，用于比例调整
    beta = sumValue * sumValue * alpha; // 计算beta值，用于比例调整
    minRatio = Math.max(maxValue / beta, beta / minValue); // 计算最小宽高比

    // 继续添加节点，直到宽高比不再改善
    for (; i1 &amp;lt; n; ++i1) {
      sumValue += nodeValue = nodes[i1].value; // 累加节点值到总和
      if (nodeValue &amp;lt; minValue) minValue = nodeValue; // 更新最小值
      if (nodeValue &amp;gt; maxValue) maxValue = nodeValue; // 更新最大值
      beta = sumValue * sumValue * alpha; // 重新计算beta
      newRatio = Math.max(maxValue / beta, beta / minValue); // 计算新的宽高比
      if (newRatio &amp;gt; minRatio) { sumValue -= nodeValue; break; } // 如果新宽高比大于最小宽高比，结束循环
      minRatio = newRatio; // 更新最小宽高比
    }

    // 定位并记录行的方向
    rows.push(row = {value: sumValue, dice: dx &amp;lt; dy, children: nodes.slice(i0, i1)});
    if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += dy * sumValue / value : y1); // 如果行是垂直的，调用treemapDice
    else treemapSlice(row, x0, y0, value ? x0 += dx * sumValue / value : x1, y1); // 如果行是水平的，调用treemapSlice
    value -= sumValue, i0 = i1; // 从总值中减去当前行的值，并更新i0为下一行的起始索引
  }

  return rows; // 返回所有行的数组
}

// 定义并导出一个函数，接受一个比例值，返回一个平铺函数
export default (function custom(ratio) {

  function squarify(parent, x0, y0, x1, y1) {
    squarifyRatio(ratio, parent, x0, y0, x1, y1); // 使用指定的比例值来平铺矩形
  }

  // 设置比例值的方法
  squarify.ratio = function(x) {
    return custom((x = +x) &amp;gt; 1 ? x : 1); // 如果输入的x大于1，则使用x，否则使用1
  };

  return squarify; // 返回平铺函数
})(phi); // 使用默认的黄金分割比例phi来初始化
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>书评 | 《我在北京送快递》</title><link>https://www.ender-wiggin.com/posts/review-beijing-express/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-beijing-express/</guid><description> </description><pubDate>Thu, 28 Dec 2023 22:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;p&gt;前段时间读《马斯克传》的时候，发现自己相较于关注马斯克作为领导者的雄韬伟略或者独断专权，更感兴趣的是作为底下的员工应该如何在高压下权衡事业与生活。因此当我看到《我在北京送快递》的时候感觉正合心意，也期待可以从书中看到另一面现实。&lt;/p&gt;
&lt;p&gt;这本书和我预先设想的内容有轻微出入，不过依然具有极具个人色彩的独特性。我归纳为以下三点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;工作的多样性&lt;/strong&gt;。这里的多样性不止工作类别的多样性（售货员、保安），还有社会阶级上的多样性。作者既当过快递员这种纯粹的无产劳动者，也从事过服务业以及个体经营户，也有过小规模的创业，一次工作内容某种意义上来说涵盖了多个阶级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;行文的平实性&lt;/strong&gt;。平实也分为三个方面：一是文字上的平铺直叙，因此也被部分人批评为流水账。我个人认为流水账也是一种行文风格，更何况作者是刻意为之的平铺直叙，因此这更像是一个特点而非缺点。二是情绪上的平淡，作者有着二十年的工作经历，心境上也从刚工作时的天真稚嫩变成了一种大理隐居式的平静洒脱。我正处在作者笔下那个无知幼稚的时期，如果对于作者久经风霜后的生活态度评头论足显然有些大言不惭了，因此阅读过程中一直当做在听一位前辈讲故事聊天，阅读体验也比较轻松愉悦。三是政治上的平和，能看出来作者旁敲侧击了诸多社会问题但并没有延伸，而是通过戏谑自己后知后觉或者心态转变的方式点到为止。当下社会对于政治有两种极端思维，一种听到政治关键词就避之不及并念叨“莫谈政治”，而另一种见到什么社会问题都会搬出一套阶级斗争论大肆批判。作者采取了一种成熟且温和的叙述方式旁敲侧击，同时将读者注意力从政治阶级上的发散转移到聚焦个体的生命力与复杂性，我认为对于初入社会的青年以及酷爱宏观叙事但对社会一无所知的学生大有裨益。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内容的聚焦性&lt;/strong&gt;。这本书写的是作者工作经历，因此几乎没有任何工作经历之外的内容，比如作者的爱情经历、兴趣爱好（写作本身算是职业的一部分了）。描写人物也是如此，都是聚焦于人物本身，没有先验预设刻板印象，也没有后验总结归纳人物特质。一个人就是一个人，他可以是一位业务能力强但是偷窃店内设备的打工者，也可以是一位毫无经营才能但是待人友善的管理者。作者本身更是如此，既可以是任劳任怨的服务生，也可以是抢占艺校女生生意的商人。作者试图呈现每个个体本身性格与行为的多样性，而不是对多个人进行总结提炼出某些类别上的“共性”。这种一以贯之的聚焦性与作者的平实语言相辅相成，弱化了社会现实背后激烈的矛盾，而是以一种豁达的人生态度轻松乐观的呈现，当然其中暗含的苦闷读者们也能切实感受到。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;书中篇幅最长的物流公司经历真实且残酷：杂乱的工作环境、零和博弈的同事竞争、形同虚设的劳动法，以及精通PUA的领导，描绘了一幅弱小螺丝钉在宏大资本机器中孤立无援的景象。
其中让我感触最深的是人与人之间的冷漠与疏离感。一方面员工之间因为任务分配问题会陷入零和博弈，而轻微的地位差距（比如一个人资历更老）又会加剧分配的不平等。另一方面在员工所受到的压迫与不公中，一部分只能自认倒霉，但另一部分会以迁怒的方式扩散到其它人身上，比如收快递的居民以及路上的行人。试想，如果每个人都带有这种扩散性的情绪宣泄，那么社会的水面会泛起无尽的涟漪。&lt;/p&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;世界上只有一种真正的英雄主义，就是认清了生活的真相后还依然热爱它&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果说这本书有什么缺点的话，我个人觉得在最后一章中体现得比较具体，即作者将工作与自由二元对立。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;其实我想说的自由，是一种建立在高度发展的自我意识上的个人追求和自我实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这当然也不是作者的问题，他在书中也提到了二者结合的可遇不可求，甚至从世界上大多数人的经历上来看作者的体悟是恰如其分且令人动容的，但是我心中那一块理想主义的角落仍有些自己的幻想。
正如作者在书中开头所说，没什么人会对于快递员这个职业真正热爱，大多数人都只是为了谋生罢了。这一点在作者的其它很多份工作上也有体现，而作者从事的漫画助手或者美编等工作，虽然说有出于真正热爱的成分但是实质的工作与他的理想还是有出入。
但是我认为在现在这个时刻来看，作者选择作为一名作家——虽然他在书中表示“不能通过写作谋生是一件值得庆幸的事”——&lt;strong&gt;确实&lt;/strong&gt;将工作与自由很好的结合起来了。也许作者未来确实可以靠稿费自给自足，并实现他心中的那个自由。
我想到了病房中作画的“渴望生活”的梵高，最近也在网上看到了不少将开源作为工作与人生爱好的程序员，现在这个年纪的我还是相信这种工作与自由相得益彰的可能性，也希望未来的我回头看也还是这种天真的想法。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第14章-DOM</title><link>https://www.ender-wiggin.com/posts/note-js-14/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-14/</guid><description> </description><pubDate>Sat, 09 Dec 2023 21:02:00 GMT</pubDate><content:encoded>&lt;h1&gt;DOM&lt;/h1&gt;
&lt;p&gt;文档对象模型（DOM, Document Object Model）是HTML和XML文档的编程接口。DOM表示由多层节点构成的文档，通过它开发者可以添加、删除和修改页面的各个部分。&lt;/p&gt;
&lt;h2&gt;节点层级&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;document&lt;/code&gt;节点表示每个文档的根节点。文档元素（&lt;code&gt;documentElement&lt;/code&gt;）是文档最外层的元素，所有其他元素都存在于这个元素之内。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在HTML页面中，文档元素始终是&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;元素。&lt;/li&gt;
&lt;li&gt;在XML文档中，则没有这样预定义的元素，任何元素都可能成为文档元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Node 类型&lt;/h2&gt;
&lt;p&gt;Node 一共有12个类型，但是对于浏览器而言主要使用的就是 &lt;code&gt;Node.ELEMENT_NODE&lt;/code&gt; 和 &lt;code&gt;Node.TEXT_NODE&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;nodeName与nodeValue&lt;/h3&gt;
&lt;p&gt;对元素而言，n&lt;code&gt;odeName&lt;/code&gt;始终等于元素的标签名，而&lt;code&gt;nodeValue&lt;/code&gt;则始终为&lt;code&gt;null&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;节点关系&lt;/h3&gt;
&lt;p&gt;每个节点都有一个&lt;code&gt;childNodes&lt;/code&gt;属性，其中包含一个&lt;code&gt;NodeList&lt;/code&gt;的实例。&lt;code&gt;NodeList&lt;/code&gt;是一个类数组对象，用于存储可以按位置存取的有序节点。
注意，&lt;code&gt;NodeList&lt;/code&gt;&lt;strong&gt;不是&lt;/strong&gt;Array的&lt;code&gt;实例&lt;/code&gt;，但可以使用中括号访问它的值，而且它也有&lt;code&gt;length&lt;/code&gt;属性。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NodeList&lt;/code&gt;其实是一个对DOM结构的查询，因此DOM结构的变化会自动地在NodeList中反映出来。因此NodeList是实时的活动对象，而不是第一次访问时所获得内容的快照。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;和数组的主要区别（以HTMLCollection为例）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HTMLCollection&lt;/code&gt;是与DOM绑定的，它的&quot;动态&quot;特性是由浏览器的DOM实现提供的，确保&lt;code&gt;HTMLCollection&lt;/code&gt;始终映射到当前文档的状态。&lt;/li&gt;
&lt;li&gt;普通数组是JavaScript的数据结构，它不会自动与DOM的状态同步。数组的内容只有在开发者执行显式操作时才会改变。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    let firstChild = someNode.childNodes[0];
    let secondChild = someNode.childNodes.item(1);
    let count = someNode.childNodes.length;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;code&gt;Array.from()&lt;/code&gt;静态方法或者&lt;code&gt;Array.prototype.slice()&lt;/code&gt;可以把&lt;code&gt;NodeList&lt;/code&gt;对象转换为数组。&lt;/p&gt;
&lt;p&gt;每个节点都有一个&lt;code&gt;parentNode&lt;/code&gt;属性，指向其DOM树中的父元素。
此外，&lt;code&gt;childNodes&lt;/code&gt;列表中的每个节点都是同一列表中其他节点的同胞节点。而使用&lt;code&gt;previousSibling&lt;/code&gt;和&lt;code&gt;nextSibling&lt;/code&gt;可以在这个列表的节点间导航（首位的前后都是&lt;code&gt;null&lt;/code&gt;，参考链表）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hasChildNodes()&lt;/code&gt;这个方法如果返回true则说明节点有一个或多个子节点。&lt;/p&gt;
&lt;p&gt;因为所有关系指针都是只读的，所以DOM又提供了一些操纵节点的方法。&lt;/p&gt;
&lt;p&gt;以下几个方法必须先后去父节点，才可以操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;appendChild()&lt;/code&gt;，用于在childNodes列表末尾添加节点。如果把文档中已经存在的节点传给appendChild()，则这个节点会从之前的位置被&lt;strong&gt;转移&lt;/strong&gt;到新位置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;insertBefore()&lt;/code&gt;插入的节点会变成参照节点的前一个同胞节点，并被返回。如果参照节点是null，则insertBefore()与appendChild()效果相同。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replaceChild()&lt;/code&gt;方法接收两个参数：要插入的节点和要替换的节点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;removeChild()&lt;/code&gt;方法接收一个参数，即要移除的节点。被移除的节点会被返回。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几个方法对于所有节点类型都有效：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cloneNode()&lt;/code&gt;，会返回与调用它的节点一模一样的节点。&lt;code&gt;cloneNode()&lt;/code&gt;方法接收一个布尔值参数，表示是否深复制。在传入&lt;code&gt;true&lt;/code&gt;参数时，会进行深复制，即复制节点及其整个子DOM树。如果传入&lt;code&gt;false&lt;/code&gt;，则只会复制调用该方法的节点。这个复制的节点&lt;strong&gt;没有&lt;/strong&gt;指定父节点。
注意&lt;code&gt;cloneNode()&lt;/code&gt;方法不会复制添加到DOM节点的JavaScript属性，比如事件处理程序。这个方法&lt;strong&gt;只复制HTML属性&lt;/strong&gt;，以及可选地复制子节点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;normalize()&lt;/code&gt;这个方法唯一的任务就是处理文档子树中的文本节点。由于解析器实现的差异或DOM操作等原因，可能会出现并不包含文本的文本节点，或者文本节点之间互为同胞关系。在节点上调用normalize()方法会检测这个节点的所有后代，从中搜索上述两种情形。如果发现空文本节点，则将其删除；如果两个同胞节点是相邻的，则将其合并为一个文本节点。&lt;/p&gt;
&lt;h2&gt;Document类型&lt;/h2&gt;
&lt;p&gt;Document类型是JavaScript中表示文档节点的类型。在浏览器中，文档对象document是&lt;code&gt;HTMLDocument&lt;/code&gt;的实例（HTMLDocument继承Document），表示整个HTML页面。&lt;/p&gt;
&lt;p&gt;document是window对象的属性，因此是一个全局对象。
Document类型的节点有以下特征：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nodeType等于9；&lt;/li&gt;
&lt;li&gt;nodeName值为&quot;#document&quot;；&lt;/li&gt;
&lt;li&gt;nodeValue值为null；&lt;/li&gt;
&lt;li&gt;parentNode值为null；&lt;/li&gt;
&lt;li&gt;ownerDocument值为null；&lt;/li&gt;
&lt;li&gt;子节点可以是DocumentType（最多一个）、Element（最多一个）、ProcessingInstruction或Comment类型&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;文档子节点&lt;/h3&gt;
&lt;p&gt;DOM规范规定Document节点的子节点可以是DocumentType、Element、Processing-Instruction或Comment。&lt;/p&gt;
&lt;p&gt;虽然&lt;code&gt;document.childNodes&lt;/code&gt;中有所有节点，但是下面的属性可以快速访问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;documentElement&lt;/code&gt;属性，始终指向HTML页面中的&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;body&lt;/code&gt; 属性指向&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;document.doctype&lt;/code&gt; 可以获取 &lt;code&gt;&amp;lt;!doctype&amp;gt;&lt;/code&gt; 部分的信息。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;元素是 &lt;code&gt;HTMLHtmlElement&lt;/code&gt; 的实例&lt;/strong&gt;：这意味着某个 DOM 元素是 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 标签对应的对象。在 JavaScript 中，每个标签都有一个对应的对象类型，&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 标签对应的对象类型是 &lt;code&gt;HTMLHtmlElement&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;HTMLHtmlElement&lt;/code&gt; 继承自 &lt;code&gt;HTMLElement&lt;/code&gt;&lt;/strong&gt;：&lt;code&gt;HTMLElement&lt;/code&gt; 是所有 HTML 元素的基类。所有特定的 HTML 元素，如 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 等，都是 &lt;code&gt;HTMLElement&lt;/code&gt; 的扩展。因此，&lt;code&gt;HTMLHtmlElement&lt;/code&gt; 具有 &lt;code&gt;HTMLElement&lt;/code&gt; 的所有属性和方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;HTMLElement&lt;/code&gt; 继承自 &lt;code&gt;Element&lt;/code&gt;&lt;/strong&gt;：&lt;code&gt;Element&lt;/code&gt; 是所有 DOM 元素的基类，不仅包括 HTML 元素，还包括例如 SVG 元素。&lt;code&gt;Element&lt;/code&gt; 提供了所有 DOM 元素共有的基础功能，比如属性和方法来操作它们的子节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTML文档可以包含子节点，但不能多于一个&lt;/strong&gt;：在一个 HTML 文档中，&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 元素是顶层元素，也就是说，它是文档树的根节点。它可以包含 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 和 &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 这两个子节点，但在规范的 HTML 文档中，不应该有多个 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 元素。因此，虽然 &lt;code&gt;Element&lt;/code&gt; 类型的对象可以有多个子节点，但对于 &lt;code&gt;HTMLHtmlElement&lt;/code&gt;（即 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 元素），在一个合规的 HTML 文档中，它作为根节点，只能有一个实例。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;文档信息&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt;包含&lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;元素中的文本，通常显示在浏览器窗口或标签页的标题栏。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;URL&lt;/code&gt;包含当前页面的完整URL（地址栏中的URL）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;domain&lt;/code&gt;包含页面的域名（&lt;strong&gt;可设置&lt;/strong&gt;为父域名的值，可以用于&lt;code&gt;&amp;lt;frame&amp;gt;/&amp;lt;iframe&amp;gt;&lt;/code&gt;跨子域）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;referrer&lt;/code&gt;包含链接到当前页面的那个页面的URL。如果当前页面没有来源，则&lt;code&gt;referrer&lt;/code&gt;属性包含空字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;定位元素&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;getElementById()&lt;/code&gt;方法接收一个参数，即要获取元素的ID，如果找到了则返回这个元素，如果没找到则返回null。多个同id元素只返回第一个。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;getElementsByTagName()&lt;/code&gt;方法接收一个参数，即要获取元素的标签名（&lt;strong&gt;HTML中不区分大小写&lt;/strong&gt;），返回包含零个或多个元素的NodeList。在HTML文档中，这个方法返回一个&lt;code&gt;HTMLCollection&lt;/code&gt;对象（也是实时列表）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let allElements = document.getElementsByTagName(&quot;*&quot;);
let images = document.getElementsByTagName(&quot;img&quot;);

alert(images.length);         // 图片数量
alert(images[0].src);         // 第一张图片的src属性
alert(images.item(0).src);   // 同上
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;HTMLCollection&lt;/code&gt;对象还有一个额外的方法&lt;code&gt;namedItem()&lt;/code&gt;，可通过标签的name属性取得某一项的引用。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img src=&quot;myimage.gif&quot; name=&quot;myImage&quot;&amp;gt;

let myImage = images.namedItem(&quot;myImage&quot;);
let myImage = images[&quot;myImage&quot;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;HTMLCollection&lt;/code&gt;不是数组，主要有如下几个原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;历史上，Web API 和 JS 早期是两套环境与标准。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTMLCollection&lt;/code&gt; 被设计为一个实时的、&lt;strong&gt;动态&lt;/strong&gt;反映文档状态的集合。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;HTMLCollection&lt;/code&gt; 是一个数组，每次文档更新时，都需要创建一个新的数组来反映这些变化，这可能会导致性能问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;getElementsByName()&lt;/code&gt;返回具有给定&lt;code&gt;name&lt;/code&gt;属性的所有元素。最常用于单选按钮 radio，因为同一字段的单选按钮必须具有相同的&lt;code&gt;name&lt;/code&gt;属性才能确保把正确的值发送给服务器。&lt;code&gt;getElementsByName()&lt;/code&gt;方法也返回&lt;code&gt;HTMLCollection&lt;/code&gt;。不过在这种情况下，&lt;code&gt;namedItem()&lt;/code&gt;方法&lt;strong&gt;只会取得第一项&lt;/strong&gt;（因为所有项的name属性都一样）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;特殊集合&lt;/h3&gt;
&lt;p&gt;document对象上还暴露了几个特殊集合，这些集合也都是HTMLCollection的实例。这些集合是访问文档中公共部分的快捷方式。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;document.anchors包含文档中所有带name属性的&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;元素。&lt;/li&gt;
&lt;li&gt;&lt;s&gt;&lt;code&gt;document.applets&lt;/code&gt;包含文档中所有&lt;code&gt;&amp;lt;applet&amp;gt;&lt;/code&gt;元素（因为&lt;code&gt;&amp;lt;applet&amp;gt;&lt;/code&gt;元素已经不建议使用，所以这个集合已经废弃）。&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;document.forms&lt;/code&gt;包含文档中所有&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;元素（与&lt;code&gt;document.getElementsByTagName (&quot;form&quot;)&lt;/code&gt;返回的结果相同）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;document.images&lt;/code&gt;包含文档中所有&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;元素（与&lt;code&gt;document.getElementsByTagName (&quot;img&quot;)&lt;/code&gt;返回的结果相同）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;document.links&lt;/code&gt;包含文档中所有带href属性的&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;文档写入&lt;/h3&gt;
&lt;p&gt;网页输出流中写入内容对应4个方法：&lt;code&gt;write()&lt;/code&gt;、&lt;code&gt;writeln()&lt;/code&gt;、&lt;code&gt;open()&lt;/code&gt;和&lt;code&gt;close()&lt;/code&gt;。其中，&lt;code&gt;write()&lt;/code&gt;和&lt;code&gt;writeln()&lt;/code&gt;方法都接收一个字符串参数，可以将这个字符串写入网页中。&lt;code&gt;write()&lt;/code&gt;简单地写入文本，而&lt;code&gt;writeln()&lt;/code&gt;还会在字符串末尾追加一个换行符。&lt;/p&gt;
&lt;p&gt;这两个方法可以用来在&lt;strong&gt;页面加载期间&lt;/strong&gt;向页面中动态添加内容。
如果是在页面加载完之后再调用&lt;code&gt;document.write()&lt;/code&gt;，则输出的内容会重写整个页面。&lt;/p&gt;
&lt;h2&gt;Element类型&lt;/h2&gt;
&lt;p&gt;Element类型就是Web开发中最常用的类型了。Element表示XML或HTML元素，对外暴露出访问元素标签名、子节点和属性的能力。特点如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nodeType等于1；&lt;/li&gt;
&lt;li&gt;nodeName值为元素的标签名；&lt;/li&gt;
&lt;li&gt;nodeValue值为null；&lt;/li&gt;
&lt;li&gt;parentNode值为Document或Element对象；&lt;/li&gt;
&lt;li&gt;子节点可以是Element、Text、Comment、ProcessingInstruction、CDATASection、EntityReference类型。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以通过nodeName或tagName属性来获取元素的标签名（&lt;strong&gt;大写格式&lt;/strong&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;myDiv&quot;&amp;gt;&amp;lt;/div&amp;gt;

let div = document.getElementById(&quot;myDiv&quot;);
alert(div.tagName); // &quot;DIV&quot;
alert(div.tagName == div.nodeName); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HTML元素&lt;/h3&gt;
&lt;p&gt;HTMLElement直接继承Element并增加了一些属性。每个属性都对应下列属性之一，它们是所有HTML元素上都有的标准属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;id，元素在文档中的唯一标识符；&lt;/li&gt;
&lt;li&gt;title，包含元素的额外信息，通常以提示条形式展示；&lt;/li&gt;
&lt;li&gt;lang，元素内容的语言代码（很少用）；&lt;/li&gt;
&lt;li&gt;dir，语言的书写方向（&quot;ltr&quot;表示从左到右，&quot;rtl&quot;表示从右到左，同样很少用）；&lt;/li&gt;
&lt;li&gt;className，相当于class属性，用于指定元素的CSS类（因为class是ECMAScript关键字，所以不能直接用这个名字）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所有HTML元素都是HTMLElement或其子类型的实例。&lt;/p&gt;
&lt;h3&gt;取得属性&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;getAttribute()&lt;/code&gt;、&lt;code&gt;setAttribute()&lt;/code&gt;和&lt;code&gt;removeAttribute()&lt;/code&gt;这些方法主要用于操纵属性，包括在HTMLElement类型上定义的属性。
注意，如果要获取 &lt;code&gt;class&lt;/code&gt;，要使用 &lt;code&gt;div.getAttribute(&apos;class&apos;)&lt;/code&gt; 而不是 &lt;code&gt;className&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果在 HTML中自定义了属性，也可以通过这种方式获得值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;myDiv&quot; data-HELLO=&quot;hello!&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;let value = div.getAttribute(&quot;data-hello&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;根据HTML5规范的要求，自定义属性名应该前缀&lt;code&gt;data-&lt;/code&gt;以方便验证。&lt;/li&gt;
&lt;li&gt;此外，属性名不区分大小写。&lt;/li&gt;
&lt;li&gt;元素的公认属性同时也是DOM对象的属性，但是自定义属性不会成为DOM对象属性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;几个DOM属性 !== getAttribute() 的特殊情况&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;style&lt;/code&gt;，&lt;code&gt;getAttribute()&lt;/code&gt; 返回 CSS 字符串而 &lt;code&gt;DOM.style&lt;/code&gt;返回 &lt;code&gt;CSSStyleDeclaration&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;事件处理程序，比如 &lt;code&gt;onclick&lt;/code&gt;，&lt;code&gt;getAttribute&lt;/code&gt; 返回字符串而DOM返回的是函数。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;综上，一般情况都使用DOM属性，除非是需要获取自定义属性。&lt;/p&gt;
&lt;h3&gt;设置属性&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;setAttribute()&lt;/code&gt;接收两个参数：要设置的属性名和属性的值，不存在就直接创建，存在则覆盖。
设置的属性名会规范为&lt;strong&gt;小写形式&lt;/strong&gt;，因此&quot;ID&quot;会变成&quot;id&quot;。&lt;/p&gt;
&lt;p&gt;当然也可以直接在DOM对象上赋值，但是和上面的&lt;code&gt;getAttribute()&lt;/code&gt;类似，自定义属性不会自动加入DOM对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;div.mycolor = &quot;red&quot;;
alert(div.getAttribute(&quot;mycolor&quot;)); // null（IE除外）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;removeAttribute()&lt;/code&gt;用于从元素中删除属性（整个属性完全从元素中去掉）。&lt;/p&gt;
&lt;h3&gt;attributes属性&lt;/h3&gt;
&lt;p&gt;使用较为麻烦，仅做介绍。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Element&lt;/code&gt;类型是唯一使用&lt;code&gt;attributes&lt;/code&gt;属性的DOM节点类型。&lt;code&gt;attributes&lt;/code&gt;属性包含一个&lt;code&gt;NamedNodeMap&lt;/code&gt;实例，是一个类似&lt;code&gt;NodeList&lt;/code&gt;的“实时”集合。元素的每个属性都表示为一个&lt;code&gt;Attr&lt;/code&gt;节点，并保存在这个&lt;code&gt;NamedNodeMap&lt;/code&gt;对象中。&lt;code&gt;NamedNodeMap&lt;/code&gt;对象包含下列方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getNamedItem(name)，返回nodeName属性等于name的节点；&lt;/li&gt;
&lt;li&gt;removeNamedItem(name)，删除nodeName属性等于name的节点；&lt;/li&gt;
&lt;li&gt;setNamedItem(node)，向列表中添加node节点，以其nodeName为索引；&lt;/li&gt;
&lt;li&gt;item(pos)，返回索引位置pos处的节点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;attributes属性中的每个节点的nodeName是对应属性的名字，nodeValue是属性的值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let id = element.attributes.getNamedItem(&quot;id&quot;).nodeValue;
let id = element.attributes[&quot;id&quot;].nodeValue; // 简写
let oldAttr = element.attributes.removeNamedItem(&quot;id&quot;); // 删除
element.attributes.setNamedItem(newAttr); // 非常少用，接受的是一个新的Attr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;attributes属性最有用的场景是需要迭代元素上所有属性的时候。这时候往往是要把DOM结构序列化为XML或HTML字符串。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function outputAttributes(element) {
    let pairs = [];
    for (let i = 0, len = element.attributes.length; i &amp;lt; len; ++i) {
     const attribute = element.attributes[i];
     pairs.push(`${attribute.nodeName}=&quot;${attribute.nodeValue}&quot;`);
    }
    return pairs.join(&quot; &quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建元素&lt;/h3&gt;
&lt;p&gt;可以使用&lt;code&gt;document.createElement()&lt;/code&gt;方法创建新元素。这个方法接收一个参数，即要创建元素的标签名。
使用该方法创建新元素的同时也会将其&lt;code&gt;ownerDocument&lt;/code&gt;属性设置为&lt;code&gt;document&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在新元素上设置这些属性只会附加信息。因为这个元素还没有添加到文档树，所以不会影响浏览器显示。要把元素添加到文档树，可以使用appendChild()、insertBefore()或replaceChild()。&lt;/p&gt;
&lt;p&gt;元素被添加到文档树之后，浏览器会立即将其渲染出来。之后再对这个元素所做的任何修改，都会立即在浏览器中反映出来。&lt;/p&gt;
&lt;h3&gt;元素后代&lt;/h3&gt;
&lt;p&gt;childNodes属性包含元素所有的子节点，这些子节点可能是其他元素、文本节点、注释或处理指令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;ul id=&quot;myList&quot;&amp;gt;
      &amp;lt;li&amp;gt;Item 1&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;Item 2&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;Item 3&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在解析以上代码时，&lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;元素会包含7个子元素，其中3个是&lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;元素，还有4个Text节点（表示&lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;元素&lt;strong&gt;周围的空格&lt;/strong&gt;）。
如果把元素之间的空格删掉，则所有浏览器都会返回同样数量的子节点。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    for (let i = 0, len = element.childNodes.length; i &amp;lt; len; ++i) {
      if (element.childNodes[i].nodeType == 1) {
        // 执行某个操作
      }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如可以遍历某个元素的子节点，并且只在nodeType等于1（即Element节点）时执行某个操作。&lt;/p&gt;
&lt;p&gt;注意：要取得某个元素的子节点和其他后代节点，可以使用元素的&lt;code&gt;getElementsByTagName()&lt;/code&gt;方法。在元素上调用这个方法与在文档上调用是一样的，只不过搜索范围限制在当前元素之内，即只会返回当前元素的后代。&lt;/p&gt;
&lt;h2&gt;Text类型&lt;/h2&gt;
&lt;p&gt;nodeType === 3 的 Node，文本值为nodeValue的值。方法如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;appendData（text），向节点末尾添加文本text；&lt;/li&gt;
&lt;li&gt;deleteData（offset, count），从位置offset开始删除count个字符；&lt;/li&gt;
&lt;li&gt;insertData（offset, text），在位置offset插入text；&lt;/li&gt;
&lt;li&gt;replaceData（offset, count, text），用text替换从位置offset到offset+count的文本；&lt;/li&gt;
&lt;li&gt;splitText（offset），在位置offset将当前文本节点拆分为两个文本节点；&lt;/li&gt;
&lt;li&gt;substringData（offset, count），提取从位置offset到offset+count的文本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HTML或XML代码（取决于文档类型）会被转换成实体编码，即小于号、大于号或引号会被转义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 输出为&quot;Some &amp;amp;lt; strong&amp;amp;gt; other&amp;amp;lt; /strong&amp;amp;gt; message&quot;
    div.firstChild.nodeValue = &quot;Some &amp;lt;strong&amp;gt;other&amp;lt;/strong&amp;gt; message&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;let element = document.createElement(&quot;div&quot;); // 创建元素
element.className = &quot;message&quot;;
let textNode = document.createTextNode(&quot;Hello world! &quot;); // 创建文本节点
element.appendChild(textNode); // 添加节点
document.body.appendChild(element); // 添加到文档树

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般一个元素只有一个文本子节点（浏览器解析文档时），但也可以有多个。两个文本节点直接的文本不会包含空格。如果要规范化这种情况，可以使用 Node 上的方法 &lt;code&gt;element.normalize()&lt;/code&gt;，可以拼接字符串。&lt;/p&gt;
&lt;p&gt;Text类型也定义了一个与&lt;code&gt;normalize()&lt;/code&gt;相反的方法——&lt;code&gt;splitText()&lt;/code&gt;。这个方法可以在指定的偏移位置拆分nodeValue，将一个文本节点拆分成两个文本节点。&lt;/p&gt;
&lt;h2&gt;Comment类型&lt;/h2&gt;
&lt;p&gt;nodeType === 8 的 Node，注释值为nodeValue的值。&lt;/p&gt;
&lt;p&gt;Comment类型与Text类型继承同一个基类（&lt;code&gt;CharacterData&lt;/code&gt;），因此拥有除splitText()之外Text节点所有的字符串操作方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;myDiv&quot;&amp;gt;
	&amp;lt;! -- A comment --&amp;gt;
&amp;lt;/div&amp;gt;

// 注释节点可以作为父节点的子节点来访问。
let div = document.getElementById(&quot;myDiv&quot;);
let comment = div.firstChild;
alert(comment.data); // &quot;A comment&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;let comment = document.createComment(&quot;A comment&quot;); // 创建注释，几乎没用
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一些其它类型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CDATASection类型表示XML中特有的CDATA区块。&lt;/li&gt;
&lt;li&gt;DocumentType对象，&lt;strong&gt;nodeName值为文档类型的名称&lt;/strong&gt;，nodeValue值为null；DocumentType对象保存在&lt;code&gt;document.doctype&lt;/code&gt;属性中。&lt;/li&gt;
&lt;li&gt;Attr类型，虽然是节点但是一般都是使用上面提到的 &lt;code&gt;getAttribute()&lt;/code&gt; 操作。&lt;/li&gt;
&lt;li&gt;DocumentFragment类型，文档片段定义为“轻量级”文档，能够包含和操作节点，却没有完整文档那样额外的消耗。不能直接把文档片段添加到文档。相反，文档片段的作用是&lt;strong&gt;充当其他要被添加到文档的节点的仓库&lt;/strong&gt;（&lt;code&gt;document.createDocumentFragment()&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;let fragment = document.createDocumentFragment(); // 创建文档片段
let ul = document.getElementById(&quot;myList&quot;); // 从文档数中获得节点
for (let i = 0; i &amp;lt; 3; ++i) {
  let li = document.createElement(&quot;li&quot;);
  li.appendChild(document.createTextNode(`Item ${i + 1}`));
  fragment.appendChild(li); // 在 fragment 中插入
}
ul.appendChild(fragment); // 最终一次性插入，避免多次渲染
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;DOM 编程&lt;/h2&gt;
&lt;h3&gt;动态脚本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;foo.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

// 等价于
let script = document.createElement(&quot;script&quot;);
script.src = &quot;foo.js&quot;;
document.body.appendChild(script);

// 可以实现动态重载
function loadScript(url) {
  let script = document.createElement(&quot;script&quot;);
  script.src = url;
  document.body.appendChild(script);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 兼容多种浏览器的函数插入
    function loadScriptString(code){
      var script = document.createElement(&quot;script&quot;);
      script.type = &quot;text/javascript&quot;;
      try {
        script.appendChild(document.createTextNode(code)); // 除了早期IE都支持
      } catch (ex){
        script.text = code; // 除了早期 Safari 都支持
      }
      document.body.appendChild(script);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，通过&lt;code&gt;innerHTML&lt;/code&gt;属性创建的&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;元素永远不会执行（防止 XSS 攻击）。浏览器会尽责地创建&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;元素，以及其中的脚本文本，但解析器会给这个&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;元素打上永不执行的标签。只要是使用&lt;code&gt;innerHTML&lt;/code&gt;创建的&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;元素，以后也没有办法强制其执行。&lt;/p&gt;
&lt;h3&gt;动态样式&lt;/h3&gt;
&lt;p&gt;CSS样式在HTML页面中可以通过两个元素加载。&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;元素用于包含CSS外部文件，而&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;元素用于添加嵌入样式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// link 方式
    function loadStyles(url){
      let link = document.createElement(&quot;link&quot;);
      link.rel = &quot;stylesheet&quot;;
      link.type = &quot;text/css&quot;;
      link.href = url;
      let head = document.getElementsByTagName(&quot;head&quot;)[0];
      head.appendChild(link);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// style 方式
    let style = document.createElement(&quot;style&quot;);
    style.type = &quot;text/css&quot;;
    style.appendChild(document.createTextNode(&quot;body{background-color:red}&quot;));
    let head = document.getElementsByTagName(&quot;head&quot;)[0];
    head.appendChild(style);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;操作表格&lt;/h3&gt;
&lt;p&gt;为了方便创建表格，DOM 给表格元素添加了一些属性和方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 创建表格
    let table = document.createElement(&quot;table&quot;);
    table.border = 1;
    table.width = &quot;100%&quot;;
    // 创建表体
    let tbody = document.createElement(&quot;tbody&quot;);
    table.appendChild(tbody);
    //创建第一行
    tbody.insertRow(0); // 插入一行，放在0的位置
    tbody.rows[0].insertCell(0); // 插入 cell
    tbody.rows[0].cells[0].appendChild(document.createTextNode(&quot;Cell1,1&quot;));
    tbody.rows[0].insertCell(1);
    tbody.rows[0].cells[1].appendChild(document.createTextNode(&quot;Cell2,1&quot;));
    //创建第二行
    tbody.insertRow(1);
    tbody.rows[1].insertCell(0);
    tbody.rows[1].cells[0].appendChild(document.createTextNode(&quot;Cell1,2&quot;));
    tbody.rows[1].insertCell(1);
    tbody.rows[1].cells[1].appendChild(document.createTextNode(&quot;Cell2,2&quot;));
    // 把表格添加到文档主体
    document.body.appendChild(table);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用NodeList&lt;/h3&gt;
&lt;p&gt;理解NodeList对象和相关的NamedNodeMap、HTMLCollection，是理解DOM编程的关键。这3个集合类型都是“实时的”，意味着文档结构的变化会实时地在它们身上反映出来，因此它们的值始终代表最新的状态。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let divs = document.getElementsByTagName(&quot;div&quot;);
for (let i = 0; i &amp;lt; divs.length; ++i){
  let div = document.createElement(&quot;div&quot;);
  document.body.appendChild(div);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;MutationObserver接口&lt;/h2&gt;
&lt;p&gt;使用MutationObserver可以观察整个文档、DOM树的一部分，或某个元素。此外还可以观察元素属性、子节点、文本，或者前三者任意组合的变化。&lt;/p&gt;
&lt;h3&gt;基本用法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    let observer = new MutationObserver(() =&amp;gt; console.log(&apos;DOM was mutated! &apos;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当观测对应元素后&lt;code&gt;observer.observe(document.body, { attributes: true })&lt;/code&gt;，&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;元素上任何属性发生变化都会被这个MutationObserver实例发现，然后就会异步执行注册的回调函数。&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 元素后代的修改或其他非属性修改都不会触发回调进入任务队列（因为上面的例子没有配置，具体配置见下一段）。&lt;/p&gt;
&lt;p&gt;每个回调都会收到一个&lt;code&gt;MutationRecord&lt;/code&gt;实例的数组。&lt;code&gt;MutationRecord&lt;/code&gt;实例包含的信息包括发生了什么变化，以及DOM的哪一部分受到了影响。&lt;/p&gt;
&lt;h4&gt;disconnect()方法&lt;/h4&gt;
&lt;p&gt;同步调用 &lt;code&gt;observer.disconnect()&lt;/code&gt; 之后，不仅会停止此后变化事件的回调，&lt;strong&gt;也会抛弃已经加入任务队列&lt;/strong&gt;要异步执行的回调。&lt;/p&gt;
&lt;p&gt;要想让已经加入任务队列的回调执行，可以使用&lt;code&gt;setTimeout()&lt;/code&gt;让已经入列的回调执行完毕再调用&lt;code&gt;disconnect()&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let observer = new MutationObserver(() =&amp;gt; console.log(&apos;&amp;lt;body&amp;gt; attributes changed&apos;));
    observer.observe(document.body, { attributes: true });
    document.body.className = &apos;foo&apos;;
    setTimeout(()=&amp;gt;{
      observer.disconnect();
      document.body.className = &apos;bar&apos;;
    }, 0);
    // 只会输出 &amp;lt;body&amp;gt; attributeschanged
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;复用MutationObserver&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;: 字符串，表示所记录变化的类型。它可以是以下值之一：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;attributes&quot;&lt;/code&gt;: 表示属性值的变化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;characterData&quot;&lt;/code&gt;: 表示节点文本内容的变化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;childList&quot;&lt;/code&gt;: 表示子节点列表的变化（添加、移除或者更换）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target&lt;/code&gt;: 节点对象（Node），表示发生变化的 DOM 节点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;addedNodes&lt;/code&gt;: 节点列表（NodeList），包含所有被添加的子节点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;removedNodes&lt;/code&gt;: 节点列表（NodeList），包含所有被移除的子节点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;previousSibling&lt;/code&gt;: 节点（Node），表示变化前的前一个兄弟节点。如果没有前一个兄弟节点，则为 &lt;code&gt;null&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nextSibling&lt;/code&gt;: 节点（Node），表示变化后的下一个兄弟节点。如果没有下一个兄弟节点，则为 &lt;code&gt;null&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;attributeName&lt;/code&gt;: 字符串，仅当 &lt;code&gt;type&lt;/code&gt; 是 &lt;code&gt;&quot;attributes&quot;&lt;/code&gt; 时才有值，表示发生变化的属性的名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;attributeNamespace&lt;/code&gt;: 字符串，仅当 &lt;code&gt;type&lt;/code&gt; 是 &lt;code&gt;&quot;attributes&quot;&lt;/code&gt; 时才有值，表示发生变化的属性的命名空间。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oldValue&lt;/code&gt;: 字符串或 &lt;code&gt;null&lt;/code&gt;，根据 &lt;code&gt;type&lt;/code&gt; 的不同而有不同的含义：
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;type&lt;/code&gt; 是 &lt;code&gt;&quot;attributes&quot;&lt;/code&gt;，则表示变化前的属性值。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;type&lt;/code&gt; 是 &lt;code&gt;&quot;characterData&quot;&lt;/code&gt;，则表示变化前的文本节点内容。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;type&lt;/code&gt; 是 &lt;code&gt;&quot;childList&quot;&lt;/code&gt;，则此属性为 &lt;code&gt;null&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let observer = new MutationObserver(
				(mutationRecords) =&amp;gt; console.log(mutationRecords.map((x)=&amp;gt;
x.target)));
// 向页面主体添加两个子节点
let childA = document.createElement(&apos;div&apos;),
	childB = document.createElement(&apos;span&apos;);
document.body.appendChild(childA);
document.body.appendChild(childB);
// 观察两个子节点
observer.observe(childA, {attributes: true});
observer.observe(childB, {attributes: true});
// 修改两个子节点的属性
childA.setAttribute(&apos;foo&apos;, &apos;bar&apos;);
childB.setAttribute(&apos;foo&apos;, &apos;bar&apos;);
//[&amp;lt;div&amp;gt;, &amp;lt;span&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;disconnect()&lt;/code&gt; 之后可以通过 &lt;code&gt;observe() &lt;/code&gt;重用观察者。&lt;/p&gt;
&lt;h3&gt;MutationObserverInit与观察范围&lt;/h3&gt;
&lt;p&gt;MutationObserverInit对象用于控制对目标节点的观察范围。粗略地讲，观察者可以观察的事件包括属性变化、文本变化和子节点变化。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;childList&lt;/code&gt;: 布尔值，如果为 &lt;code&gt;true&lt;/code&gt;，则观察器会观察目标节点的直接子节点的添加或移除。如果使用&lt;code&gt;document.body.insertBefore(document.body.lastChild, documentBody.firstChild)&lt;/code&gt;, 实际上是先删除再增加，所以会触发两次。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;attributes&lt;/code&gt;: 布尔值，如果为 &lt;code&gt;true&lt;/code&gt;，则观察器会观察目标节点的属性变更。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;characterData&lt;/code&gt;: 布尔值，如果为 &lt;code&gt;true&lt;/code&gt;，则观察器会观察目标节点的&lt;strong&gt;文本内容&lt;/strong&gt;变更。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;subtree&lt;/code&gt;: 布尔值，如果为 &lt;code&gt;true&lt;/code&gt;，则除了目标节点外，还会观察目标节点的&lt;strong&gt;所有后代节点&lt;/strong&gt;的变更。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;attributeOldValue&lt;/code&gt;: 布尔值，如果为 &lt;code&gt;true&lt;/code&gt;，且 &lt;code&gt;attributes&lt;/code&gt; 也为 &lt;code&gt;true&lt;/code&gt;，则记录属性变更前的旧值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;characterDataOldValue&lt;/code&gt;: 布尔值，如果为 &lt;code&gt;true&lt;/code&gt;，且 &lt;code&gt;characterData&lt;/code&gt; 也为 &lt;code&gt;true&lt;/code&gt;，则记录文本变更前的旧值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;attributeFilter&lt;/code&gt;: 字符串数组，用于指定一个属性名称的列表，如果设置了这个列表，则 &lt;code&gt;MutationObserver&lt;/code&gt; 只会观察列表中指定的属性变更。这个属性只在 &lt;code&gt;attributes&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt; 时有效。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;异步回调与记录队列&lt;/h3&gt;
&lt;p&gt;每次MutationRecord被添加到MutationObserver的记录队列时，仅当之前没有已排期的微任务回调时（队列中微任务长度为0），才会将观察者注册的回调（在初始化MutationObserver时传入）作为微任务调度到任务队列上。这样可以保证记录队列的内容不会被回调处理两次。&lt;/p&gt;
&lt;p&gt;调用MutationObserver实例的takeRecords()方法可以清空记录队列，取出并返回其中的所有MutationRecord实例。&lt;/p&gt;
&lt;h3&gt;性能、内存与垃圾回收&lt;/h3&gt;
&lt;h4&gt;MutationObserver的引用&lt;/h4&gt;
&lt;p&gt;MutationObserver实例与目标节点之间的引用关系是&lt;strong&gt;非对称&lt;/strong&gt;的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MutationObserver 对目标节点的弱引用&lt;/strong&gt;：意味着 MutationObserver 不会阻止其所观察的 DOM 节点被垃圾回收。如果没有其他强引用指向这些节点，它们可以被正常回收。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标节点对 MutationObserver 的强引用&lt;/strong&gt;：意味着只要目标节点仍然存在于 DOM 中，或者存在其他引用指向目标节点，那么与之关联的 MutationObserver 就不会被垃圾回收。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;MutationRecord的引用&lt;/h4&gt;
&lt;p&gt;至少包含一个DOM节点的引用（&lt;code&gt;childList&lt;/code&gt;可以有多个）。
最佳实践是，从&lt;code&gt;MutationRecor&lt;/code&gt;d 中抽取需要的信息到新对象中，然后释放 &lt;code&gt;MutationRecord&lt;/code&gt;。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | LangChain Expression Language</title><link>https://www.ender-wiggin.com/posts/note-lcel/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-lcel/</guid><description> </description><pubDate>Wed, 22 Nov 2023 21:03:00 GMT</pubDate><content:encoded>&lt;h1&gt;&lt;code&gt;Runnable&lt;/code&gt; protocol&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;Runnable&lt;/code&gt; 接口主要为了实现链式调用。&lt;/p&gt;
&lt;h2&gt;主要方法&lt;/h2&gt;
&lt;p&gt;主要包括如下方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://python.langchain.com/docs/expression_language/interface#stream&quot;&gt;&lt;code&gt;stream&lt;/code&gt;&lt;/a&gt;: 流式输出&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://python.langchain.com/docs/expression_language/interface#invoke&quot;&gt;&lt;code&gt;invoke&lt;/code&gt;&lt;/a&gt;: 正常调用方式&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://python.langchain.com/docs/expression_language/interface#batch&quot;&gt;&lt;code&gt;batch&lt;/code&gt;&lt;/a&gt;: 列表输入，内部异步实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三个方法都有对应的异步方法。&lt;/p&gt;
&lt;h3&gt;输入输出&lt;/h3&gt;
&lt;p&gt;输入输出在 &lt;code&gt;runnable&lt;/code&gt; 中可以显示。&lt;/p&gt;
&lt;h4&gt;Input&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;chain.input_schema.schema()&lt;/code&gt; 查看&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Input Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompt&lt;/td&gt;
&lt;td&gt;Dictionary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retriever&lt;/td&gt;
&lt;td&gt;Single string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM, ChatModel&lt;/td&gt;
&lt;td&gt;Single string, list of chat messages or a PromptValue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool&lt;/td&gt;
&lt;td&gt;Single string, or dictionary, depending on the tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OutputParser&lt;/td&gt;
&lt;td&gt;The output of an LLM or ChatModel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;output&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;chain.output_schema.schema()&lt;/code&gt; 查看&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Output Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChatModel&lt;/td&gt;
&lt;td&gt;ChatMessage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt&lt;/td&gt;
&lt;td&gt;PromptValue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retriever&lt;/td&gt;
&lt;td&gt;List of documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool&lt;/td&gt;
&lt;td&gt;Depends on the tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OutputParser&lt;/td&gt;
&lt;td&gt;Depends on the parser&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;样例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;model = ChatOpenAI()
prompt = ChatPromptTemplate.from_template(&quot;tell me a joke about {topic}&quot;)
chain = prompt | model
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;chain.input_schema.schema()
# {&apos;title&apos;: &apos;PromptInput&apos;,
# &apos;type&apos;: &apos;object&apos;,
# &apos;properties&apos;: {&apos;topic&apos;: {&apos;title&apos;: &apos;Topic&apos;, &apos;type&apos;: &apos;string&apos;}}}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;await chain.abatch([{&quot;topic&quot;: &quot;bears&quot;}]) # 异步 `abatch`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于单参数的情况，可以使用 &lt;code&gt;RunnablePassthrough()&lt;/code&gt; ，同时传入 &lt;code&gt;chain&lt;/code&gt; 的参数可以直接是字符串&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;model_parser = model | StrOutputParser()

# 可以直接将一个 chain 包在 dict 中间：{&quot;color&quot;: model_parser}
color_generator = (
{&quot;attribute&quot;: RunnablePassthrough()} | prompt1 | {&quot;color&quot;: model_parser}
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;bind&lt;/code&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Runnable.bind(stop=)&lt;/code&gt; 用于设定停止词，输出会在这个词出现之前被截断&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Runnable.bind(function_call=)&lt;/code&gt; 用于 &lt;code&gt;bing&lt;/code&gt; OpenAI functions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;RunnableMap&lt;/code&gt; / &lt;code&gt;RunnableParallel&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from langchain.schema.runnable import RunnableMap, RunnablePassthrough

# RunnableMap 生成输入 prompt 的 key，`foo=RunnablePassthrough()` 传入字符串参数
# 等价于 `{&quot;foo&quot;: RunnablePassthrough()}`
map_ = RunnableMap(foo=RunnablePassthrough()
chain = (
    map_
    | prompt
    | model.bind(function_call={&quot;name&quot;: &quot;joke&quot;}, functions=functions)
    | JsonKeyOutputFunctionsParser(key_name=&quot;setup&quot;)
)

# 字符串调用
chain.invoke(&quot;bears&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;RunnableMap&lt;/code&gt; 的不同 &lt;code&gt;key&lt;/code&gt; 自带并行效果，比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chain = (
planner
| {
&quot;results_1&quot;: chain1, # 并行
&quot;results_2&quot;: chain2, #
&quot;original_response&quot;: itemgetter(&quot;base_response&quot;),
}
| final_responder
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;配置&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;configurable_fields&lt;/code&gt; 中定义了可配置的参数 &lt;code&gt;temperature&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;model = ChatOpenAI(temperature=0).configurable_fields(
    temperature=ConfigurableField(
        id=&quot;llm_temperature&quot;,
        name=&quot;LLM Temperature&quot;,
        description=&quot;The temperature of the LLM&quot;,
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后续可以传入一个字典进行配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chain.with_config(configurable={&quot;llm_temperature&quot;: 0.9}).invoke({&quot;x&quot;: 0})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型切换配置化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;llm = ChatAnthropic(temperature=0).configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id=&quot;llm&quot;),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key=&quot;anthropic&quot;,

    openai=ChatOpenAI(),
    gpt4=ChatOpenAI(model=&quot;gpt-4&quot;),
)
prompt = PromptTemplate.from_template(&quot;Tell me a joke about {topic}&quot;)
chain = prompt | llm
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# We can use `.with_config(configurable={&quot;llm&quot;: &quot;openai&quot;})` to specify an llm to use
chain.with_config(configurable={&quot;llm&quot;: &quot;openai&quot;}).invoke({&quot;topic&quot;: &quot;bears&quot;})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;prompt 同理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;llm = ChatAnthropic(temperature=0)
prompt = PromptTemplate.from_template(
    &quot;Tell me a joke about {topic}&quot;
).configurable_alternatives(
    ConfigurableField(id=&quot;prompt&quot;),
    default_key=&quot;joke&quot;,
    poem=PromptTemplate.from_template(&quot;Write a short poem about {topic}&quot;),
)
chain = prompt | llm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;code&gt;configurable_alternatives&lt;/code&gt; 可以运行在任何 &lt;code&gt;ruunable&lt;/code&gt; 上，也就是说可以在 llm 和 prompt 上分别设置配置项，并在链式调用中一并配置&lt;/p&gt;
&lt;h2&gt;Fallback&lt;/h2&gt;
&lt;p&gt;模型 API fallback&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openai_llm = ChatOpenAI(max_retries=0)
anthropic_llm = ChatAnthropic()
llm = openai_llm.with_fallbacks([anthropic_llm])

# 指定报错类型
llm = openai_llm.with_fallbacks(
[anthropic_llm], exceptions_to_handle=(KeyboardInterrupt,)
)

#  直接对一整个 chain 设置 fallback
chain = bad_chain.with_fallbacks([good_chain])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;RunnableLambda&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from langchain.schema.runnable import RunnableLambda
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI
from operator import itemgetter


def length_function(text):
    return len(text)


def _multiple_length_function(text1, text2):
    return len(text1) * len(text2)


# 调用需要符合上面提到的 Input 类型
def multiple_length_function(_dict):
    return _multiple_length_function(_dict[&quot;text1&quot;], _dict[&quot;text2&quot;])


prompt = ChatPromptTemplate.from_template(&quot;what is {a} + {b}&quot;)
model = ChatOpenAI()

chain1 = prompt | model

chain = (
    {
        &quot;a&quot;: itemgetter(&quot;foo&quot;) | RunnableLambda(length_function),
        &quot;b&quot;: {&quot;text1&quot;: itemgetter(&quot;foo&quot;), &quot;text2&quot;: itemgetter(&quot;bar&quot;)}
        | RunnableLambda(multiple_length_function),
    }
    | prompt
    | model
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;RunnableConfig&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;没看懂有什么用&lt;/strong&gt; &lt;a href=&quot;https://python.langchain.com/docs/expression_language/how_to/functions#accepting-a-runnable-config&quot;&gt;Run arbitrary functions&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.schema.runnable import RunnableConfig
from langchain.schema.output_parser import StrOutputParser

import json


def parse_or_fix(text: str, config: RunnableConfig):
    fixing_chain = (
        ChatPromptTemplate.from_template(
            &quot;Fix the following text:\n\n```text\n{input}\n```\nError: {error}&quot;
            &quot; Don&apos;t narrate, just respond with the fixed data.&quot;
        )
        | ChatOpenAI()
        | StrOutputParser()
    )
    for _ in range(3):
        try:
            return json.loads(text)
        except Exception as e:
            text = fixing_chain.invoke({&quot;input&quot;: text, &quot;error&quot;: e}, config)
    return &quot;Failed to parse&quot;

from langchain.callbacks import get_openai_callback

with get_openai_callback() as cb:
    RunnableLambda(parse_or_fix).invoke(
        &quot;{foo: bar}&quot;, {&quot;tags&quot;: [&quot;my-tag&quot;], &quot;callbacks&quot;: [cb]}
    )
    print(cb)

# Tokens Used: 65
# 	Prompt Tokens: 56
#	Completion Tokens: 9
# Successful Requests: 1
# Total Cost (USD): $0.00010200000000000001
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;RunnableBranch&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;分支运行，执行第一个为 &lt;code&gt;True&lt;/code&gt; 的分支，如果没有则执行默认分支。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.schema.runnable import RunnableBranch

branch = RunnableBranch(
    (lambda x: &quot;anthropic&quot; in x[&quot;topic&quot;].lower(), anthropic_chain),
    (lambda x: &quot;langchain&quot; in x[&quot;topic&quot;].lower(), langchain_chain),
    general_chain,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以通过 &lt;code&gt;RunnableLambda&lt;/code&gt; 实现同样效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def route(info):
    if &quot;anthropic&quot; in info[&quot;topic&quot;].lower():
        return anthropic_chain
    elif &quot;langchain&quot; in info[&quot;topic&quot;].lower():
        return langchain_chain
    else:
        return general_chain
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Retrieval&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;vectorstore = FAISS.from_texts(
    [&quot;harrison worked at kensho&quot;], embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()

template = &quot;&quot;&quot;Answer the question based only on the following context:
{context}

Question: {question}
&quot;&quot;&quot;
prompt = ChatPromptTemplate.from_template(template)

model = ChatOpenAI()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;chain = (
    {&quot;context&quot;: retriever, &quot;question&quot;: RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;template = &quot;&quot;&quot;Answer the question based only on the following context:
{context}

Question: {question}

Answer in the following language: {language}
&quot;&quot;&quot;
prompt = ChatPromptTemplate.from_template(template)

chain = (
    {
	    # 先获取问题，再在 `retriever` 中搜索结果作为 `context`
        &quot;context&quot;: itemgetter(&quot;question&quot;) | retriever,
        &quot;question&quot;: itemgetter(&quot;question&quot;),
        &quot;language&quot;: itemgetter(&quot;language&quot;),
    }
    | prompt
    | model
    | StrOutputParser()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;Document&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from langchain.schema import Document
from langchain.prompts import PromptTemplate

doc = Document(page_content=&quot;This is a joke&quot;, metadata={&quot;page&quot;: &quot;1&quot;})
prompt = PromptTemplate.from_template(&quot;Page {page}: {page_content}&quot;)
format_document(doc, prompt)
# &quot;Page 1: This is a joke&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;RunnablePassthrough.assign&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In some cases, it may be useful to pass the input through while adding some keys to the output. In this case, you can use the assign method.
Uasge: &lt;strong&gt;Merge the Dict input with the output produced by the mapping argument.&lt;/strong&gt; 将传入的 &lt;code&gt;RunnablePassthrough&lt;/code&gt; 与新参数合并&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.schema.runnable import RunnablePassthrough, RunnableParallel

 def fake_llm(prompt: str) -&amp;gt; str: # Fake LLM for the example
    return &quot;completion&quot;

runnable = {
    &apos;llm1&apos;:  fake_llm,
    &apos;llm2&apos;:  fake_llm,
}
| RunnablePassthrough.assign(
    total_chars=lambda inputs: len(inputs[&apos;llm1&apos;] + inputs[&apos;llm2&apos;])
  )

runnable.invoke(&apos;hello&apos;)
# {&apos;llm1&apos;: &apos;completion&apos;, &apos;llm2&apos;: &apos;completion&apos;, &apos;total_chars&apos;: 20}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;例子：构建聊天历史记录&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;from langchain.schema.runnable import RunnableMapfrom langchain.schema import format_document
from langchain.prompts.prompt import PromptTemplate

_template = &quot;&quot;&quot;Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.Chat History:{chat_history}Follow Up Input: {question}Standalone question:&quot;&quot;&quot;
CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template)

template = &quot;&quot;&quot;Answer the question based only on the following context:{context}Question: {question}&quot;&quot;&quot;
ANSWER_PROMPT = ChatPromptTemplate.from_template(template)

DEFAULT_DOCUMENT_PROMPT = PromptTemplate.from_template(template=&quot;{page_content}&quot;)

# 将 doc 合并为一个长字符串
def _combine_documents(
    docs, document_prompt=DEFAULT_DOCUMENT_PROMPT, document_separator=&quot;\n\n&quot;
):
    doc_strings = [format_document(doc, document_prompt) for doc in docs]
    return document_separator.join(doc_strings)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;from typing import Tuple, List

def _format_chat_history(chat_history: List[Tuple]) -&amp;gt; str:
    buffer = &quot;&quot;
    for dialogue_turn in chat_history:
        human = &quot;Human: &quot; + dialogue_turn[0]
        ai = &quot;Assistant: &quot; + dialogue_turn[1]
        buffer += &quot;\n&quot; + &quot;\n&quot;.join([human, ai])
    return buffer
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# 生成一个新的 dict
_inputs = RunnableMap(
	# 其中 standalone_question 在原有参数之上，新添加了历史记录 chat_history
    standalone_question=RunnablePassthrough.assign(
        chat_history=lambda x: _format_chat_history(x[&quot;chat_history&quot;])
    )
    | CONDENSE_QUESTION_PROMPT
    | ChatOpenAI(temperature=0)
    | StrOutputParser(),
)
_context = {
	# 在获得具体问题后，运行检索并合并
    &quot;context&quot;: itemgetter(&quot;standalone_question&quot;) | retriever | _combine_documents,
    &quot;question&quot;: lambda x: x[&quot;standalone_question&quot;],
}
conversational_qa_chain = _inputs | _context | ANSWER_PROMPT | ChatOpenAI()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;conversational_qa_chain.invoke(
    {
        &quot;question&quot;: &quot;where did he work?&quot;,
        &quot;chat_history&quot;: [(&quot;Who wrote this notebook?&quot;, &quot;Harrison&quot;)],
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，历史记录也可以通过 &lt;code&gt;langchain.memory&lt;/code&gt; 轻松调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from operator import itemgetter
from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(
    return_messages=True, output_key=&quot;answer&quot;, input_key=&quot;question&quot;
)

# # This adds a &quot;memory&quot; key to the input object
loaded_memory = RunnablePassthrough.assign(
chat_history=RunnableLambda(memory.load_memory_variables) | itemgetter(&quot;history&quot;),
)

# 目前需要手动保存
memory.save_context(inputs, {&quot;answer&quot;: result[&quot;answer&quot;].content})
memory.load_memory_variables({})

# {&apos;history&apos;: [HumanMessage(content=&apos;where did harrison work?&apos;, additional_kwargs={}, example=False),
# AIMessage(content=&apos;Harrison was employed at Kensho.&apos;, additional_kwargs={}, example=False)]}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Agent&lt;/h1&gt;
&lt;p&gt;Building an agent from a runnable usually involves a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Data processing for the intermediate steps. These need to represented in a way that the language model can recognize them. This should be pretty tightly coupled to the instructions in the prompt&lt;/li&gt;
&lt;li&gt;The prompt itself&lt;/li&gt;
&lt;li&gt;The model, complete with stop tokens if needed&lt;/li&gt;
&lt;li&gt;The output parser - should be in sync with how the prompt specifies things to be formatted.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;agent = (
    {
        &quot;question&quot;: lambda x: x[&quot;question&quot;],
        &quot;intermediate_steps&quot;: lambda x: convert_intermediate_steps(
            x[&quot;intermediate_steps&quot;]
        ),
    }
    | prompt.partial(tools=convert_tools(tool_list))
    | model.bind(stop=[&quot;&amp;lt;/tool_input&amp;gt;&quot;, &quot;&amp;lt;/final_answer&amp;gt;&quot;])
    | XMLAgent.get_default_output_parser()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;中间态处理。输入 &lt;code&gt;intermediate_steps&lt;/code&gt; 输出可被识别的(结构化)结果&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;def convert_intermediate_steps(intermediate_steps):
	log = &quot;&quot;
	for action, observation in intermediate_steps:
	log += (
	f&quot;&amp;lt;tool&amp;gt;{action.tool}&amp;lt;/tool&amp;gt;&amp;lt;tool_input&amp;gt;{action.tool_input}&quot;
	f&quot;&amp;lt;/tool_input&amp;gt;&amp;lt;observation&amp;gt;{observation}&amp;lt;/observation&amp;gt;&quot;
	)
	return log
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;prompt 处理。一般需要通过 &lt;code&gt;partial&lt;/code&gt; 传入 &lt;code&gt;tools&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;def convert_tools(tools):
	return &quot;\n&quot;.join([f&quot;{tool.name}: {tool.description}&quot; for tool in tools])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Routing&lt;/h2&gt;
&lt;p&gt;通过特定函数调用&lt;strong&gt;不同链&lt;/strong&gt;获得不同输出。&lt;/p&gt;
&lt;h3&gt;Embedding&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()

# 模版列表
prompt_templates = [physics_template, math_template]

# 输入 list 和 可选参数 chunk_size
# 返回向量列表 list[list[float]]
prompt_embeddings = embeddings.embed_documents(prompt_templates)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;def prompt_router(input):
	# 将输入转化为向量
	query_embedding = embeddings.embed_query(input[&quot;query&quot;])
	# 计算两个等宽矩阵的相似性,返回一个矩阵,取第一行(即包含了2个不同模版相似结果数值的列表)
	similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
	# 取最大值的索引,获得最接近的模版
	most_similar = prompt_templates[similarity.argmax()]

	print(&quot;Using MATH&quot; if most_similar == math_template else &quot;Using PHYSICS&quot;)
	# 选择对应模版
	return PromptTemplate.from_template(most_similar)

chain = (
	{&quot;query&quot;: RunnablePassthrough()}
	| RunnableLambda(prompt_router)
	| ChatOpenAI()
	| StrOutputParser(
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;其余补充&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ChatMessagePromptTemplate&lt;/code&gt; 可以自己定义角色
但是现在 OpenAI 不支持这个，所以没用！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.prompts import ChatMessagePromptTemplate

prompt = &quot;May the {subject} be with you&quot;

chat_message_prompt = ChatMessagePromptTemplate.from_template(role=&quot;Jedi&quot;, template=prompt)
chat_message_prompt.format(subject=&quot;force&quot;)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第12章-BOM</title><link>https://www.ender-wiggin.com/posts/note-js-12/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-12/</guid><description> </description><pubDate>Wed, 22 Nov 2023 21:02:00 GMT</pubDate><content:encoded>&lt;h1&gt;BOM&lt;/h1&gt;
&lt;p&gt;浏览器对象模型（BOM, Browser Object Model）&lt;/p&gt;
&lt;h2&gt;window 对象&lt;/h2&gt;
&lt;p&gt;BOM的核心是&lt;code&gt;window&lt;/code&gt;对象，表示浏览器的实例。&lt;code&gt;window&lt;/code&gt;对象在浏览器中有两重身份，一个是ECMAScript中的&lt;code&gt;Global&lt;/code&gt;对象，另一个就是浏览器窗口的JavaScript接口。这意味着网页中定义的所有对象、变量和函数都以&lt;code&gt;window&lt;/code&gt;作为其&lt;code&gt;Global&lt;/code&gt;对象，都可以访问其上定义的&lt;code&gt;parseInt()&lt;/code&gt;等全局方法。&lt;/p&gt;
&lt;p&gt;在全局中使用 &lt;code&gt;var&lt;/code&gt; 定义可以将变量定义于 &lt;code&gt;window&lt;/code&gt; 中，而 &lt;code&gt;let&lt;/code&gt; 不会添加。&lt;/p&gt;
&lt;p&gt;另外，访问未声明的变量会抛出错误，但是可以在window对象上查询是否存在可能未声明的变量。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 这会导致抛出错误，因为oldValue没有声明
var newValue = oldValue;
// 这不会抛出错误，因为这里是属性查询
// newValue会被设置为undefined
var newValue = window.oldValue;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;窗口关系&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;top&lt;/code&gt;对象始终指向最上层（最外层）窗口，即浏览器窗口本身。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parent&lt;/code&gt;对象则始终指向当前窗口的父窗口。如果当前窗口是最上层窗口，则&lt;code&gt;parent&lt;/code&gt;等于&lt;code&gt;top&lt;/code&gt;（都等于&lt;code&gt;window&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;最上层的&lt;code&gt;window&lt;/code&gt;如果不是通过&lt;code&gt;window.open()&lt;/code&gt;打开的，那么其name&lt;code&gt;属性&lt;/code&gt;就不会包含值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;self&lt;/code&gt;对象始终会指向window。实际上，&lt;code&gt;self&lt;/code&gt;和&lt;code&gt;window&lt;/code&gt;就是同一个对象。之所以还要暴露&lt;code&gt;self&lt;/code&gt;，就是为了和&lt;code&gt;top&lt;/code&gt;、&lt;code&gt;parent&lt;/code&gt;保持一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;窗口位置与像素比&lt;/h3&gt;
&lt;p&gt;现代浏览器提供了&lt;code&gt;screenLeft&lt;/code&gt;和&lt;code&gt;screenTop&lt;/code&gt;属性，用于表示窗口相对于屏幕左侧和顶部的位置，返回值的单位是CSS像素。&lt;/p&gt;
&lt;p&gt;移动窗口使用moveTo()和moveBy()。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 把窗口移动到左上角
window.moveTo(0,0);
// 把窗口向左移动50 像素
window.moveBy(-50, 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;CSS像素（CSS Pixels）&lt;/h4&gt;
&lt;p&gt;CSS像素是Web开发中使用的&lt;strong&gt;统一&lt;/strong&gt;像素单位。这个单位的背后其实是一个角度：0.0213°。，称为设备独立像素（Device Independent Pixels, DIPs）。它是一个抽象的单位，旨在为开发者提供一个统一的度量标准，使得不同设备和分辨率上的网页元素能够保持大致相同的&lt;strong&gt;视觉大小&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;物理像素（Physical Pixels）&lt;/h4&gt;
&lt;p&gt;物理像素是显示屏幕上实际的光点，它们是构成屏幕显示内容的最小单位。不同设备的屏幕可能有不同的物理像素密度，即PPI（Pixels Per Inch，每英寸像素数）。&lt;/p&gt;
&lt;h4&gt;设备像素比（Device Pixel Ratio, DPR）&lt;/h4&gt;
&lt;p&gt;由于物理像素的密度可以非常高（尤其是在现代设备上），CSS像素和物理像素之间的关系由设备像素比（DPR）定义。DPR是物理像素数和CSS像素数的比例。例如，如果DPR为2，则意味着一个CSS像素占据了2x2个物理像素。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;window.devicePixelRatio&lt;/code&gt;实际上与每英寸像素数（DPI, dots per inch）是对应的。DPI表示单位像素密度，而&lt;code&gt;window.devicePixelRatio&lt;/code&gt;表示物理像素与逻辑像素之间的缩放系数。&lt;/p&gt;
&lt;h3&gt;窗口大小&lt;/h3&gt;
&lt;p&gt;不同的窗口大小属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;outerWidth&lt;/code&gt;和&lt;code&gt;outerHeight&lt;/code&gt;返回浏览器窗口自身的大小（不管是在最外层window上使用，还是在窗格&lt;code&gt;&amp;lt;frame&amp;gt;&lt;/code&gt;中使用）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;innerWidth&lt;/code&gt;和&lt;code&gt;innerHeight&lt;/code&gt;返回浏览器窗口中&lt;strong&gt;页面视口&lt;/strong&gt;的大小（不包含浏览器边框和工具栏）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;document.documentElement.clientWidth&lt;/code&gt;和&lt;code&gt;document.documentElement.clientHeight&lt;/code&gt;返回页面视口的宽度和高度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以使用&lt;code&gt;resizeTo()&lt;/code&gt;和&lt;code&gt;resizeBy()&lt;/code&gt;方法调整窗口大小。这两个方法都接收两个参数，&lt;code&gt;resizeTo()&lt;/code&gt;接收新的宽度和高度值，而&lt;code&gt;resizeBy()&lt;/code&gt;接收宽度和高度各要缩放多少。&lt;/p&gt;
&lt;h3&gt;视口位置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;window.pageXoffset&lt;/code&gt;/&lt;code&gt;window. scrollX&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;window.pageYoffset&lt;/code&gt;/&lt;code&gt;window.scrollY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;滚动页面使用 &lt;code&gt;scroll()&lt;/code&gt;、&lt;code&gt;scrollTo()&lt;/code&gt; 和 &lt;code&gt;scrollBy()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这几个方法也都接收一个ScrollToOptions字典，除了提供偏移值，还可以通过behavior属性告诉浏览器是否平滑滚动。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 正常滚动
    window.scrollTo({
      left: 100,
      top: 100,
      behavior: &apos;auto&apos;
    });
    // 平滑滚动
    window.scrollTo({
      left: 100,
      top: 100,
      behavior: &apos;smooth&apos;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;导航与打开新窗口&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;window.open()&lt;/code&gt;方法可以用于导航到指定URL，也可以用于打开新浏览器窗口。这个方法接收4个参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;要加载的URL&lt;/li&gt;
&lt;li&gt;目标窗口（如果不是已有窗口，则会打开一个新窗口或标签页）&lt;/li&gt;
&lt;li&gt;特性字符串&lt;/li&gt;
&lt;li&gt;表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let wroxWin = window.open(&quot;http://www.wrox.com/&quot;,
                  &quot;wroxWindow&quot;,
                  &quot;height=400, width=400, top=10, left=10, resizable=yes&quot;);
    // 缩放
    wroxWin.resizeTo(500, 500);
    // 移动
    wroxWin.moveTo(100, 100);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新创建窗口的window对象有一个属性opener，指向打开它的窗口。这个属性只在弹出窗口的最上层window对象（top）有定义，是指向调用window.open()打开它的窗口或窗格的指针。&lt;/p&gt;
&lt;p&gt;在某些浏览器中，每个标签页会运行在独立的进程中。如果一个标签页打开了另一个，而window对象需要跟另一个标签页通信，那么标签便不能运行在独立的进程中。在这些浏览器中，可以将新打开的标签页的opener属性设置为null，表示新打开的标签页可以运行在独立的进程中。&lt;/p&gt;
&lt;p&gt;把opener设置为null表示新打开的标签页不需要与打开它的标签页通信，因此可以在独立进程中运行。这个连接一旦切断，就无法恢复了。&lt;/p&gt;
&lt;h3&gt;系统对话框&lt;/h3&gt;
&lt;p&gt;使用&lt;code&gt;alert()&lt;/code&gt;、&lt;code&gt;confirm()&lt;/code&gt;和&lt;code&gt;prompt()&lt;/code&gt;方法，可以让浏览器调用系统对话框向用户显示消息。这些对话框与浏览器中显示的网页无关，而且也不包含HTML。它们的外观由操作系统或者浏览器决定，无法使用CSS设置。
此外，这些对话框都是&lt;strong&gt;同步&lt;/strong&gt;的模态对话框，即在它们显示的时候，代码会停止执行，在它们消失以后，代码才会恢复执行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;confirm() &lt;/code&gt;返回确认和取消按钮的对话框。
要知道用户单击了OK按钮还是Cancel按钮，可以判断&lt;code&gt;confirm()&lt;/code&gt;方法的返回值：true表示单击了OK按钮，false表示单击了Cancel按钮或者通过单击某一角上的X图标关闭了确认框。&lt;/p&gt;
&lt;p&gt;最后一种对话框是提示框，通过调用&lt;code&gt;prompt()&lt;/code&gt;方法来显示。提示框的用途是提示用户输入消息。除了OK和Cancel按钮，提示框还会显示一个文本框，让用户输入内容。&lt;code&gt;prompt()&lt;/code&gt;方法接收两个参数：要显示给用户的文本，以及文本框的默认值（可以是空字符串）。
如果用户单击了OK按钮，则&lt;code&gt;prompt()&lt;/code&gt;会返回文本框中的值。如果用户单击了Cancel按钮，或者对话框被关闭，则&lt;code&gt;prompt()&lt;/code&gt;会返回&lt;code&gt;null&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;JavaScript还可以显示另外两种&lt;strong&gt;异步&lt;/strong&gt;对话框：&lt;code&gt;find()&lt;/code&gt;和&lt;code&gt;print()&lt;/code&gt;。这两种对话框都是异步显示的，即控制权会立即返回给脚本。用户在浏览器菜单上选择“查找”（find）和“打印”（print）时显示的就是这两种对话框。通过在&lt;code&gt;window&lt;/code&gt;对象上调用&lt;code&gt;find()&lt;/code&gt;和&lt;code&gt;print()&lt;/code&gt;可以显示它们。&lt;/p&gt;
&lt;h2&gt;location对象&lt;/h2&gt;
&lt;p&gt;location是最有用的BOM对象之一，提供了当前窗口中加载文档的信息，以及通常的导航功能。这个对象独特的地方在于，它既是window的属性，也是document的属性。也就是说，&lt;code&gt;window.location和document.location&lt;/code&gt;指向同一个对象。&lt;/p&gt;
&lt;p&gt;location对象不仅保存着当前加载文档的信息，也保存着把URL解析为离散片段后能够通过属性访问的信息。&lt;/p&gt;
&lt;h3&gt;查询字符串&lt;/h3&gt;
&lt;p&gt;虽然&lt;code&gt;location.search&lt;/code&gt;返回了从问号开始直到URL末尾的所有内容，但没有办法逐个访问每个查询参数。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;URLSearchParams&lt;/code&gt; 提供了一组标准API方法，通过它们可以检查和修改查询字符串。给&lt;code&gt;URLSearchParams&lt;/code&gt;构造函数传入一个查询字符串，就可以创建一个实例。&lt;/p&gt;
&lt;p&gt;大多数支持&lt;code&gt;URLSearchParams&lt;/code&gt;的浏览器也支持将&lt;code&gt;URLSearchParams&lt;/code&gt;的实例用作可迭代对象。&lt;/p&gt;
&lt;h3&gt;操作地址&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;location.assign(&quot;http://www.xxx.com&quot;)&lt;/code&gt;这行代码会立即启动导航到新URL的操作，同时在浏览器历史记录中增加一条记录。
如果给&lt;code&gt;location.href&lt;/code&gt;或&lt;code&gt;window.location&lt;/code&gt;设置一个URL，也会以同一个URL值调用&lt;code&gt;assign()&lt;/code&gt;方法。&lt;/p&gt;
&lt;p&gt;修改l&lt;code&gt;ocation&lt;/code&gt;对象的属性也会修改当前加载的页面。其中，&lt;code&gt;hash&lt;/code&gt;、&lt;code&gt;search&lt;/code&gt;、&lt;code&gt;hostname&lt;/code&gt;、&lt;code&gt;pathname&lt;/code&gt;和&lt;code&gt;port&lt;/code&gt;属性被设置为新值之后都会修改当前URL。
除了&lt;code&gt;hash&lt;/code&gt;之外，只要修改l&lt;code&gt;ocation&lt;/code&gt;的一个属性，就会导致页面重新加载新URL。&lt;/p&gt;
&lt;p&gt;在以前面提到的方式修改URL之后，浏览器历史记录中就会增加相应的记录如果不希望增加历史记录，可以使用&lt;code&gt;replace()&lt;/code&gt;方法。这个方法接收一个URL参数，但重新加载后不会增加历史记录。&lt;/p&gt;
&lt;p&gt;最后一个修改地址的方法是&lt;code&gt;reload()&lt;/code&gt;，它能重新加载当前显示的页面。调用&lt;code&gt;reload()&lt;/code&gt;而不传参数，页面会以最有效的方式重新加载。也就是说，如果页面自上次请求以来没有修改过，浏览器可能会从缓存中加载页面。如果想强制从服务器重新加载，可以给&lt;code&gt;reload()&lt;/code&gt;传个&lt;code&gt;true&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;脚本中位于reload()调用之后的代码可能执行也可能不执行，这取决于网络延迟和系统资源等因素。为此，最好把reload()作为最后一行代码。&lt;/p&gt;
&lt;h2&gt;navigator 对象&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;window.navigator&lt;/code&gt; 是一个包含了用户浏览器信息的对象，它是 &lt;code&gt;window&lt;/code&gt; 对象的一个属性，代表了浏览器的状态和身份。&lt;code&gt;navigator&lt;/code&gt; 对象提供了很多属性和方法，可以让开发者获取到关于浏览器的信息，并且与之交互。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;浏览器检测&lt;/strong&gt;：可以通过 &lt;code&gt;navigator&lt;/code&gt; 对象来检测用户正在使用的浏览器及其版本。常用的属性包括 &lt;code&gt;navigator.userAgent&lt;/code&gt;、&lt;code&gt;navigator.appVersion&lt;/code&gt;、&lt;code&gt;navigator.appName&lt;/code&gt; 等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;平台信息&lt;/strong&gt;：可以使用 &lt;code&gt;navigator.platform&lt;/code&gt; 来获取用户操作系统的信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络状态&lt;/strong&gt;：&lt;code&gt;navigator&lt;/code&gt; 对象提供了 &lt;code&gt;navigator.onLine&lt;/code&gt; 属性，可以用来检测用户的设备是否连接到网络。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;地理位置&lt;/strong&gt;：通过 &lt;code&gt;navigator.geolocation&lt;/code&gt; 对象，可以获取用户的地理位置信息（当然，这需要用户的授权）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多媒体功能&lt;/strong&gt;：&lt;code&gt;navigator&lt;/code&gt; 对象提供了访问用户媒体设备（如摄像头和麦克风）的能力，通过 &lt;code&gt;navigator.mediaDevices&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;插件信息&lt;/strong&gt;：过去，&lt;code&gt;navigator&lt;/code&gt; 对象可以用来检测浏览器安装的插件，但由于安全和隐私的原因，这个功能已经被逐渐淘汰。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注册协议处理器&lt;/strong&gt;：如之前所述，&lt;code&gt;navigator.registerProtocolHandler&lt;/code&gt; 允许网站注册为特定协议（例如 &lt;code&gt;mailto&lt;/code&gt; 或 &lt;code&gt;webcal&lt;/code&gt;）的处理程序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务工作线程&lt;/strong&gt;：&lt;code&gt;navigator.serviceWorker&lt;/code&gt; 允许注册和管理 Service Workers，这些是运行在浏览器背后的脚本，可以用来支持离线体验、网络请求拦截和背景同步等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推送通知&lt;/strong&gt;：&lt;code&gt;navigator&lt;/code&gt; 对象可以用来管理和控制Web推送通知。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语言和国际化&lt;/strong&gt;：&lt;code&gt;navigator.language&lt;/code&gt; 属性表示用户的首选语言，&lt;code&gt;navigator.languages&lt;/code&gt; 则是一个包含用户首选语言的数组。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能分析&lt;/strong&gt;：&lt;code&gt;navigator&lt;/code&gt; 对象提供了 &lt;code&gt;navigator.sendBeacon&lt;/code&gt; 方法，可以在卸载文档之前异步地发送小量数据到服务器，用于性能分析和统计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;电池状态&lt;/strong&gt;：&lt;code&gt;navigator.getBattery&lt;/code&gt; 方法（如果可用）允许访问设备电池状态的信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;剪贴板操作&lt;/strong&gt;：&lt;code&gt;navigator.clipboard&lt;/code&gt; 提供了读取和修改剪贴板内容的能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;凭证管理&lt;/strong&gt;：&lt;code&gt;navigator.credentials&lt;/code&gt; 用于访问和管理用户的凭证信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件并发&lt;/strong&gt;：&lt;code&gt;navigator.hardwareConcurrency&lt;/code&gt; 属性表示可用于运行线程的逻辑处理器的数量。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;检测插件&lt;/h3&gt;
&lt;p&gt;都可以通过&lt;code&gt;window.navigator.plugins&lt;/code&gt;数组来确定。这个数组中的每一项都包含如下属性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name：插件名称。&lt;/li&gt;
&lt;li&gt;description：插件介绍。&lt;/li&gt;
&lt;li&gt;filename：插件的文件名。&lt;/li&gt;
&lt;li&gt;length：由当前插件处理的MIME类型数量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常，name属性包含识别插件所需的必要信息，尽管不是特别准确。检测插件就是遍历浏览器中可用的插件，并逐个比较插件的名称&lt;/p&gt;
&lt;h3&gt;注册处理程序&lt;/h3&gt;
&lt;p&gt;现代浏览器支持navigator上的（在HTML5中定义的）registerProtocolHandler()方法。&lt;/p&gt;
&lt;p&gt;要使用registerProtocolHandler()方法，必须传入3个参数：要处理的协议（如&quot;mailto&quot;或&quot;ftp&quot;）、处理该协议的URL，以及应用名称。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;navigator.registerProtocolHandler(&quot;mailto&quot;, &quot;http://www.somemailclient.com?cmd=%s&quot;, &quot;Some Mail Client&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当用户点击一个 &lt;code&gt;mailto&lt;/code&gt; 链接时，浏览器会打开 &lt;code&gt;http://www.somemailclient.com&lt;/code&gt; 这个地址，并且 &lt;code&gt;%s&lt;/code&gt; 会被替换为实际的 &lt;code&gt;mailto&lt;/code&gt; URL。例如，如果 &lt;code&gt;mailto&lt;/code&gt; 链接是 &lt;code&gt;mailto:user@example.com&lt;/code&gt;，那么 &lt;code&gt;%s&lt;/code&gt; 就会被替换为 &lt;code&gt;mailto:user@example.com&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;screen对象&lt;/h2&gt;
&lt;p&gt;这个对象中保存的纯粹是客户端能力信息，也就是浏览器窗口外面的客户端显示器的信息，比如像素宽度和像素高度。每个浏览器都会在screen对象上暴露不同的属性。&lt;/p&gt;
&lt;h2&gt;history对象&lt;/h2&gt;
&lt;p&gt;history对象表示当前窗口首次使用以来用户的导航历史记录。因为history是window的属性，所以每个window都有自己的history对象。出于安全考虑，这个对象不会暴露用户访问过的URL，但可以通过它在不知道实际URL的情况下前进和后退。&lt;/p&gt;
&lt;h3&gt;导航&lt;/h3&gt;
&lt;p&gt;go()方法可以在用户历史记录中沿任何方向导航，可以前进也可以后退。这个方法只接收一个参数，这个参数可以是一个整数，表示前进或后退多少步。
go()有两个简写方法：back()和forward()。
history对象还有一个length属性，表示历史记录中有多个条目。&lt;/p&gt;
&lt;p&gt;注意 如果页面URL发生变化，则会在历史记录中生成一个新条目。对于主流浏览器，这包括改变&lt;strong&gt;URL的散列值&lt;/strong&gt;（因此，把location.hash设置为一个新值会在这些浏览器的历史记录中增加一条记录）。这个行为常被单页应用程序框架用来模拟前进和后退，这样做是为了不会因导航而触发页面刷新。&lt;/p&gt;
&lt;h3&gt;历史状态管理&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;hashchange&lt;/code&gt;会在页面URL的散列变化时被触发，开发者可以在此时执行某些操作。而状态管理API则可以让开发者改变浏览器URL而不会加载新页面。为此，可以使用&lt;code&gt;history.pushState()&lt;/code&gt;方法。这个方法接收3个参数：一个state对象、一个新状态的标题和一个（可选的）相对URL。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let stateObject = {foo:&quot;bar&quot;};
    history.pushState(stateObject, &quot;My title&quot;, &quot;baz.html&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pushState()&lt;/code&gt;方法执行后，状态信息就会被推到历史记录中，浏览器地址栏也会改变以反映新的相对URL。除了这些变化之外，即使location.href返回的是地址栏中的内容，浏览器页不会向服务器发送请求。第二个参数并未被当前实现所使用，因此既可以传一个空字符串也可以传一个短标题。第一个参数应该包含正确初始化页面状态所必需的信息。为防止滥用，这个状态的对象大小是有限制的，通常在500KB～1MB以内。&lt;/p&gt;
&lt;p&gt;因为&lt;code&gt;pushState()&lt;/code&gt;会创建新的历史记录，所以也会相应地启用“后退”按钮。此时单击“后退”按钮，就会触发window对象上的&lt;code&gt;popstate&lt;/code&gt;事件。&lt;code&gt;popstate&lt;/code&gt;事件的事件对象有一个state&lt;code&gt;属性&lt;/code&gt;，其中包含通过&lt;code&gt;pushState()&lt;/code&gt;第一个参数传入的&lt;code&gt;state&lt;/code&gt;对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    window.addEventListener(&quot;popstate&quot;, (event) =&amp;gt; {
      let state = event.state;
      if (state) { // 第一个页面加载时状态是null
        processState(state);
      }
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;replaceState&lt;/code&gt; 传入的参数和 &lt;code&gt;pushState&lt;/code&gt; 一致，但是只会覆盖当前历史记录。&lt;/p&gt;
&lt;p&gt;注意，使用HTML5状态管理时，要确保通过&lt;code&gt;pushState()&lt;/code&gt;创建的每个“假”URL背后都对应着服务器上一个真实的物理URL。否则，单击“刷新”按钮会导致404错误。所有 SPA 框架都必须通过服务器或客户端的某些配置解决这个问题。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第11章-异步</title><link>https://www.ender-wiggin.com/posts/note-js-11/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-11/</guid><description> </description><pubDate>Wed, 22 Nov 2023 21:01:00 GMT</pubDate><content:encoded>&lt;h2&gt;起源&lt;/h2&gt;
&lt;p&gt;早期 JS 实现需要通过计时器和回调实现异步，会导致多层嵌套的“回调地狱”。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function double(value, success, failure) {
  setTimeout(() =&amp;gt; {
    try {
      if (typeof value !== &quot;number&quot;) {
        throw &quot;Must provide number as first argument&quot;;
      }
      success(2 * value);
    } catch (e) {
      failure(e);
    }
  }, 1000);
}
const successCallback = (x) =&amp;gt; {
  double(x, (y) =&amp;gt; console.log(`Success: ${y}`));
};
const failureCallback = (e) =&amp;gt; console.log(`Failure: ${e}`);
double(3, successCallback, failureCallback); // Success: 12（大约1000 毫秒之后）
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;期约 Promise&lt;/h2&gt;
&lt;p&gt;2010年，CommonJS项目实现的Promises/A规范日益流行起来。后续出现了多种期约实现，2012年Promises/A+组织 fork 了CommonJS的Promises/A建议，并以相同的名字制定了Promises/A+规范。这个规范最终成为了ECMAScript6规范实现的范本。&lt;/p&gt;
&lt;h3&gt;状态机&lt;/h3&gt;
&lt;p&gt;期约是一个有状态的对象，可能处于如下3种状态之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;待定（pending）&lt;/li&gt;
&lt;li&gt;兑现（fulfilled，或resolved）-&amp;gt; 产生一个私有内部值 value&lt;/li&gt;
&lt;li&gt;拒绝（rejected）-&amp;gt; 产生一个私有内部理由 reason&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;期约的状态是私有的，不能直接通过JavaScript检测到。这主要是为了避免根据读取到的期约状态，以同步方式处理期约对象。另外，期约的状态也不能被外部JavaScript代码修改。这与不能读取该状态的原因是一样的：期约故意将异步行为封装起来，从而隔离外部的同步代码。&lt;/p&gt;
&lt;h3&gt;执行函数&lt;/h3&gt;
&lt;p&gt;由于期约的状态是私有的，所以只能在内部进行操作。内部操作在期约的执行器函数中完成（resolve 和 reject）。&lt;/p&gt;
&lt;p&gt;执行器函数是&lt;strong&gt;同步&lt;/strong&gt;执行的。这是因为执行器函数是期约的初始化程序。&lt;/p&gt;
&lt;p&gt;无论resolve()和reject()中的哪个被调用，状态转换都不可撤销了。于是继续修改状态会静默失败。&lt;/p&gt;
&lt;h4&gt;Promise.resolve()&lt;/h4&gt;
&lt;p&gt;通过调用Promise.resolve()静态方法，可以实例化一个解决的期约。这个解决的期约的值对应着传给Promise.resolve()的&lt;strong&gt;第一个&lt;/strong&gt;参数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setTimeout(console.log, 0, Promise.resolve());
// Promise &amp;lt;resolved&amp;gt;: undefined
setTimeout(console.log, 0, Promise.resolve(3));
// Promise &amp;lt;resolved&amp;gt;: 3
// 多余的参数会忽略
setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise &amp;lt;resolved&amp;gt;: 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Promise.resolve()是一个幂等方法，如果传入的参数本身是一个期约，那它的行为就类似于一个空包装。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p));
// true
setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));
// true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，这个静态方法能够包装任何非期约值，包括错误对象，并将其转换为解决的期约。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let p = Promise.resolve(new Error(&apos;foo&apos;)); setTimeout(console.log, 0, p);
//Promise&amp;lt;resolved&amp;gt;: Error: foo
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Promise.reject()&lt;/h4&gt;
&lt;p&gt;Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误（这个错误不能通过try/catch捕获，而只能通过拒绝处理程序捕获）。&lt;/p&gt;
&lt;p&gt;关键在于，&lt;code&gt;Promise.reject()&lt;/code&gt;并没有照搬&lt;code&gt;Promise.resolve()&lt;/code&gt;的幂等逻辑。如果给它传一个期约对象，则这个期约会成为它返回的拒绝期约的理由。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setTimeout(console.log, 0, Promise.reject(Promise.resolve()));
// Promise &amp;lt;rejected&amp;gt;: Promise &amp;lt;resolved&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;期约真正的异步特性：它们是同步对象（在同步执行模式中使用），但也是异步执行模式的媒介。比如下面的例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    thrownewError(&apos;foo&apos;);
  } catch(e) {
    console.log(e); // Error: foo
  }
  try {
    Promise.reject(newError(&apos;bar&apos;));
  } catch(e) {
    console.log(e);
  }
  //Uncaught(inpromise)Error: bar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拒绝期约的错误并没有抛到执行同步代码的线程里，而是通过浏览器异步消息队列来处理的。因此，try/catch块并不能捕获该错误。代码一旦开始以异步模式执行，则唯一与之交互的方式就是使用异步结构——更具体地说，就是期约的方法。&lt;/p&gt;
&lt;p&gt;期约可以以任何理由拒绝，包括undefined，但最好统一使用错误对象。这样做主要是因为创建错误对象可以让浏览器捕获错误对象中的&lt;strong&gt;栈追踪信息&lt;/strong&gt;，而这些信息对调试是非常关键的。&lt;/p&gt;
&lt;h3&gt;期约的实例方法&lt;/h3&gt;
&lt;p&gt;期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据，处理期约成功和失败的结果，连续对期约求值，或者添加只有期约进入终止状态时才会执行的代码。&lt;/p&gt;
&lt;h4&gt;Thenable接口&lt;/h4&gt;
&lt;p&gt;在ECMAScript暴露的异步结构中，任何对象都有一个&lt;code&gt;then()&lt;/code&gt;方法。这个方法被认为实现了Thenable接口。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyThenable {
	then() {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Promise.prototype.then()&lt;/h4&gt;
&lt;p&gt;这个&lt;code&gt;then()&lt;/code&gt;方法接收最多两个参数：&lt;code&gt;onResolved&lt;/code&gt; 处理程序和&lt;code&gt;onRejected&lt;/code&gt;处理程序。这两个参数都是可选的。传给then()的任何&lt;strong&gt;非函数类型&lt;/strong&gt;的参数都会被静默忽略。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Promise.prototype.then()&lt;/code&gt;方法返回一个新的期约实例。
这个新期约实例基于&lt;code&gt;onResovled&lt;/code&gt;处理程序的返回值构建。换句话说，该处理程序的返回值会通过&lt;code&gt;Promise.resolve()&lt;/code&gt;包装来生成新期约。如果没有提供这个处理程序，则&lt;code&gt;Promise.resolve()&lt;/code&gt;就会包装上一个期约解决之后的值。如果没有显式的返回语句，则&lt;code&gt;Promise.resolve()&lt;/code&gt;会包装默认的返回值&lt;code&gt;undefined&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;onRejected&lt;/code&gt;处理程序也与之类似：&lt;code&gt;onRejected&lt;/code&gt;处理程序返回的值也会被&lt;code&gt;Promise.resolve()&lt;/code&gt;包装。&lt;/p&gt;
&lt;h4&gt;Promise.prototype.catch()&lt;/h4&gt;
&lt;p&gt;这个方法就是一个语法糖，调用它就相当于调用&lt;code&gt;Promise.prototype. then(null, onRejected)&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;Promise.prototype.finally()&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Promise.prototype.finally()&lt;/code&gt;方法用于给期约添加&lt;code&gt;onFinally&lt;/code&gt;处理程序，这个处理程序在期约转换为解决或拒绝状态时都会执行。这个方法可以避免&lt;code&gt;onResolved&lt;/code&gt;和o&lt;code&gt;nRejected&lt;/code&gt;处理程序中出现冗余代码。但&lt;code&gt;onFinally&lt;/code&gt;处理程序没有办法知道期约的状态是解决还是拒绝，所以这个方法主要用于添加清理代码。&lt;/p&gt;
&lt;p&gt;这个新期约实例不同于&lt;code&gt;then()&lt;/code&gt;或&lt;code&gt;catch()&lt;/code&gt;方式返回的实例。因为&lt;code&gt;onFinally&lt;/code&gt;被设计为一个&lt;strong&gt;状态无关&lt;/strong&gt;的方法，所以在大多数情况下它将表现为父期约的传递（父期约什么值，它就是什么值）。对于已解决状态和被拒绝状态都是如此。&lt;/p&gt;
&lt;p&gt;特殊情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回待定期约&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onFinally&lt;/code&gt;处理程序抛出了错误（显式抛出或返回了一个拒绝期约）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;非重入期约方法&lt;/h4&gt;
&lt;p&gt;“非重入”（non-reentrancy）特性保证了当期约进入落定状态时，与该状态相关的处理程序仅仅会被排期，而非立即执行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let synchronousResolve;
    // 创建一个期约并将解决函数保存在一个局部变量中
    let p = new Promise((resolve) =&amp;gt; {
      synchronousResolve = function() {
        console.log(&apos;1: invoking resolve()&apos;);
        resolve();
        console.log(&apos;2: resolve() returns&apos;);
      };
    });
    p.then(() =&amp;gt; console.log(&apos;4: then() handler executes&apos;));
    synchronousResolve();
    console.log(&apos;3: synchronousResolve() returns&apos;);
    // 实际的输出：
    //1: invokingresolve()
    // 2: resolve()returns
    // 3: synchronousResolve() returns
    // 4: then() handler executes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;期约连锁与期约合成&lt;/h3&gt;
&lt;p&gt;把期约逐个地串联起来称为期约连锁。
要真正执行异步任务，可以让每个执行器都返回一个期约实例。这样就可以让每个后续期约都等待之前的期约，也就是&lt;strong&gt;串行化异步任务&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let p1 = new Promise((resolve, reject) =&amp;gt; {
  console.log(&apos;p1 executor&apos;);
  setTimeout(resolve, 1000);
});
p1.then(() =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
    console.log(&apos;p2 executor&apos;);
    setTimeout(resolve, 1000);
  }))
  .then(() =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
    console.log(&apos;p3 executor&apos;);
    setTimeout(resolve, 1000);
  }))
  .then(() =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
    console.log(&apos;p4 executor&apos;);
    setTimeout(resolve, 1000);
  }));
// p1 executor（1 秒后）
// p2 executor（2 秒后）
// p3 executor（3 秒后）
// p4 executor（4 秒后）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把生成期约的代码提取到一个工厂函数中，就可以写成这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function delayedResolve(str) {
  return new Promise((resolve, reject) =&amp;gt; {
    console.log(str);
    setTimeout(resolve, 1000);
  });
}
delayedResolve(&apos;p1 executor&apos;)
  .then(() =&amp;gt; delayedResolve(&apos;p2 executor&apos;))
  .then(() =&amp;gt; delayedResolve(&apos;p3 executor&apos;))
  .then(() =&amp;gt; delayedResolve(&apos;p4 executor&apos;))
// p1 executor（1 秒后）
// p2 executor（2 秒后）
// p3 executor（3 秒后）
// p4 executor（4 秒后）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种写法优化了之前的“回调地狱”问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function delayedExecute(str, callback = null) {
  setTimeout(() =&amp;gt; {
	console.log(str);
	callback &amp;amp;&amp;amp; callback();
  }, 1000)
}
delayedExecute(&apos;p1 callback&apos;, () =&amp;gt; {
  delayedExecute(&apos;p2 callback&apos;, () =&amp;gt; {
	delayedExecute(&apos;p3 callback&apos;, () =&amp;gt; {
	  delayedExecute(&apos;p4 callback&apos;);
	});
  });
});
// p1 callback（1 秒后）
// p2 callback（2 秒后）
// p3 callback（3 秒后）
// p4 callback（4 秒后）
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Promise.all()和Promise.race()&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Promise.all()&lt;/code&gt;静态方法创建的期约会在一组期约全部解决之后再解决。这个静态方法接收一个可迭代对象，返回一个新期约。&lt;/p&gt;
&lt;p&gt;合成的期约只会在每个包含的期约都解决之后才解决。如果至少有一个包含的期约待定，则合成的期约也会待定。&lt;/p&gt;
&lt;p&gt;如果有期约拒绝，则第一个拒绝的期约会将自己的理由作为合成期约的拒绝理由。之后再拒绝的期约不会影响最终期约的拒绝理由。不过，这并不影响所有包含期约正常的拒绝操作。合成的期约会静默处理所有包含期约的拒绝操作。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Promise.race()&lt;/code&gt;静态方法返回一个包装期约，是一组集合中最先解决或拒绝的期约的镜像。这个方法接收一个可迭代对象，返回一个新期约。无论是解决还是拒绝，只要是第一个落定的期约，&lt;code&gt;Promise.race()&lt;/code&gt;就会包装其解决值或拒绝理由并返回新期约。&lt;/p&gt;
&lt;h4&gt;串行期约合成&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function addTwo(x) {return x + 2; }
function addThree(x) {return x + 3; }
function addFive(x) {return x + 5; }
functioncompose(...fns){
  return(x)=&amp;gt;fns.reduce((promise, fn)=&amp;gt;promise.then(fn), Promise.resolve(x))
}
let addTen = compose(addTwo, addThree, addFive);
addTen(8).then(console.log); // 18
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;期约扩展&lt;/h2&gt;
&lt;h3&gt;期约取消&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class CancelToken {
  constructor(cancelFn) {
	this.promise = new Promise((resolve, reject) =&amp;gt; {
	  cancelFn(resolve);
	});
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button id=&quot;start&quot;&amp;gt;Start&amp;lt;/button&amp;gt;
&amp;lt;button id=&quot;cancel&quot;&amp;gt;Cancel&amp;lt;/button&amp;gt;
&amp;lt;script&amp;gt;
class CancelToken {
  constructor(cancelFn) {
	this.promise = new Promise((resolve, reject) =&amp;gt; {
	  cancelFn(() =&amp;gt; {
		setTimeout(console.log, 0, &quot;delay cancelled&quot;);
		resolve();
	  });
	});
  }
}
const startButton = document.querySelector(&apos;#start&apos;);
const cancelButton = document.querySelector(&apos;#cancel&apos;);
function cancellableDelayedResolve(delay) {
  setTimeout(console.log, 0, &quot;set delay&quot;);
  return new Promise((resolve, reject) =&amp;gt; {
	const id = setTimeout((() =&amp;gt; {
	  setTimeout(console.log, 0, &quot;delayed resolve&quot;);
	  resolve();
	}), delay);
	const cancelToken = new CancelToken((cancelCallback) =&amp;gt;
	  cancelButton.addEventListener(&quot;click&quot;, cancelCallback));
	cancelToken.promise.then(() =&amp;gt; clearTimeout(id));
  });
}
startButton.addEventListener(&quot;click&quot;, () =&amp;gt; cancellableDelayedResolve(1000));
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;期约进度通知&lt;/h3&gt;
&lt;p&gt;扩展原有的 &lt;code&gt;Promise&lt;/code&gt; 类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TrackablePromise extends Promise {
  constructor(executor) {
    const notifyHandlers = []; // 储存通知回调函数
    super((resolve, reject) =&amp;gt; { // 传递新的执行器函数
      return executor(resolve, reject, (status) =&amp;gt; {
        notifyHandlers.map((handler) =&amp;gt; handler(status));
      });
    });
    this.notifyHandlers = notifyHandlers;
  }
  notify(notifyHandler) {
    // 允许外部代码注册一个通知处理函数，该函数会在 executor 函数中的 notify 被调用时执行。
    console.log(&quot;previous&quot;, this.notifyHandlers);
    this.notifyHandlers.push(notifyHandler);
    console.log(&quot;after&quot;, this.notifyHandlers);
    return this; // 链式调用
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let p = new TrackablePromise((resolve, reject, fn) =&amp;gt; {
  function countdown(x) {
    if (x &amp;gt; 0) {
      fn(`${20 * x}% remaining`);
      setTimeout(() =&amp;gt; countdown(x - 1), 1000);
    } else {
      resolve();
    }
  }
  countdown(5);
});
// 注册 notify handler 函数
p.notify((x) =&amp;gt; setTimeout(console.log, 0, &quot;progress:&quot;, x));
p.then(() =&amp;gt; setTimeout(console.log, 0, &quot;completed&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的例子中只有 &lt;code&gt;notifyHandlers&lt;/code&gt; 只注册了一个通知函数，但是因为 &lt;code&gt;notify&lt;/code&gt; 支持链式调用，所以可以注册多个。&lt;/p&gt;
&lt;h2&gt;异步函数 async/await&lt;/h2&gt;
&lt;p&gt;使用&lt;code&gt;async&lt;/code&gt;关键字可以让函数具有异步特征，但总体上其代码仍然是同步求值的。而在参数或闭包方面，异步函数仍然具有普通函数的正常行为。&lt;/p&gt;
&lt;p&gt;不过，异步函数如果使用&lt;code&gt;return&lt;/code&gt;关键字返回了值（如果没有&lt;code&gt;return&lt;/code&gt;则会返回&lt;code&gt;undefined&lt;/code&gt;），这个值会被&lt;code&gt;Promise.resolve()&lt;/code&gt;包装成一个期约对象。异步函数始终返回期约对象。&lt;/p&gt;
&lt;p&gt;异步函数的返回值期待（但实际上并不要求）一个实现&lt;code&gt;thenable&lt;/code&gt;接口的对象，但常规的值也可以。如果返回的是实现&lt;code&gt;thenable&lt;/code&gt;接口的对象，则这个对象可以由提供给&lt;code&gt;then()&lt;/code&gt;的处理程序“解包”。如果不是，则返回值就被当作已经解决的期约。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // 返回一个原始值
    async function foo() {
      return &apos;foo&apos;;
    }
    foo().then(console.log);
    // foo
    // 返回一个没有实现thenable接口的对象
    async function bar() {
      return [&apos;bar&apos;];
    }
    bar().then(console.log);
    // [&apos;bar&apos;]
    // 返回一个实现了thenable接口的非期约对象
    async function baz() {
      const thenable={
        then(callback){callback(&apos;baz&apos;);}
      };
      return thenable;
    }
    baz().then(console.log);
    // baz
    // 返回一个期约
    async function qux() {
      return Promise.resolve(&apos;qux&apos;);
    }
    qux().then(console.log); // 返回期约等效于返回期约的值
    // qux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与在期约处理程序中一样，在异步函数中抛出错误会返回拒绝的期约：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    async function foo() {
      console.log(1);
      throw 3;
    }
    // 给返回的期约添加一个拒绝处理程序
    foo().catch(console.log);
    console.log(2);
    // 1
    // 2
    // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt;关键字会暂停执行异步函数后面的代码，让出JavaScript运行时的执行线程。这个行为与生成器函数中的yield关键字是一样的。&lt;code&gt;await&lt;/code&gt;关键字同样是尝试“解包”对象的值，然后将这个值传给表达式，再异步恢复异步函数的执行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt;关键同样字期待一个实现&lt;code&gt;thenable&lt;/code&gt;接口的对象，则这个对象可以由&lt;code&gt;await&lt;/code&gt;来“解包”。&lt;/p&gt;
&lt;p&gt;注意：对拒绝的期约使用&lt;code&gt;await&lt;/code&gt;则会释放（unwrap）错误值（&lt;strong&gt;将拒绝期约返回&lt;/strong&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    async function foo() {
      console.log(1);
      await Promise.reject(3);
      console.log(4); // 这行代码不会执行！
    }
    // 给返回的期约添加一个拒绝处理程序
    foo().catch(console.log);
    console.log(2);
    // 1
    // 2
    // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JavaScript运行时在碰到&lt;code&gt;await&lt;/code&gt;关键字时，会记录在哪里暂停执行。等到&lt;code&gt;await&lt;/code&gt;右边的值可用了，JavaScript运行时会向&lt;strong&gt;消息队列&lt;/strong&gt;中推送一个任务，这个任务会恢复异步函数的执行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    async function foo() {
      console.log(await Promise.resolve(&apos;foo&apos;));
    }
    async function bar() {
      console.log(await &apos;bar&apos;); // 等价于await Promise.resolve(&apos;bar&apos;)
    }
    async function baz() {
      console.log(&apos;baz&apos;);
    }
    foo();
    bar();
    baz();
    // baz
    // foo
	// bar
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    async function foo() {
      console.log(2);
      await null;
      console.log(4);
    }
    console.log(1);
    foo();
    console.log(3);
    // 1
    // 2
    // 3
    // 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;栈追踪与内存管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    function fooPromiseExecutor(resolve, reject) {
      setTimeout(reject, 1000, &apos;bar&apos;);
    }
    function foo() {
      new Promise(fooPromiseExecutor);
    }
    foo();
    // Uncaught (in promise) bar
    //    setTimeout
    //    setTimeout (async)
    //   fooPromiseExecutor
    //   foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;栈追踪信息应该相当直接地表现JavaScript引擎当前栈内存中函数调用之间的嵌套关系。在超时处理程序执行时和拒绝期约时，我们看到的错误信息包含嵌套函数的标识符，那是被调用以创建最初期约实例的函数。
可是，我们知道这些函数已经返回了，因此栈追踪信息中不应该看到它们。但是因为JavaScript引擎会在创建期约时尽可能保留完整的调用栈。当然，这意味着栈追踪信息会占用内存，从而带来一些计算和存储成本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function fooPromiseExecutor(resolve, reject) {
      setTimeout(reject, 1000, &apos;bar&apos;);
    }
    asyncfunction foo() {
      awaitnew Promise(fooPromiseExecutor);
    }
    foo();
    // Uncaught (in promise) bar
    //   foo
    //   asyncfunction(async)
    //   foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是异步的话，&lt;code&gt;fooPromiseExecutor()&lt;/code&gt;已经返回，所以它不在错误信息中。但&lt;code&gt;foo()&lt;/code&gt;此时被挂起了，并没有退出。JavaScript运行时可以简单地在嵌套函数中存储指向包含函数的指针，就跟对待同步函数调用栈一样。这个指针实际上存储在内存中，可用于在出错时生成栈追踪信息。这样就不会像之前的例子那样带来额外的消耗，因此在重视性能的应用中是可以优先考虑的。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | JS 高级程序设计-第3章-语言基础</title><link>https://www.ender-wiggin.com/posts/note-js-3/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-js-3/</guid><description> </description><pubDate>Wed, 22 Nov 2023 21:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;code&gt;let&lt;/code&gt; 声明&lt;/h2&gt;
&lt;h3&gt;暂时性死区 Temporal Dead Zone&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;let&lt;/code&gt; 声明属于块级作用域，在解析代码时实质上也会在变量声明前预创建，但是暂时性死区会让 &lt;code&gt;let&lt;/code&gt; 变量在执行声明前无法访问 （&lt;code&gt;ReferenceError&lt;/code&gt;），可以避免 &lt;code&gt;var&lt;/code&gt; 的变量提升导致的意外情况。&lt;/p&gt;
&lt;h3&gt;全局声明&lt;/h3&gt;
&lt;p&gt;与 &lt;code&gt;var&lt;/code&gt; 关键字不同，使用 &lt;code&gt;let&lt;/code&gt; 在全局作用域中声明的变量不会成为 &lt;code&gt;window&lt;/code&gt; 对象的属性（&lt;code&gt;var&lt;/code&gt; 声明的变量则会）。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;undefined&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;对于 js 而言，未初始化的变量和&lt;strong&gt;未声明的变量&lt;/strong&gt;调用 &lt;code&gt;typeof&lt;/code&gt; 都会返回 &lt;code&gt;undefined&lt;/code&gt;。
但是对于 ts 而言，因为在编译时会进行静态类型检查，要求变量在使用之前必须被显式地声明和赋值，因此 &lt;code&gt;typeof&lt;/code&gt; 会报错。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;null&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Null 类型同样只有一个值，即特殊值 &lt;code&gt;null&lt;/code&gt;。逻辑上讲，&lt;code&gt;null&lt;/code&gt; 值表示一个空对象指针，这也是给&lt;code&gt;typeof&lt;/code&gt; 传一个 &lt;code&gt;null&lt;/code&gt; 会返回 &lt;code&gt;object&lt;/code&gt; 的原因。
&lt;code&gt;undefined&lt;/code&gt; 值是由 &lt;code&gt;null&lt;/code&gt; 值派生而来的，因此 ECMA-262 将它们定义为表面上相等，即 &lt;code&gt;null == undefined&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;Number&lt;/code&gt; 类型&lt;/h2&gt;
&lt;p&gt;整数也可以用八进制（以8为基数，严格模式下无效，0 开头）或十六进制（以16为基数，0x 开头）字面量表示。如果字面量中包含的数字超出了应有的范围，就会忽略前缀的零，后面的数字序列会被当成十进制数。&lt;/p&gt;
&lt;p&gt;正零和负零在所有情况下都被认为是等同的。&lt;/p&gt;
&lt;h3&gt;值的范围&lt;/h3&gt;
&lt;p&gt;如果某个计算得到的数值结果超出了可以表示的范围（&lt;code&gt;&amp;lt; Number.MIN_VALUE | &amp;gt; Number.MAX_VALUE&lt;/code&gt;），那么这个数值会被自动转换为一个特殊的 &lt;code&gt;Infinity&lt;/code&gt; 值。任何无法表示的负数以 &lt;code&gt;-Infinity&lt;/code&gt; 表示，任何无法表示的正数以 &lt;code&gt;Infinity&lt;/code&gt; 表示。&lt;/p&gt;
&lt;p&gt;要确定一个值是不是有限大，可以使用 &lt;code&gt;isFinite()&lt;/code&gt; 函数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Number.NEGATIVE_INFINITY&lt;/code&gt; 和 &lt;code&gt;Number.POSITIVE_INFINITY&lt;/code&gt; 也可以获取正、负Infinity。这两个属性包含的值分别就是 &lt;code&gt;-Infinit&lt;/code&gt;y 和 &lt;code&gt;Infinity&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;NaN&lt;/h3&gt;
&lt;p&gt;NaN 意思是“不是数值”（Not a Number），用于表示本来要返回数值的操作失败了（而不是抛出错误）。NaN不等于包括NaN在内的任何值。判断是否是 NaN 使用 &lt;code&gt;isNaN()&lt;/code&gt; 函数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;isNaN()&lt;/code&gt; 可以用于测试对象。此时，首先会调用对象的 &lt;code&gt;valueOf()&lt;/code&gt; 方法，然后再确定返回的值是否可以转换为数值。如果不能，再调用 &lt;code&gt;toString()&lt;/code&gt; 方法，并测试其返回值，和许多内置函数和操作符的工作方式相似。&lt;/p&gt;
&lt;h3&gt;数值转换&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Number()&lt;/code&gt; 函数将可以合法转化的值转成数字，包括整数和浮点数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parseInt()&lt;/code&gt; 函数有两个参数。第一个参数中如果第一个字符不是数值字符、加号或减号，立即返回NaN。这意味着空字符串也会返回NaN（这一点跟Number()不一样，它返回0）。第二个参数，用于指定底数（进制数）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parseFloat()&lt;/code&gt; 解析到字符串末尾或者解析到一个无效的浮点数值字符为止，并且只能解析十进制值。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;code&gt;String&lt;/code&gt; 类型&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;toString()&lt;/code&gt; 方法可见于数值、布尔值、对象和字符串值。&lt;code&gt;null&lt;/code&gt; 和 &lt;code&gt;undefined&lt;/code&gt; 值没有 &lt;code&gt;toString()&lt;/code&gt; 方法。
多数情况下，&lt;code&gt;toString()&lt;/code&gt; 不接收任何参数。不过，在对数值调用这个方法时，&lt;code&gt;toString()&lt;/code&gt; 可以接收一个底数参数，即以什么底数来输出数值的字符串表示。
如果你不确定一个值是不是 &lt;code&gt;null&lt;/code&gt; 或 &lt;code&gt;undefined&lt;/code&gt;，可以使用 &lt;code&gt;String()&lt;/code&gt; 转型函数，它始终会返回表示相应类型值的字符串（&lt;code&gt;&quot;null&quot;&lt;/code&gt; 和 &lt;code&gt;&quot;undefined&quot;&lt;/code&gt;）。&lt;/p&gt;
&lt;h3&gt;模板字面量&lt;/h3&gt;
&lt;p&gt;使用模版字面量的字符串插值时，对于任何插入的值都会使用 &lt;code&gt;toString()&lt;/code&gt; 强制转型为字符串。&lt;/p&gt;
&lt;p&gt;模板字面量也支持定义标签函数，标签函数接收到的参数依次是原始字符串数组和对每个表达式求值的结果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let a = 6;
let b = 9;
functionsimpleTag(strings, ...expressions){
  console.log(strings);
  for(const expression of expressions) {
	console.log(expression);
  }
  return &apos;foobar&apos;;
}
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
// [&quot;&quot;, &quot; + &quot;, &quot; = &quot;, &quot;&quot;]
// 6
// 9
// 15
console.log(taggedResult);   // &quot;foobar&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于有n个插值的模板字面量，传给标签函数的表达式参数的个数始终是n，而传给标签函数的第一个参数所包含的字符串个数则始终是n+1。&lt;/p&gt;
&lt;h3&gt;原始字符串&lt;/h3&gt;
&lt;p&gt;使用默认的 &lt;code&gt;String.raw&lt;/code&gt; 标签函数可以直接获取原始的模板字面量内容（如换行符或Unicode字符），而不是被转换后的字符表示。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(`\u00A9`);               // ©
console.log(String.raw`\u00A9`);   // \u00A9
// 换行符示例
console.log(`first line\nsecond line`);
// first line
// second line
console.log(String.raw`first line\nsecond line`); // &quot;first line\nsecond line&quot;

// 对实际的换行符来说是不行的
// 它们不会被转换成转义序列的形式
console.log(`first line
second line`);
// first line
// second line
console.log(String.raw`first line
second line`);
// first line
// second line
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;Symbol&lt;/code&gt; 类型&lt;/h2&gt;
&lt;p&gt;Symbol（符号）是ES6新增的数据类型。符号是原始值，且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符，不会发生属性冲突的危险。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let sym = Symbol(); // 初始化方式，不能 new，为了了避免创建符号包装对象
let symWithDesc = Symbol(&apos;desc&apos;); // 初始化方式，不能 new
console.log(typeof sym); // symbol
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果运行时的不同部分需要共享和重用符号实例，那么可以用一个字符串作为键，在&lt;strong&gt;全局符号注册表&lt;/strong&gt;中创建并重用符号。&lt;code&gt;Symbol.for() &lt;/code&gt;对每个字符串键都执行幂等操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let fooGlobalSymbol = Symbol.for(&apos;foo&apos;);         // 创建新符号
let otherFooGlobalSymbol = Symbol.for(&apos;foo&apos;);   // 重用已有符号

let localSymbol = Symbol(&apos;foo&apos;); // 不使用 for 不会注册
console.log(fooGlobalSymbol === otherFooGlobalSymbol);   // true
console.log(localSymbol === fooGlobalSymbol); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还可以使用 &lt;code&gt;Symbol.keyFor() &lt;/code&gt;来查询全局注册表，这个方法接收符号，返回该全局符号对应的字符串键。如果查询的不是全局符号，则返回 &lt;code&gt;undefined&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 创建全局符号
let s = Symbol.for(&apos;foo&apos;);
console.log(Symbol.keyFor(s));    // foo
// 创建普通符号
let s2 = Symbol(&apos;bar&apos;);
console.log(Symbol.keyFor(s2));   // undefined

Symbol.keyFor(123); // 如果传入的不是 Symbol 会报错 TypeError
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;符号也可以作为属性&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let s1 = Symbol(&apos;foo&apos;),
        s2 = Symbol(&apos;bar&apos;),
        s3 = Symbol(&apos;baz&apos;),
        s4 = Symbol(&apos;qux&apos;);
    let o = {
      [s1]: &apos;foo val&apos; // 对象字面量中只能是计算属性
    };
    // 这样也可以：o[s1] = &apos;foo val&apos;;
    console.log(o);
    // {Symbol(foo): foo val}
    Object.defineProperty(o, s2, {value: &apos;bar val&apos;});
    console.log(o);
    // {Symbol(foo): foo val, Symbol(bar): bar val}
    Object.defineProperties(o, {
      [s3]: {value: &apos;baz val&apos;},
      [s4]: {value: &apos;qux val&apos;}
    });
    console.log(o);
    // {Symbol(foo): foo val, Symbol(bar): bar val,
    //   Symbol(baz): baz val, Symbol(qux): qux val}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类似于 &lt;code&gt;Object.getOwnPropertyNames()&lt;/code&gt; 返回对象实例的常规属性数组 &lt;code&gt;Object.getOwnPropertySymbols()&lt;/code&gt; 返回对象实例的符号属性数组。这两个方法的返回值彼此互斥。&lt;code&gt;Object.getOwnPropertyDescriptors()&lt;/code&gt; 会返回同时包含常规和符号属性描述符的对象。&lt;code&gt;Reflect.ownKeys()&lt;/code&gt; 会返回两种类型的键。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | Poetry: Python 包管理器</title><link>https://www.ender-wiggin.com/posts/note-poetry/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-poetry/</guid><description> </description><pubDate>Wed, 22 Nov 2023 21:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;依赖组&lt;/h2&gt;
&lt;p&gt;Poetry 提供了一种按组组织依赖项的方法。例如，您可能具有仅测试项目或构建文档所需的依赖项。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.poetry.group.test]  # This part can be left out

[tool.poetry.group.test.dependencies]
pytest = &quot;^6.0.0&quot;
pytest-mock = &quot;*&quot;

[tool.poetry.group.docs]
optional = true # 说明该组可选, 可以通过 poetry install --with docs 安装

[tool.poetry.group.docs.dependencies]
mkdocs = &quot;*&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;除隐式 &lt;code&gt;main&lt;/code&gt; 组之外的依赖项组必须仅包含开发过程中所需的依赖项。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[tool.poetry.extras]&lt;/code&gt; 可以设定可选依赖，安装时也可以使用 &lt;code&gt;-E|--extras&lt;/code&gt; 选项指定额外内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;poetry add pytest --group test&lt;/code&gt; 可以将依赖添加到配置文件对应组中&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[tool.poetry.group.dev.dependencies]&lt;/code&gt; 是开发依赖的首选形式, 早期版本存在 &lt;code&gt;[tool.poetry.dev-dependencies]&lt;/code&gt;, 目前暂时向下兼容&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;poetry install --with test,docs --without docs
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;通过 &lt;code&gt;with&lt;/code&gt; 和 &lt;code&gt;without&lt;/code&gt; 关键词可以选择组, 其中 &lt;code&gt;without&lt;/code&gt; 有更高优先级&lt;/li&gt;
&lt;li&gt;只想安装特定组, 可以使用 &lt;code&gt;only&lt;/code&gt; ( &lt;code&gt;--only-root&lt;/code&gt; 只安装根目录)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;poetry install --sync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Poetry 支持所谓的依赖同步。依赖项同步可确保 &lt;code&gt;poetry.lock&lt;/code&gt; 文件中锁定的依赖项是环境中唯一存在的依赖项，从而删除任何不需要的内容。&lt;/p&gt;
&lt;h2&gt;打包&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;poetry build
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;poetry publish # 发布到 PyPl

poetry publish -r my-repository # 发布到私有仓库
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;私有仓库配置 &lt;a href=&quot;https://python-poetry.org/docs/repositories/#adding-a-repository&quot;&gt;Repositories | Documentation | Poetry - Python dependency management and packaging made easy&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;环境&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;poetry env use /full/path/to/python # 指定路径
poetry env use python3.7 # 使用安装过的版本 (or poetry env use 3.7)
poetry env use system # 默认行为

poetry env info # 检查环境
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;poetry env list # 列出与当前项目关联的所有虚拟环境
poetry env list --full-path # 显示完整路径

poetry env remove 3.7 # 删除环境
poetry env remove --all # 删除所有
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;更新&lt;/h2&gt;
&lt;h3&gt;Caret requirements&lt;/h3&gt;
&lt;p&gt;Caret 要求允许 SemVer 兼容更新到指定版本。如果新版本号不修改主要、次要、补丁分组中最左边的非零数字，则允许更新。例如，如果我们之前运行 &lt;code&gt;poetry add requests@^2.13.0&lt;/code&gt; 并想要更新库并运行 &lt;code&gt;poetry update requests&lt;/code&gt; ，诗歌会将我们更新到版本 &lt;code&gt;2.14.0&lt;/code&gt; （如果可用），但会不要将我们更新为 &lt;code&gt;3.0.0&lt;/code&gt; 。如果我们将版本字符串指定为 &lt;code&gt;^0.1.13&lt;/code&gt; ，将更新为 &lt;code&gt;0.1.14&lt;/code&gt; 而不是 &lt;code&gt;0.2.0&lt;/code&gt; 。 &lt;code&gt;0.0.x&lt;/code&gt; 不被视为与任何其他版本兼容。&lt;/p&gt;
&lt;h3&gt;Tilde requirements&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;REQUIREMENT 要求&lt;/th&gt;
&lt;th&gt;VERSIONS ALLOWED 允许的版本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~1.2.3&lt;/td&gt;
&lt;td&gt;&amp;gt;=1.2.3 &amp;lt;1.3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~1.2&lt;/td&gt;
&lt;td&gt;&amp;gt;=1.2.0 &amp;lt;1.3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~1&lt;/td&gt;
&lt;td&gt;&amp;gt;=1.0.0 &amp;lt;2.0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Wildcard requirements&lt;/h3&gt;
&lt;p&gt;即使用 &lt;code&gt;*&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Using the &lt;code&gt;@&lt;/code&gt; operator&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;poetry add django@^4.0.0 # 类似于 == (Django = &quot;^4.0.0&quot;)
poetry add django@latest # 特殊关键词
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;git 依赖&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[tool.poetry.dependencies]
requests = { git = &quot;https://github.com/requests/requests.git&quot; } # 最基础情况

# 更加复杂的情况
[tool.poetry.dependencies]
# Get the latest revision on the branch named &quot;next&quot;
requests = { git = &quot;https://github.com/kennethreitz/requests.git&quot;, branch = &quot;next&quot; }
# Get a revision by its commit hash
flask = { git = &quot;https://github.com/pallets/flask.git&quot;, rev = &quot;38eb5d3b&quot; }
# Get a revision by its tag
numpy = { git = &quot;https://github.com/numpy/numpy.git&quot;, tag = &quot;v0.13.2&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# 添加
poetry add &quot;git+https://github.com/myorg/mypackage_with_subdirs.git#subdirectory=subdir&quot;

# ssh 配置
[tool.poetry.dependencies]
requests = { git = &quot;git@github.com:requests/requests.git&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外本地目录以及远程链接见文档 &lt;a href=&quot;https://python-poetry.org/docs/dependency-specification/#path-dependencies&quot;&gt;Dependency specification | Documentation | Poetry - Python dependency management and packaging made easy&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>影评 | 《亨利·休格的神奇故事》</title><link>https://www.ender-wiggin.com/posts/review-the-wonderful-story-of-henry-sugar/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-the-wonderful-story-of-henry-sugar/</guid><description>剧透警告</description><pubDate>Wed, 04 Oct 2023 22:00: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;另外，故事对于“真实性”的讨论值得思考。一方面是叙述层级递进中的真实性：瑜伽行者的话是否真实？领悟到瑜伽行者奥妙的 The man who sees without his eyes 的经历是否真实？记录事件的医生所言是否真实？阅读了医生记录的亨利·休格的故事是否真实？将亨利·休格的故事写成的书籍《亨利·休格的神奇故事》是否真实？又或者现在这部改编后的电影看起来是否真实？从理性上来说一个童话故事肯定没人会信以为真，但是每个子故事中又总有那么些元素将故事包装得有些真实性，思考哪些元素带来了真实性也是个有趣的思维活动。并且递归来看，上述层级的不同人可能也都对上一个层级的真实性抱有怀疑，但是他们选择了相信并且创造出了下一个“真实”的故事。&lt;/p&gt;
&lt;p&gt;另一方面是亨利·休格赚到一笔大钱后对人生真实性或者说确定性的质疑，看见不可见之物的引申含义是可以准确看见人生中各种可能性与不确定性。功成的亨利·休格可以看到生命的红线，也可以预知自己未来的财富，这个角度透露出的人生观是非常积极的存在主义。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>书评 | 《马斯克传》</title><link>https://www.ender-wiggin.com/posts/review-elon-musk/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-elon-musk/</guid><description> </description><pubDate>Sat, 30 Sep 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这本最近几周新出版的《马斯克传》在我看来有 2 个最明显的特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这本传记是在传主事业中期完成的，这与主流的死后或者生涯退役作传是有较大区别的，因此读者对于传记中很多事迹和想法只有概率学上的认知，但某种意义上来说也有“开盲盒”似的未知感。&lt;/li&gt;
&lt;li&gt;传主作为一个在不同工业科技领域影响世界的人，他的许多行为与决策会很大程度地影响人类生活（例如星链和星舰）。这种关乎自身未来的不确定性与第一点叠加，即带来了阅读上的新鲜感与趣味性，同时也让读者更能感受到传记背后的现实意义。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此本篇书评我希望从三个方面展开讨论，分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从书籍创作的角度，探究一下本传记的行文结构和整体内容&lt;/li&gt;
&lt;li&gt;从自身体验的角度，谈论一下了解传主生平对我的个人意义&lt;/li&gt;
&lt;li&gt;从社会影响的角度，思考一下书中提及与影射的现实问题与未来展望&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;行文风格与结构&lt;/h2&gt;
&lt;p&gt;在这本书发售之前，出版方就已经在互联网上造势，发布了前几章的试读吊足了读者胃口。我当时对这本书的第一想法是：很吸引人，但感觉叙述方式有些浮夸。当本书正式在微信读书中上线时，我发现前面几个评论（才刚上线没多久就有书评了）大意都是认为本书不够客观、有夸大之嫌。
在我通篇读完之后，我感觉这本书前后的风格确实有明显差异：&lt;/p&gt;
&lt;p&gt;前期像是非常典型的创业故事，行文风格带有马斯克特有的狂热感。阅读体验很像超级英雄电影的角色介绍画面，在给人物做了个简短的起源故事后伴随翻页声立刻在画面上闪入一个大比例立绘，并用醒目的方式显示角色的名字或称号。叙述上的主观性某种意义上也带来了血脉偾张的畅快阅读体验，有一种传记小说的感觉。
但是因为这些早期经历作者只能通过他人转述提取信息，这种畅快体验的代价就是细节上的夸张或缺漏。因为我自己也是程序员，所以对于书中的一些编程相关内容比较敏感，例如第一章这一处：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;马斯克运用逆向工程思维搞明白了这款游戏背后的设计思路，所以他每次都能获胜。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我所关心的怎么运用、什么思路作者只字未提，这种结论性叙述在前期中不时有出现，或许也是因为作者 Walter Isaacson 作为文科生并不深入了解技术内容，尽管他曾经也出版了几部非常畅销的技术类非虚构作品。&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;因此马斯克很难成为普通人心中的榜样，甚至可以认为是一个反面教材。可以设想在不同平行宇宙中，有的马斯克倒在了 Zip2 的创业中，有的马斯克败给了 08 年的危机，还有的马斯克输在了 18 年的量产危机中...我们这个宇宙的马斯克可能会覆灭在现在的推特风波之下，也可能是未来的某次大事件，但或许他会一直成功下去。他性格中的这种冒险性无疑为他个人经历增添了许多传奇色彩，因此在这个不确定的节点出版这本书也别有存在主义风味，让读者对于未被书写的历史有一种见证者般的期待与现实感。&lt;/p&gt;
&lt;p&gt;当马斯克创业之后，我更加代入的其实是他的那些员工们，毕竟我自己也只是一个普普通通的打杂人员，没有那种挥斥方遒的气魄。他对员工的很多行为唤起了我刚工作时的经历，比如程序员非常诟病的 &quot;deploy on Friday&quot;。一个最典型的事例应该是他对推特大刀阔斧的调整，我在 2022 年感觉他就像是个跳梁小丑。书中描写的比我之前了解的更加有戏剧性，一家注重 work life balance、允许 remote work、注重员工福利的互联网公司简直就是我的理想工作地，但是被马斯克硬生生”篡权“为一个人人自危的集权帝国。我目前的眼界肯定无法站在管理层的角度思考问题，并且站在那个角度还容易被网友批为”工贼“，得不偿失。但我在思考的一个问题是，即便是这种工作氛围，为什么还是有不少数量的员工愿意追随？&lt;/p&gt;
&lt;p&gt;我能想到的有两个原因。
一方面，尽管传记中很多故事看起来很有压迫感，但他们毕竟频率没那么高，马斯克至少下限还是差不多有劳动法的（虽然按书中的说法他也是美国工会的众矢之的）。比如书中提到，马斯克要求那些在家办公的推特员工重返公司坐班，并且一周至少待够 40 小时。对于推特这些原先员工这无疑是天打雷劈，但是对于我们中国的程序员来说，还有这种好事？
另一方面，马斯克的独裁尽管有很多负面性，但从做实事的角度来说他确实做成了很多看似不可能的事情，而员工参与这些项目也是有足够成就感的。按我个人体验来说，参与公司的一些自己感兴趣的项目时确实是会有兴奋感，平常我即便下班的也会自己写代码，因此如果喜欢当前所做的事情，对于我这种还没有成家的人来说上班与下班的界限并不会这么明显。但是，如果在工作中遇到了无意义的任务，尤其是因为种种原因无法推进或搁置项目时会有一种挫败感。马斯克的风格就是很极端地删减官僚方面的低效率与无意义内容，让员工们狂热地朝着一个看似前途光明的险径一往无前。那么，对于员工而言，他们也就是在能够成事的正向情绪与影响个人生活的负面情绪之间权衡，&lt;/p&gt;
&lt;h2&gt;未来与冒险&lt;/h2&gt;
&lt;p&gt;比起”我们也不应该将未来交付给马斯克“这个问题，我认为更应该思考的是”我们如何应对这个马斯克所参与构建的未来“。当然，未来肯定是由多方面构成的，比如现在的 AI 狂潮马斯克是否可以赶上都是一个未知数，但他或多或少一定是会影响人类未来的。
比如星链，在消息公布之时便在知乎上掀起一阵担忧，”非我族类其心必异“这句古话在有反全球化和民粹主义趋势的今天确实有一定现实意义。马斯克对于乌克兰的星链援助仿佛就是个人恩泽，个人意志可以完全主导星链的使用权，这对于未来军事有很大的警示性。他本人在推特上也表示自己的爱国主义会让他优先考虑美国的福祉，这句话站在个人角度肯定无可指摘，但是对于不同国家的其他人无疑是应该警惕的。
书中提到了哈耶克的经典名言：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;马斯克强调了推动其公司发展的使命所在。“我只是想把人类送上火星，用星链实现信息自由，用特斯拉加速可持续能源技术推广，把人们从繁重的驾驶任务中解放出来。”他说，“当然，人们美好的意愿可能铺就一条通往地狱的道路，但绝大多数情况下，通往地狱的道路都是由邪恶的意图铺就的。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;他不是典型的追求财富的富人，很难不让人联想到哈耶克另外关于“富人掌权”的观点。再考虑马斯克在挥舞“言论自由”大旗后一人专权封禁或者解封账户，以及高呼自己为了全人类利益但对许多个人生活毫不关心，这些矛盾的景象在许多的专权者身上都可以看到。某种意义上来说，这种富可敌国的超级公司比政体更加危险，因为公司确实可能是一个人独断专权。
作为科幻爱好者，简化版的马斯克生平很像是一个阿西莫夫《基地》中的故事——个体一意孤行改变银河未来。也正如他在书中开篇所言：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于所有曾被我冒犯的人，我只想对你们说，我重新发明了电动车，我要用火箭飞船把人类送上火星。
可我要是个冷静、随和的普通人，你们觉得我还能做到这些吗？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我很喜欢书中他对于未来的热情，比如“技术进步不是必然的”，他的许多事迹也从一个方面体现了保守主义甚至高度自由的民主制度很难推动科技进步。但是有一个无法避开的问题就是，一个人可以影响世界，但如何保证这种“影响”是正向的？
假设按照他和《基地》的思路，那么未来只有三条路：个人（专权者）牺牲他人让世界进步、专权者牺牲他人却让世界退步、保守主义的原地踏步，这个选择是一个非常困难的问题。&lt;/p&gt;
&lt;p&gt;退一步说，我在想，如果有一天马斯克真的发明了前往火星的飞船，我敢不敢踏上这个征途？
我认为我是不敢的，看到了他在书中这么多武断的降本措施，以及他对于特斯拉自动驾驶伤亡事故的不管不问，他这种以最大的宏观人类角度的人对于“代价”二字显然有着非常残酷的认知。
但话说回来这也正是冒险的魅力。书中有个耐人寻味的故事，穆勒在加入 SpaceX 时要求马斯克要将穆勒两年的薪水交由第三方托管。书中说到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;穆勒不是互联网行业的有钱人，所以他不想万一有一天SpaceX失败的话，自己还要担着被老板拖欠工资的风险。马斯克同意了，不过这样做了以后，他就会认为穆勒是雇员，而不是SpaceX的联合创始人。他认为，如果你不愿意躬身入局，用你的金钱和智慧去投资一家公司，你就没有资格成为创始人。“你不能一边要求把两年的工资托管起来，一边还觉得自己是联合创始人。”马斯克说，“想成为联合创始人，必须在付出聪明才智、辛勤耕耘的同时勇于承担风险。”&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>影评 | 《AI创世者》：为什么未来不像未来</title><link>https://www.ender-wiggin.com/posts/review-the-creator/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-the-creator/</guid><description> </description><pubDate>Tue, 26 Sep 2023 22:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;今天体验了一场“盲盒”电影——在影片播放前没人知道这是什么电影，只知道是一部提前上映的电影。这种感觉本身挺奇妙的，因为在养成了看电影前先看评分和简介/预告片的习惯后，这种完全随机的未知感仿佛很遥远了。当荧幕亮起，映入眼帘的是一架高空悬浮的大型科幻武器，我的心情很是激动，因为这些年来能让人期待的科幻电影实在是太少了。&lt;/p&gt;
&lt;p&gt;随着故事的展开，期待感也慢慢的被磨灭，因为这又是一个人工智能的故事，尤其是在 ChatGPT 已经上线快一年的 2023 年的人工智能故事，还是在循规蹈矩地重复着老生常谈的内容。回望影史，《人工智能》是 2001 年的电影，而《我，机器人》是 2004 年的电影，让我很困惑的是为什么电影界发展了 20 年但是却可以产出仿佛倒退的电影？&lt;/p&gt;
&lt;p&gt;人工智能题材的重复是一方面，末日废土设定的重复则是另一方面。这些年科幻电影明显对废土风格偏爱有加，我认为其中一个原因就是拍摄成本低——制作一个极具未来感的世界多累啊，设定困难，特效成本也高。&lt;/p&gt;
&lt;p&gt;我想到最近《马斯克传》中的一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么未来看起来不像是未来？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一般废土设定都可以逻辑自洽：足以毁灭人类的战争或其它类似威胁让人类文明科技大幅度倒退。但是这部电影中的事故从逻辑上并没有阻碍文明发展或者导致文明倒退的理由。一场“人为事故”的核爆，加上以自卫为目的与“天网”“母体”性质完全不同的机器人社会，这部影片的设定并不贴合废土的定义，但是呈现出来的效果并不那么未来。&lt;/p&gt;
&lt;p&gt;因此，相较于正统意义上的未来主义科幻，我更愿意将这部影片的艺术风格称之为古典柴油朋克。这个词是 Jakub Rozalski 这位画家的绘画风格，我认为和这部电影的风格非常贴近：
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20230927232027.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我想特别声明的一点是，从艺术设定的角度，我虽然更希望这部电影是未来主义风格，但是蒸汽朋克柴油朋克的风格也不差，影片中的机器人藏族风格僧侣很带感。但是这种设定是很风格化的，也就是说为了艺术传达和画面构成会牺牲一定科学上的合理性，比如为什么这些僧侣要将胡须、皱纹、老者皮肤都制作得如此逼真？因此，这部电影从艺术的角度而言可圈可点，但是按照比较原教旨的科幻角度来说科技感有些不足。
此外，影片中似乎出现了一个挺有科幻感的设定：记忆提取，扫描死者脑部结构并在机器人上重现。但是科幻爱好者们很容易就会发现这设定似曾相识——而那部同样设定的《少数派报告》是 2002 年的电影，更不用说 PKD 的原著了。&lt;/p&gt;
&lt;p&gt;抛开艺术和科幻设定不谈，接下来我就单纯聊聊剧情本身。&lt;/p&gt;
&lt;p&gt;作为又一部探讨人与机器人关系的电影，这部作品在人文角度也显得有些苍白。里面绝大多数的人和机器人都是纯背景板，而着重笔墨聚焦的却又是设定老套的男主和小女孩。如果说除去这一层科幻外衣，这种传统公路片设定被多少影史佳作完爆了。即便是在科幻电影类别中，《人工智能》里面凭借”剪母亲头发“和”询问知晓一切的机器如何找妈妈“这两段轻轻松松就勾勒出了一个渴望母爱却饱经风霜的小机器人男孩形象，但这部电影中今年花了这么多篇幅我却还是感觉导演没有深入到小女孩的内心，让我从观感上看不出她和一个普通的人类小女孩有什么区别。想起来我去年对《芬奇》这部同样风格的机器人公路片评价也不高，但是一对比我突然感觉《芬奇》的人情味还是挺丰富的。&lt;/p&gt;
&lt;p&gt;总的来说，这部电影作为连贯的艺术风格虽然不够未来但是也可圈可点，但是从科幻设定和剧情的角度来说都平平无奇，为近几年平庸的科幻电影市场再添一员。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>殖民火星 | 《殖民火星：前序 2》</title><link>https://www.ender-wiggin.com/posts/terraforming-mars-prelude-2/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/terraforming-mars-prelude-2/</guid><description> </description><pubDate>Thu, 21 Sep 2023 09:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言：有什么新东西？&lt;/h2&gt;
&lt;p&gt;《殖民火星：前序 2》（也称之为《重塑火星：序幕 2》）即将发售，包含大量新牌、随机里程碑以及新地图。
这篇文章将会介绍截止至 9 月 21 号公布的所有新资讯，有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;21张新项目卡&lt;/li&gt;
&lt;li&gt;10 张新前序&lt;/li&gt;
&lt;li&gt;7 张新公司&lt;/li&gt;
&lt;li&gt;1 张新地图&lt;/li&gt;
&lt;li&gt;数个新里程碑/奖励&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;新地图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20230921235113.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;新里程碑/奖励&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-06-000139..jpg&quot; alt=&quot;picgo-2023-09-06-000139..jpg&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;里程碑&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;贿赂者（Briber）：为了获得这个里程碑，支付12MC，加上正常里程碑费用8MC（总共20M€）。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;建筑师（Builder）：7个建筑标志。&lt;/li&gt;
&lt;li&gt;海岸警卫（Coastguard）：3个靠近海洋的板块。&lt;/li&gt;
&lt;li&gt;多样化者（Diversifier）：8个不同的标志。&lt;/li&gt;
&lt;li&gt;生态学家（Ecologist）：4个微生物标志。&lt;/li&gt;
&lt;li&gt;能量供应者（Energizer）：6个能源产能。&lt;/li&gt;
&lt;li&gt;工程师（Engineer）：能源和热量产能总和达到10。&lt;/li&gt;
&lt;li&gt;农夫（Farmer）：动物和微生物资源总和达到5。&lt;/li&gt;
&lt;li&gt;慈善家（Filantrope）：拥有5张非负VP的卡牌。（计算每2个微生物或其他类似卡牌的1个VP也计入其中。）&lt;/li&gt;
&lt;li&gt;林务员（Forester）：3个植物产能。&lt;/li&gt;
&lt;li&gt;筹款者（Fundraiser）：12M€产能。&lt;/li&gt;
&lt;li&gt;园艺师（Gardener）：3个绿化板块。&lt;/li&gt;
&lt;li&gt;多面手（Generalist）：至少拥有每种资源的1个产能。&lt;/li&gt;
&lt;li&gt;地质学家（Geologist）：3个位于或靠近火山地区（地图上以粗体标记）的板块。如果使用没有火山地区的地图，请替换此里程碑。&lt;/li&gt;
&lt;li&gt;水文学家（Hydrologist）：放置了4个海洋。（在每个海洋上放置所有者标记，直到被认领。但是，这个所有者标记并不意味着海洋板块归你所有。）&lt;/li&gt;
&lt;li&gt;造景师（Landshaper）：1个城市，1个绿化板块和1个特殊板块。&lt;/li&gt;
&lt;li&gt;传奇人物（Legend）：至少打出4张事件卡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;游说者（Lobbyist）：所有7个代表都加入了政党（政党领袖和主席也计入其中）。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;市长（Mayor）：3个城市。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;商人（Merchant）：支付里程碑费用后，拥有每种标准资源至少2个。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;冶金师（Metallurgist）：钢铁和钛合金产能总和达到6。&lt;/li&gt;
&lt;li&gt;拓荒者（Pioneer）：4个殖民地。&lt;/li&gt;
&lt;li&gt;行星学家（Planetologist）：2个地球标志，2个金星标志和2个木星标志。&lt;/li&gt;
&lt;li&gt;规划师（Planner）：手中有16张卡牌。&lt;/li&gt;
&lt;li&gt;生产者（Producer）：总产能至少达到16。不需要代表所有类型，负数M€产能从总数中减去。&lt;/li&gt;
&lt;li&gt;研究员（Researcher）：4个科学标志。&lt;/li&gt;
&lt;li&gt;边缘殖民者（Rim settler）：3个木星标志。&lt;/li&gt;
&lt;li&gt;太空旅行者（Spacefarer）：4个太空标志。&lt;/li&gt;
&lt;li&gt;赞助商（Sponsor）：拥有3张成本为20 M€或更高的卡牌。&lt;/li&gt;
&lt;li&gt;战术家（Tactician）：拥有4张有打出前置的卡牌。&lt;/li&gt;
&lt;li&gt;改造者（Terraformer）：29TR。&lt;/li&gt;
&lt;li&gt;地球人（Terran）：5个地球标志。&lt;/li&gt;
&lt;li&gt;解冻者（Thawer）：将温度提升5次。（每次提升温度时，在温度轨道上用您的玩家标记标记，直到认领该里程碑。）&lt;/li&gt;
&lt;li&gt;交易商（Trader）：卡牌上有3种不同类型的资源。&lt;/li&gt;
&lt;li&gt;大亨（Tycoon）：10张蓝色和绿色卡牌。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;奖励&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;行政官（Administrator）：没有标志的卡牌最多（包括公司和前序）。&lt;/li&gt;
&lt;li&gt;银行家（Banker）：最高的MC产能。&lt;/li&gt;
&lt;li&gt;恩人（Benefactor）：最高的TR。如果使用此奖项，请在计算其他奖项和里程碑之前计算它。&lt;/li&gt;
&lt;li&gt;生物学家（Biologist）：植物、微生物和动物标志总数最多。&lt;/li&gt;
&lt;li&gt;植物学家（Botanist）：最高的植物产能。&lt;/li&gt;
&lt;li&gt;名人（Celebrity）：成本为20 M€或更高的卡牌最多。&lt;/li&gt;
&lt;li&gt;收藏家（Collector）：在您的玩家板和卡牌上拥有最多种不同类型的资源。&lt;/li&gt;
&lt;li&gt;建筑师（Constructor）：殖民地和城市数量最多的组合。&lt;/li&gt;
&lt;li&gt;承包商（Contractor）：建筑标志最多。&lt;/li&gt;
&lt;li&gt;耕作者（Cultivator）：绿化板块最多。&lt;/li&gt;
&lt;li&gt;电工（Electrician）：电力标志最多。&lt;/li&gt;
&lt;li&gt;地产经纪人（Estate Dealer）：靠近海洋的板块最多。&lt;/li&gt;
&lt;li&gt;古怪者（Excentric）：卡牌上的资源最多。&lt;/li&gt;
&lt;li&gt;预测师（Forecaster）：有要求的卡牌最多。&lt;/li&gt;
&lt;li&gt;创始人（Founder）：靠近特殊板块的板块最多。&lt;/li&gt;
&lt;li&gt;高地人（Highlander）：与海洋不相邻的板块最多。&lt;/li&gt;
&lt;li&gt;公司董事（Incorporator）：成本为10 M€或更低的卡牌最多。&lt;/li&gt;
&lt;li&gt;投资者（Investor）：地球标志最多。&lt;/li&gt;
&lt;li&gt;地主（Landlord）：板块最多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;造景师（Landscaper）：连接在一起的板块最多（每个玩家计算自己最大的板块组）。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;大亨（Magnate）：绿色卡牌最多。&lt;/li&gt;
&lt;li&gt;制造商（Manufacturer）：钢铁和热量产能总和最高。&lt;/li&gt;
&lt;li&gt;都市主义者（Metropolist）：城市数量最多。&lt;/li&gt;
&lt;li&gt;矿工（Miner）：钢铁和钛合金资源总和最多。&lt;/li&gt;
&lt;li&gt;大亨（Mogul）：钢铁、钛合金、植物、能源和热量产能总和最高（除了M€产能）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;政治家（Politician）：党派领袖和影响力总和最多。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;推广员（Promoter）：您的事件牌堆中的卡牌最多。&lt;/li&gt;
&lt;li&gt;科学家（Scientist）：科学标志最多。&lt;/li&gt;
&lt;li&gt;太空贵族（Space Baron）：太空标志最多。&lt;/li&gt;
&lt;li&gt;郊区人（Suburbian）：地图边缘区域的板块最多。&lt;/li&gt;
&lt;li&gt;供应商（Supplier）：钢铁和能源资源总和最多。&lt;/li&gt;
&lt;li&gt;热能专家（Thermalist）：热量资源最多。&lt;/li&gt;
&lt;li&gt;旅行者（Traveller）：木星和地球标志总和最多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有远见者（Visionary）：手中的卡牌最多。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;动物学家（Zoologist）：动物和微生物资源总和最多。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Promo&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000250..jpg&quot; alt=&quot;picgo-2023-09-21-000250..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;防沙技术&lt;/li&gt;
&lt;li&gt;文本:将你的植物和钢铁产量各提高 1 级。获得 30 MC。&lt;/li&gt;
&lt;li&gt;背景文字: 结合土壤的植物与细菌结合，将盐转化为建筑材料。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000249..jpg&quot; alt=&quot;picgo-2023-09-21-000249..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第谷磁力公司&lt;/li&gt;
&lt;li&gt;文本: X 电抽 1，保留 1 张&lt;/li&gt;
&lt;li&gt;背景文字: 第谷是迄今为止最大的超先进超级计算机的建造者，消耗了大量的能源。这个寻找新技术的强大工具将帮助他们在火星和其他世界的竞争中脱颖而出。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000251..jpg&quot; alt=&quot;picgo-2023-09-21-000251..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;水渠系统&lt;/li&gt;
&lt;li&gt;文本:抽3张带有建筑标志的牌。&lt;/li&gt;
&lt;li&gt;背景文字: 向火星仍然干旱的地区提供水将使我们能够更快地建设基础设施。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000252..jpg&quot; alt=&quot;picgo-2023-09-21-000252..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阿斯特拉机械&lt;/li&gt;
&lt;li&gt;文本:将你打出的的 2 张事件回手，不能是放置特殊板块的牌。&lt;/li&gt;
&lt;li&gt;背景文字: 进一步开发成功的太空工程项目&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000253..jpg&quot; alt=&quot;picgo-2023-09-21-000253..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;碳纳米系统&lt;/li&gt;
&lt;li&gt;文本:当你打出科学标志时，包括本卡，在这里添加石墨烯资源。当打出太空或城市标志时，每个石墨烯可以代替 4 MC。&lt;/li&gt;
&lt;li&gt;背景文字: 石墨烯——比钛轻，比钢强&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000255..jpg&quot; alt=&quot;picgo-2023-09-21-000255..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;赛博利亚系统&lt;/li&gt;
&lt;li&gt;文本:增加钢铁产量1级。复制其他 2 张带有建筑标志的卡牌的产能框。&lt;/li&gt;
&lt;li&gt;背景文字: 使用卓越的机器人自动化来提高你的产量&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000256..jpg&quot; alt=&quot;picgo-2023-09-21-000256..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;既定方法&lt;/li&gt;
&lt;li&gt;文本:获得 30 MC，然后至少花费这么多在执行标准行动上。&lt;/li&gt;
&lt;li&gt;背景文字: 利用成熟的技术促进您的起步&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000257..jpg&quot; alt=&quot;picgo-2023-09-21-000258..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;巨型太阳能集热器&lt;/li&gt;
&lt;li&gt;文本:增加电力产能 2 格。提升金星 1 格。&lt;/li&gt;
&lt;li&gt;背景文字: 利用阳光产生电能而不是热量&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000258..jpg&quot; alt=&quot;picgo-2023-09-21-000258..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;火星赫尔墨斯教团&lt;/li&gt;
&lt;li&gt;文本:增加你的M€产量2格。与你板块相邻的每个空白区域可获得 1 M€。&lt;/li&gt;
&lt;li&gt;背景文字: 在火星上比在地球上更容易找到孤独&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;刺客信条: 赫尔墨斯主义者为赫尔墨斯神教的成员，是一群神秘主义教徒，行动的根据来自于伊述成员赫尔墨斯·特利斯墨吉斯忒斯的哲学与宗教教诲，自称为“改造中的人类”。
现实: 黄金黎明被认为是20世纪对西方神秘主义影响最大的神秘学教团之一，因将犹太卡巴拉、伊斯兰教、印度教、古希腊、古埃及乃至古代凯尔特密教的隐秘知识进行整合，发展出系统的塔罗牌与神通术/招灵仪式课程而闻名西方世界。著名的塔罗牌体系、韦特塔罗的创作者亚瑟·爱德华·韦特（Arthur Edward Waite）是其成员之一。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000259..jpg&quot; alt=&quot;picgo-2023-09-21-000259..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内环境稳定局&lt;/li&gt;
&lt;li&gt;文本:当你升高温度时，获得 3 M€ (每格)。&lt;/li&gt;
&lt;li&gt;背景文字: 管理改造进程并对升高温度有特殊监督&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000260..jpg&quot; alt=&quot;picgo-2023-09-21-000253..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;辉夜科技&lt;/li&gt;
&lt;li&gt;文本:增加M€产量2步。抽 1 张牌。移除 1 块绿化（不影响氧气）。在移除的地方放置一个城市，无视任何限制，并获得放置奖励。&lt;/li&gt;
&lt;li&gt;背景文字: Kaguya Tech 开发了一种纤维素基建筑材料作为钢铁的替代品，旨在廉价而高效地在火星上建造定居点&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20230922003344.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;大筒木辉夜，日本漫画《火影忍者》及其衍生作品中的女性角色，本作最终BOSS。来自于大筒木一族，拥有白眼、轮回写轮眼两大瞳术，擅长使用血继网罗“黄泉比良坂”、“天之御中”等。上古时期辉夜与大筒木一式作为搭档来到地球种植神树，不愿作为活祭品被十尾吃掉的辉夜向一式发动偷袭，最终辉夜将身受重伤的一式献祭给十尾，并通过吞食神树果实得到了强大的力量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000261..jpg&quot; alt=&quot;picgo-2023-09-21-000261..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;柯伊伯合作社&lt;/li&gt;
&lt;li&gt;文本:在做小行星或海洋标准项目时，每个小行星资源可以被当作1M€使用。&lt;/li&gt;
&lt;li&gt;背景文字: 在海王星以外的柯伊伯带建设基础设施，以将小行星和彗星投向火星。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000262..jpg&quot; alt=&quot;picgo-2023-09-21-000262..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;火星游牧民族&lt;/li&gt;
&lt;li&gt;文本:效果：将游牧民族移动到相邻的非保留区域并获得放置奖励，就像在那里放置一块特殊板块一样。游牧者所在区域不可以放置板块。打出效果：将游牧者（金色立方体）放置在地图上非保留的空白区域。&lt;/li&gt;
&lt;li&gt;背景文字: 自由的人们在浩瀚的火星上旅行，游牧民族强调不要过度使用他们赖以生存的资源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意事项&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以获得临靠海洋的奖励&lt;/li&gt;
&lt;li&gt;游牧者不可以放置在Arcadian community的保留区，但是Arcadian community可以放置在游牧者的区域。&lt;/li&gt;
&lt;li&gt;首次放置时不会获得奖励，只有移动后才能触发&lt;/li&gt;
&lt;li&gt;官方说明可以触发城建公司的技能效果&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000263..jpg&quot; alt=&quot;picgo-2023-09-21-000263..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;海王星电力顾问公司&lt;/li&gt;
&lt;li&gt;文本:效果：当放置任何海洋时，你可以支付 5 M€（可以使用钢铁），以提高电力产量 1 级并在此处添加 1 水力发电资源。&lt;/li&gt;
&lt;li&gt;背景文字: 利用海洋的原始力量为您和您的家人带来现代舒适&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000264..jpg&quot; alt=&quot;picgo-2023-09-21-000253..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;火星木材公司&lt;/li&gt;
&lt;li&gt;文本:支付建筑卡时，每个植物可抵 3 MC。&lt;/li&gt;
&lt;li&gt;背景文字: 种植森林为建筑提供木材，火星木材公司以质量好、价格优惠而闻名&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000265..jpg&quot; alt=&quot;picgo-2023-09-21-000265..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;红船&lt;/li&gt;
&lt;li&gt;文本:行动: 每个临靠海洋的城市和特殊板块(无论其所有者如何) 获得 1 MC。&lt;/li&gt;
&lt;li&gt;背景文字: 随着火星上海洋的不断扩大，通过轮船或滑板船进行运输与在地球上一样具有成本效益&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000266..jpg&quot; alt=&quot;picgo-2023-09-21-000266..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;太阳能物流&lt;/li&gt;
&lt;li&gt;文本:当你打出地球标志时，减少 2 MC。当任何玩家打出太空事件时，你抓一张牌。&lt;/li&gt;
&lt;li&gt;背景文字: Solar Logistics 创造了第一艘往返于火星和地球之间的无人驾驶航天飞机，建立了一条重要的商业贸易路线&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000267..jpg&quot; alt=&quot;picgo-2023-09-21-000267..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;特斯拉磁场装置&lt;/li&gt;
&lt;li&gt;文本:降低 1 点电力产量来增加植物产量 1 格。&lt;/li&gt;
&lt;li&gt;背景文字: 通过利用磁场保护生物体免受宇宙辐射，在火星上种植植物将会容易得多&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000268..jpg&quot; alt=&quot;picgo-2023-09-21-000267..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;库比蒂诺圣约瑟夫传教团&lt;/li&gt;
&lt;li&gt;文本:花费 5 MC（可以使用钢铁）在城市板块上放置一座大教堂（银色立方体）。每个城市最多 1 个。城市所有者可以支付 2 MC 抽 1 张牌。&lt;/li&gt;
&lt;li&gt;背景文字: 供奉宇航员守护神的宗教团体。脱离了人类在地球上的根源，许多人将在火星上寻求精神指引&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;库比蒂诺的约瑟夫是一位意大利方济各会修道士，被誉为基督教神秘主义者和圣人，他一生都经历过奇迹般的悬浮。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-21-000269..jpg&quot; alt=&quot;picgo-2023-09-21-000267..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;战略基地规划&lt;/li&gt;
&lt;li&gt;文本:花费 3M€。放置一个殖民地和一个城市板块。&lt;/li&gt;
&lt;li&gt;背景文字: 有如此多的新世界等待开发，优秀的规划师是抢手货。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;《序幕 2》&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000290..jpg&quot; alt=&quot;picgo-2023-09-22-000290..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ecotec&lt;/li&gt;
&lt;li&gt;文本:当你打出生物标志 (微生物、植物或动物) 时，包括本卡，获得植物或在任何卡上添加微生物。&lt;/li&gt;
&lt;li&gt;背景文字: 起始：42 MC，1 点植产&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000291..jpg&quot; alt=&quot;picgo-2023-09-22-000291..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nirgal Enterprises&lt;/li&gt;
&lt;li&gt;文本:效果：奖励和里程碑始终花费 0 M€。&lt;/li&gt;
&lt;li&gt;背景文字: 起始：33 MC, 增加电力、植物、钢铁产能 1 格。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000292..jpg&quot; alt=&quot;picgo-2023-09-22-000292..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Palladin Shipping&lt;/li&gt;
&lt;li&gt;文本:效果：当你打出钛事件时，获得1钛。行动：花费2钛提高温度1级。&lt;/li&gt;
&lt;li&gt;背景文字: 起始：36 MC, 5 钛。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000294..jpg&quot; alt=&quot;picgo-2023-09-22-000294..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sagitta Frontier Services&lt;/li&gt;
&lt;li&gt;文本:效果：当你打出没有标志的卡牌（包括这张卡）时，获得 4M€。当你打出一张牌恰好有1个标志时，获得 1M€。&lt;/li&gt;
&lt;li&gt;背景文字: 起始：28 MC。增加电力产量 1 步，增加 M€ 产量 2 步。 作为你的第一个行动，抽一张没有标志的牌。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;小彩蛋：你能在动荡扩的封面中找到这个公司的身影吗？
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/pic4598371.jpg&quot; alt=&quot;pic4598371.jpg&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000296..jpg&quot; alt=&quot;picgo-2023-09-22-000296..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spire Corporation&lt;/li&gt;
&lt;li&gt;文本:效果：当你打出至少有2个标志的卡牌时（包含本卡），在此添加科学资源。当您使用标准项目时，此处的科学资源每个可以抵 2 MC。&lt;/li&gt;
&lt;li&gt;背景文字: 起始：44 MC。作为你的第一个行动，抽 4 张牌，然后从手牌中弃掉 3 张牌。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;前序&lt;/h3&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000273..jpg&quot; alt=&quot;picgo-2023-09-22-000273..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用科学&lt;/li&gt;
&lt;li&gt;文本:移除此处的 1 个科学资源以获得 1 个标准资源，或将 1 个资源添加到任何具有资源的卡上。&lt;/li&gt;
&lt;li&gt;背景文字: 就像胶带一样，科学几乎可以应用于任何事物&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000285..jpg&quot; alt=&quot;picgo-2023-09-22-000285..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;殖民地贸易中心&lt;/li&gt;
&lt;li&gt;文本:放置任何殖民地时，获得 2 M€。&lt;/li&gt;
&lt;li&gt;背景文字: 打出效果：增加你的电能产能 1 格。获得 3 钛。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000286..jpg&quot; alt=&quot;picgo-2023-09-22-000286..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;聚焦组织&lt;/li&gt;
&lt;li&gt;文本:行动：弃掉1张牌并花费1个标准资源来抽1张牌并获得1个标准资源。&lt;/li&gt;
&lt;li&gt;背景文字: 打出效果：抽一张牌并获得 1 个标准资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000276..jpg&quot; alt=&quot;picgo-2023-09-22-000276..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大气增强器&lt;/li&gt;
&lt;li&gt;文本:将温度/氧气/金星提升 2 格。展示牌堆顶中的牌，直到展示 2 张带有浮动图标的牌。将这两张牌加入手牌并弃置其余的牌。&lt;/li&gt;
&lt;li&gt;背景文字: 结合土壤的植物与细菌结合，将盐转化为建筑材料。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000287..jpg&quot; alt=&quot;picgo-2023-09-22-000287..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;诺贝尔奖&lt;/li&gt;
&lt;li&gt;文本:效果：获得 2 MC。抽 2 张有前置的牌。&lt;/li&gt;
&lt;li&gt;背景文字: 背景描述：阿尔弗雷德·诺贝尔发明了炸药并利用他的财富来促进科学发展。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000289..jpg&quot; alt=&quot;picgo-2023-09-22-000289..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旧采矿殖民地&lt;/li&gt;
&lt;li&gt;文本:打出效果：增加钛产量1级。放置 1 个殖民地。弃 1 张牌。&lt;/li&gt;
&lt;li&gt;背景文字: 最早建立企业的殖民地。许多人仍然活跃于此。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;项目卡&lt;/h3&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000277..jpg&quot; alt=&quot;picgo-2023-09-22-000277..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;峰会物流&lt;/li&gt;
&lt;li&gt;文本:你拥有的每个行星标志和殖民地获得 1 M€。抽 2 张牌。&lt;/li&gt;
&lt;li&gt;背景文字: 班车服务欢迎科学家参加奥林巴斯会议&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000281..jpg&quot; alt=&quot;picgo-2023-09-22-000281..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;谷神星科技市场&lt;/li&gt;
&lt;li&gt;文本:从你的手上丢弃任意数量的卡牌，每张丢弃的卡牌将获得 2 M€。&lt;/li&gt;
&lt;li&gt;背景文字: 谷神星是小行星带的商业中心，在这里你可以见到来自外太阳系的独立买家。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000282..jpg&quot; alt=&quot;picgo-2023-09-22-000282..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LI交易终端&lt;/li&gt;
&lt;li&gt;文本:当你贸易时，首先将殖民地板块轨道增加 2 步。&lt;/li&gt;
&lt;li&gt;背景文字: 打出效果：将资源添加到 3 张不同的卡牌上。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000283..jpg&quot; alt=&quot;picgo-2023-09-22-000283..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;伊什塔尔远征队&lt;/li&gt;
&lt;li&gt;文本:获得 3 钛并抽 2 张金星牌。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000282..jpg&quot; alt=&quot;picgo-2023-09-22-000282..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;红色绥靖政策&lt;/li&gt;
&lt;li&gt;文本:增加M€产量2步。跳过本时代。&lt;/li&gt;
&lt;li&gt;背景文字: 承诺暂时停止你的地球化改造工作&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-22-000272..jpg&quot; alt=&quot;picgo-2023-09-22-000272..jpg&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;金星贸易中心&lt;/li&gt;
&lt;li&gt;文本:当你贸易时，获得 3 M€。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>所思所想 | 逃出大英博物馆</title><link>https://www.ender-wiggin.com/posts/thoughts-british-museum/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/thoughts-british-museum/</guid><description> </description><pubDate>Sun, 10 Sep 2023 18:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本文是知乎问题&lt;a href=&quot;https://www.zhihu.com/question/621371579/answer/3208697863&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;blockquote&gt;
&lt;p&gt;清末的王道士向政府反馈多次千佛洞无果，因此决定自己管理千佛洞并向附件居民出售经卷。后来，英国的斯坦因闻讯前来并以极低的价格购入大量经卷，日后存放在了大英博物馆。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这其实就是当下历史文物追溯这一复杂问题的缩影：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大英博物馆认为自己从道义上是无可指摘的，因为这些文物都是收藏家主动出售或者捐赠的。&lt;/li&gt;
&lt;li&gt;英国收藏家也认为自己无可非议，即便自己以极低的价格购入，但是好歹也是符合流程的买卖。&lt;/li&gt;
&lt;li&gt;早年中国的出售者同时也不理亏，上层本身不重视文物，自己谋求生计又有何妨。当然，上层自己可能与国外本就有不少合作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;追溯这种历史遗留问题无疑不太现实，因此要求文物的归还更像是一个缥缈的道义问题。但是我认为个人树立文物保护的意识很有必要，因为很多网络上的言论透露出了一种对于历史文物的轻浮感，这我认为是很可怕的。对于今日这个事件他们的立场可能是正确的，但是这是某种意义上的“结果正确，过程全错”，说不定未来哪天这些人也成了历史上的“那些人”，又要让后人的后人痛心疾首不已。&lt;/p&gt;
</content:encoded><category>所思所想</category><author>Ender</author></item><item><title>方舟动物园 | 基础地图强度排名</title><link>https://www.ender-wiggin.com/posts/ark-nova-maps/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/ark-nova-maps/</guid><description> </description><pubDate>Sat, 26 Aug 2023 21:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;动物园的地图排名也算是一个经久不衰的讨论话题了。对于基础8张地图中的第一第二名想必大家都没什么疑问，但是一直困扰我的一个问题是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果一定要给出一个硬性排名，后6个地图应该如果排列？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在地图二选一的时期，由于我基本只玩最强的这几张图，以致于我对其它图的实际强度完全没有概念。但BGA的竞技场模式中是随机分发地图，因此我“被迫”玩了不少局这些我之前看不起的地图，也相应地对他们的强度有了一些更加具体的认知。&lt;/p&gt;
&lt;p&gt;看过我其它一些桌游文章的朋友应该知道，我个人还是比较喜欢写一些“引战”的排名贴的，一个原因就是各种群体的桌游玩家确实都比较喜欢排名/梯度，比如早些年在一些聊天群中也总能看到“XXX强不强”的日经问题，这些问题显然不能脱离实际情况进行分析，所以这其中透露出的一个很有意思的心理就是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;大家单纯是喜欢“讨论”这个行为而非“讨论出正确思路”这个结果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我这篇文章也同理，我只是非常主观且武断地给出我个人的排名。但我相信既然读者都是桌游玩家，那么肯定具备足够的独立思考性，在实际游戏中一定会灵活变通。因此这篇文章从某种意义上来说就是图一乐，如果说它真的可以给读者带来一些思想上的启发那就再好不过了。&lt;/p&gt;
&lt;h2&gt;排名规则&lt;/h2&gt;
&lt;p&gt;本次参与排名的地图一共是基础的 map 1-8，主要考虑二人局情况，但三四人局也有相似的地方可以参考。&lt;/p&gt;
&lt;p&gt;本着相对的“严谨性”，我还是给地图一个梯度参考，同一个梯度内地图强度大致是差不多的。这次的梯度将包含 T0, T1, T2, T3 四个评级。&lt;/p&gt;
&lt;p&gt;作为排名考察的维度，我暂且定义如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;地图收益。包括了地图的特殊技能、钱、声望、牌、X等奖励。其中前期重要性最高的无疑是钱和声望。&lt;/li&gt;
&lt;li&gt;协会收益。一方面是开动保对应的奖励，这在每个地图中都是不对称的，部分奖励可以影响地图的整体玩法；另一方面是第四个工人/第三个大学/第四个动物园的解锁奖励，这三者的总和固定是3盾，但不同的分布方式直接带来了不同的强度。&lt;/li&gt;
&lt;li&gt;地形优劣。最直观的就是地图山/水的位置，这会直接影响动物的可居住性。此外铺满地图的难易程度也需要考虑，因为满建的7分和相应终局计分都不容忽视。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;TOP 8 观测塔&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan1.jpg&quot; alt=&quot;观测塔&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度: T3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;思来想去，还是决定把观测塔排在倒数第一的位置。&lt;/p&gt;
&lt;p&gt;观测塔的技能收益其实还不错，一般可以发挥3-6次，平均收益差不多8票也就是24块钱，算上本身自带的10块钱，经济收益30多块钱算是中规中矩。但是考虑到前期纯钱肯定是比票更有用，所以这个票的这个经济本身是要打折扣的。&lt;/p&gt;
&lt;p&gt;3元钱1票虽然是基本模型，但是套用我在&lt;a href=&quot;https://www.ender-wiggin.com/2022/06/15/Terraforming-Mars-TOP50/&quot;&gt;火星TOP50&lt;/a&gt;文章中的一句话：
&lt;strong&gt;如果整局游戏都在做不亏（符合模型）的行动，那么很可能会垫底。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此外，这个地图在地形方面是相当差的，原因有三：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第一轮1声望/5块钱/贴观测塔得2票基本只能三选一，几个核心奖励比较分散。&lt;/li&gt;
&lt;li&gt;同时满足山水的区域基本只有上方一个选项（因为底下一声望的位置大概率前期就会覆盖）。&lt;/li&gt;
&lt;li&gt;水域整体靠右，前期想要触发临水的观测塔技能非常困难。即便不需要临水，如果为了刻意触发观测塔效果基本只能造1格-2格的动物，等于无形中给自己打出动物增加了一个限制，让原本就不富裕的生活雪上加霜。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下图是基于上面分析给出的常规开局位置和前期谨慎覆盖的区域。这个红色区域是可以同时符合爬行馆、鸟类馆和5格饲养区的，因此在覆盖这片区域之前一定要三思。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-05-000131..jpg&quot; alt=&quot;观测塔地图标注&quot; /&gt;&lt;/p&gt;
&lt;p&gt;再考虑协会的奖励，第4个动物园2盾+第4工人0盾就是最差分配方式，没有之一。凑齐4个动物园是非常罕见的（甚至一些对局中拿一个动物园就结束游戏了），并且凑3个动物园也不能带来解锁工人的分数，因此观测塔天生就比其它图少了1-2盾。&lt;/p&gt;
&lt;p&gt;但是话说回来，这个图虽然不强，但是收益至少相对稳定，如果手牌合适配合放生/爬行馆/鸟类馆还是可以打出一些高光表现的。只是说其它地图如果是同样的手牌，可以表现更加亮眼哈哈。
并且动保奖励中的花钱打赞助商是一个值得关注的奖励。关于这个行动有两种思考的方式：&lt;/p&gt;
&lt;h3&gt;1. 和5块钱横向对比&lt;/h3&gt;
&lt;p&gt;考虑机会成本也就是8-10块钱打出一张赞助商（从我的经验来说，这个技能比较适配3强度的赞助商，因为3强度的牌一般都是实打实的收入或者盾票）。考虑过牌量的问题，这个技能在倒数第二轮开触发个两次算是一个可行思路，再不济最后一回合解锁也可以悄悄变个魔术，因此玩这个图建议&lt;strong&gt;藏好一张可以刷分的赞助商&lt;/strong&gt;，比如学校导游或者科学图书馆。&lt;/p&gt;
&lt;h3&gt;2. 和赞助商行动对比。&lt;/h3&gt;
&lt;p&gt;有一个问题是，这张图本身经济不容易领先，加上解锁这个奖励会进一步缩减资金，因此很可能会变成花钱打赞助商然后休息5过 turn 这个节奏，也就是说我们解锁的实际上是&lt;strong&gt;休息5&lt;/strong&gt;（注意：是单纯推5格休息轨道，自己不拿钱）。&lt;/p&gt;
&lt;p&gt;看到这里可能有些玩家要开喷了，休息5那不是等于啥都没有？&lt;/p&gt;
&lt;p&gt;确实是这么个道理，但是这个休息5除了大概率获得X之外，也是有额外含义的：让自己有打赞助商的权利。一般来说，休息5的一方都是钱少的一方，因此很多时候你不得不放弃打赞助商而选择休息。但是，万一有些赞助商你真的很想打，那么这个行动就带来了操作的空间。如果是3强度的牌本身还是可以小赚2块钱，6强度的话等于省一个X了。并且，我上面的假设都是基于5元收入没解锁的情况的，如果已经解锁了的话剩下的几个不一定有花钱打赞助强。最后，从大局观上来说，比较激进的休息策略可以让游戏节奏变快，双方到后期都可能会出现经济溢出的情况，这样的话一甚至可能几乎忽略己方的经济劣势。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;h2&gt;TOP 7 好莱坞&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan8.jpg&quot; alt=&quot;好莱坞&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度: T3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;好莱坞下限极低，但是问题就在于它有一个很夸张的上限。我相信经验相对丰富的玩家都是希望对手老老实实升级协会勤勤恳恳捐赠的，因为这种操作收益是平滑且可预测的。但是如果你的对手是一个升了二级赞助的好莱坞，那总会担心对手不老实突然掏出一堆奇奇怪怪的魔术牌。&lt;/p&gt;
&lt;p&gt;首先还是分析一下地图收益，这个图本身只有额外的5块钱，意味着额外的经济全得靠赞助商的发牌，这是这个地图看脸的原因之一。
其次，它的主技能是每张赞助商费用减一，这又会进一步加大马太效应，本来就很胡的人会势不可挡，但本身节奏很差的开局基本直接可以下一局了。这是看脸的原因之二。
此外，这是唯一一张只有一张声望范围抽牌效果的地图，这其实是一个潜在的危险。一般对局中总会有那么几个时间点会刷出双方都想要的牌，这时覆盖抽牌奖励是一个节奏很舒适的操作，而好莱坞在争夺好牌这方面也有着天然劣势。&lt;/p&gt;
&lt;p&gt;但好莱坞的主要缺点也只有看脸这一点了，其它方面都相对均衡。一方面它的技能在前期就可以触发，并且覆盖右边的声望开局非常平滑，不存在观测塔的取舍问题。再者，第二轮升级建造之后，可以很快覆盖所有 H 触发主技能 （比如一个萌宠馆+5强度建造）。升级赞助后5强度打出3+5或者4+4都是很好的提速点，配合足量的 X 可以打出更多华丽的操作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-02-000045..jpg&quot; alt=&quot;好莱坞技能收益&quot; /&gt;&lt;/p&gt;
&lt;p&gt;另外需要注意的是，好莱坞虽然鼓励升级赞助，但是其实从另一个角度上来说好莱坞当做普通地图来玩不升级赞助也不失为一种好打法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;动保奖励的一个动物园和右边解锁奖励的三个1盾，让传统升级协会的打法也可以获得比较稳定的额外2盾收益。&lt;/li&gt;
&lt;li&gt;把 H 的三张赞助视为是自己打动物间隙的过渡，等于是在自己缺乏赞助商时候的稳定过牌，并不强求快速覆盖 H，这样的好处是在左边区域可以留下一篇临山临水的区域，为后续操作保留足够空间。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当然是否升级赞助是看具体对局的。但我还是要提醒一句：打赞助切记不可上头。当我年少无知时曾有一次嘻嘻哈哈的玩好莱坞，当我打出的10+赞助商鸣鸣自得时，抬头一看对面已经领先我50分了。说了这么多，其实关键就是：只打&lt;strong&gt;有用&lt;/strong&gt;的赞助商。不论升不升级赞助，不论是不是好莱坞，这个思路都是一以贯之的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-08-30-000020..jpg&quot; alt=&quot;好莱坞地图标注&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;TOP6 主题餐厅&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan5.jpg&quot; alt=&quot;主题餐厅&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度: T3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其实很多玩家都还是挺喜欢主题餐厅的，因为它有着非常稳定的经济收入，相较于一些高方差的地图而言餐厅往往令人安心。&lt;/p&gt;
&lt;p&gt;但是餐厅的问题就是：收益实在太低了。这个“稳定”的收入我愿称之为低保。&lt;/p&gt;
&lt;p&gt;在谈论收入前，我首先要引入一下休息次数的概念。二人局中比较常见的休息次数是4-5次，少数天胡情况只需要3次。一般来说4次休息是一定可以结束游戏的，只是回合数会拖得长一些。如果是5轮休息的情况往往是刚休息完就有人打出连动瞬间终结游戏了，因为5轮基本上没有经济的压力。至于有些玩家会遇到的手里80块钱用不掉的情况，一般是因为休息了6次，而休息6次本身就说明这局游戏一些操作是有问题的，说明钱没有按良好的节奏花掉并且休息占比太大了。&lt;/p&gt;
&lt;p&gt;基于上面的分析，我认为只有4次休息是比较值得考虑的情况，因为多数5次休息的局都不差餐厅的这么几块钱的收入了。餐厅第一轮覆盖3个格子已经满打满算了，就算第二轮覆盖完5个，总收益也就18元，算上每个地图都有的5块钱放置奖励，加起来才23元，收益惨不忍睹。加上第四个动物园才给2盾，算得上是观测塔的难兄难弟了。&lt;/p&gt;
&lt;p&gt;但比起观测塔,餐厅还是有诸多优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;3个角落都可以放置5格临山临水建筑，因此这个图在地图规划方面堪称无脑。&lt;/li&gt;
&lt;li&gt;动保解锁有一个非常强势的奖励：白送一个爬行馆/鸟类馆。这个行动基本等于白送10块钱+一动，并且这让满建7票难度下降不少。&lt;/li&gt;
&lt;li&gt;3张抽牌的位置都比较集中，让自己在游戏前中期就可以有比较稳定的手牌质量，同时花钱打赞助商也是提供了一定的操作空间。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-05-000133..jpg&quot; alt=&quot;主题餐厅地图标注&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;TOP5 研究院&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan6.jpg&quot; alt=&quot;研究院&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度: T2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;研究院是一张上限极高的图，只是因为官方规则需要先选地图再抽牌让玩家没有足够的自信直接选择它。
研究院最引人注目的无疑是右上角的大学，这个奖励是实打实的省前期工人的一动，意义非凡。但是传统双大学开的的话，这个奖励就显得有点浪费，因此这个地图如果是大学开局第二大轮只要有能做的动保就可以大胆做，因为这样的话第二大轮可以实现2盾2大学1动物园这种3升级局面，第三大轮还可以快速4升级，节奏无可比拟。并且因为我们考虑的主要是二人局，不太需要担心被卡大学的事情，这一点在多人局是要扣分的。如果没有能做的动保，那么最好也是选择多花一动建造把这个奖励踩了，这样的话第二轮可以多拿一个动物园也是可以提速的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-05-000134..jpg&quot; alt=&quot;研究院技能收益&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个地图由于主技能和收益颇丰的右侧区域距离太远，开局基本只能二选一开局。一般来说右侧是比较常规的选择，因为5元和1声望距离非常近，可以很好打开前期节奏。左侧开局我最为推荐的是邦加跗猴，有这小猴子基本上也赢一半了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-02-000049..jpg&quot; alt=&quot;研究院地图标注&quot; /&gt;&lt;/p&gt;
&lt;p&gt;此外第四个工人2盾非常不错，在这个带有一定黑科技性质的地图中获得4工人难度较低。&lt;/p&gt;
&lt;p&gt;至于中后期，我几乎没见过有玩家选择不升级动物然后用这个技能打二级动物，因为二级动物只是升级动物的多个目的之一罢了。但是这个技能对于打出一些动物园前置的优质动物以及2科标前置动物还是有一定帮助的，总的来说虽然存在感不强但总有些时候有奇效。&lt;/p&gt;
&lt;h2&gt;TOP4 冰淇淋&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan7.jpg&quot; alt=&quot;冰淇淋&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度: T2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;冰淇淋争议颇大，但是从数据上来看收入最高的地图非它莫属。一般正常开局第二大轮可以覆盖完触发技能，最低收益是 &lt;code&gt;(4个贩售亭+每个贩售亭至少1.5块钱) * 3轮 = 30块钱&lt;/code&gt; ，考虑上地图自带了5块钱，第一轮贩售亭的1-2元收入，以及贩售亭中后期收入增加的情况，总经济收益在标准的5大轮（4休息局）肯定是大于40块钱的，都快是餐厅这图收入的两倍了。
另外，由于餐厅自带了3个贩售亭，加上鼓励建造的特点，让冰淇淋的满建7分达成难度非常低，保守估计一半的冰淇淋都是可以满建的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-12-09-000432..jpg&quot; alt=&quot;冰淇淋地图标注&quot; /&gt;&lt;/p&gt;
&lt;p&gt;冰淇淋比较被人诟病的点在于地形太差，这个图客观来说确实有诸多建造上的限制，最大的问题就是山水的明显分离，导致与水相关的所有配合体系都表现不行，包括但不限于水族馆（最差水族馆地图，拍了水族馆都没地方可以打水标）、爬行馆（爬行馆基本需要临水，这个图基本没有适合的位置）、前期临水动物（必须要从右下角开局，离声望太远）。
但是地形也要辩证的看，冰淇淋虽然水不行但是山可太行了，不论是本地蜥蜴轻松就可以刷9票，各种山标前置就和不存在一样，因此也是有地形上的优势的。&lt;/p&gt;
&lt;p&gt;以我几十局冰淇淋的经验来看，这个图只要保证了这两个红色区域（右上和右中）尽量用大板块覆盖，动物被卡地形的情况就不会很常见。右上角一定要留出一个5格位置，最好是留出鸟类馆的位置，这是最合适的一个临山+临2水的位置，我们的高前置动物全靠这个黄金位置了。右中是一个4格的临2水位置，不论是打苏门答腊虎还是澳洲2水鸟都可能需要，并且也可以向下兼容配合3格临水动物，同时留下一个机灵方便后期爆发。
另外如果右上角无法建造鸟类馆了，最好把左上角也留出空间，虽然不临水打不出白头海雕了，但是其它鸟类还是适配的，算是一个补救方案。&lt;/p&gt;
&lt;p&gt;总的来说冰淇淋这个图没什么很魔术的操作空间，基本就是朴实无华的富有，运气好的局可以靠中间的花钱打赞助实现第一轮覆盖3个冰淇淋的超展开，在收入方面无人可敌。
因此从策略上来说，当自己收入领先后，如何将经济转化为分数就是冰淇淋必须要提前规划好的问题，只有钱但是没有足够手牌(高质量大型动物)是一个很危险的信号。&lt;/p&gt;
&lt;h2&gt;TOP3 商业港口&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan4.jpg&quot; alt=&quot;商业港口&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度: T2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;港口算是魔术图的代表，稳定性不如冰淇淋，但是上限很高，总可以绝处逢生打出一些对方意想不到的操作。&lt;/p&gt;
&lt;p&gt;地图方面，主技能和1声望距离非常不远，没什么建造上的限制与压力。非常引人注目的是这个 &lt;code&gt;X2&lt;/code&gt; 奖励，这也是后期分数爆发的关键。在最后一个大轮通过 &lt;code&gt;X2&lt;/code&gt; 做两个协会5，等于是省一动+额外省了几个 &lt;code&gt;X&lt;/code&gt; ，应该是所有放置奖励中收益最高的奖励。相应地，如何控制好自己 &lt;code&gt;X2&lt;/code&gt; 的使用时机，或者如何让对方的 &lt;code&gt;X2&lt;/code&gt; 收益降低甚至直接被休息后弃掉，是这个地图的核心思考点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-05-000136..jpg&quot; alt=&quot;商业港口地图标注&quot; /&gt;&lt;/p&gt;
&lt;p&gt;主技能小日光浴非常灵活，因为动物的日光浴效果一般是一次性卖3张牌，而前中期很难有能力囤一手牌，因此港口这个随时卖牌的技能在前中期更加好用。一般来说第二大轮是这个游戏最困难的时代，因为玩家的收入只有10多块钱，而港口可以卖牌凭空变出一只大动物，是一个非常好的节奏提升点。
另外地图方面空间开阔，需要建造爬行馆/鸟类馆的区域在地图右上角，因此前期可以肆无忌惮的建造各种建筑。可能唯一需要注意的点是，尽量也同时留好2水的位置，因为2水不论是苏门答腊虎还水族馆都是这游戏中最强的几张牌之一，提前布局有备无患。&lt;/p&gt;
&lt;h2&gt;T1&lt;/h2&gt;
&lt;h2&gt;TOP2 户外营地&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan2.jpg&quot; alt=&quot;户外营地&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度: T1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我在查看我近期几局户外营地数据的时候发现，平均回合数是低于29回合的，也就是说户外营地节奏非常快且很容易胡。
这个地图在经济方面是比较微妙的，它的所有收益都来自于额外动物园的减费，以及大门的饲养区buff。纵观各种卡牌桌游，最强的卡牌一般都不是单纯的线性“收入”，而是非线性的减费和省动，营地也同理，这张地图的额外收益无疑是奔着操作上限去的，因此这图胡起来势不可挡。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-05-000137..jpg&quot; alt=&quot;户外营地地图标注&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这张图既可以开局双动物园快速升级，也可以传统大学开第三大轮基本稳定4升级，同时声望和动物园又距离比较近，各方面都很完美。此外，地形分布也是可圈可点，基本没有什么建造上的限制，唯一需要注意的地方就是右侧临山的3格饲养区，这是一个万能的位置，如果手中没看到合适的牌也可以提前预留/建造这个饲养区，因为不论如何后期总是可以打出动物的。&lt;/p&gt;
&lt;p&gt;如果一定要找缺点的话，那就是这个图由于太省空间了，满建基本上不存在，甚至连右下角的机灵都可能不太容易够到。我个人也不太建议前期看到特殊板块就规划满建的事情，为这个图满建等于基本抹平了大门的技能收益，得不偿失。此外这个图让动保奖励的2格建筑锦上添花，加上大学2盾和工人1盾，各方面都是那么的完美。另外这个图中由于大门的存在，1格动物和3格动物可能会冲突，2格与4格同理。因此思路上可能是越早开动保的2格越好，可以提前布局好所有合适的板块位置（兼顾山水），有备无患。&lt;/p&gt;
&lt;h2&gt;T0&lt;/h2&gt;
&lt;h2&gt;TOP1 银湖&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan1.jpg&quot; alt=&quot;银湖&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度: T0&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;人称“赢胡”，这游戏平衡性失衡的罪魁祸首。我很难想象一个瞭望塔看到对面银湖是什么感受，也许就像小火龙大战喷火龙吧。
户外营地的强和银湖还有些区别，户外营地上限其实比银湖要高，但是银湖在绝大多数的对局中下限极高发挥稳定，因此更容易受到玩家们爱戴，当然换一个词语就是：“无脑”。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/picgo-2023-09-05-000138..jpg&quot; alt=&quot;银湖地图标注&quot; /&gt;&lt;/p&gt;
&lt;p&gt;前期完全不缺钱+2声望，后期有全游戏最强动保奖励——决心，仿佛就是设计师亲儿子，不知道怎么设计出来的。
这张图我甚至不知道应该怎么设置红色区域，在我看来这个图几乎没有建造上的限制，只是说在中上部分留个5格馆容错更高，同时中下的山区域需要稍微留意一下，可能会影响前期打出临山动物。但是这些地形上的小限制，如果被后面的一些地图听到估计要哭出声了。&lt;/p&gt;
&lt;p&gt;所有地图生而平等，但有些地图比其他地图更平等。&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>影评 | 《奥本海默》</title><link>https://www.ender-wiggin.com/posts/review-oppenheimer/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-oppenheimer/</guid><description> </description><pubDate>Fri, 21 Jul 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;下午正好在柏林参观了介绍纳粹德国历史的恐怖地形图博物馆，展览中的每一个人都缄默地观看一张张苍白的照片，冰冷的灰色建筑笼罩着一种历史的沉重感。
《奥本海默》从一个典型的科学故事出发，但诺兰选择用听证会主线的回忆片段穿插和主演敏感的情感传达淡化了理科浪漫，而以人文的视角将兔死狗烹的政治现实和压抑冰冷的后二战时期设为电影中心。电影中黑白画面与强对比的色彩构成都营造了一种纪录片式的镜头语言，并有意地体现了科学与政治、事业与家庭之间的疏离感。
此外，诺兰看起来也是刻意淡化了时间线——全片中除了新闻播报中体现的时间，剩下的画面中没有出现一处提示性的时间刻度，这在传记电影中并不常见。这种淡化时间的做法，与时间线的剪切打乱，以及奥本海默在电影中多次“预知”般的画面闪回我认为都服务于一个共同目的，即暗示原子弹爆炸后的新纪元，新的潘多拉魔盒已经打开，人们再也无法回到过去的时代，现在与未来仿佛停滞或错乱一般，营造出世界终焉般的压抑感。
初中历史中介绍奥本海默生平时提到了他在原子弹爆炸时脑子浮现的诗句：
只有一千个太阳,才能与其争辉。
小时候读到这句诗句时只感觉气势恢宏，九个太阳已经如同炼狱，千日凌空又是何等壮烈。这大抵也是当时科学家的思维，看到原子弹爆炸时更多的是兴奋与震撼。电影中替换成了“我是死神，我是世界的毁灭者”——这虽然也在奥本海默晚年回忆中提及，但看起来更像是一位经历一切的老者对往昔岁月的回忆与提炼，也少了一分科学突破的雄心壮志，而多了一分历史的沉重感。
从二战的历史到当下种种事件，不论是国内还是世界范围，越是了解越让人觉得难以轻描淡写一笑了之。二战的相关影视已经很多了，这一部作品始于单纯的科学故事，终于战争惨剧、政治权谋与意识形态，180分钟的观影体验在递进中也保持了连贯与统一，实为佳作。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>所思所想 | 评价冰箱需要自己会制冷吗？</title><link>https://www.ender-wiggin.com/posts/thoughts-fridge/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/thoughts-fridge/</guid><description> </description><pubDate>Fri, 14 Jul 2023 18:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;冰箱制冷&lt;/h2&gt;
&lt;p&gt;最近几年感觉时不时能看到“我评论个冰箱还得会制冷吗”这种存在明显逻辑漏洞的话术，因此这篇随笔算是对这种表达的一个系统性反驳。
这个提问本身具有误导性，评价冰箱固然不用会制冷，但是不会制冷就可以随意评论冰箱了吗？我在写这篇文章前也在知乎上搜索了相关问题，前面几个回答大多都是在讨论外行和内行、用户与专业人士的问题，比如如果是站在用户体验的角度评价一个冰箱制冷效果不好当然可以，但是如果站在专业的角度谈论冰箱原理存在问题就不合适。这些观点没什么问题，但是不够本质，因为用户和专业人士这些身份并不一定是对立的。比如假如我是一个喜欢研究机械构造的普通中学生，那我能不能对于冰箱结构有一些自己的评价？再比如我是一个养尊处优从来不自己使用冰箱的小孩，那我作为一个用户我是否可以对冰箱进行评价？
因此能否评论无关于身份，主要取决于自身是否具有评论对象的共同视角。
共同视角是评价一个事物或者性质的基础，对于冰箱制冷而言，共同视角就是知道冰箱制冷的程度。比如“水放到冷冻层过一段时间会结冰”是多数人的认知，那么如果我发现几天后水依然无法结冰，我可以轻松的得出“这个冰箱制冷效果不好”这个结论。但是如果我这辈子从没有用过冰箱，我能否得出这个结论呢？
所以“冰箱制冷”这个问题最大的迷惑性在于，“冰箱可以制冷”这个概念在可以上网的人群中是公认的观点，因此共同视角显得毫无存在感。但很遗憾，在其它很多讨论中讨论双方并不一定有共同视角。&lt;/p&gt;
&lt;h2&gt;共同视角&lt;/h2&gt;
&lt;p&gt;如果是讨论一款菜品是否好吃，好吃与否是认知主体，但是共同视角应当是讨论者品尝过这个菜。类似地，如果是讨论一本小说或者电影是否好看，那么共同视角大概是讨论者是否看过这个作品。评价小说的共同视角，显然比冰箱制冷的共同视角要窄得多，获取这个视角的时间成本（看完一本小说）也比冰箱制冷的共同视角（只需要与冰箱有过接触）长得多。也正因为如此，对于这些高时间成本需求的讨论话题，很多的讨论从最初的时候就毫无意义，因为双方根本没有站在同一个视角下。
我前段时间看到知乎上开始对莫言口诛笔伐，不少人开始旁征博引诺奖评选委员会的颁奖词和一些其他作家的立场与观点试图证明莫言的立场问题。但是我想，要对这个问题展开讨论，那么是否应该要读过至少一部莫言的作品，以及是否应该对其它同时代作家以及文学史有一定了解？这个讨论所需要的共同视角限制就太高了，以至于其中多数的回答从根源上就是参考下不大的。
目前我的论述是先说明了讨论问题前共同视角的必要性，然后列举了几个其它例子试图说明人们共享视角的难度差异，但实际上还有一个问题是无法避开的：共同视角是谁来定义的？&lt;/p&gt;
&lt;h2&gt;如何共享？&lt;/h2&gt;
&lt;p&gt;电影的评论区中有一种发言屡见不鲜：“这部电影真无聊，我看了10分钟就关了”。然后底下时常有人冷嘲热讽，认为连电影看都没看完就开始肆意评论。但是如果深究的话，评价电影是否好看的共同视角，是否是“看完电影”呢？这个“共同”，究竟是谁来定义的呢？
另一个角度，有不少电影，所谓的学院派影评人和大众的评分相距甚远，这其中还有学习了不同程度电影史知识的普通影迷，那么这些不同人群中的视角是应该统一，还是应该硬性切分，还是说人群分布是连续的无法拆分？
“电影”还只算是文化领域的一个分支，其它领域例如现代艺术、经济政策、女权和性别议题，诸多讨论根本没有一个清晰可见的“共同视角”。
但是无法共享，并不代表着无法讨论。或者说，“知道不存在共同视角”这在讨论中本身可以作为一个共同视角。对于这个电影评论的例子，如果评论的内容是“我只看了10分钟，但我觉得电影有些无聊”，这是否会让评论区的讨论更加平和且有效？
“知道不存在共同视角”，本身可以让讨论者具有包容性，而不会非黑即白的给一些抽象问题下定论。可以说，冰箱问题所涉及的问题，本质上是一种道德约束，并不是在以最低限度谈论法律问题，也不太关乎言论自由的议题。比如我用都没用过就是想抹黑一款冰箱，从最低限度的言论自由来说完全可行。但这其中还有着网络道德问题，这种道德伦理希望人们可以共创一个有价值的良好讨论环境。&lt;/p&gt;
&lt;h2&gt;共享视角？还是共享立场&lt;/h2&gt;
&lt;p&gt;理论上这篇文章可以在上面一段结束了，但是我还是想在最后提及一下视角与立场的问题。上面所列举的共同视角，例如“知道冰箱制冷”、“品尝过对应食物”、“看过对应作品”，都是建立在一个客观事实上，而非一个主观立场。
一个有关共享立场的例子是，当我们一部电影看了10分钟感觉不好看的时候，可能会点开电影评分网站看看评分。如果评分很低，那么可能就会心一笑直接不看了。但如果评分很高，那么我们会怀疑是不是自己没有看进去，可能会多看一段时间。这种“共同立场”还会延伸，比如有部电影我没看过但是非常著名，那么当我听到一个人向我抱怨电影的诸多缺点时，可能我的第一反应是“这部电影评价这么高你却说不好看”，而不是仔细聆听他的观点。
共享立场带来的先入为主在新闻领域可能更加值得警惕。新闻的共享视角应该是对事件的过程有一个客观的认知，但显然这个“客观”是非常苛刻的。现在不少网络新闻事件一边在媒体平台上写小作文，另一边可能放出聊天记录，然后这边可能又有朋友发声...事件可能会反转又反转，最终也许警方给出了官方声明也还是有人不买账。围观群众们实际上获取的并不是一个新闻的客观事实，而是舆论意义上的“大部分网友都在批判XXX”这个预设的立场。甚至比如“因为XXX是微博大V，所以他的观点更加可信”等极具倾向性的非理性判断。再比如“反对西方媒体的舆论入侵”，“西方媒体的舆论”是一个价值而非事实判断，“入侵”也是一个价值判断，“反对”这个行为亦然。
话题谈论到这个角度已经快步入不可知论的范畴了，我也准备就此停笔。在这个所谓“后真相”的时代，以及 AGI 诞生后应运而生的信息污染，对于错综复杂的信息保持足够的认知实在困难。因此这篇文章也算是个人的警醒，希望可以在某些时刻做出“正确”的判断。&lt;/p&gt;
</content:encoded><category>所思所想</category><author>Ender</author></item><item><title>技术 | 使用Python对PDF进行处理</title><link>https://www.ender-wiggin.com/posts/programming-python-pdf-processing/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/programming-python-pdf-processing/</guid><description> </description><pubDate>Sat, 08 Jul 2023 18:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;之前在写一个游戏网站的时候需要提取PDF中的文本和图像数据，对于这种重复性工作，Acrobat显得有点太机械了，于是我决定用万能的工具语言Python尝试一下。因此这篇文章是关于PDF处理的一些方法的整理和优劣分析。&lt;/p&gt;
&lt;h2&gt;具体需求&lt;/h2&gt;
&lt;p&gt;有如下的一个PDF文件，需要从中提取名称、描述、图片等信息，生成结构化文档。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202023-07-11%20at%2003.17.32%402x.png&quot; alt=&quot;CleanShot 2023-07-11 at 03.17.32@2x.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;PDF读写库：borb&lt;/h2&gt;
&lt;p&gt;由于 pdf 的特性，即便是最方便的处理库使用体验依然相对繁琐，和 html 的处理库 &lt;code&gt;beautifulsoup4&lt;/code&gt; 比起来相距甚远。在PDF领域有一些老牌库比如 &lt;code&gt;PyPDF2&lt;/code&gt;做一些简单处理也还不错，但目前功能最强大且依然稳定更新的应该还是 &lt;a href=&quot;https://borbpdf.com/index.html&quot;&gt;borb | Read, write, and edit PDF files with borb, a pure python library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;这个库的优势是有非常完整的文档，但是官网的导览做得有点混乱，并且很多文章代码都存在时效性的问题，这是一个很大的坑。实际上的最新文档和样例是在这个 &lt;a href=&quot;https://github.com/jorisschellekens/borb-examples&quot;&gt;GitHub 仓库&lt;/a&gt; 里面。&lt;/p&gt;
&lt;p&gt;这个库在写 PDF 领域大有可为，不过我这次的需求只是提取文字和图片，以及将之前的文字全都删除，因此主要的操作还是停留在读取这个领域。&lt;/p&gt;
&lt;h3&gt;字体筛选&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;def text_filter(file_name: str, filter: str) -&amp;gt; dict:
    l0: FontNameFilter = FontNameFilter(filter)

    # filtered text just gets passed to SimpleTextExtraction
    l1: SimpleTextExtraction = SimpleTextExtraction()
    l0.add_listener(l1)

    # read the Document
    doc: typing.Optional[Document] = None
    with open(file_name, &quot;rb&quot;) as in_file_handle:
        doc = PDF.loads(in_file_handle, [l0])

    # check whether we have read a Document
    assert doc is not None
    return l1.get_text()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FontNameFilter&lt;/code&gt; 可以过滤 PDF 中对应字体的文本，这在字体繁多的排版设计类 PDF 中大有可为。比如在上面的样例中，我可以通过这个函数过滤出不同类型的文本，例如标题和描述文本。
但是提取文本可能会存在特殊字符，这应该和 PDF 本身的储存方式有关系。比如我上面的文档，提取标题的结果如下，有 &lt;code&gt;GR&lt;/code&gt;  和 &lt;code&gt;ZEBR&lt;/code&gt; 这种 不知道怎么出现的字符，以至于整个文本几乎不可用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{4: &apos;ORAN GRGE CL EVYZEBR OWNF AISH&apos;,
 5: &apos;PALETT GRE S EVY UZEBR RGEOA NFISH\nTrade&apos;,
 6: &apos;Z GR OO EP VY LANK ZEBR TO AN\nSea Animal Magnet&apos;,
 7: &apos;BLAGR CKE SVY IDE HA ZEBR WA KFISH\nPosturing 1&apos;,
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我尝试的一个解决方式是正则+GPT API，几乎可以解决这个问题，但是对于一个小规模的需求来说有点过于繁琐了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    def infer_correct_name_from_GPT(input):
        ori = re.sub(&apos;\n.*&apos;, &apos;&apos;, input)
        ori = re.sub(&apos;GR|EVY|ZEBR|VY|A &apos;, &apos;&apos;, ori)
        ori = remove_extra_spaces_after_second(ori)
        prompt = f&quot;&quot;&quot;
        This is an animal name (usually a sea animal) but has some extra characters, such as &apos;GR&apos; and &apos;EVY&apos;.\
        Please infer the origin animal name and output only the name (warpped with &apos;&apos;): {ori}&quot;&quot;&quot;
        response = get_completion(prompt)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;图片提取&lt;/h2&gt;
&lt;p&gt;相较于效果并不理想的文字提取，图片提取还算差强人意。但是提取出来的图片有些是镜像翻转的，有些又不是，我也不知道为什么，因此可能还需要使用 &lt;code&gt;PIL&lt;/code&gt; 库翻转一下图片。
另外需要注意的是，一些 PDF 中会嵌入 svg ，但是嵌入后的 svg 几乎就不可能提取出来了。总而言之，从其它格式导出成 PDF 轻而易举，但是逆向操作非常麻烦，有些操作甚至完全不可行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import typing

from borb.pdf import Document
from borb.pdf import PDF
from borb.toolkit import ImageExtraction


def extract_images_from_pdf(filename: str, updated_number_dict: dict):
    l: ImageExtraction = ImageExtraction()

    # load
    doc: typing.Optional[Document] = None
    with open(filename, &quot;rb&quot;) as in_file_handle:
        doc = PDF.loads(in_file_handle, [l])

    # check whether we have read a Document
    assert doc is not None

    index = 0
    for key, value in l.get_images().items():
        image = value[1]
        image = ImageOps.mirror(image)  # 镜像反转
        card_id = updated_number_dict[&apos;card_id&apos;][index]
        filename = f&apos;images/{card_id}.jpg&apos;
        index += 1
        image.save(filename)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PDF转图片：pdf2image&lt;/h2&gt;
&lt;p&gt;前面说到 &lt;code&gt;brob&lt;/code&gt; 文字提取效果不好，所以我决定直接简单粗暴地将 PDF 转为图片然后直接OCR。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pdf2image import convert_from_path
import pytesseract
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;整个操作非常简单，转成图片后先使用 &lt;code&gt;crop&lt;/code&gt; 裁切需要识别的文字区域，然后调用 &lt;code&gt;image_to_string&lt;/code&gt; 就行了。运行速度也远优于前面的读写操作。后续也可以裁切一些具有典型 pattern 的区域，做一些图像识别。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# to image
images = convert_from_path(filename)

for index in range(len(images)):
	# get image
	image = images[index]

	# get the card name
	name_box = (50, 390, 449, 432)
	name_region = image.crop(name_box)
	card_name = pytesseract.image_to_string(name_region)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;PDF 批量处理是个大坑，比较可行的操作就是使用各种读写操作库进行图片提取，或者转成图片后进行文字识别。尽量能转成图片就对图片做操作，毕竟对图片进行操作的库就太多了。&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | Heliopolis 赫利奥波利斯神话体系</title><link>https://www.ender-wiggin.com/posts/note-heliopolis/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/note-heliopolis/</guid><description> </description><pubDate>Mon, 26 Jun 2023 18:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;回想自己以前在博物馆看到埃及展品时经常由于知识的匮乏而不得不走马观花，因此今年年初看到关于古埃及的科普绘本《莎草绘卷》众筹的时候立刻就下单了。目前终于到货，这种以知识普及为主的书籍对于我这种非专业读者确实挺友好的。因为神话体系确实挺难记忆的，所以我决定记个笔记方便温故知新。&lt;/p&gt;
&lt;h2&gt;九神团&lt;/h2&gt;
&lt;h3&gt;Atum 亚图姆&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;太阳神&lt;/li&gt;
&lt;li&gt;特点：头戴红白双冠&lt;/li&gt;
&lt;li&gt;其它形态：狮子、公牛、蜥蜴、圣甲虫、蛇&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;诞生于原初之水努恩 (Nun)，并创造了休和泰芙努特。&lt;/p&gt;
&lt;h3&gt;Shu 休&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;空气之神（阳光）&lt;/li&gt;
&lt;li&gt;特点：头戴羽毛、手持 Ankh (生命之符 ☥)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般站在他儿子盖布身上，手举女儿努特，意味着天地分立。&lt;/p&gt;
&lt;h3&gt;Tefnut 泰芙努特&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;水汽女神（湿润）&lt;/li&gt;
&lt;li&gt;特点：头顶日轮、面部为女狮&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;与水相关的神话密不可分。可以从身体中制造纯净的水，用铭文提到可以供国王饮用及清洁身体。&lt;/p&gt;
&lt;h3&gt;Geb 盖布&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;大地之神&lt;/li&gt;
&lt;li&gt;身份：努特的丈夫&lt;/li&gt;
&lt;li&gt;特点：头顶鹅形头饰/鹅首以及手持瓦斯权杖以及 Ankh 的深肤色男性 （绿色-&amp;gt;芦苇/黑色-&amp;gt;淤泥）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在与其它神话类似的内容之外，古埃及人也认为地震的怪声是他在大笑。同时他也是邪恶者灵魂的囚禁者。
在《荷鲁斯与赛特的争斗》中，盖布作为审判庭庭长在“盖布大殿”中裁决二者谁有资格获得王位。&lt;/p&gt;
&lt;h3&gt;Nut 努特&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;天空之神&lt;/li&gt;
&lt;li&gt;身份：盖布的妻子&lt;/li&gt;
&lt;li&gt;特点：蜜蜂/母牛/四肢支撑地面的蓝色皮肤女性，装饰众多星形符号&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;古埃及人认为太阳每天傍晚会被努特用嘴吞下，在她身体内部进行夜间旅程。也有壁画描绘到，太阳神站在太阳船上，在群星相伴下沿着努特被拉长的身体航行。
与盖布的笑声相似，他们认为雷声是努特的笑声。
此外，由于她吞食太阳的特征，她也被视为可以给国王带来新生的死亡女神。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20230629052843.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Osiris 奥西里斯&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;冥界之神、冥界杜亚特之王、亡灵审判官、世界边缘之王（Neb-ertcher）&lt;/li&gt;
&lt;li&gt;身份：盖布与努特之子，伊西斯的丈夫&lt;/li&gt;
&lt;li&gt;特点：绿色皮肤，头戴阿提夫（Atef）王冠，交叉双臂，手持连枷和勾杖&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Osiris 经历了死亡（主流说法是被赛特所杀），并最终成为了冥界的国王。
在拉和阿努比斯的帮助下制作成了木乃伊，伊西斯让他短暂复活并和他生下儿子荷鲁斯。
他也被视为”玛阿特之主“，监督部下使用”玛阿特羽毛“在天平上对死者进行审判。
此外，由于他绿色皮肤和死而复生的特点，也被古埃及人视为农业神，象征着植物的再生。&lt;/p&gt;
&lt;h2&gt;Isis 伊西斯&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;魔法女神&lt;/li&gt;
&lt;li&gt;身份：盖布与努特之女&lt;/li&gt;
&lt;li&gt;特点：穿着长套裙，头戴王座符号；手持 Ankh 或者提耶特（长寿、繁荣）或者莲花和悬铃木。在一些地方神话中会和哈索尔等同，因此有时会头带有牛角的太阳圆盘，手持哈索尔的象征物——叉铃（sistrum）和梅娜特项链（menat）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;古埃及人认为知道众神的真实名字就可以拥有控制他们的魔力，而伊西斯是极少数知道所有神名字的神。
因为她使奥西里斯复活，古埃及人也将她视为亡灵的守护神与令死者重生的女神。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20230629052607.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>影评 | 《小行星城》</title><link>https://www.ender-wiggin.com/posts/review-asteroid-city/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-asteroid-city/</guid><description> </description><pubDate>Sat, 24 Jun 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;尽管《小行星城》在豆瓣上一致差评我还是决定去观看一下，因为我确实很喜欢《法兰西特派》。不少评论认为自《法兰西特派》开始韦斯安德森就开始陷入形式主义的桎梏，但我恰恰被这部作品其中关于报纸这种文学载体的影视化呈现与对于艺术形式的多角度思考所吸引，不论是其中颇有巧思的文本设计还是平面道具设计以及舞台调度的规划，给我的整体观感像是踱步于一位艺术家的个展。一部本应以叙事为导向的电影变格为以参观体验为导向的展览，这种艺术形式的切换在不同观众的眼中确实会存在分化，有人会赞叹与艺术上的形式美，也会有人批评其中刻意的形式主义。&lt;/p&gt;
&lt;p&gt;但是我上述关于《法兰西特派》的观点，在这一部作品中却并不那么适用。因为虽然说《法兰西特派》已经在艺术形式上比较刻意为之，但是它的场景主体与它的叙事传达是一致的。相反地，《小行星城》给观众的第一印象就是独特滤镜下的高饱和天空与沙漠。但是在我眼中这个沙漠过于“精致”了，精致到观众难以感受到它固有的炎热属性与沙漠应有的旷野感，仅凭影片中的台词“It&apos;s so hot” 和鲜有的几声蚊子嗡嗡声显然不够。我一度很喜欢导演的拍摄美学，但是他此前的作品场景主要是室内或者自然光不强烈的建筑周边，因此这一部非常精致的沙漠甚至有些弱化场景原有的效果，参考荒漠处理的代表作《黄金三镖客》对比尤为强烈。&lt;/p&gt;
&lt;p&gt;我也看到相关文章表示布景上的不真实感正是导演暗示戏里戏外的差异，导演本人说，「我们有一个剧本，要演一个虚假的故事，一个在1950年代，虚构的沙漠城镇的故事」。但是以我粗浅的认知，这种处理手法让我对于剧中人物有疏离感，对舞台和真实世界的切换有隔离感，也许对于这个主题我还是更习惯《雨中曲》这种主流叙事风格。“电影中的舞台布景是为了表现舞台的虚拟感”，也许导演是站在更高的艺术高度进行探讨，但显然这种逻辑自洽的理论观众是很难买账的。&lt;/p&gt;
&lt;p&gt;此外，剧中大部分人物的相关展开都是非常寡淡的。这也许可以理解为这是导演通过去主题化展现每个个体的平淡生活以传达存在主义思想，但多数人物在影片中的“存在”实在太平淡且重复了，缺少了人物群像应有的个体亮点。并且，如果说是高屋建瓴式的人物群像，那么为什么又在不少角色间穿插情愫交叠，让人物的展开缺乏新意？&lt;/p&gt;
&lt;p&gt;总的来说《小行星城》的主题太过于晦涩，通过著名演员的堆积让它看起来像是成熟且专业的商业片，却又在讨论比多数文艺片与纪录片更加缥缈的议题。我依然认为是值得因为其艺术风格和较为诙谐的台词与故事发展去电影院体验的，但是整部电影的感受就像一颗精致的水晶球，在精致的布置外部有一层难以逾越的隔阂。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>方舟动物园 | 二人局的基本节奏</title><link>https://www.ender-wiggin.com/posts/ark-nova-2p/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/ark-nova-2p/</guid><description> </description><pubDate>Thu, 15 Jun 2023 22:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;本文是我个人对目前2p思路的一些总结，由于目前还处在摸索期，因此部分理论后续可能会修改。
2p和3p/4p最大的区别就是它的休息轨道只有9格，这意味着2次抽牌+1次赞助商休息5就可以过掉一个大轮，因此对于休息的把控尤为重要。
本文只谈论宏观上的思路，不会对地图/卡牌做出具体分析。我大致上将会从以下几个角度展开：&lt;/p&gt;
&lt;ol&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;li&gt;轮次规划&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;协会选择&lt;/h2&gt;
&lt;p&gt;由于2p意味着第一轮的两个优质大学可以一人一个，因此绝大多数对局都是大学开，并且大部分情况推荐&lt;strong&gt;5牌&lt;/strong&gt;大学。5牌可以让自己在前期有绝对的休息主动权，不会应该顾虑手牌上限而导致不敢做卡牌行动，同时可以留存中后期刷分牌的可能性。
如果是&lt;strong&gt;2声望&lt;/strong&gt;大学，除开需要打科标前置或者被对方先选择的5牌的被动情况，玩家主动选择2声望开局主要都是为了在第一大轮凑齐5声望。但是凑齐5声望往往需要拉长一时代战线，因此对于休息轨需要格外关注，这一部分我会在第二章节【休息规划】中具体说明。&lt;/p&gt;
&lt;p&gt;那么，什么时候适合动物园开局呢？我首先说明一下动物园开局的弊端：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;多数情况第二大轮不能双升级，这对于前期节奏是巨大影响&lt;/li&gt;
&lt;li&gt;缺乏声望导致关键牌无法争抢，这在此消彼长的二人局尤为重要&lt;/li&gt;
&lt;li&gt;几乎所有局最后都会有至少2大学。第一大轮做了强度3的行动意味着后续轮次需要做强度4的大学行动，这会拖慢整个协会的节奏，尤其是关乎后期强度5的动保&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，适合动物园开局的情况主要就2.5种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;手里有对应的动物园动保可以第二大轮打出。这种情况可以让第二轮至少双升级，同时声望并不太落后，上限也比传统大学开更高&lt;/li&gt;
&lt;li&gt;天胡5动物贴动保。这样第二大轮可以做个5盾动保，如果5盾的奖励是动物园/大学等高质量板块，那么节奏也会起飞&lt;/li&gt;
&lt;li&gt;有比较强力的动物可以打出一些combo，例如邦加跗猴可以保证声望不落后，金刚鹦鹉可以带来客观的经济收益，倭河马有机会第一大轮光速休息过回合。但是我把这个类型算作0.5种，因为其实即便有这种牌也未必就要开局打。比如第二/第三大轮靠再拿亚洲动物园配合邦加跗猴冲8声望也是不错的思路。&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202023-06-15%20at%2000.55.41%402x.png&quot; alt=&quot;CleanShot 2023-06-15 at 00.55.41@2x.png&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不论如何，大学开一定是比动物园开更稳定的，因此有时候没必要追求高风险换取并不太高的收益。&lt;/p&gt;
&lt;h2&gt;休息规划&lt;/h2&gt;
&lt;p&gt;基于“2次抽牌+1次赞助商休息5过turn”这个特性，开局的节奏一般有三种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;常规开局。双方都在互相试探对方，最终双方一人一张赞助商卡，一次抽牌，最终有一个人选择了休息5。这个休息5又有几种原因：被动的情况可能是缺钱没牌没事干，而主动的情况可能是可以卡对方节奏。比如开局很常见的情况是双方的协会都到5了而大家没工人，其中一方可能会把协会放到1拿X，这时候如果另外一方休息5那么他可以优先双升级，有机会滚雪球。&lt;/li&gt;
&lt;li&gt;速推开局。是否速推主要看对方的反应。在一些情况下（例如下图），玩家1可以【协会】拿5牌-&amp;gt;【卡牌】，而玩家2由于被发牌员安排了基本只能【卡牌】-&amp;gt;【其它】。第三动如果玩家1直接【赞助】休息5，那么玩家2直接裂开，因为他甚至连一次协会都没有做就被过turn了，同时手牌还超上限了。并且需要注意的是， 这种情况未必少见，因此如果你是这个运气不好的玩家2，那么在喷发牌员的同时要谨慎执行第一次的【卡牌】行动。而对于想要速推的玩家，也要明确速推最好是卡掉对方一个人，其次是让对方因为手牌上限只有3而大量弃牌打乱他的布局计划，千万不要为了贪一个动物的几票而让休息晚了一轮。 &lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202023-06-15%20at%2001.16.38%402x.png&quot; alt=&quot;CleanShot 2023-06-15 at 01.16.38@2x.png&quot; /&gt;&lt;/li&gt;
&lt;li&gt;折磨开局。这种开局的特点就是双方在第一大轮疯狂拖动，非常折磨。选择这个方案的主要原因是：a. 己方可以开局5声望并且还可以保持一定数量的钱，因此可以升级建造在第一轮通过贩售亭+票亭实现超展开；b. 开局有机会做一个动保，打算动保解锁人实现第一大轮2次协会；c.手里有足够数量的优质蓝卡，在第一大轮可以获得高额经济收益。但需要注意的是，如果你决定开局折磨，那么第一次的【卡牌】行动基本是不能做的，因为做了之后对方可以没有压力的休息5过turn。不过我个人并不是很建议这种开局，虽然有时候确实是最优选择，但可能会搞对手心态，冤冤相报何时了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;说完了开局休息的思路，接下来谈论一下其它轮次的休息抉择。从BGA当前赛季前几名的玩家以及国内比较知名玩家的数据来看，2p基本都是4次休息结束，而他们普遍的休息次数都是大于2次的。这也说明了水平较高的玩家对于休息的时机有着更清晰的把握，他们可以对一些不错的赞助商卡视而不见而选择休息过turn，因为在他们的计算中休息相较于赞助商有着更好的节奏。
相反地，我再BGA上看到不少萌新开局（尤其是第二大轮）前几懂就傻傻地打动物花光所有费用，然后因为贪赞助商不选择休息。这种情况另一方还有几万个行动而他无事可干，这种打赞助商的行为就是纯纯的搞自己。
休息确实是一个反直觉的设计，德式玩家基本第一眼看到休息这种行动就会觉得是一个亏模型的行动，但事实上休息也是这游戏的一大亮点与难点。&lt;/p&gt;
&lt;h2&gt;卡牌升级&lt;/h2&gt;
&lt;p&gt;卡牌升级一般定式就是第二轮双大学双升【建造】+【动物】，如果声望只有4点那就先【动物】再【建造】，没什么大差别。除了少数情况早翻【协会】有收益（例如已有1盾，可以通过协会的2元1盾再升级一张卡牌），一般协会并不会很着急，反而升级【卡牌】让声望突破9比较关键。
但是我最近对于【协会】和【赞助商】有了一些新的看法。因为升级协会普遍比较晚，并且2p实际上只有前三格（2、5、7）是赚费的，其实后升的那个玩家完全可以考虑不升级协会。升级协会的意义除了捐赠，就是第3个动物园以及5+3这种连动。但是不少地图第三个协会工人是1盾或者0盾（如营地银湖），5+3这种行动虽说挺赚但后期很可能还是老老实实两次强度5的动保。而对于第三个动物园，其实只要拿了亚洲，我觉得其它动物园并没有什么太关键的动物让你因为没升级协会而追悔莫及。
另一方面，二级赞助商还是有几张不错的卡牌可以为玩家带来即时经济收益和高上限，相较于7元1盾，这些牌如果能抽到基本必然就回本了。再不济休息5多5块钱，说不定都比捐赠一次划算呢。 &lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202023-06-15%20at%2001.58.11%402x.png&quot; alt=&quot;CleanShot 2023-06-15 at 01.58.11@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但注意我上面对于赞助商的建议，是和比较晚的协会对比的。我并不提倡先升级赞助商然后无脑打赞助商的行为，相反我是建议玩家在对数值模型有足够认知的情况下多比对一下协会和赞助商的收益，说不定会有些新的发现。&lt;/p&gt;
&lt;h2&gt;动保规划&lt;/h2&gt;
&lt;p&gt;其实关于动保并没有特别多需要说明的，可能主要就是要注意一下那个第一格和第二格被覆盖的那两个。第一格被覆盖的情况下，如果是亚洲美洲这类第二格只有3盾的动保，基本无脑选择第三格2盾就行，并且不建议把这个动保作为主路线。而第二格被覆盖的情况意味着如果双方都想做这个动保，那么快的人是5盾而慢的人即便只晚了一动也只剩下2盾了，一来一回可是6盾的差距，这6盾是很可能直接决定游戏胜负的。&lt;/p&gt;
&lt;h2&gt;轮次规划&lt;/h2&gt;
&lt;p&gt;2p的统计数据是平均4次休息一共5个大轮，大致的行动数是34动。当然其实行动数没太多参考价值，开局拖动的局最终40动都有可能，而休息5次的局可能30动就可以140分。因此我一般是按分数作为参考，一般如果分数到了55分，那么在下一次休息后很可能游戏就结束了。比方说休息后有50块钱，那么两只动物（大熊猫+随便一个动保对应动物）就差不多25分了，然后一个动保+奖励+捐赠就结束游戏了，还可能附带着填满地图或者最后一个大学这些杂七杂八的分，如果有金雕马来熊这些连动卡牌那就更快了。
因此最后一次休息需要特别关注。如果你手里都是一些朴素的小动物（例如那些正常模型或者超模个一两票的爬行），那么尽量不要让下一次休息到来。因为如果对方生产了一波钱的话他如果突然掏出金雕马来熊老虎熊猫这些超模牌，那分数涨得看都看不懂。并且根据二人卡牌游戏的常见情况，如果好牌不在你手上，那么大概率就在对方手上了。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;本文算是对2p的一个简单思路分享吧。前几天已经有大佬写了一个完整攻略了，因此我这篇算是针对2p的一些思路补充。我目前也在向各位高手学习技术，估计过段时间也会有更多的理解，这篇文章就算是一个阶段性记录吧。&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>影评 | 《蜘蛛侠：纵横宇宙》：令人炫目的加法</title><link>https://www.ender-wiggin.com/posts/review-spider-man-across-the-spider-verse-part-one/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-spider-man-across-the-spider-verse-part-one/</guid><description> </description><pubDate>Fri, 02 Jun 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;《蜘蛛侠：纵横宇宙》是我今年最期待的动画电影，在看到影院上映后第一后迫不及待地观看了。还记得第一部也是在刚考完final之后去看的，在高压的备考之后观看一场试听盛宴确实是一种享受。
第一部的抽帧与漫画风格化表现在18年圣诞前夕给观众带来了全新的动画观影观影体验，而这一部将画面构成提升到了一个新的高度：从开头格温宇宙表现主义风格到反派秃鹫的达芬奇手稿素描风格再到朋克宇宙的朋克文化与拼贴艺术，以及自第一部一脉相承的波普艺术...各式各样的艺术风格令人应接不暇，前几十分钟的观影就仿佛是在影院中开盲盒，让观众对下一幕会呈现哪种风格充满期待。&lt;/p&gt;
&lt;p&gt;但是在惊喜之余，这部电影在一些方面也让我感到有些遗憾。&lt;/p&gt;
&lt;p&gt;首先这部的“平行宇宙”这个概念，虽说是第一部的延伸，但是在去年《瞬息全宇宙》之后让观众对于概念诠释的阈值又提高了不少，在主题拓展上似乎并没有带来预期的惊喜（当时这并不是电影本身的问题）。
另外，从我个人的角度而言，我认为在动画电影中加入真人角色和影视片段显得有点流俗。第一部对于平行宇宙的玩法，不论如何始终是停留在漫画/动画这个维度，即便二次元少女看起来仍有些违和，但是她的机甲还是传统的动画3D风格，因此在多数人物群像画面中还算和谐。但是这一部中真人人物以及乐高的串台，对我而言并不能带来足够的新鲜感，反而有种海绵宝宝的既视感，仿佛一堆全新概念中突然出现了一个用了几十年的老点子，带给了我一定心理落差。影视片段同理，虽然我很喜欢加菲和托比的蜘蛛侠，但是当我看到动画电影中嵌入真人片段时总感觉太违和了，加上此前荷兰弟版《蜘蛛侠3》的“三蛛同框”，甚至又带来了似曾相识的既视感，进一步冲击了此前艺术风格的新鲜感。&lt;/p&gt;
&lt;p&gt;其次，蜘蛛宇宙确实令人炫目，但我个人觉得有点太眼花缭乱了。140分钟中塞了数不清的蜘蛛侠，我认为给出一些画面作为彩蛋是合理的，但是让密密麻麻的蜘蛛侠们上演追逐戏，感觉既弱化了每个蜘蛛侠的人物特征，同时并没有带来足够的画面冲击力，让人有些摸不着头脑。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/20230603011131.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;关于所谓平行宇宙的宿命论以及主角的反抗，对我来说也有点陈词滥调了，观感体验可能都不如第一部中雪地墓前的谈心：构图、意境、对白都及其优秀，有种“今月曾经照古人”的诗意表达。
当然，当迈尔斯穿越到了42号宇宙，剧情又突然精彩了起来。因此我愿意将前面未来纽约的故事视为世界观的呈现与剧情铺垫，后续的 part II 可能依然有着亮眼的表现。&lt;/p&gt;
&lt;p&gt;最后，配乐也是第一部的一大亮点。第一部在引入迈尔斯世界观时，用街头涂鸦配上说唱音乐，HipHop 氛围营造非常出色。第一部的 Soundtrack中例如 What&apos;s Up Danger, Sunflower, Familia...首首重量级，让整部电影在几乎所有维度都无懈可击。
这一部中 Annihilate, Am I Dreaming 等单曲质量也不低，背景音乐中808鼓的节奏感与情绪递进也很出色，但我个人感觉是比不上第一部的，有点像《冰雪奇缘》第一和第二部音乐的区别。&lt;/p&gt;
&lt;p&gt;总而言之，这部电影绝对是今年不容错过的动画佳作，也在第一部的惊喜之上依然带来了足够惊艳的观感与视觉冲击。但是我认为第二部在一些维度上提升了，但是其它的一些维度（人物、叙事、音乐）可能由于做了太多加法导致体验不及个人预期。但不排除有的观众就是喜欢这种光怪陆离的氛围感，因此这些维度也谈不上是缺陷，只是个人喜好的差异罢了。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>剧评 | 《漫长的季节》</title><link>https://www.ender-wiggin.com/posts/review-the-long-season/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-the-long-season/</guid><description>剧透警告</description><pubDate>Tue, 02 May 2023 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;THIS REVIEW CONTAINS SPOILERS 剧透预警&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;看到这个剧在豆瓣上获得了9.5的高分，于是这两天献祭了不少写Kotlin的时间追完了。悬疑作品好在篇幅一般不长，但是坏在看了开头就可能会停不下来。&lt;/p&gt;
&lt;h2&gt;叙事&lt;/h2&gt;
&lt;p&gt;这部作品最大的特点就是占据大部分篇幅的生活化描写，比常规的社会推理篇幅都长多了。其实这个作品的推理部分，如果进一步打散时间线再配合更多的叙诡，感觉也可以做出类似《断掌事件》那种阴沉短篇的感觉。但是导演编剧反其道而行，用繁复的生活细节弱化了推理的要素，将观众的视野更多的聚焦在了人物群像和社会现状上。
从故事背景上来说，我觉得这部作品对于八九十年代读大学、工作的人应该会更有共鸣。以前时常会听母亲聊起她小时候的钢铁厂食堂、大学时的《恋曲 1990》、分配工作后的《泰坦尼克号》、以及工作单位的那一代人都或多或少经历过的不公平行为。我虽为所谓的“新生代”，但也住过需要穿过铁轨才能到达的小屋，也经历过刚到大城市的颠沛流离。很多过去的记忆并不值得被珍藏，但是也不应该被遗忘。&lt;/p&gt;
&lt;h2&gt;谈谈我眼中的“中国风”&lt;/h2&gt;
&lt;p&gt;在前几个月看《中国奇谭》时我就在想，“中国风”的现代化诠释应该是什么？比如当我们谈到美国精神和日本文化，在不同时代下有着不同的诠释。但是对于“中国风”，似乎还停留在网络歌曲上辞藻堆砌的古风词汇，影视作品中千篇一律的齐天大圣。那么在2023年的当下，这个中国印象是否有什么新的诠释？《中国奇谭》不少导演将视角投射在农村，这确实是中国很重要的意象，但是这个意象在影视上从80年代的《黄土地》就已经开始了，而文学上从20世纪初开始一直延伸至今日，但是如今的“乡土中国”对于荧幕前不少一直居住在城市的观众其实并没有很多的代入感。因此，聚焦于社会多面性本身的中国更加值得被荧幕所记录。在家庭关系上，能看到这几年不论是国内外都有不少对于东亚原生家庭氛围的诠释。但是对于中国自身的社会现状的描写一直并不多，或者说很多分析是停留在学术研究层面的而难以进入大众视野。
这部作品在家庭与社会上都展现出来不错的维度。家庭上，比这两年《瞬息全宇宙》《青春变形记》《怒呛人生》这些视角投射在西方社会中华裔生活的作品更加带有中国本身的气息。前者们主要以西方视角描绘家庭关系的压抑感，但是《漫长的季节》使用东北口音以及带有一定荒诞性的人物呈现出了一种在压抑中人们还苦中作乐的矛盾生活，让压抑的生活没那么深沉而幸福的时光却也不那么明快，正如剧集海报在明亮色调中的诡异感。社会上，透露出了自上而下的管理制度以及身不由己的个体命运。警务办事效率低下、工厂官商勾结、工人辛勤工作但退休后却得不到原有的承诺。这一切虽说在作品中发生在90年代，但是时至今日这些景象依然烙印在许多国人心中，因此我认为作为当下的“中国风”还是有理有据的。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;可以看到这几年中国影视相比于前些年都有显著的进步，我一向认为中国不缺少好的作品，只是在影视上缺少一个表态的空间，对比受众面窄一些的文学作品、以及受众面更窄的学术专著的笔触力度便可见一斑。因此我能看到这部精良的剧集还是挺开心的，也希望未来能看到更多用心投入的中国影视。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>方舟动物园 | 赞助商一览</title><link>https://www.ender-wiggin.com/posts/ark-nova-sponsors/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/ark-nova-sponsors/</guid><description> </description><pubDate>Mon, 01 May 2023 22:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;综述：赞助商到底强不强？&lt;/h2&gt;
&lt;p&gt;一般来说赞助商在前期价值是比较大的，玩家由于有限的经济和手牌上限，以及未升级行动卡的缘故，容易无事可做进退两难。这时候如果玩家有一手的赞助商，应对这种情况会显得游刃有余，可以把打动物的行动和钱节约下来用于布局建筑和打出赞助商，同时还可以避免被动物园的低素质卡牌安排，可谓一举三得。
但是赞助商天生存在着如下限制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大部分赞助商不具有动物/大洲标志，沉迷打牌可能会让自己与动保无缘&lt;/li&gt;
&lt;li&gt;游戏节奏变化万千，很可能存在你不得不休息的时刻，这种情况下等于错失了打牌的机会&lt;/li&gt;
&lt;li&gt;手牌数量和过牌能力是有限的，到了游戏的中后期很可能无牌可打，或者卡牌收益贬值不值得打出&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;综上，如果不考虑极端情况的话，大部分对局中玩家都是在前期有啥打啥，中期之后听天由命。&lt;/p&gt;
&lt;p&gt;目前动物园在形式上有点类似于火星基础，动保的影响太大，以至于脱落动保的路线基本是没有前途的，因此动物园是一款非常克制的打牌游戏，并不像火星一样是每个人都可以构建出不错的引擎快乐打牌的”爽游“。&lt;/p&gt;
&lt;h2&gt;赞助商强度模型&lt;/h2&gt;
&lt;p&gt;按照前面的理论，赞助商卡顺眼即可，因此很多时候并不会太介意模型，或者说即便模型不算优秀但一般而言还是比休息5要厉害的。
下面两张属于我觉得比较均衡且基本上没有前置的卡牌，前者&lt;strong&gt;动物园学校&lt;/strong&gt;5强度对应1盾1声望1牌1建筑，后者&lt;strong&gt;学校导游&lt;/strong&gt;3强度对应1票1-2盾。我也没有仔细计算模型，但我觉得再后续分析的时候可以与这两张卡牌比较，如果一张卡收益持平但又更高的前置或者费用，那一般来说那张卡就是比较弱的。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202023-04-30%20at%2021.56.15%402x.png&quot; alt=&quot;CleanShot 2023-04-30 at 21.56.15@2x.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;基于这一点，附带难度不高的终局记分效果的赞助商，至少已经带有1盾了，因此对于特效可以降一些标准。&lt;/p&gt;
&lt;h2&gt;单卡分析&lt;/h2&gt;
&lt;p&gt;{% raw %}
&amp;lt;div style=&quot;position: relative; width: 100%; height: 0; padding-bottom: 75%;&quot;&amp;gt;
&amp;lt;iframe src=&quot;//player.bilibili.com/player.html?aid=783163603&amp;amp;bvid=BV1r14y1Z7Lv&amp;amp;cid=1114518727&amp;amp;page=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot; style=&quot;position: absolute; width: 100%; height: 100%; Left: 0; top: 0;&quot; &amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/div&amp;gt;
{% endraw %}&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>学习笔记 | Prompt Engineering 03-08</title><link>https://www.ender-wiggin.com/posts/chatgpt-prompt-engineering-for-developers-03-08/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/chatgpt-prompt-engineering-for-developers-03-08/</guid><description> </description><pubDate>Tue, 25 Apr 2023 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;03 Iterative Prompt Develelopment&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202023-04-29%20at%2002.19.18%402x.png&quot; alt=&quot;CleanShot 2023-04-29 at 02.19.18@2x.png&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;其实就是一步步迭代prompt&lt;/li&gt;
&lt;li&gt;例如给prompt加上更多的限制: 篇幅(words, sentences, characters limitation), 输出内容, 输出格式...&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;04 Summarizing&lt;/h2&gt;
&lt;p&gt;本节讨论如何在总结类应用中实现更好的效果。&lt;/p&gt;
&lt;h3&gt;Summarize with limit or focus topic&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;限定篇幅&lt;/li&gt;
&lt;li&gt;限定总结的方向 (例如针对不同角度的总结，比如以运输的角度或者是价格的角度进行总结)&lt;/li&gt;
&lt;li&gt;Summaries include topics that are not related to the topic of focus&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Try &quot;extract&quot; instead of &quot;summarize&quot;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;prompt = f&quot;&quot;&quot;
Your task is to extract relevant information from \
a product review from an ecommerce site to give \
feedback to the Shipping department.

From the review below, delimited by triple quotes \
extract the information relevant to shipping and \
delivery. Limit to 30 words.

Review: &amp;lt;prod_review&amp;gt;
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;05 Inferring&lt;/h2&gt;
&lt;p&gt;本节主要介绍如何从文本中推断sentiment and topics&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;&quot;&quot;
What is the sentiment of the following product review,
which is delimited with triple backticks?

Give your answer as a single word, either &quot;positive&quot; \
or &quot;negative&quot;
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;值得学习的一点是，利用API的格式化输出，对接应用的其它功能。比如先分析语义输出JSON格式的关键词，然后将JSON展示与前端。这样等于人人不需要训练模型就可以开发NLP相关应用，大大降低了门槛。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;prompt = f&quot;&quot;&quot;
Identify the following items from the review text:
- Sentiment (positive or negative)
- Is the reviewer expressing anger? (true or false)
- Item purchased by reviewer
- Company that made the item

The review is delimited with triple backticks. \
Format your response as a JSON object with \
&quot;Sentiment&quot;, &quot;Anger&quot;, &quot;Item&quot; and &quot;Brand&quot; as the keys.
If the information isn&apos;t present, use &quot;unknown&quot; \
as the value.
Make your response as short as possible.
Format the Anger value as a boolean.

Review text: &apos;&apos;&apos;{lamp_review}&apos;&apos;&apos;
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;06 Transforming&lt;/h2&gt;
&lt;p&gt;本节主要关于文本翻译、语法检查、语气/格式转换&lt;/p&gt;
&lt;h3&gt;Translation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;语言选择有很多种，甚至可以是&lt;code&gt;English pirate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;可以让模型自行判断文本是哪种语言&lt;/li&gt;
&lt;li&gt;笔者注：多语言翻译确实很方便，比如之前在GitHub上看到了有&lt;code&gt;i18n&lt;/code&gt;的命令行翻译工具，可以自动生成翻译文件&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Tone Transformation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;produce different tones&lt;/li&gt;
&lt;li&gt;such as &lt;code&gt;Translate the following from slang to a business letter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Format Conversion&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;格式转换&lt;/li&gt;
&lt;li&gt;such as &lt;code&gt;Translate the following python dictionary from JSON to an HTML&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Spellcheck/Grammar check&lt;/h3&gt;
&lt;hr /&gt;
&lt;h2&gt;07 Expanding&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;扩写&lt;/li&gt;
&lt;li&gt;example: generate customer service emails that are tailored to each customer&apos;s review&lt;/li&gt;
&lt;li&gt;例如可以让模型根据不同情感作出不同反应&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;prompt = f&quot;&quot;&quot;
You are a customer service AI assistant.
Your task is to send an email reply to a valued customer.
Given the customer email delimited by ```, \
Generate a reply to thank the customer for their review.
If the sentiment is positive or neutral, thank them for \
their review.
If the sentiment is negative, apologize and suggest that \
they can reach out to customer service.
Make sure to use specific details from the review.
Write in a concise and professional tone.
Sign the email as `AI customer agent`.
Customer review: &amp;lt;{review}&amp;gt;
Review sentiment: {sentiment}
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;temerature&lt;/code&gt;越高，输出结果越随机 &lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/CleanShot%202023-04-29%20at%2021.45.15%402x.png&quot; alt=&quot;CleanShot 2023-04-29 at 21.45.15@2x.png&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;08 Chatbot&lt;/h2&gt;
&lt;p&gt;本节主要介绍如何实现一个对话机器人&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def get_completion_from_messages(messages, model=&quot;gpt-3.5-turbo&quot;, temperature=0):
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=temperature, # degree of randomness
    )
    return response.choices[0].message[&quot;content&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;role&lt;/code&gt; contains of:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;system&lt;/code&gt;: 预设的prompt，全局生效&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user&lt;/code&gt;: 用户内容&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assistant&lt;/code&gt;: AI生成的内容&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;messages =  [
{&apos;role&apos;:&apos;system&apos;, &apos;content&apos;:&apos;You are an assistant that speaks like Shakespeare.&apos;},
{&apos;role&apos;:&apos;user&apos;, &apos;content&apos;:&apos;tell me a joke&apos;},
{&apos;role&apos;:&apos;assistant&apos;, &apos;content&apos;:&apos;Why did the chicken cross the road&apos;},
{&apos;role&apos;:&apos;user&apos;, &apos;content&apos;:&apos;I don\&apos;t know&apos;}  ]

response = get_completion_from_messages(messages, temperature=1)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;可以通过以下代码实现对话上下文:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;def collect_messages(_):
    prompt = inp.value_input
    inp.value = &apos;&apos;
    context.append({&apos;role&apos;:&apos;user&apos;, &apos;content&apos;:f&quot;{prompt}&quot;})
    response = get_completion_from_messages(context)
    context.append({&apos;role&apos;:&apos;assistant&apos;, &apos;content&apos;:f&quot;{response}&quot;})
    panels.append(
        pn.Row(&apos;User:&apos;, pn.pane.Markdown(prompt, width=600)))
    panels.append(
        pn.Row(&apos;Assistant:&apos;, pn.pane.Markdown(response, width=600, style={&apos;background-color&apos;: &apos;#F6F6F6&apos;})))

    return pn.Column(*panels)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>学习笔记 | Prompt Engineering 01-02</title><link>https://www.ender-wiggin.com/posts/chatgpt-prompt-engineering-for-developers-01-02/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/chatgpt-prompt-engineering-for-developers-01-02/</guid><description> </description><pubDate>Tue, 25 Apr 2023 18:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Guidelines for Prompting&lt;/h1&gt;
&lt;p&gt;笔者注: 新技术每个人掌握的程度参差不齐，既有可以开发&lt;code&gt;agent&lt;/code&gt;的大佬，也有一key难求的小白。整个视频课程看下来对于我个人而言有一些收获但是不多，可能因为内容虽然官方背书但是不够深入。我把课程中我觉得有价值的部分做了些摘要，可供读者快速参阅。&lt;/p&gt;
&lt;h2&gt;Types of Large Language Models&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Base LLM: predicts next word based on text training data 模仿与续写&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instruction Tuned LLM&lt;/strong&gt;: fine-tune on instructions and good attempts at following those instructions 根据指令做出反应&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Principles of Prompting&lt;/h2&gt;
&lt;h3&gt;01 Write clear and specific instructions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;clean != short&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Tactic 1: Use delimiters&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;clearly indicate distinct parts of the input&lt;/li&gt;
&lt;li&gt;将prompt与文本隔离，避免模型将文本内容视为prompt一部分&lt;/li&gt;
&lt;li&gt;分割的方式有很多种：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// triple quotes: &quot;&quot;&quot;
// triple backticks: ```
// triple dashes: ---
// XML tags: &amp;lt;tag&amp;gt;&amp;lt;/tag&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Tactic 2: Ask for a structured output&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;结构化输出，例如JSON或者HTML&lt;/li&gt;
&lt;li&gt;笔者注：&lt;code&gt;Jupyter&lt;/code&gt;文件可在&lt;a href=&quot;https://learn.deeplearning.ai/chatgpt-prompt-eng/lesson/2/guidelines&quot;&gt;课程官网&lt;/a&gt;直接运行&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;prompt = f&quot;&quot;&quot;
Generate a list of three made-up book titles along \
with their authors and geres.
Provide them in JSON format with the following keys:
book_id, title, author, genre.
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Tactic 3: Ask the model to check whether conditions are satisfied&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;类似于代码中的&lt;code&gt;try...except&lt;/code&gt;， 可以要求模型的格式化输出&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;text_1 = f&quot;&quot;&quot;
Making a cup of tea is easy! First, you need to get some \
water boiling. While that&apos;s happening, \
grab a cup and put a tea bag in it. Once the water is \
hot enough, just pour it over the tea bag. \
Let it sit for a bit so the tea can steep. After a \
few minutes, take out the tea bag. If you \
like, you can add some sugar or milk to taste. \
And that&apos;s it! You&apos;ve got yourself a delicious \
cup of tea to enjoy.
&quot;&quot;&quot;
prompt = f&quot;&quot;&quot;
You will be provided with text delimited by triple quotes.
If it contains a sequence of instructions, \
re-write those instructions in the following format:

Step 1 - ...
Step 2 - …
…
Step N - …

If the text does not contain a sequence of instructions, \
then simply write \&quot;No steps provided.\&quot;

Text: &amp;lt;{text}&amp;gt;
&quot;&quot;&quot;
response = get_completion(prompt)
print(&quot;Completion for Text 1:&quot;)
print(response)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Tactic 4: &quot;Few-shot&quot; prompting&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;提供样例&lt;/li&gt;
&lt;li&gt;例如下面的例子是让模型模拟类似大师和弟子的对话：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;prompt = f&quot;&quot;&quot;
Your task is to answer in a consistent style.

&amp;lt;child&amp;gt;: Teach me about patience.

&amp;lt;grandparent&amp;gt;: The river that carves the deepest \
valley flows from a modest spring; the \
grandest symphony originates from a single note; \
the most intricate tapestry begins with a solitary thread.

&amp;lt;child&amp;gt;: Teach me about resilience.
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;02 Give the model time to think&lt;/h3&gt;
&lt;h4&gt;Tactic 1: Specify the steps required to complete a task&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;例如指定模型按照指定步骤&quot;思考&quot;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;prompt_1 = f&quot;&quot;&quot;
Perform the following actions:
1 - Summarize the following text delimited by triple \
backticks with 1 sentence.
2 - Translate the summary into French.
3 - List each name in the French summary.
4 - Output a json object that contains the following \
keys: french_summary, num_names.

Separate your answers with line breaks.

Text: &amp;lt;{text}&amp;gt;
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;也可以相应地设定输出格式:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&quot;&quot;&quot;
Use the following format:
Text: &amp;lt;text to summarize&amp;gt;
Summary: &amp;lt;summary&amp;gt;
Translation: &amp;lt;summary translation&amp;gt;
Names: &amp;lt;list of names in Italian summary&amp;gt;
Output JSON: &amp;lt;json with summary and num_names&amp;gt;

Text: &amp;lt;{text}&amp;gt;
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Tactic 2: Instruct the model to work out its own solution before rushing to a conclusion&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;由于模型很可能会粗略的浏览全文，因此可能发现不了细节错误：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;prompt = f&quot;&quot;&quot;
Determine if the student&apos;s solution is correct or not.

Question:
I&apos;m building a solar power installation and I need \
 help working out the financials.
- Land costs $100 / square foot
- I can buy solar panels for $250 / square foot
- I negotiated a contract for maintenance that will cost \
me a flat $100k per year, and an additional $10 / square \
foot
What is the total cost for the first year of operations
as a function of the number of square feet.

Student&apos;s Solution:
Let x be the size of the installation in square feet.
Costs:
1. Land cost: 100x
2. Solar panel cost: 250x
3. Maintenance cost: 100,000 + 100x
Total cost: 100x + 250x + 100,000 + 100x = 450x + 100,000
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;解决方案: 通过prompt要求模型先自己计算结果，然后与上述的结果比对&lt;/li&gt;
&lt;li&gt;这也说明了很多时候直接询问模型是非判断很可能&lt;strong&gt;得不到&lt;/strong&gt;正确的结果&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Model LImitations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Hallucination&lt;/li&gt;
&lt;li&gt;Makes statesments that sound plausible but are not true&lt;/li&gt;
&lt;li&gt;也就是老生常谈的&quot;编答案&quot;&lt;/li&gt;
&lt;li&gt;一个解决方案: first find relevant information, then answer the question based on the relevant information&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>学习笔记</category><author>Ender</author></item><item><title>漫评 | 《天国大魔境》：神作预定</title><link>https://www.ender-wiggin.com/posts/review-heavenly-delusion/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-heavenly-delusion/</guid><description> </description><pubDate>Wed, 19 Apr 2023 10:00: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;至于一个饱受争议的ntr情节（这应该不算剧透吧，感觉点开豆瓣点赞最多的就是这一条），我个人认为作者在叙事呈现上一定是有不足的。倒不是说ntr本身，而是情节的设置在逻辑完整性上有些突兀。至于ntr，从长远了说也许有伏笔，退一步讲即便这就是事实那展现人性多面性同时作为人物性格转折点倒也无可厚非。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>殖民火星 | 官方小说中英双语版本分享</title><link>https://www.ender-wiggin.com/posts/terraforming-mars-novel/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/terraforming-mars-novel/</guid><description> </description><pubDate>Sun, 05 Mar 2023 06:30:29 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是Ender。2天前ChatGPT的新API发布，便宜且高效的特性为翻译英文小说提供了巨大帮助，在看到程序员大佬yihong发布的&lt;a href=&quot;https://github.com/yihong0618/bilingual_book_maker&quot;&gt;翻译脚本&lt;/a&gt;后，我突然就想到了已经出版一年多的火星官方小说&lt;strong&gt;In the Shadow of Deimos&lt;/strong&gt; （个人暂译《火卫二的阴影》）。这本改编小说凭借与桌游《殖民火星》极高的主题契合度以及过硬的剧情设定，在外国论坛中被奉为冷门佳作，但由于缺少汉化版本导致在国内毫无关注。因此，不论你是火星这款桌游的爱好者，还是科幻作品的粉丝，我都推荐来读一读这本小说，领略下改造火星的壮阔史诗。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;写给桌游玩家的小说介绍&lt;/h2&gt;
&lt;p&gt;火星作为一款经典德式桌游，想必大部分玩家都有所耳闻。而这本小说非常贴合原版桌游，基础游戏中的著名公司悉数登场，例如Credicor、UNMI、ThorGate、Ecoline。当我自己读到关于这些公司的故事时，才发现这是我第一次深入理解游戏中一个公司的背景故事与商业动机，例如为什么Credicor如此富有而Ecoline开局资金惨不忍睹、诺提斯城为什么在火星地图中显得这么重要、蓄意破坏这张卡牌的真实体现...
阅读这本小说会将你的记忆带回那个阳光四溢的午后，你一边听着游戏的基础规则，一边阅读关于自己公司卡的背景描述和技能效果，一边满怀期待的开启改造火星之旅...
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1677990011193.png&quot; alt=&quot;image-1677990011193&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;写给科幻爱好者的小说介绍&lt;/h2&gt;
&lt;p&gt;其实许多人玩《殖民火星》这款游戏就是因为对于科幻题材感兴趣。这款桌游的设计者Jacob Fryxelius本身是一名化学博士，他的专业知识为游戏中的许多大胆的概念构想提供了一定的理论基础。
而这款桌游的灵感来源，就来自荣获了雨果奖星云奖等多个科幻奖项的《火星三部曲》。这三部曲有着严谨的科学细节与现实的社会构想，并没有将改造火星写成心想事成的乌托邦童话，而是一篇尔虞我诈政治权斗的现实史诗。
随着《殖民火星》桌游扩张的影响力，它俨然从单纯的一款桌游变成了一个品牌、一个世界观。在基于《火星三部曲》的现实主义论调之上，一个新的故事篇章《火卫二的阴影》诞生了。
《火卫二的阴影》少了些《火星三部曲》中事无巨细的科学设定，多了些悬念重重的情节展开。笔者作为科幻的小小粉丝，秉持着公正态度的话，我认为这部作品显然算不上科幻经典。但是我更愿意将这本书与游戏结合起来，也正像很多改编小说所做的那样，它会让读者被世界观所吸引，从而对原本的游戏产生好奇，以更多的维度领略科幻世界的丰富。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1677990683995.png&quot; alt=&quot;image-1677990683995&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;翻译介绍&lt;/h2&gt;
&lt;p&gt;我使用ChatGPT API 基于&lt;a href=&quot;https://github.com/yihong0618/bilingual_book_maker&quot;&gt;yihong的脚本&lt;/a&gt;翻译了目前已经推出的两本作品《火卫二的阴影》和《风暴边缘》（暂译），制作了双语版本的epub文件。通过下图可以看到，目前ChatGPT相较于此前的模型，在翻译的准确度和生动性上都有了一定的提升。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1677989375212.png&quot; alt=&quot;image-1677989375212&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是不能否认的是，一方面翻译脚本目前处在项目初期，另一方面AI对于部分名词、主谓的居中位置仍有欠缺，阅读起来难免有些小影响。笔者个人已经通读了第一本《火卫二的阴影》并修正了机翻的明显缺漏，剩下如果大家在阅读中发现了什么句意有误的地方也可在下方评论区提出。
等未来笔者时间足够后，会考虑优化第二本《风暴边缘》的翻译并分享。&lt;/p&gt;
&lt;h2&gt;资源下载&lt;/h2&gt;
&lt;p&gt;本文中出现的资料仅供爱好者学习交流使用，请在下载24小时内删除。任何组织或个人不得以商业目的使用本资源，出现任何法律问题均与翻译者无关。
请支持 Fryx Game以及正版《重塑火星》游戏。&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/%E7%81%AB%E5%8D%AB%E4%BA%8C%E7%9A%84%E9%98%B4%E5%BD%B1.epub&quot; target=&quot;_blank&quot;&amp;gt;《火卫二的阴影.epub》&amp;lt;/a&amp;gt;&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>剧评 | 《万神殿》：流于平庸的科幻童话</title><link>https://www.ender-wiggin.com/posts/review-pantheon/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-pantheon/</guid><description>剧透警告</description><pubDate>Fri, 21 Oct 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;THIS REVIEW CONTAINS SPOILERS 剧透预警&lt;/em&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;ol&gt;
&lt;li&gt;
&lt;p&gt;人物过多，亲情占比太高。一般而言涉及自我意识的作品都是比较严肃的，并且不会对人物有类似第一人称视角的主观描写，因为试图用三言两语呈现一个人物本我难于登天。而本片中女主作为一个年幼的孩童，关注点基本就在于父女之情。如果这是一部类似《人工智能》那样主要探讨情感表达的作品那我认为合情合理，但这部作品显然对情感的设定和探讨少之又少，基本上将“UI具备真人的所有情感”视为公理。由于上述原因，《万神殿》除去科幻外壳很像一部家庭伦理剧。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重现实而轻UI。作为一部以UI为核心概念的作品，其实对于UI并没有很多的讨论，UI自己说的最多的一局话就是“我也不知道自己是什么”。当我看到剧中女UI对女主她爸说“你根本不知道自己能做到什么”时，我还很期待编剧可以脑洞大开，通过动画形式展现一下想象力的无限可能。结果就只是将人物变成神通广大的超能力游戏角色，最后几集甚至不少篇幅的超能力战斗场景，属实有点哭笑不得。
不过还是要提一下，前几集对Chanda周而复始工作的呈现方式还是很出彩的。或者说这部作品的细节还是颇有巧思，但是主体框架略显单薄。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UI的能力。其实我还是挺欣赏开篇对于UI的设定，及具有人类思维（以及情感）执行任务（从事工作）的智能体。这种设定下的UI权力很小，也就意味着有很大的发展空间，可以根据剧情发展合适地拓宽边界。但是作品中的UI很快就意识觉醒，从入侵公司内网到控制一切物联网设备再到掌握核弹发射权力，UI的能力有点太强了，就像《龙珠》里面的战力膨胀一样，一发不可收拾。基于这一点，我都很难想象这部剧集如何拍出第二季，因为关于UI的设定完全没有留下后续发展的余力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;人物的深度。作品中的人物似乎没几个深谋远虑之人，大多都是直来直去毫无城府者。对于技术开发人员倒可以理解，但是公司领导者和各国政要，感觉也十分不明所以。比如Caspian的母亲，前几集看起来妥妥一个幕后二把手，结果只是忠心于Stephen的狂热信仰者。还有现在的CEO，也许第二季会呈现出他的小心机，但现在看起来就是个忠心耿耿的小跟班，花费18年精心培育就是为了给克隆体让位。对于涉及超级公司/大国政府的故事背景，观众喜欢看一盘大棋，喜欢看政治权谋勾心斗角，缺乏布局和阴谋论会让故事缺少期待，角色台词过于空泛。另外，我的观点一向是，大部分科幻作者对于政治社科领域都是不太熟悉的，所以一般处理方式就是简写略写避之不提。但第一季的结局，基本将剧情发展为了”世界各国的UI军备竞赛“，这主题大得有点夸张，很难想象如何收场。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以我将这部作品称为”科幻童话“，因为主要人物是孩童，人物性格也较为稚嫩，游戏人物打斗等剧情也有些浅显。&lt;/p&gt;
&lt;p&gt;最后，我试图表达一下我对于科幻中哲学元素的想法，这个问题也萦绕在我心中许久。我小时候也和很多人一样津津乐道于严密的硬科幻设定，但到了高中后被pkd和Ted Chiang等人富含人文风情与社会哲思的行文风格所吸引。那时候可能更加吸引我的不是剧情发展，而是其中极具创意的设定，以及背后的哲学思考。我贪婪的阅读同种风格的作品，直到很后来才想到”那为什么不直接阅读哲学作品呢“？&lt;/p&gt;
&lt;p&gt;科幻作品的核心就是既有巧思也有叙事，叙述是思想的载体，思想是叙事的动力。但是对于很多主题，即便花费很多篇幅描写，也可能很难表达准确其中的哲学思想。一个“科幻故事”某种程度上也就是一场思想实验，是哲学中的一个例子而非哲学本身，这也是为什么尽管当下这么多关于自由意志的作品，却鲜能给出读者观众所希望看到的一些答案。&lt;/p&gt;
&lt;p&gt;换个角度说，在科幻中想要阐释哲学是非常困难的，但是科幻是很好的想象力载体。时不时冒出一些天马行空的构想，这本身就是值得惊喜的事情。&lt;/p&gt;
&lt;p&gt;而这部作品，试图传达哲学思想却不通透；试图展现想象力但却不够大胆。好在还是存在不少有趣的小细节作为调味品存在，因此看完还是挺开心的。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>方舟动物园 | 地图扩展1汉化素材下载</title><link>https://www.ender-wiggin.com/posts/ark-nova-map-9-10/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/ark-nova-map-9-10/</guid><description> </description><pubDate>Sat, 06 Aug 2022 23:19:35 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;完整的卡表网站以及地图咨询可以参考&lt;a href=&quot;https://arknova.ender-wiggin.com&quot;&gt;方舟动物园卡表&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;地图9 地理动物园&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan9-1660621013424.jpg&quot; alt=&quot;plan9-1660621013424&quot; /&gt;地图介绍&lt;/p&gt;
&lt;h3&gt;技能描述&lt;/h3&gt;
&lt;p&gt;游戏开始时，在版图上每个大洲的空缺位置上放置一个玩家标记。每个大洲限一次，当你放置对应大洲动物到版图对应大洲区域时，可以移除大洲标记并选择右侧5个奖励中的一个，并将玩家标记移回供应区。&lt;/p&gt;
&lt;h3&gt;FAQ&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不论是普通饲养区还是特殊饲养区都可以触发技能。&lt;/li&gt;
&lt;li&gt;群居动物无法触发技能，因为群居动物并不会使用特定的饲养区。&lt;/li&gt;
&lt;li&gt;左侧动保奖励效果是移除一个大洲玩家标记，并选择下方奖励中的一个。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;地图10 急救站&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan10-1660621012897.jpg&quot; alt=&quot;plan10-1660621012897&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;技能描述&lt;/h3&gt;
&lt;p&gt;挖掘1(你可以从手牌中弃1张牌并摸1张牌，或者从展示区弃1张牌并补充1张牌)。如果你弃置的是动物牌(除了萌宠)，可以将卡牌放置到上方绿色箭头区域，该卡牌视为“已打出”，但不会获得其它卡牌效果(例如魅力和卡牌上的特殊技能)。&lt;/p&gt;
&lt;h3&gt;FAQ&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;已打出的内容包括：标志(可以触发赞助卡牌的打出标志效果)、饲养区需求(例如可以完成小型动物的保护项目)、岩石标水域需求。&lt;/li&gt;
&lt;li&gt;你最多只能放置3张动物卡牌，如果在触发技能时已经放置了3张动物，则你只能触发挖掘1的效果。&lt;/li&gt;
&lt;li&gt;你不能放生上方的动物。
&apos;]
description: &apos; &apos;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;素材下载&lt;/h2&gt;
&lt;p&gt;已经按照原尺寸排版，直接下载就行。注意需要A3下载，因为地图比A4稍大。另外也感谢帮忙提供翻译意见和文本勘误的朋友们。&lt;/p&gt;
&lt;h3&gt;直接下载&lt;/h3&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/plan9%2610_a3.pdf&quot; target=&quot;_blank&quot;&amp;gt;地图9&amp;amp;10下载&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;h3&gt;百度网盘&lt;/h3&gt;
&lt;p&gt;链接: &lt;a href=&quot;https://pan.baidu.com/s/1oAzahNuHSs4hew2Uppl7OQ&quot;&gt;https://pan.baidu.com/s/1oAzahNuHSs4hew2Uppl7OQ&lt;/a&gt;
提取码: 073t&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>殖民火星 | 时过境迁TOP50</title><link>https://www.ender-wiggin.com/posts/terraforming-mars-top50/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/terraforming-mars-top50/</guid><description> </description><pubDate>Wed, 15 Jun 2022 09:12:19 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;本文将会收录我个人认为火星最强的50张卡牌。
据我上次发布火星排名贴已有两年之久，这期间火星的环境变化还是非常大的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;大盒版promo的加入。在我写第一版火星排名的时候，大盒版promo还没有推出，因此并没有加入排名。事实上大盒版promo有众多强力好牌，因此会对原有卡牌排名有重大冲击。&lt;/li&gt;
&lt;li&gt;网页版粉丝扩展的加入。由于国内外网页版火星生态优质，众多粉丝扩展/玩家自定义规则层出不穷，例如“7热升温”的变体以及加强弱势公司的“界限突破”计划。但是考虑到本文读者可能存在的多样性（可能完全忠实于官方设置，也可能allin粉丝扩展群魔乱舞），本文中所有讨论的卡牌均为&amp;lt;span style=&quot;color:red&quot;&amp;gt;官方卡牌&amp;lt;/span&amp;gt;，后续关于粉丝扩展可能会另起文章。&lt;/li&gt;
&lt;li&gt;个人游戏理解的变化。一般来说游戏结束条件（游戏轮次）不固定的游戏，往往会为不同打法倾向的玩家带来截然不同的游戏环境。对于火星而言，改造之风盛行的四人局，一般在6-7时代便可以结束游戏；而喜欢打牌的玩家，时代数一般8时代起步（当然，如果说你们环境的四人局可能会打到10时代，那我认为是玩家操作出现了问题）。我在写完第一版排名后，也在国内线上/线下进行了数百局，同时有段时间也是在外服和老外们斗智斗勇，各种游戏环境都有所见识，对卡牌有更多角度的认识。&lt;/li&gt;
&lt;li&gt;火星网页版的5000局四人局数据可以为许多卡牌的强度判断提供数据支持。TOP50的基本打出后平均排位都在2.5之前，平均排位的胜率上届基本就是反重力（平均2.1）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其实在这个时间点，做一个非常硬性的排名是非常困难的，很多时候往往都是使用梯度代替。我其实也只想做梯度的，但是为了衔接之前的文章，还是暂且保留序数排名，只是会先预设一个梯度。同一梯度内的卡牌强度差异不大，排名仅图一乐。
并且由于这几年间强卡实在太多了，我后面也是忍痛删掉很多张我认为强度不错的卡，并且把部分类似的卡牌合并，才勉勉强强删到了50这个数字。&lt;/p&gt;
&lt;h2&gt;排名规则&lt;/h2&gt;
&lt;p&gt;截止到本文撰写之时，火星的官方项目卡数量为371张。除开众所周知的官方全扩，还存在42张promo，而这其中有不少改变环境的强势卡牌。&lt;/p&gt;
&lt;p&gt;关于火星卡牌，我暂且定义梯度评级如下：&lt;/p&gt;
&lt;p&gt;E: 亏模卡。数值不符合正常模型，绝大多数时候没有打出的价值。
D: 均模卡。符合正常模型，一般情况下没有打出必要。
C: 轻度超模。超出正常模型，但是否使用仍有待商榷。
B: 中度超模。强度较高，在轮抽中一般常见于第二抓。
A: 重度超模。一般常见于轮抽第一抓，如果是第二抓那就属于捡到宝了。
S: 无敌。基本不可能从对手手中传过来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特别提示: 卡牌特性不同，有的是梦想，有的是纯分，有的是体系核心。具体情况具体分析，排名仅供娱乐，实际游戏灵活变通才是正道。&lt;/strong&gt;
火星打牌的核心原则是：&lt;strong&gt;如果整局游戏都在做不亏（符合模型）的行动，那么很可能会垫底。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也是基于此，所有在TOP50中的卡牌，至少也是C级起步。&lt;/p&gt;
&lt;h2&gt;C级卡牌&lt;/h2&gt;
&lt;p&gt;其实C级卡牌已经是强度非常不错的卡牌了，只是因为火星超模卡太多，这些牌被迫挤压到了C这个级别。&lt;/p&gt;
&lt;h3&gt;No.50 - 外部注入氢气/营养物输入&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655249907571.webp&quot; alt=&quot;image-1655249907571&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这两张牌效果大致分为两部分，即植物和得分。外部注入氢气可以配合海洋的2叶子一次性获得5叶子，有望快速凑齐8叶子种树；营养物输入的4叶子同理。一方面考虑到绿党的种树回4元政策，种树的性价比非常高；另一方面由于火星中的陨石牌神出鬼没，很多时候3叶子如果不能迅速种树，可能一会就被烧掉了，这就是火星种树黄金等式“3=0”。
营养物输入的第二个能力4微生物，如果有合适的微生物载体配合则是2分或者12块钱，上限很高。
因此外部注入氢气适合前期打出快速种树，而营养物输入更适合藏在手里后期偷偷变魔术。&lt;/p&gt;
&lt;h3&gt;No.49 - 北极海藻&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655397028686.webp&quot; /&gt;
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655251137984.webp&quot; alt=&quot;image-1655251137984&quot; /&gt;
北极海藻的上限是15元换19植物，基本等于是种了2颗树。并且拍海获得植物这个技能由于是非线性收益，有机会突然一波爆发，比如在绿党回合连拍几海直接种树，也可以降低被烧叶子的风险。
但是缺点就是，由于一时代绿党种树的高收益，一时代玩家拍海的概率高了不少，甚至前序也可以直接双海。当海洋已经有一定数量后，这张牌打出后的收益就会直线下降。&lt;/p&gt;
&lt;h3&gt;No.48 - 表土塑料&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655251517850.webp&quot; alt=&quot;image-1655251517850&quot; /&gt;
按照“一分=5元”转化后，这牌打出后只需要消耗8个铁就不亏，消耗13铁就可以白嫖1分，在有铁殖民地时效果更佳。
也许会有小伙伴疑惑：这么强的卡，只能排在第48名？
我也很无奈，只能说“呵呵，好牌太多了”。&lt;/p&gt;
&lt;h3&gt;No.47 - 地球产能&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655251900780.webp&quot; alt=&quot;image-1655251900780&quot; /&gt;
地球产能一共有这么4张，倒不是说TOP50里面的地球牌就那么几张，只是与地球相关的产能牌就这些。
我为什么要强调“产能”呢？
因为很多时候产能就是陷阱。对于没有完全熟悉火星的玩家来说，巨额产能带来的冲击力是远大于其它属性的。比如玩家A说“我打出这张地球钱产，加10点钱产”。其它一听就会觉得“怎么这么胡啊”，手中的攻击牌已经开始蠢蠢欲动，殊不知某些挂载了减费回钱等魔术组件的人笑笑不说话。
&lt;strong&gt;另一方面，在游戏初期打出大量地球牌的玩家，往往是为了凑齐地球组件舍弃了其它关键卡牌的，比如过牌/高前置卡牌/后期魔术牌&lt;/strong&gt;。这种玩家很可能在前期将地球牌倾泻一通后就如同强弩之末了，甚至可能没有几张手牌，高额的钱产只能用来标动。这种景象一般被称之为“炸胡”。&lt;/p&gt;
&lt;h3&gt;No.46 - 分解者&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655252958961.webp&quot; alt=&quot;image-1655252958961&quot; /&gt;
分解者理论上来说是生物流派的核心组件之一，但是如果考虑期望的话很多时候也就8元2分，属于高方差卡牌。此外，自带吸引蚂蚁效果。&lt;/p&gt;
&lt;h2&gt;B级卡牌&lt;/h2&gt;
&lt;p&gt;B级卡牌多数时候都是稳赚不亏，之所以排在B级仅仅是因为赚得还不够多，或者是前置相较于收益有些高。&lt;/p&gt;
&lt;h3&gt;No.45 - 量子提取&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655254712081.webp&quot; alt=&quot;image-1655254712081&quot; /&gt;
4个电产在前期用于凑3电贸易的时候很不错，但是考虑到4科标的前置也不算很低，一般也得要3时代左右才能打出来，这时候1时代凑出3电产的玩家已经贸易2轮了，孰优孰劣还说不太准。
而这张牌一旦到了中后期，就是纯纯的打牌陷阱。16块钱0分，想要让这张牌回本还是挺有难度的。&lt;/p&gt;
&lt;h3&gt;No.44 - 木星灯笼/漂浮仓&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655291707331.webp&quot; alt=&quot;image-1655291707331&quot; /&gt;
云得分核心。在我的排位中，得分核心的排名往往会靠前，因为这些牌可以直接决定整局游戏的主要路线。
顺便解释一下什么是得分核心：
木星体系的4张木星计分；
计分载体（微生物、动物、云）；
其它高额得分牌（一般来说单卡分数不小于5分）；
这些牌单轮模型可能并没有那么强，比如一张牌25元换5分，其实来说也就是标准的换分模型。
但事实上正是因为有它们，玩家才可以达到标准的换分模型。否则玩家后期很可能只能标动换分，转化效率感人。
说回云计分，这两张牌可以让玩家的云有一个明确的去处，不然玩家的云可能只是在花式提高产能但是根本转化不了分数。
木星灯的排位是2.2，漂浮仓2.4，都算是不错的排位了。前者可以同时契合木星体系，分数转化效率很高；而后者也有机会配合另外一张“每有1木星标，增加1云”的事件牌摇身一变成为木星小哥，也是个梦幻combo。&lt;/p&gt;
&lt;h3&gt;No.43 - 能源市场&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655255084516.webp&quot; alt=&quot;image-1655255084516&quot; /&gt;
我个人很喜欢的小费魔术牌，就算是最后一回合上手也是可以白赚钱的。前期可以各种花式凑3电，如果配合科技电体系也可以低成本换取8块钱，玩法多多。但是考虑到电产如果没有科技电配合并不算低成本，并且城市也需要消耗电产，这张牌想要打出上限还是有些苛刻。&lt;/p&gt;
&lt;h3&gt;No.42 - 收费站/殖民钱产&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655291543493.webp&quot; alt=&quot;image-1655291543493&quot; /&gt;
两大钱产魔术牌。收费站的打出时间往往是在游戏中期，定量来说一般是4-5时代，收益8-12钱产。在偏改造的环境中打出来后再过2个时代游戏就结束了，一次顶多是多赚一次生产的收益，即白嫖8-12元。但是在拖拖环境，如果能到9时代，那么收益一般是白嫖20-30元，收益显著。
值得一提的是，如今这张牌依然是在老外的ban位上（很长一段时间在国内也是如此）。是因为这牌有多么超模吗？确实超模，但是和优化合金、全卡减费这些牌相比还是稍逊风骚。
那么为什么会上ban位呢？还是那句话，产能天然带有嘲讽。蓝卡叠加带来的收益很多时候不好量化，但是高额钱产玩家一眼就可以看到。从这个角度来说高钱产自带负面效果。
殖民钱产和收费站类似，也是拖拖局的神牌，4科标的限制虽然会延后打出时间，但这牌本身就没有快速打出的必要性。此外，由于本卡自带1分，倒数第二时代打出白嫖一分也不亏。
最后提一下木星钱产，这牌以我的经验在打牌局也是强力好卡，但是在改造局强度过低，可能会发现游戏结束了都找不到合适的打出时机。&lt;/p&gt;
&lt;h3&gt;No.41 - 火卫二/巨冰/大型行星&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655291805938.webp&quot; alt=&quot;image-1655291805938&quot; /&gt;
这三张牌属于改造大牌，对于结束游戏有着重要作用。
如果这三张牌都打出了，加上8氧自带的1温，差不多就可以推完一半的温度。
因此，这3张牌对于改造人，就是保证可以结束游戏的定心剂；对于打牌党，卡掉他们则可以成为阻碍改造进程的绊脚石，怎样都有着举足轻重的作用。
具体分析每张卡牌：
火卫二的3格温度是推温利器，同时高概率可以抢到0度海洋奖励，附带的版块也是玩法多多；
巨冰的4格参数势如破竹，海洋温度的组合拳可以打拖拖一个措手不及，瞬间破局。但是缺点是海洋可能会被提前拍完，从而打不满收益。
大型行星本身一般，但在联合党的加持下成本并不高。一些拖拖都会直接选择打出本卡，配合钛减费和一些回钱效果甚至可以变成理财卡。
此外陨石都附带有烧叶子的效果，一般来对于改造玩家而言如果氧气不好推可以不烧叶子，其余情况能烧就烧，向其他玩家证明火星的互动性。并且如果放出去很可能就是自己被烧叶子，对于改造流而言一来一回可能差不多就等于2颗树了。&lt;/p&gt;
&lt;h3&gt;No.40 - 土星冲浪/公关办公室&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655292019707.webp&quot; alt=&quot;image-1655292019707&quot; /&gt;
两张地球回钱，综合来说前者更强。
土星冲浪：从火星的基础模型中我们可以看出一个木星标志是值5块钱的，因为木星标可以配合木星计分。但是这张牌在打牌流手里，高概率可以做到白嫖1分加上白送木星地球标，再配合联合党的执政奖励属实赚麻了。
公关办公室：相较于土星冲浪，这牌有机会在最后一时代打出直接回钱，对于打牌流来说基本上可以白嫖1TR。即便是前期，改造度所带来的钱产也可以提供一定的经济收益。&lt;/p&gt;
&lt;h3&gt;No.39 - 金星动物&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655292116930.webp&quot; alt=&quot;image-1655292116930&quot; /&gt;
科标得分核心，无数科标人的梦想。
这牌上限有多高也不需要我过多解释，但是下限也挺低的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;金星如果到不了18，直接打不出来。顺便一提，很多玩家为了拖就选择升金星，但在没有看到金星动物时选择升金星可能是在为他人做嫁衣，反而有时候推氧气可以让改造党无氧可推。&lt;/li&gt;
&lt;li&gt;科标牌很多时候也未必适合藏手里，比如减费/过牌，一般都是早出早收益。打出金星动物后可能也就出2-3张科标牌，只能说达到了基本收益。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;No.38 - 优质的过牌们&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655292611695.webp&quot; alt=&quot;image-1655292611695&quot; /&gt;
评估过牌强度先看抽得多不多，抽牌量级一样时再会考虑其它属性，比如分数和性价比。绝大多数的火星玩家看到过牌都会走不动路，管它三七二十一先抽牌再说，正如老外制作的表情包：
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655292820935.webp&quot; alt=&quot;image-1655292820935&quot; /&gt;
过牌固然重要，这也是火星的魅力。但过牌的目的是找到好牌，因此玩家需要明确：到底什么是好牌？全卡减费这些毋庸置疑，但游戏中存在很多得分牌，他们同样不容忽视。
过牌和产能一样容易然玩家对火星这款游戏产生误解，比如“为什么我过了那么多牌还是没什么能用的？辣鸡运气游戏”。或许在上家给你传来优质产能和过牌时，他们自己留下了真正“有用”的牌。&lt;/p&gt;
&lt;h3&gt;No.37 - 大型护航队&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655297921485.webp&quot; alt=&quot;image-1655297921485&quot; /&gt;
刷分巨兽，上限7分2牌，但一般打出时海洋早没了，实则6分2牌。值得一提的是，高额得分卡基本也都是钛牌，同时联合党一般一局游戏至少执政2次，因此钛产和大钛牌对于玩家刷分都至关重要。这些大钛牌用钱出可能一轮的钱产收入都无法支付，但是在联合党回合随便用几个钛就打出了，眼都不用眨一下。这也是我前文说钱产不行的原因之一，因为钛产/钛资源实在太强了。&lt;/p&gt;
&lt;h3&gt;No.36 - 野生生物穹顶&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655296785522.webp&quot; alt=&quot;image-1655296785522&quot; /&gt;
本身18元一棵树已然不错，毕竟在这个人人植树的版本很多时候标动树都挺赚的。当然，这牌的额外优势在于2个生物标志，尤其是动物标。在一时代打出这牌配合绿党回钱，剑指8标或者4生物标里程碑，属于朴实刚健的好卡。&lt;/p&gt;
&lt;h3&gt;No.35 - 高科技/AI&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655297087025.webp&quot; alt=&quot;image-1655297087025&quot; /&gt;
还是前面那句话，过牌卡的强度取决于过牌数量。一张早出的AI过牌量在10张左右；高科技在有足量电量的情况下可以千金博君一笑。
但是这两张牌在大量过牌的背后，有着高成本和电量的限制，因此不算最强过牌。
顺便一说，一局游戏中玩家打出的卡牌大部分应该来自于轮抽，因为轮抽的质量相对是比较高的：第一抓是4选1，第二抓可以理解为在抽走了一张好牌情况下的3选1。如果在轮抽中都没有获得“好牌”，那么凭什么直接裸抽牌就可以抽到自己想要的牌呢？火星牌库371张牌，好牌也就100张，大致1/3的概率意味着抽3张才能抽到一张模型上不错的卡牌，而至于是否契合自身体系还是后话。&lt;/p&gt;
&lt;h2&gt;A级卡牌&lt;/h2&gt;
&lt;p&gt;从A级卡牌开始，就进入了大超模时代。主要类别有以下几种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;大量回钱（比如白赚个20块这种）&lt;/li&gt;
&lt;li&gt;小量回钱，白嫖小分&lt;/li&gt;
&lt;li&gt;高成本，高分数&lt;/li&gt;
&lt;li&gt;高质量过牌&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;No.34- 肉类工厂&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655298170090.webp&quot; alt=&quot;image-1655298170090&quot; /&gt;
动物回钱，隐藏收益的好手。有些人看起来就那么个位数的钱产，但是钱怎么都花不光，也就是有这种牌的加持。
但是从我参与的这么多局来看，肉产出场率低得可怜，估计主要原因还是被其它玩家卡了。因为这张牌并不是人人适用，往往一局游戏中只有一个人使用这张牌收益爆炸，其他人顶多是可以回本。但是一旦被有很多动物的玩家拿到了这张牌，配合宠物/生态区/金星动物等动物魔术牌，经济分数双管齐下，其它玩家往往难以望其项背。&lt;/p&gt;
&lt;h3&gt;No.33 - 蚂蚁/金星昆虫/硫磺菌&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396794799.webp&quot; alt=&quot;image-1655396794799&quot; /&gt;
前两张为微生物得分载体。由于现在微生物配合牌大幅度增加，但是高质量微生物载体却比较有限，因此这两张2个1分的计分载体弥足珍贵。
最后一张的是微生物经济载体，1微生物换3块，也是魔术师的常有组件。&lt;/p&gt;
&lt;h3&gt;No.32 - 大规模转换器&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655298345416.webp&quot; alt=&quot;image-1655298345416&quot; /&gt;
最强科技电。缺点和4科标的量子提取类似，但是优点是便宜了一些。6电基本上永久解决了电产问题，也可以配合能源市场/高科技等高电力需求牌。&lt;/p&gt;
&lt;h3&gt;No.31 - 企鹅/平流层鸟类&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655298510034.webp&quot; alt=&quot;image-1655298510034&quot; /&gt;
动物得分载体。其实动物载体相较于微生物多了不少，但是这两张出场率较高，可以打出的时机也比较靠前，所以选了这两张作为代表。其实比如13氧前置的鸟也是不错的卡牌，后期炸一位玩家2植产很可能就是让那个玩家少了一棵树，也挺有说法。动物载体和微生物载体相似，不一定要有很多张，但是从无到有很关键，只需要1张就可以盘活所有相关配合牌。&lt;/p&gt;
&lt;h3&gt;No.29 - 红树林&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655298699489.webp&quot; alt=&quot;image-1655298699489&quot; /&gt;
自带分数的树，如果氧气还没有推满那么收益非常可观，即便推满了自带的分数也和1TR抵消了。此外水中树的特性打破常规，既可以为临海城预留了一个树位，同时还可以偷鸡临海奖励或者绿化数量奖励。&lt;/p&gt;
&lt;h3&gt;No.28 - 峰会&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655298793216.webp&quot; alt=&quot;image-1655298793216&quot; /&gt;
我眼中的第三强过牌，主要优点是没有前置，3标志属性优秀。上限也还不错，比如10科标5牌。至于下限，再打出3科标就可以回本。
但另一方面，这张牌由于其亲民的特点，一些改造党都喜欢打出来试试水，这就掉入打牌陷阱。这也是打牌流的小心思之一，即通过传出优质打牌流卡牌诱惑改造流，很多改造一看到AI峰会这种牌就走不动路了。&lt;/p&gt;
&lt;h3&gt;No.27 - 曲率引擎&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655298889093.webp&quot; alt=&quot;image-1655298889093&quot; /&gt;
相比于AI和科标电等打牌陷阱，至少这牌有2分。打出这牌后，再打两张钛牌即可回本。考虑到得分大牌大多是钛牌，这牌还是很有梦想的。&lt;/p&gt;
&lt;h3&gt;No.26 - 太空港殖民地&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655298918858.webp&quot; alt=&quot;image-1655298918858&quot; /&gt;
殖民大哥。殖民大哥和前文所说的殖民钱产类似，主要取决于游戏环境。我在外服也曾经历过重度殖民的环境，即只要有钱/牌/铁/钛殖民地，不论有几个到三时代一定全部占满。这种环境下殖民大哥随随便便就是5分起步，极端情况7分也有，实属离谱。
但是殖民的特点就是，所有人都上和所有人都不上都是优势策略，所以也完全存在没人上殖民地的环境，甚至luna都不一定上。原因也很简单，都上luna也就是17元4钱产；但是如果其它人都不上，那么傻傻标动的那个人就是17元2钱产，丧了别人等于自己赚了。
因此也还是需要再声明一下，排名中的卡牌强度本身就是浮动的，不同环境会直接改变卡牌强度。文章只是抛砖引玉，灵活打牌方可进步。&lt;/p&gt;
&lt;h3&gt;No.25 - 空气制动&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655341963173.webp&quot; alt=&quot;image-1655341963173&quot; /&gt;
钛事件魔术牌，我个人非常喜欢的卡牌之一。作为打牌流玩家，由于自身在争夺里程碑时一般是比不过改造的，版图分数也不高，因此得分一般就三条主思路：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;小分牌堆分&lt;/li&gt;
&lt;li&gt;木星体系&lt;/li&gt;
&lt;li&gt;生物体系&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而空气制动可以配合打牌体系的钛减费、钛增值等光环效果，用较低的成本打出陨石，通过比改造流更高效率的改造参数获得分数，等于是拓宽了打牌流的得分手段。并且这牌作为可以直接威胁游戏进度的卡牌，完全可以在其他玩家pass之后，一通魔术瞬间推完火星参数，直接扼杀其它拖拖变魔术的机会。因此这牌进可抢改造玩家参数，退可背刺拖拖，实在是搅局利器。
另外，钛事件也远不止陨石，纯打牌流也可以使用，多的热甚至可以不升温，偷偷抢到改造人自以为稳妥的热能奖励。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655342572928.webp&quot; alt=&quot;image-1655342572928&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;No.24 - 微生物大哥&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655341290565.webp&quot; alt=&quot;image-1655341290565&quot; /&gt;
微生物刷分的顶级卡牌之一，并且非常适合藏分体系。比如上家可以看你没什么科标或者没微生物载体就放心大胆的传出去了，但下家可能早已经藏好配合牌，二话不说突然就变出10个微生物。此外，这牌也与下面这张No.23配合极佳。&lt;/p&gt;
&lt;h3&gt;No.23 - 表土合约&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655341612679.webp&quot; alt=&quot;image-1655341612679&quot; /&gt;
微生物魔术组件的核心。相较于肉类工厂虽然只是+1钱，但是微生物有机会大量增加，比如配合殖民地，或者上面这张微生物大哥。
另外这牌的标志也更优秀，自带的3植物让这牌裸出都不算亏，因为出场率非常高。&lt;/p&gt;
&lt;h3&gt;No.22 - 地球办公室&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655340670522.webp&quot; alt=&quot;image-1655340670522&quot; /&gt;
小费且超模的代表卡牌，因为很多后期魔术牌也都是地球，即便是最后一时代上手也可以有不错的发挥空间，更不用说前期上手配合地球钱产天胡开局了。
下图为大后期常见的魔术配合牌：
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655341033510.webp&quot; alt=&quot;image-1655341033510&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;No.21 - 先进的生态系统&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395065629.webp&quot; alt=&quot;image-1655395127169&quot; /&gt;
刷分魔术牌之一，本身14元3分已然不错，自身附带的3个生物标志大有可为。极限情况下，配合分解者/生态区等卡牌，加上转基因表土肉厂等生物魔术组件，甚至可以实现倒回7块钱赚5分的惊天操作。
这种单体刷分牌可以留在手里最后打出，这种藏分的意义不可小觑。比如终局收官接近尾声，玩家A 87分手里一张病毒，玩家B 88分1手牌，玩家C 85分1张手牌。这时候玩家A基本都会选择病毒吃玩家B两个动物，这时候玩家C偷偷打出生态系统即可反超分数。也是基于火星有众多攻击牌这一点，玩家藏分哭弱是必学技能，转移攻击目标一来一回可能就是双倍收益了。&lt;/p&gt;
&lt;h3&gt;No.20 - 引入氮气&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395154360.webp&quot; alt=&quot;image-1655395154360&quot; /&gt;
刷分魔术牌之一，上限0.5树+4.5分，前期苦弱后期套圈必备神器。
本身是带地球标的钛事件，还能提供额外的动物和微生物，可以和前文已经出现的各种魔术组件完美契合。&lt;/p&gt;
&lt;h3&gt;No.19 - 大流散运动&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395207684.webp&quot; alt=&quot;image-1655395207684&quot; /&gt;
刷分魔术牌之一，木星终局一般10-15个，保底倒回钱1分，由于有高贵的木星标志，配合木星大哥总收益一般是2-3分。红党前置并不算什么很大限制，因为最后一时代本身政党领导者也有1分，所以常见操作就是两人上红党然后出这牌。
另一方面也是由于这张牌总会有人留，如果玩家倒二倒一时代不知道上什么政党蹭最后一分，一般都首选红党，如果最后神抽这牌就是血赚；没抽到就说明大概率在别人手里，能卡一下是一下。&lt;/p&gt;
&lt;h3&gt;No.18 - 转基因&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395228980.webp&quot; alt=&quot;image-1655395228980&quot; /&gt;
生物流核心之一，花式理财小能手。本身这牌打出即可回2，如果在绿党回合（比如第一时代）打出保底等于是4块钱的一个科标。如果配合前面已经提及的多张生物配合牌，回钱收益非常夸张。&lt;/p&gt;
&lt;h3&gt;No.17 - 媒体集团&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395266169.webp&quot; alt=&quot;image-1655395266169&quot; /&gt;
理财魔术牌，用这张牌爽过后，以后的对局中没看到这牌都舍不得提前打事件了。由于事件不少是低费甚至零费，配合这张牌也可以表演倒回钱。有时候本身都没钱了，打了3张事件后突然又多出了9块钱，又可以开始操作了。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395422195.webp&quot; alt=&quot;image-1655395422195&quot; /&gt;
上图是随便列举的几张便宜事件。可以看到不少低费事件也是搞人事件。因此这牌也为玩家攻击他人提供了合法动机：“我打出这张牌还能倒回钱，那为什么不打呢？”&lt;/p&gt;
&lt;h3&gt;No.16 - 宠物&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395771094.webp&quot; alt=&quot;image-1655395771094&quot; /&gt;
躺着拿分第一卡，自带保护区固若金汤，挂机赚5分以逸待劳。
主要缺点是需要在前期花费13块钱打出这张牌，但换个角度想其实差不多等于买了一个里程碑还送了2个高贵的标志，也还可以接受。
并且由于最后一时代几张常见刷分卡还会自带天空城，即便最后一时代打出一般也有个2-3分，属实离谱。&lt;/p&gt;
&lt;h3&gt;No.15 - 多媒体档案库&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395807662.webp&quot; alt=&quot;image-1655395807662&quot; /&gt;
理财魔术牌，之前看了下网页版的数据库，一局的平均事件数量好像是24张，也就是这牌就算没有任何减费配合也是白赚13块，已经完爆绝大部分钱产，因为它不需在前期支付打出费用，前期省下的钱可以做其它更有意义的事情，比如抢里程碑或者买下更多的后期魔术牌。
现在开局10张牌，如果是偏改造可以留3-5张牌开局，通过一些符合收益预期的标动填补打牌真空期；但是如果是偏打牌的流派，开局6-7牌应该是比较合理的，除非手牌质量确实没眼看（如果前序和初始手牌中有过牌可以适当少留一些）。因为现在很多时候前期没有出产能的必要性，并且例如事件/生物这些体系，要在有配合卡后打出收益才更高（比如早一回合打出2钱产可以多生产2块钱，但是晚一轮打出可能减费本身就减2块了）。加上动荡提供的标志收益以及执政政策，很多时候顺政党而行，蓝党打钛牌绿党种树土党拍版块，晚几轮打牌收益真不一定就亏。
综合以上多点，不论是外服还是国服，打牌流玩家手牌在中后期保持20-30张都是挺常见的。&lt;/p&gt;
&lt;h3&gt;No.14 - 温室&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395839530.webp&quot; alt=&quot;image-1655395839530&quot; /&gt;
温室的强度取决于本局游戏的城市数量，一般来说在7-15叶子直接浮动。
9块钱1-2颗树自然是超模，但如果要仔细讨论这牌和倒回钱白嫖分这些牌谁更强，倒也不好说。
但不论如何，可以看到排名靠前的这几张牌，完全没有打出成本，不论是改造流还是打牌流都可以用，也正是因为它们极高的泛用性才能排在这么前的位置。&lt;/p&gt;
&lt;h3&gt;No.13 - 分子打印&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395864690.webp&quot; alt=&quot;image-1655395864690&quot; /&gt;
理财魔术牌，在倒回钱之余还多送了一分，很关键。并且本身是个科标，所以还可以配合金星动物/火星大学等科标体系卡。
这个排名出现的所有牌，即便是开局看到了也基本是需要留下来的。根据火星玄学，今天你抛弃了它，明天抽牌它就不来了。&lt;/p&gt;
&lt;h3&gt;No.12 - 生态保护区&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395896729.webp&quot; alt=&quot;image-1655395896729&quot; /&gt;
生物流得分核心，每次看到它都很安心。本身自带1分，版块本身也有价值，基本上再打出2个生物标就可以回本。
大多数情况下是8-12动物，但是毕竟上限夸张，这点对追梦选手很重要。此外由于可以配合其他生物组件，如果能联动起来会感觉钱怎么都花不完而分数一直在往上涨，电表倒转了属于是。&lt;/p&gt;
&lt;h2&gt;S级卡牌&lt;/h2&gt;
&lt;p&gt;其实S级卡牌和A级卡牌收益很多时候也差不多，甚至期望收益可能低于A级卡牌。但是S级卡牌上限极高，并且声名远扬，如果轮抽时放给下家会被其它玩家发“？？？”，为了避免不必要的争端还是自己默默留下比较好。&lt;/p&gt;
&lt;h3&gt;No.11 - 木星大哥&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655395992040.webp&quot; alt=&quot;image-1655395992040&quot; /&gt;
木星大哥之间亦有差距，改造木卫三独一档，自带的2分很关键，还有望终局反超改造度。
木星大哥其实正常情况下和前面的几张大分牌也差不太多，但是上限实在不可估量。如果说生物流是通过大量体系配合牌叠加量变累积达成质变，那么木星流派就是日积月累，一点一滴凑出浩瀚星辰。&lt;/p&gt;
&lt;h3&gt;No.10 - 副产品&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396024027.webp&quot; alt=&quot;image-1655396024027&quot; /&gt;
副产品本身自带2钱产，前期打出来也不会带来很大的经济负担。
从统计数据来看，打牌流20元以上的牌一般会打出5-8张，木星和其它高分钛牌基本也都是20元以上的，相得益彰。
另外，抽牌效果可以带来更多抽好牌的机会，比如钛减费，然后就可以用更低的成本打出20元以上的牌，从而形成良性循环过更多的牌。&lt;/p&gt;
&lt;h3&gt;No.09 - 病毒增强&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396060987.webp&quot; alt=&quot;image-1655396060987&quot; /&gt;
生物魔术流的最后一块拼图，适配于所有其它生物牌。有这张牌，既可瞬间变出8叶虚空种树，也能配合肉厂凭空营造火星方舟，操作感与强度并存。
最后放个生物套核心组件全家福，但可惜在正常的4人局中是几乎不可能凑齐的，这种未能圆满的遗憾也正会吸引玩家一次又一次的开局。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655397028686.webp&quot; alt=&quot;image-1655397028686&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;No.08 - 轨道清洁&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396129338.webp&quot; alt=&quot;image-1655396129338&quot; /&gt;
对于非改造流，这牌就算最后一时代打出也差不多是10元2分的标准收益，这就很夸张了。
轨道清洁强在这是个当时代就可以执行的行动，而非延后收益的产能。基本上可以理解为下限白嫖2分，上限不存在。&lt;/p&gt;
&lt;h3&gt;No.07 - 前沿科技&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396156617.webp&quot; alt=&quot;image-1655396156617&quot; /&gt;
前沿科技起初是11元，在众多玩家抱怨太强后官方也是非常体贴的做出了重大削弱：购买费用上调1元。
大部分强力卡牌都自带限制，比如科标/参数/政党。因此这张牌对于打牌流差不多等于全卡减1.5费，收益上限取决于手牌数量。&lt;/p&gt;
&lt;h3&gt;No.06 - 火星大学&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396198361.webp&quot; alt=&quot;image-1655396198361&quot; /&gt;
有火星大学，未来就可以有其它的TOP50。作为游戏中最强的过牌，可以让玩家手里的废牌不用沦为1块钱而是获得第二次生命，意义非凡。
火星这款游戏的一大魅力就是玩家可以玩得很爽。前文所提及的那些分牌只是能让玩家获得游戏胜利罢了，而火星大学可以让玩家收获无与伦比的快乐，孰优孰劣不言而喻。&lt;/p&gt;
&lt;h3&gt;No.05 - 优化合金&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396225785.webp&quot; alt=&quot;image-1655396225785&quot; /&gt;
不论是改造流还打牌流都可以使用的顶级经济来源。尤其是在殖民地中有铁/钛殖民时。在联合党回合钛殖民地非常抢手，因为标动后飞一下就是4钛16块钱，等于1块钱换了1点钛产。而此时如果有优化合金，就变成了了直接白赚3元，原地理财。
另外前序中也存在不少优质资源卡，例如3叶8铁3钛、1钛产4钛、1温4铁4钛。优化合金如果一时代上手，配合这些牌基本就可以忽略打出成本了，打出就是赚到。
再加上大盒版promo中加入了3张高费用且超模的铁牌，也降低了铁资源溢出的风险。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396474409.webp&quot; alt=&quot;image-1655396474409&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;No.04 - 太空码头&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396252659.webp&quot; alt=&quot;image-1655396252659&quot; /&gt;
全卡减一。其实和前沿收益感觉也差不多，只是说这位辈分更老，排前一位以示尊敬。
全卡减一意味着再打出21张牌即可白嫖2分，上不封顶。虽然说确实很强，但是改造流看到这种牌非常容易走入陷阱，如果说手里一共就没几张牌，那忍痛弃掉/卡掉也是可以的。
另外，码头的2地球前置未必容易达成，如果开局公司+前序+手牌都没有地球标，那这张牌可能有戏中期才能打出来，收益会有所降低。&lt;/p&gt;
&lt;h3&gt;No.03 - 研究前哨站&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396278342.webp&quot; alt=&quot;image-1655396278342&quot; /&gt;
全卡减一。相较于太空码头打出成本更低，城市从长线收益来看价值比2分更高。考虑到与其它板块不相邻这个效果，研究前哨站越早打出越厉害，到大后期甚至可能出现无法打出的特殊情况。&lt;/p&gt;
&lt;h3&gt;No.02 - 反重力&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396315678.webp&quot; alt=&quot;image-1655396315678&quot; /&gt;&lt;/p&gt;
&lt;p&gt;全卡减二等于两张全卡减一，并且打出费用减二后可以让本来有些亏模的卡不再亏模，从而让玩家可以打出更多的牌。
插句题外话，加入火星群的玩家需要填写“自己最喜欢的卡牌”，虽然没有具体统计过但我感觉反重力的出场率至少是前三（填寻找生命的也很多）。
作为全扩最高的7科标需求，尊贵的打出前置透露出反重力的高攀不起。本身17元3分基础身板，基本上打出就不亏，即便是最后一时代打出也很有望最终白嫖3分。
但不论如何，7科标对于缺少过牌手段的玩家还是有不小难度的。所以当轮抽看到反重力后，需要仔细评估下打出成功率。如果确定留下，则可以多考虑冲冲科学党。游戏可以输，10元抽3必须抽~&lt;/p&gt;
&lt;h3&gt;No.01 - 地球发射站&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-1655396334629.webp&quot; alt=&quot;image-1655396334629&quot; /&gt;
虽然从统计结果上来看，打出地球发射站后垫底的情况也不少，也许是因为唤起了其它玩家的嫉妒之心而成为众矢之的了吧。但不论是计算数值模型还是量化具体收益，这牌都实在太超模了。
另一方面，地球发射站虽然非常超模，但是其实回望排名中很多其它卡牌，他们组合起来的效果也未必弱发射站很多。比如假设打出发射站后再打出23张牌，那么也就是白嫖20元+2分。确实很夸张，但比如打出一张多媒体档案库，也有望白嫖20元，那么实际上差的就是2分而已。再比如找准时机花费8元设了个5分奖项，那么这个行动跟打出发射站比起来等于是花费了28元赚了3分。虽然转换比很低，但是如果游戏中没有合适的运营，最后钱溢出了只能标动，说不定28元还换不了3分呢。
总而言之，火星虽然单卡强度确实有差距（甚至差距很大），但是远不属于“打出某某卡就基本稳赢了”的运气游戏。相较之下运营更为重要，毕竟我看火星群里有些大神玩家胜率一直都很高，总不能是偷偷开挂开出来的吧（笑）。&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>殖民火星 | 群友扩介绍&amp;免费打印版素材</title><link>https://www.ender-wiggin.com/posts/terraforming-mars-eros/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/terraforming-mars-eros/</guid><description> </description><pubDate>Sat, 28 May 2022 22:11:32 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;群友扩是2021年制作并小规模发行的粉丝扩展，包括了20余张项目卡以及5张公司。其中所有卡牌均由国内火星QQ群玩家设计，因此直接命名为《殖民火星: 群友扩》。
当卡牌初步设计完成后，我们实现了代码并在线上进行了测试，在最终敲定后也制作了实体版本，具有不错的纪念意义。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/%E7%BE%A4%E5%8F%8B%E6%89%A9hd.jpg&quot; alt=&quot;群友扩&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;线上版本&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;线上版本可以在&lt;a href=&quot;http://www.jaing.me/&quot;&gt;殖民火星网页版&lt;/a&gt;中查看。&lt;/li&gt;
&lt;li&gt;源码参考&lt;a href=&quot;https://github.com/jaingw/terraforming-mars&quot;&gt;GitHub仓库&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实体版本&lt;/h2&gt;
&lt;p&gt;实体版本可以参考如下介绍视频，其中包括了卡牌的设计理念以及灵感来源。&lt;/p&gt;
&lt;p&gt;{% raw %}
&amp;lt;div style=&quot;position: relative; width: 100%; height: 0; padding-bottom: 75%;&quot;&amp;gt;
&amp;lt;iframe src=&quot;//player.bilibili.com/player.html?aid=889898510&amp;amp;bvid=BV1uP4y1s7re&amp;amp;cid=392796740&amp;amp;page=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot; style=&quot;position: absolute; width: 100%; height: 100%; Left: 0; top: 0;&quot; &amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/div&amp;gt;
{% endraw %}&lt;/p&gt;
&lt;p&gt;实体版相较于线上版本需要增加卡牌插图和背景描述，因此可以提高卡牌的代入感。其中卡牌插图除了不方便找到作者的网图，其余我基本获取了作者授权。尤其是 &lt;a href=&quot;https://weibo.com/n/MORNCOLOUR&quot;&gt;MORNCOLOUR&lt;/a&gt; 这位画师绘制的三体系列插画，和我们的主题性非常契合，非常难得。&lt;/p&gt;
&lt;h2&gt;素材下载&lt;/h2&gt;
&lt;p&gt;目前实体版不会重印，但考虑到之前有挺多玩家向我询问群友扩的相关事宜，我决定直接提供打印版素材，大家可以自行制作，但是请&lt;strong&gt;千万不要商用&lt;/strong&gt;。
由于我迁移博客了，也搞不了什么评论可见的操作了，那么就全凭读者自觉吧哈哈 :D&lt;/p&gt;
&lt;p&gt;链接: https://pan.baidu.com/s/1g-dOhSX5hdrNhV98IAK1kA
提取码: 530h&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>大坝 | 执行官卡牌一览以及FAQ</title><link>https://www.ender-wiggin.com/posts/barrage-cxo/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/barrage-cxo/</guid><description> </description><pubDate>Sat, 07 May 2022 22:50:55 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;由于我在23年初写了一个大坝的&lt;a href=&quot;https://barrage.ender-wiggin.com&quot;&gt;卡表网站&lt;/a&gt;,其中还包括了洛克大佬的精彩点评,因此本篇文章就不详细展开了。&lt;/p&gt;
&lt;h2&gt;卡牌版打印&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;注：该素材为玩家自制，并未获得官方许可，因此禁止商用。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;快速下载&lt;/h3&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/%E5%A4%A7%E5%9D%9D%E5%8D%A1%E7%89%8C%E7%89%880728%E6%9B%B4%E6%96%B0.pdf&quot; target=&quot;_blank&quot;&amp;gt;大坝卡牌横版&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;h3&gt;百度网盘&lt;/h3&gt;
&lt;p&gt;链接: &lt;a href=&quot;https://pan.baidu.com/s/1g8pPQkoF4YK50gUm8z9dYA&quot;&gt;https://pan.baidu.com/s/1g8pPQkoF4YK50gUm8z9dYA&lt;/a&gt;
提取码: 7eea&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>七大对决 | 游戏流派与核心思路</title><link>https://www.ender-wiggin.com/posts/seven-wonders-strategy/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/seven-wonders-strategy/</guid><description> </description><pubDate>Sun, 17 Apr 2022 21:10:41 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;本文是对于七大对决的游戏打法攻略，如果对于这游戏不太熟悉可以先阅读上一篇文章&lt;em&gt;游戏本体及扩展内容介绍&lt;/em&gt;。文章中所讨论的全扩环境，即基础+一扩+二扩，如果只玩其中部分扩展可以对我文章中所述内容有所取舍。先放个战绩，由于比较长时间没玩已经掉到快20名了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-7d9782abf962447fadd4dfbbb660e33c.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;游戏流派&lt;/h2&gt;
&lt;p&gt;众所周知七大对决一共有四种获胜方式：军事、科技、文化、政治。一般来说玩家要&lt;strong&gt;同时选择两条主要路线&lt;/strong&gt;，这样才可以给对方压力以限制抓牌。比如在走文化的同时顺便抓些科技牌，这样对方可能会因为被迫卡掉科技卡牌放弃蓝色得分牌。&lt;/p&gt;
&lt;h3&gt;科技流派&lt;/h3&gt;
&lt;p&gt;存在二扩的环境下，科技可能是个陷阱，其是一时代的科技牌如果要花钱买不如直接卖掉。原因有三：前期金币宝贵，钱要留着抢政党和抢神；科技获胜节奏慢，多数时候三时代中后期才可以斩杀，而军事或者政治路线可能二时代即可结束游戏；科技弱点明显，比如一时代购买的科技符号，如果二时代没能凑出一对或者配合斩杀，那么可能就是白亏了5块钱（购买2元+卖牌的机会成本）。反观军事，即便未能获胜，推到军事指示物的位置也能提供影响力收益，而终局计分也不可小觑。&lt;/p&gt;
&lt;p&gt;那么可能有小伙伴会问了，是不是一定不要走科技路线呢？&lt;/p&gt;
&lt;p&gt;倒也不然。
如果开局发展标记刷出了法律，并且本局游戏有绿神，那么科技虽然效率较低，但是一旦苟活到后期，斩杀成功率是非常高的。再一步，如果有蓝神（存在哈迪斯的可能性）以及陵墓，那么科技获胜的成功率也会进一步上升。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-f7245a7addac4295a4885664dbdbb912.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;军事流派&lt;/h3&gt;
&lt;p&gt;如果说之前基础+二扩的版本存在一时代直接红牌莽一波的情况，那么在加入一扩之后这个路线基本走不通了。原因是，二时代第一轮玩家很可能需要抢夺强力神祗（至于什么是强力神祇后文会说），因此一时代最后一动的所属权至关重要，如果为了早期军事白送一个核心神，那么可能这局节奏就崩了。&lt;/p&gt;
&lt;p&gt;因此，军事的发力点应该是二时代，所有2军事的卡牌都可以考虑。最核心的策略是，&lt;strong&gt;军事是手段而非目的&lt;/strong&gt;——军事多数时候只是为了达到军事指示物的位置获取参议院收益，并且给予对方军事压力而被迫卡红牌。因此能否军事获胜不得而知，但可以确定的是二三时代多选择红牌更容易带来胜利。&lt;/p&gt;
&lt;p&gt;也是基于这一点，军事和政治路线相性极佳。一方面军事指示物可以在参议院获取更多影响力，另一方面参议院中的红牌减资源/红牌回钱/一军力这三个政令都可以反哺军事。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;政治流派&lt;/h3&gt;
&lt;p&gt;政治路线还是我心目中的版本答案，原因有三：成本低，不需要任何基建支持；收益高，即便未能获胜，参议院带来的收益也已非常可观；启动快，多数时候在二时代即可达成政治胜利，如果2蓝开局很可能4张白牌即可获胜，这是科技与军事所不可比拟的。&lt;/p&gt;
&lt;p&gt;对于一时代而言，政治路线最为关键的就是凑2张蓝牌。如果能做到这一点，就意味着对手在二时代早期之前不论如何都不能通过一张白牌执行两次参议院行动。因此即便是抓取同等数量的白牌，对手的效率也会低己方不少。&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;蓝卡+2：神中神，每一次成功的一时代斩杀背后都有蓝卡+2默默无闻的付出。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-98c61d8c4b754ba1b406ee723b3cd8f0.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;文化流派&lt;/h3&gt;
&lt;p&gt;一般来说双方水平越接近，越容易拖到终局文化计分。这有点像弈棋，双方水平差距过大的话可能随便过几招一方就被将军了。对于文化胜利其实没什么好说的，由于工会卡牌变为了神殿，实际上蓝卡的强度是进一步提高的，因为之前往往工会卡牌的收益是要大于6-7分的。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;经济：动力源泉&lt;/h2&gt;
&lt;p&gt;当然，不论是什么流派经济都是必不可少的，尤其是一扩中加入了价值不菲的各位神祇。获得经济的手段一共有如下几种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;棕灰基建：这是一时代最为直观的经济提升手段，基本属于第一优先级。其中灰牌价值往往更高，基本是见到必抓的卡牌。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;黄牌：黄牌一共有3种：减费卡/资源任选其一/纯钱。其中一时代的减费卡我认为稍弱于棕灰牌，但是弥补短板无可厚非；二时代的黄牌都非常强力，基本远强于同时代棕灰牌；纯钱牌一共有2张，即一时代的4元和二时代的3元，这两张卡我认为是顶级卡牌，因为不用付出任何代价就可以换取高额经济。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;神祇与奇迹：其实我不知道设计师是怎么考虑的，在我看来可以获得钱的商业神（7元、12元）怎么看都是无脑强。也许设计师是认为请神本质是亏动换取高收益，但问题是停一动很多时候根本不亏，而让对方开牌意义非凡。尤其是这个12元神，就算在对方的3元位（我方的8元位），那直接请神不也是等于白赚4元，此消彼长四舍五入对方亏了一个亿。至于奇迹，一般观点都是纯钱奇迹无脑强，基本都是第一优先级的（仅次于那个阴谋奇迹）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;政令：桌游中往往辛辛苦苦造基建的都是老实人，真正的魔术师都是用各种科技组件达成质变然后暴打原始人（比如大坝的坝身转、运牛的10ab、火星的科技套装、甚至炉石的电表倒转）。在七大对决里政令就是魔法手段。比如一时代斩杀呀、二时代中期三四十块钱呀，这些全都是靠政令变魔术变出来的。政令强度可以参考下方我主观排序的梯度表：
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-b13f974d7ee346f8b892a50d5e48c458.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;核心操作分析&lt;/h2&gt;
&lt;p&gt;首先对于一时代的神话标记，我个人看法是不必刻意争抢，多数时候让对方开牌还是有好处的，毕竟神话标记只能选择神祗的位置和效果，并不能决定所有权。但如果出现了&lt;strong&gt;埃及神&lt;/strong&gt;（灰色），那么抓取的价值会高不少，因为有三分之二的可能性获得全扩第二超模卡牌——拉（第一应该是更加bug的阴谋卡勒索，可以一时代直接启动，唯一缺点是不一定摸得到）。此外商业神价值也较高，因为可能是理财产品。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-6332098d88534346b789278a66617d60.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最重要的，就是在一二时代交际之时，先手的争夺权问题了。&lt;/strong&gt; 二时代的先手玩家有机会直接第一动请神抢奇迹，如果抢夺的是连动奇迹那么对双方面板影响是非常大的，很可能就是胜负手。出于这个考虑，如果判断我方不具有争抢先手的能力，那么应该做的是：主动抢埃及神话标记不让【拉】入场；或者限制对面金币让他第一动无法请神。
这里顺便提一句，我之前看到对手为了反制偷奇迹有个天才想法，即一时代直接把4个奇迹全部造完让我无奇迹可偷。这个打法虽然后继无力，但是在明确已知对方可以偷奇迹的时候是一个不错的止损方式。&lt;/p&gt;
&lt;p&gt;此外，一扩中的请神奇迹，以及神殿中的大门卡，也是可以配合【拉】打出梦幻操作的。只能说，堂堂埃及太阳神，为什么要做这种卑劣的勾当呢？&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;总的来说七大对决还是一款小毛线，很多的策略比较直观，多玩几局就可以基本参悟，也不需要过多的言语解释。&lt;/p&gt;
&lt;p&gt;那么全扩的简要攻略就到这里结束了，如果有什么疑问或者建议欢迎和我讨论，也欢迎关注我，我日后会尽量不鸽并做出更多你们想看的内容。&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>七大对决 | 游戏本体及扩展内容介绍</title><link>https://www.ender-wiggin.com/posts/seven-wonders-intro/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/seven-wonders-intro/</guid><description> </description><pubDate>Sun, 17 Apr 2022 12:31:46 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;《七大奇迹：对决》作为一款简单上手的二人游戏，凭借其短平快的特点深受国内外玩家的喜爱。截止至本文撰写时间4月17号，基础游戏及其扩展《万神殿》《市政广场》均超过8分。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-b513faa5a576471a91e040f5c586a238.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;本文主要面向对象是刚听闻这款游戏的小白，或者是刚购买了这款游戏的萌新玩家。文章中会介绍不同游戏set的特点及优劣，即单基础、基础+万神殿扩（后文简称一扩）、基础+市政广场扩（后文简称二扩）、基础+一二扩的游戏环境差异。在了解了不同组合的优劣后，玩家可以自行选择自己感兴趣的组合开始游戏，我后续也会再写一篇文章介绍不同流派的打法思路以及核心卡牌。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;正文&lt;/h2&gt;
&lt;h3&gt;单基础&lt;/h3&gt;
&lt;p&gt;《七大奇迹：对决》（后文简称七大对决）的基础游戏已经具备了极高的完整性，也是基于这一点在出完二扩的当下依然不少玩家钟爱于单基础。&lt;/p&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;规则简单。在很多情况下，10分钟就可以教完规则，15-20分钟就可以完成第一局紧张刺激的游戏，这在缺少小伙伴或者等人到齐开4人桌游的时候都大有作为。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;攻杀激烈。单基础的随机性无疑是最低的，这也让双方熟知卡牌分布的竞技模式应运而生，这和《卡卡颂》单基础异曲同工。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;画风优质。其实很多时候游戏性之外的元素也是不容忽视的，这也是为什么桌游往往都要套上一个背景讲究代入感。七大奇迹系列的美术风格一直都是业内顶尖，古希腊元素非常讨喜。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-2508d77f220e4496b06320e9db74efb3.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;先后手不平衡。这一点是基础游戏最为诟病的缺陷，先手在绝大多数情况下都可以掌控全局的主动权，之前的数据是先后手55:45，现在具体变化如何暂时不得而知。&lt;/li&gt;
&lt;li&gt;流派不均衡。基础游戏中绝大多数的情况都是文化胜利，穿插着20%的科技胜利以及10%左右的军事胜利。这意味着军事流派基本是严格劣势策略，同时军事领先还会导致后续时代顺位靠后。究其根源是科技和军事胜利需要的卡牌有限，对手只要选择卡掉其中几张就会完全破坏我方这两种路线的获胜可能性。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;基础+一扩&lt;/h3&gt;
&lt;p&gt;2016年萧瑟的冬夜，万神殿扩惊天出世，力挽狂澜填补了基础的明显缺点，让基础+一扩成为了多数玩家眼中的完美二人游戏。&lt;/p&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;先后手平衡的尝试。一二时代的卡牌上方标志让玩家选牌时多了很多变化，开牌一方也不再是一无所获的被动角色。同时请神的行动会改变游戏的常规轮次，这也让游戏节奏更加不可捉摸，双方攻防互换而非基础的施压与抗压模式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;科技流派的加强。一扩的加入，让科技胜利的难度大幅降低。主要原因是科技神中有两个可以额外提供科技符号，这对于科技胜利这种极其一套符号必杀的偏自闭套路获胜难度大幅降低。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;神祇不平衡。其实大部分神祇不平衡玩家都可以接受，但唯独这个【拉】我完全受不了。看着对方不声不响把我辛辛苦苦轮抽的奇迹化为己用，在我眼中比直接输了都屈辱。这种肉眼可见价值大约0.5胜利的卡牌（打出来赢一半）的卡牌不知道设计师怎么想出来的。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-8abd5b0790894a4c9cea4b6b6a8a6fe4.png&quot; alt=&quot;image.png&quot; title=&quot;阳光男孩·拉&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打法依然不够多元。在基础+一扩的环境下核心思路依然没有变化，比如前期争夺棕牌灰牌、前期弃置红牌蓝牌。总的来说打法还是相对定式，大部分卡牌的价值都是保持恒定，从竞技性来说无可厚非但是从可玩性来说变化略有不足。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;基础+二扩&lt;/h3&gt;
&lt;p&gt;2020年，在bga上悄然登场的二扩为这个游戏带来了翻天覆地的变化。二扩中新增的阴谋和参议院机制，让游戏的随机性进一步增加，这也在当时引起了不小的争议。我也曾在bgg上对二扩提出了自己的观点: &lt;a href=&quot;https://boardgamegeek.com/thread/2537120/my-personal-review-after-99th-game-baseagora&quot;&gt;My Personal Review after the 99th game of base+agora&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;同时我也曾制作了二扩的规则讲解，对二扩还不熟悉的玩家也可以围观围观。&lt;/p&gt;
&lt;p&gt;&amp;lt;joe-bilibili bvid=&quot;BV1Zy4y1i7D6&quot;&amp;gt;&amp;lt;/joe-bilibili&amp;gt;&lt;/p&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;可玩性的进一步提升。其实我玩桌游一直不排斥随机性，我的观点是：即便对局中运气成分很大，但如果最终个人依然可以保持较高胜率（比如二人游戏75%或者四人游戏50%），那么这款游戏依然具备一定的竞技性以及平衡性，而非部分玩家所鄙夷的”运气游戏“。七大对决的二扩在我看来运气成分是相当离谱的，但很多新手玩家根本把握不住机会，因此再好的运气也可能带来不了胜利。在二扩中，玩家可以通过阴谋牌打出花里胡哨的操作，也可以通过控制参议院几乎摒弃传统棕灰基建流，甚至可以直接玩起前期蓝红流的杂技流派，还有机会一时代直接斩杀对手，这让游戏充满变数与乐趣。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;先后手依然不平衡。这一点是和基础一脉相承的，并没有怎么改变。理论上一时代先手玩家，配合二扩中的奇迹“克诺索斯王宫”，可以打出后手玩家完全无法反制的操作。可以说，绝大部分的一时代斩杀，都和这张卡牌拖离不了干系。
&lt;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/image-c9d360fdecbe4f97926a8ffa7bb480e6.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;超模阴谋粉墨登场。设计师：“必须要有一个拉”。在二扩中玩家们虽然可以逃离埃及神·拉的无情黑手，但是却迎来了同样效果甚至更加强大的阴谋牌“勒索”。在双方实力旗鼓相当的情况下，一方打出这种严重破坏平衡的卡牌会完全践踏游戏的公平性。或许七大采用其它很多桌游都有的“禁卡制”会是个不错的平衡手段。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;基础+一扩+二扩（全扩）&lt;/h3&gt;
&lt;p&gt;“一扩平衡了先后手，而二扩丰富了打法路线，那么一扩+二扩会不会如虎添翼？”想必许多玩家在第一次听到这个组合时都会有这个想法。
然而，事实是全扩的七大对决已经成为了神仙打架，和七大基础这种凡人互斗的游戏形同陌路。&lt;/p&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;可玩性max。根据桌游的基本规律，多个扩展的叠加效果就是神仙打架，可以打出基础模式下很多想都不敢想的操作。这对于喜欢操作，希望玩得快乐的玩家来说无疑是极大的优点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;上手成本变高。在两个扩展的加持下，七大的游戏体量已经不小了，基本上可以视为一款3.2重度左右的中策游戏了，这对于日常推新来说有着不小的难度。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;那么到底应该选择哪种游戏配置呢&lt;/h3&gt;
&lt;p&gt;这想必也是很多新手玩家在入坑时非常关注的问题。我的观点是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;购买时直接全扩。因为七大全扩的价格也就是其他重德单基础的价格，而提前购买了全扩可以保证自己游戏的完整性，不会出现玩了某一个扩展后想加入另外一个扩展，却发现自己并未购入的后悔景象。&lt;/li&gt;
&lt;li&gt;游戏时优先选择基础+一扩配置。这也是我上文所说的许多玩家心目中的完美配置。并且因为二扩复杂度较高，熟悉基础+一扩环境可以让玩家对于游戏模型和概念有一个基本了解，也可以为未来全扩做好铺垫。&lt;/li&gt;
&lt;li&gt;对于有一定经验的玩家，追求竞技性选择基础+一扩，追求可玩性选择全扩。我个人一般是选择全扩的，因为全扩七大非常快乐，并且可以秀操作。如果过多考虑竞技性，那或许直接重德是更加不错的选择。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>历史巨轮 | 全扩环境分析-一时代科技卡牌篇</title><link>https://www.ender-wiggin.com/posts/tta-age-i-tech/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/tta-age-i-tech/</guid><description> </description><pubDate>Tue, 15 Mar 2022 17:44:17 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是Ender。在笔者撰文时，&lt;a href=&quot;https://account.czechgames.com/tournaments/detail/3391&quot;&gt;TTA全球锦标赛&lt;/a&gt;也即将进入16强的阶段。由于这个比赛参与人数众多，如何在打法迥异的玩家中间取得比较稳定的成绩成为了重点问题，这也是我本文分析卡牌的一个方面。&amp;lt;br&amp;gt;
另外，我最近几个月依然没怎么练习TTA，感觉技术比较粗糙。因此尝试在博客中发布这篇文章，这样如果读者有意见可以直接发布，也可以避免贴吧的吞楼问题。&lt;/p&gt;
&lt;h2&gt;综述&lt;/h2&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
科技卡牌时TTA中玩家提升自身面板的核心途径。一局游戏如果完全不使用领袖/奇迹或许也可以获胜，但不打出科技牌想要获胜应该是难于登天的。&amp;lt;br&amp;gt;由于科技牌的打出取决于自身科技储备、白点富余程度、以及面板需求，一局游戏打出的科技牌数量是非常不确定的，每张科技牌的价值也是处在动态变化之中，因此本文不会过多强调排名/抓位等问题，而是侧重于每张卡牌的特性。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h2&gt;AGE I 科技&lt;/h2&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
一时代的科技表面上是打好基建，为后续时代布局。但TTA非常神奇的一点在于某些一时代卡牌的性价比远高于二三时代，这也使得这些牌完全足够使用到游戏结束。此外由于一时代黄牌价值偏低，选择合适的一时代科技牌做长线布局也是非常重要的一点。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;法典&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：法典在大部分的对局都价值2白点，极特殊情况可以考虑3白，重要性可见一斑。由于这张牌只有一张，率先获得的一方可以变成5白打4白的轻度压制局面，或者防止4白进二时代而使得操作受限。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：6科技的花费在一时代基本属于高额消费了，如果没有科技领袖/奇迹/黄牌的科技补充，打出法典之后可能存在科技真空期，对节奏会有一定影响。此外在一时代末期的法典会贬值，因为如果因打出法典而不能快速演变二时代政体，那就因小失大了。&lt;/p&gt;
&lt;p&gt;&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;铁矿&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：铁矿或许是新手玩家最容易被吸引的卡牌，也是最容易取得稳定内政的手段。如果说法典是白点生白点，那么铁矿就是矿生矿，是一种非常合适的滚雪球的手段。根据统计结果来看，90%以上的对局玩家都会选择升级铁矿，可见其泛用性。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：选择铁矿往往意味着将游戏变成中长线布局，会牺牲一定前期的灵活性和军事潜力，这一点在对手一时代高效提升军力时会格外明显。比如我方升铁，三矿换额外一矿产；而对方使用巴巴罗萨快速攀升军力，三矿可以提升大于2军力。这时候万一吃了几发侵略，或者二时代遭遇早期战争，那么可能节奏就裂开了。
&amp;lt;br&amp;gt;
目前来看，“弃铁”的打法在剑拔弩张的高端局中出场次数变多。究其根源，铁矿的优点只是相对稳定，收益未必很高。比如如果二时代选择煤矿，那么终局矿物总量很可能是比铁矿多的；再比如铁矿这牌很容易被对方卡在6点位，如果白点受限的话2白抓打铁矿这个操作比较笨重，节奏可能会落后于对手。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;戏剧&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：一开始戏剧被认为是巴赫莎翁等跑分领袖的配合牌，但后来玩家们逐渐发现这牌即便不需要领袖配合也是非常强力的城建。3灯的消耗一般来说并不多，而4矿2分的得分效率非常高效，相较于歌剧的8矿3分和宗教的3矿1分。此外，由于戏剧本身提供笑脸，意味着哭脸限制的情况下也可以建造，并不会增加额外的笑脸负担。目前来看这牌在一时代2白拿下是比较常见的操作，至于打出时机则非常灵活，主要还是取决于节奏。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：戏剧基本上没明显短板，唯一要注意的点可能就是跑分的时机。快速启动跑分意味着牺牲了一部分基础建设，这牺牲的部分对于较熟练的玩家而言可以忽略不计，因为分数也是影响对手的一个重要因素。但是对于还在熟悉卡牌的玩家，快速跑分的打法对于内政以及军事都是有一定影响的。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;印刷术&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：印刷术的优势主要体现在两个方面：一是图书馆这种建筑的自身优越性，既可以配合科技领袖（达芬奇、古腾堡）或者跑分领袖（达尔文莎翁），也可以在后续升级为多媒体这一最高上限城建，整体性价比高实验室非常多；二是属性的加成，建造印刷术意味着在没有政体的情况下科技增速就可以达到3-4，中期有一定科技存量后操作会相对灵活，同时和哲学一样的花费但额外提供文化增速，在终局也可以收益不少分数。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：与戏剧不同，印刷术本身占据人口。如果本身已经存在2哲学的话，在一时代建造印刷术对人口是很大的挑战。基于这点，印刷术一般适合以下几种情景建造： &amp;lt;br&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;存在额外的人口或者笑脸补充，例如大帝、神权、殖民地。&amp;lt;br&amp;gt;&lt;/li&gt;
&lt;li&gt;单哲学开局，要么是配合不依赖第二个哲学的领袖/奇迹度过前几回合，比如孔子/小亚/大图，要么是在第三回合就2白抓取印刷，直接为之布局。&amp;lt;br&amp;gt;
此外，不论如何建造印刷后意味着一时代基本不存在军事压制的可能性了，甚至还要考虑抗压的问题，因此如果是为了求稳的话抓取印刷术时要思考再三。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;灌溉&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：至少是一张可以提升自身面板的牌。在军事主导的对局中人口的需求量非常高，灌溉可以提供足够的续航让己方在军备竞赛中获胜，或是通过足量人口组成一时代阵型施压。另一方面，在高效跑分的对局中，例如配合莎翁的情况，本身建造一时代城建基本不需要花费资源，因此人口的重要性也是远高于矿物。
&amp;lt;br&amp;gt;
此外，如果对局是比较保守的中长线规划局（二时代中后期-三时代发力），在需要4粮拉人的时候可以伺机建造第三个灌溉，这样意味着依然可以每回合一个人。并且需要注意的是，具有闲置人口建造第三个灌溉的时机并不多（因为往往需要建造3灌溉的局意味着人口是相对受限的），&amp;lt;b&amp;gt;很可能一局只有一次机会&amp;lt;/b&amp;gt;，如何把握机会是关键。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：主要两点：
&amp;lt;br&amp;gt;
一方面人口的替代品很多，例如3农、二时代选种/游轮/女神像、殖民体系。如果一时代走的是铁矿+科技路线的话，往往要到二时代才有机会建造灌溉，这时候如果看到高贵的游轮或者选种，会感觉自己打出小小的灌溉有点可爱。
&amp;lt;br&amp;gt;
另一方面人口相对没那么关键。首先早期粮食过多意味着自己一定有白点要去拉人，否则会腐败，这在白点紧缺的情况下可能会影响操作；再者，过多的拉人会让人口提前进入4粮区，那么也就等于自身损失增速了；另外，鼠害这张牌对灌溉开的玩家简直是噩梦，如果看到对手有灌溉但一直不出一定要想想他是不是有什么坏心思。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;砖石建筑&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：神中神，隐藏收益第一卡。增速、白点、存量这些东西玩家一眼就可以看到，很容易对局势进行判断。但是有些东西收益并没有那么直观，比如巴巴罗萨的技能/丝路哈佛的额外效果/砖石的省白点效果。砖石这牌可能新人初一看感觉不怎么样，但是打到中期不知不觉已经输了。
&amp;lt;br&amp;gt;
砖石省矿的技能使得玩家对于铁矿的需求进一步降低，和一时代城建搭配最佳。
&amp;lt;br&amp;gt;
奇迹省白点的技能收益主要取决于奇迹（4步或更多步奇迹），这在开局就可以基本明确收益。配合A时代奇迹属于奢望，但如果能配合到那开局会非常顺利。主要配合对象是一时代奇迹（紫禁城、丝路）和二时代奇迹（游轮、铁路、运河）。可以看到这些牌本身就不弱，因此有砖石的加持收益会更高，可以利用省下来的白点做更多事情。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：不可迷信。这张卡牌的打出代价为2白+3科技，虽然说成本不高但是与之配合还是有一定条件的。比如如果仅在一时代配合奇迹省了1白，在后续时代由于矿物限制造不了奇迹，那可能是一张负收益卡牌。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;面包与马戏&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：高效率笑脸建筑。以我的观点来看，笑脸本身不存在什么收益，但是缺少笑脸会带来负面影响，并且一时代的事件牌叛乱几乎是所有没摸到这张事件的玩家的梦魇。基于这一点，玩家往往需要一种笑脸解决方案，但是如果花费太多成本在笑脸上却又可能得不偿失，最理想的情况是在不影响主路线的情况下顺便解决笑脸问题，比较典型的例子就是教堂和黄点殖民地。
&amp;lt;br&amp;gt;
一般来说选择笑脸建筑是止损之举，但相比于宗教体系，竞技场的建筑性价比会高一些，因为费用相对低廉且军力在大部分情况下都是有作用的。
&amp;lt;br&amp;gt;
面包与达尔文顾拜旦等领袖存在不错的配合，搭配军事领袖例如巴巴罗萨也是不错的选择。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：没分数。在绝大多数时候整个面板的文化增速微乎其微是个比较危险的信号，这意味着玩家在中后期必须要依靠极高性价比卡牌（文战、跑分城建、奇迹）填补分差。但由于面包本身就属于城建，建造该城建会挤压跑分系城建的空间，因此很多时候如果想要追分只能选择文战。基于这一点，选择面包的思路后一般要在军事上建立优势，暴力跑分或者纯暴力都是可选思路，但核心要领一定是把握时间差，在我方已解决笑脸问题或者对方受笑脸限制的时间点有所作为。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;神学&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：我在第一次写本文时直接忘记了这张卡牌，节约了当时几分钟的打字时间，这或许是它仅存的优点。
&amp;lt;br&amp;gt;
缺点：性价比太低。一般来说只有第一个神学是由玩家从宗教升上去的（宗教发展帮忙造了个宗教），后续神学无疑是要由自己建造，那么5矿换取2笑脸实在不是个划算的买卖。唯一有用的情况就是在自己完全已经红脸面临暴动的时刻，这时候神学可以临时救场。但是一般来说，能打出这种局面，也离输不远了。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;炼金术&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：至少有点科技增速，在二时代科技会相对充裕一些，这或许是这种牌仅存的优点了。虽然说和古腾堡/达芬奇有配合，但这也是领袖比较强，狐假虎威了属于是。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：
TTA的卡牌，风险和收益是成正比的（其实也可能不是正比，可能是指数关系，多一点代价可能换回来巨大收益）。而炼金作为反面例子，是TTA保守风格的代表。不愿多使用人口、想着聊胜于无而选择升级炼金，一方面打法非常笨重，另一方面收益微乎其微，还会影响拆哲学获取人口这一操作，在多数情况下都是不如不造。
&amp;lt;br&amp;gt;
TTA中有些面板打出来就是危险的信号，比如灌铁炼三升、大金紫禁城法典的7白打法。之所以说这些面板危险，是因为他们这种板子收益不高且有明显短板，除非对方的操作比己方还要亏，要不然凭什么可以获胜呢。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;制图学&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：一般在自身已经塞了一定殖民地，或者存在较高殖民潜力（埃莉诺、多红点）或殖民收益（库克、运河）的情况下，为了稳定争抢殖民地可以打出。基于上面的条件，制图学主要是由殖民主动方打出，因为被动方往往连有没有殖民地都不能确定，甚至即便打出了制图学依然抢不到殖民地。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：
制图学对于殖民主动方而言虽然稳妥，但是不赚，因为2白4科技的代价还是不小的。在选择这张蓝科前一定要多斟酌，如果认为自己不需要制图也可以大概率抢到（例如手上不少D卡），那么可以多多尝试不选择制图学，这张牌很多时候属于新人陷阱。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;战争机构&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：在对方一时代军事高强度施压的对局中作为求稳手段非常关键。比如对方是孙子或者3红点面板，那么摸到侵略是大概率事件，1军1红意味着至少被侵略的阈值提升2点，同时额外的红点容易摸到更多D卡以及二时代阵型，只要撑过前期就是黎明。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：
对于进攻方而言效率非常低下，远不如直接切换一时代政体实在。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;骑士&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：一时代兵种的意义主要就是军事施压，大部分情况下思路都是配合一时代阵型，伺机转型二时代阵型。其中骑士本身存在3个一时代配合阵型，且在二时代转型马马、马炮、中古等都还相对合适。
&amp;lt;br&amp;gt;
而当一方选择了军事施压时，这时候另一方是否也要出兵(打兵种)跟军力，这其实是要对局势有一定预判的。比方说，对方是中古农场主，那么即便我方暂时跟上了军力，到二时代初的某个时间点还是会形成一定的军力差。再比如我方只有2红点而对方有4红点，那么如果双方保持军备竞赛的僵持局面，但对方可以持续摸二时代黑牌，那么在军事潜力的方面我方是远不如对面的。军事作为游戏的重要组成部分，很多时候不容易量化收益，但作为防守方核心思路就一句话：&amp;lt;br&amp;gt;
&amp;lt;b&amp;gt;不要被对方代入节奏。&amp;lt;/b&amp;gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：
选择前期军事的路线肯定会舍弃一部分内政，至于投入军事是否可以取得足够回报（通过事件或者侵略取得优势、或是打乱对方节奏让对面收益更低），这就需要视局势而定了。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;剑士&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：由于二时代火枪只有一张，一时代选择剑士可以不用在二时代争抢火枪去凑几个依赖步兵的二时代阵型。同时和骑士一起抓取的话，可以以高性价比凑齐阵型，在二时代中期发力攻势会比较凶猛。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：
如果堆了太多剑士的话，二时代想要转型阵型比较看运气，尤其是需要防御阵。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;君主制&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
政体放到最后来讲，因为这是最关键的科技牌之一，往往一张牌可以起到多张其它科技牌组合的效果。
优点：
1白1红属性均衡，同时解锁第三个建筑位可以拓宽操作。配合法典/金字塔的白点卡牌可以达到6白3红的基础面板，一般来说这个面板可以安稳度过二时代，在偏军事的局甚至直接使用到游戏结束也是很常见的事情。如果在一时代早期就抢到君主制并选择革命，那么前期操作空间会比较大。
&amp;lt;br&amp;gt;
如果是比较偏向内政的布局，一般在三时代还有余力演变三时代政体，这样又可以迎来一次质变，节奏顺滑且科技消耗不大。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：
在双方资源比较富余的对局中如果一时代晚期才来，有可能在我方切换政体后被对方早二时代政体打乱节奏，或者在己方回合看到了3白的二时代政体而进退维谷。总的来说君主制是求稳的政体，早期的白点和红点意味着要在前期扩大优势，如果并没有取得足够优势的话，二时代有可能会陷入被动。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;神权&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
优点：
存在额外属性：笑脸意味着可以额外解放一个闲置工人、军力和红点不论是进攻还是防守都比较灵活、文化增速聊胜于无。
&amp;lt;br&amp;gt;
由于其4白的巨大限制，一般来说是要配合额外白点卡牌的，例如萨拉丁/法典/金字塔/巴巴罗萨/古腾堡。特别说一下古腾堡，一方面可以增加建筑位，另一方面笑脸可以缓解印刷的人口压力，同时在军事上有一定容错，配合简直天衣无缝。
&amp;lt;br&amp;gt;
另外这张牌对于军事进攻方有着至关重要的作用，比如贞德/巴巴罗萨，额外的军力加成和笑脸可以让这些领袖的军力突破一时代的正常军力上限，对于防守方威慑巨大。也是基于这一点，如果是偏被动的防守方可以考虑卡掉这张牌，不然万一对手运气好一点，这局游戏可能在二时代中期就结束了。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
缺点：
4白的初始面板对于长线布局的路线来说成本还是太高，并且如果后续想要切换政体的话这个突然损失一个笑脸可能会打乱节奏。这个政体实际上强度是很不错的，但是对使用者大局观要求比较高。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
看下来一时代好牌还是不少的，但由于前期白点和科技的限制很多时候必须要做出取舍。很多时候切忌贪心，要对自身资源调度有一个预计规划，要不然很可能拿了一手科技打不出来，甚至因为手牌上限的问题直接崩盘。
&amp;lt;br&amp;gt;
法典、铁这些牌也是如此，拿取的时候先想想自己什么时候可以打出来（甚至能不能打出来），再想想这牌对于对手是否关键，多思考才能对卡牌价值和预期收益有个基本概念。
&amp;lt;/p&amp;gt;&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>影评 | 《Ron&apos;s Gone Wrong》</title><link>https://www.ender-wiggin.com/posts/review-rons-gone-wrong/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-rons-gone-wrong/</guid><description> </description><pubDate>Mon, 03 Jan 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前中部分4星，后半部分3星，整体的感觉就是：在你期待更好的剧情时，这部作品却没有往那方面发展，实属可惜。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;”泡泡公司“的外观设计令人眼前一亮，而社交网络的功能很容易让人联想到当下的某公司，我认为这是一个很有开发潜力的概念。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是偏偏塞了两个脸谱化人物——一位开发天才与他的功利老板，这个人物配置老套的以至于在人物亮相之时就可以预知未来的一些情节走向与故事发展的主要矛盾。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;主角与ron相识之时的一些互动挺温馨，比如安睡时床底的暖光。其实故事基调是比较靠近校园童话的——即主角会遇到各种挫折但都是孩童成长层面的，整体基调应该一方面靠近校园生活，一方面带有现实世界不存在的幻想。但是在这部作品中偏偏有一条节奏怪异的主线：公司成员追杀主角机器人，甚至受困森林，二人一度濒死。这种“惨烈”的情节我认为在这部作品原本基调中是有些奇怪的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;结局。非常突兀，云端上传没什么问题，但是为什么主角的机器人就会因此消失呢？剧中给出的代码碎片化云云，从那位开发天才口中讲出来简直是程序员笑话。就算当时消失了，那为什么到最后主角还是孑然一身呢？明明都有现成代码+数据库，这不复生归来让我很难接受，剧情杀的未免太直白了些。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当然，为什么消失从作品的创作动机来说还是有迹可循的，因为作品中透露出的基调无疑是对泛滥的虚拟网络社交的排斥，或许还夹带着一些隐私安全性的顾虑。所以从在机架时通过监视发现“他们也很孤独”到最后的结局，也是暗指从虚拟回归现实，打破网络的隔阂。至于这个立意以我个人的观点来说，不能说是错误，因为这确实是互联网弊端的一个体现。但是也不算高明，我认为处理方式还不如《头号玩家》最后的回归现实，因为后者大家在回归现实前是真的热爱虚拟世界。&lt;/p&gt;
&lt;p&gt;最后，我感觉这部作品很可能会与《超能陆战队》做对比，虽然我认为二者除了“大白”与“小白”的设定以外没什么共同点。在我心中《超能》是有很高地位的，倒不是因为大白这个热门人物形象，而是主角hiro，尤其是在初次演讲时怯懦的试探以及随着而来的蜂鸣声，让当时与主角同龄的我感同身受，无形中提供了前行的动力。我认为在人与机器人共处的科幻作品中，如果侧重点是人那么一定要突出“人”的主观能动性，如果人大部分需求都托付给机器，那么这就是纯现实的工业世界而非幻想的科幻世界了。&lt;/p&gt;
&lt;p&gt;但是反观这部作品，我不知道是我心态变了还是什么的，以下这个观点可以与读到本篇文章的人讨论讨论：我认为这部作品中主角并没有实际的成长，如果我小时候观看这部影片，也对我带来不了什么影响。（至于讨论面向孩童的动画作品是否一定要关乎成长这就是另外一个问题了，在此先不赘述，但我的个人观点是：根据题材，如果题材是那种一波三折的个人经历故事，那么成长是必须的。）首先主角一直社恐，有了机器人后核心推动都是靠机器人完成（比如发传单，与他人互动），甚至最后结局中众人相聚长椅这一幕我也不能看出来究竟有主角个人什么功劳，电影并没有很好的给出“为什么其他人会愿意与主角交友”这个问题的答案。如果说，最后镜头是主角鼓起勇气邀请他们参加排队，侧重于个人心理描写，我认为会更好。现在的处理方式就是流于表面，诚意之中似乎有带有那么些敷衍。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>历史巨轮 | 全扩环境分析-领袖奇迹篇</title><link>https://www.ender-wiggin.com/posts/tta-leaders-and-wonders/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/tta-leaders-and-wonders/</guid><description> </description><pubDate>Tue, 30 Nov 2021 12:37:20 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是Ender。这版本是在&lt;a href=&quot;https://account.czechgames.com/tournaments/detail/3391&quot;&gt;TTA全球锦标赛&lt;/a&gt;刚刚开始时更新的，也距离上一版本过了半年。在这段时间内，虽说我个人玩TTA的时间大幅减少，但国内TTA玩家的讨论热度逐渐高涨，颇有雅典学院之风范。因此这个新版本也就集百家之长，再佐以我个人一些浅显的见解，作为一篇面向新手进阶的教学贴。&lt;/p&gt;
&lt;p&gt;如果说我每次在更新这篇文章时都有什么感想的话，那就是——
TTA的观点和理论一直在进步，不论是个体还是整体环境。&lt;/p&gt;
&lt;p&gt;同时也感谢国外爱好者打造的&lt;a href=&quot;https://ttapulse.com/&quot;&gt;天梯平台&lt;/a&gt;为二人对局提供了优质土壤，以及国内玩家对平台数据的整理与统计，5000余局的对局数据将是本篇分析的坚实基石。&lt;/p&gt;
&lt;h2&gt;AGE A 领袖&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;领袖&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;选取率&lt;/th&gt;
&lt;th&gt;胜率&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;strong&gt;孔子&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;28.7%&lt;/td&gt;
&lt;td&gt;54.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56.02&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;汉谟拉比&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;31.7%&lt;/td&gt;
&lt;td&gt;51.4%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.63&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;亚里士多德&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10.9%&lt;/td&gt;
&lt;td&gt;52.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.21&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;希波克拉底&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;26.5%&lt;/td&gt;
&lt;td&gt;50.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.84&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;孙子&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.8%&lt;/td&gt;
&lt;td&gt;51.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.72&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;布狄卡&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.0%&lt;/td&gt;
&lt;td&gt;52.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.72&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;凯撒&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16.2%&lt;/td&gt;
&lt;td&gt;47.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.75&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;无领袖&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4.4%&lt;/td&gt;
&lt;td&gt;50.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.71&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;埃及艳后&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;22.5%&lt;/td&gt;
&lt;td&gt;46.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.29&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;摩西&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4.8%&lt;/td&gt;
&lt;td&gt;48.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;45.67&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;亚历山大大帝&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.7%&lt;/td&gt;
&lt;td&gt;44.4%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;43.95&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;阿育王&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.7%&lt;/td&gt;
&lt;td&gt;44.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;41.98&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;荷马&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.2%&lt;/td&gt;
&lt;td&gt;40.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37.59&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
A时代的领袖整体差异也不大，本身选择空间也较为局限，一般就是来哪个用哪个。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;孔子&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
孔子的技能分为两部分，一是每轮稳定一科技产量，在速出法典、铁、第三轮出印刷方面都有着优异的发挥，孔子+早法典是直接获得巨大优势的开局（法典对于孔子二点拿很赚，三点未必亏）；二是对于事件的软控制，即可以控制事件牌堆中己方事件的数量，但是对于其质量未必有很好的把握（比如军事事件和殖民不一定是自己可以享受的，这也是和凯撒的一个区别）。但是如果对方是非事件型领袖，尤其是拉比这种自带少1红效果的领袖，这时候孔子就能体现出对事件牌堆的控制能力。就算你塞的都是1灯事件，只要控到事件牌堆对面也未必敢开，可以打出前期的事件压制。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;需要注意的是，是否要塞全场得1灯事件是个重要的抉择点。往往是越缺科技的人越需要科技事件，比如对面4灯突然开出一个1灯事件可能直接就打出铁矿升3个铁了。因此不论是孔子的技能，还是基础的事件【科学突破】或是扩展的事件【先祖智慧】，都需要谨慎塞入。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
时过境迁依然没人可以撼动孔子的地位，虽说孔子的塞牌可能带来一定负面性，但是整体收益确实非常稳定且高效。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;汉谟拉比&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
在国内玩家讨论中一般简称“拉比”。
基础版的最强领袖，在扩展中被砍了一刀。一白的收益理论上是A时代领袖之最，所以主要抉择点就放在了少的一红上面——面对凯撒孔子孙子这种事件压制的领袖要谨慎使用拉比，相反军事压力不大想安心做内政拉比还是不错的选择。从孔子凯撒以及拉比的胜率可以看出，早期能够控到事件牌堆的玩家是可以建立不小优势的，只是这种优势不容易量化，因此可能会被一些偏向内政思维的玩家忽略。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;拉比因为白点多，所以应该多控牌列，把对面需要的牌卡在牌列6号位（“卡6”），这样可以进一步扩大自己的优势。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，拉比与孔子神医等领袖还有一点区别在于，他对于牌列发牌是高度敏感的。比如要是第三轮的二点区卡牌都是非关键牌，那么在前几轮中每轮的收益可能只是1张A黄牌，实际上并不能达到理想化的1白点效果。白点一般来说是越往后越强，对比不同时代黄卡的效果也可以理解。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;这可能也是后文会谈到的萨拉丁强势的原因之一——虽然与拉比一样都是白点收益，但是I、II时代之交的白点，可能比拉比前几轮拿取A时代卡牌的白点收益是要高不少的。同理，拉比最适合的情况就是早期关键牌发放非常密集，尤其是有君主这种可以早期革命的政体。拉比+早政体革命，胜率也应该是很可观的。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;亚里士多德&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
在国内玩家讨论中一般简称“小亚”。
小亚是所有A时代领袖中操作难度最高的之一，会玩的内政节奏顺滑，不会玩的要么到换领袖了只赚了1-2灯，要么一手科技牌卡手。考虑到这较低的选取率以及夸张的胜率，小亚更像是老玩家们的绝活领袖，新手一般都不怎么使用。首先小亚与奇迹的相性不是那么好，因为要造多步奇迹的话就没有那么多白点去拿科技牌了，这样的话技能收益不能最大化，甚至被孔子完爆了；其次小亚可能多拖一轮就可能多获得两灯，属于偏后期发力的领袖，所以对于节奏的把控至关重要。 根据国内TTA顶尖玩家SandHippo（后文简称河马）的观点，小亚可以选择不拿A奇迹开局，如果拿的话与金字塔配合更佳。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;希波克拉底&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
在国内玩家讨论中一般简称“神医”。
神医技能收益一般两黄足矣，第三黄收益不高。神医的强度主要取决于这局I时代领袖的整体分布，如果哥伦布埃莉诺巴巴这些收益时间不确定的领袖较多那么可能神医不一定合适，反之如果萨拉丁古腾堡这些持续收益的领袖较多神医非常适合快速过渡。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;神医的缺点就是I时代不能够提供即时的收益，在前期节奏上面会略有吃亏，适合与前期互动性不强的领袖和平发展。但是当步入III时代的时候，神医省下的两黄可以发挥巨大的作用，多一个黄点就可能是多一个兵或者多一个城建。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;孙子&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
这个领袖要看玩家怎么理解了，要是你喜欢升铁做内政，那孙子对于你而言可能是零收益领袖；但要是你打法偏向控事件（殖民、军事事件），那孙子或许可以试试。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;选了孙子就应该打激进一些，3白抢个农场主也是很常见的事情。要是莽赢了，那很可能对面II时代就体退了，至于莽输了嘛…那就是另一个故事了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，孙子配合巨人像是莽夫中的莽夫，非常快乐，可以一试。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
孙子的表现在这几个月内格外突出，不少玩家也逐渐开始认可孙子的强度。虽说我使用孙子的次数依然很少，但作为DS学子只好屈服于数据。其实从我个人体验来说孙子胜在稳定，可以较好地解决军事流的风险问题，事件/殖民/阵型/侵略基本都不会缺。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;布狄卡&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
布狄卡一般来说就是延后一回合收益的孔子或者艳后，但相对灵活一些，比如想拉人但是正好缺少一个粮食的时候可以变出一粮。至于军劣2矿的技能，其实比较尴尬——前期造兵看似赚了2矿，但实际上是对于内政零提升的；更可能没有足够的人口，额外的2矿一直就这样浪费掉了，甚至会引起腐败。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;布狄卡最好的节奏应该是在有兵种以后跟对方军力然后换领袖，比如造一个马。基于以上的分析：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.在对方能选凯撒、孙子、大帝的时候尽量不要选布狄卡，因为这种情况下布狄卡的技能在前几轮很可能是白板。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.奇迹最好选择可以缓解笑脸压力的（大道、斗兽），这样有机会最大化布狄卡的效果。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;凯撒&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
凯撒的打法有两种：第一种就是纯粹暴力布局，前期通过控事件和摸取黑牌建立军事优势，I时代转其他暴力领袖诸如农场主或者巴巴罗萨进行持续施压，力求打乱对手节奏并通过可能的侵略及战争建立优势。此外凯撒与大图、巨石的相性都是非常好的，因为这两个奇迹可以暂时省出一个人口。甚至比如说在第三轮摸到了侵略牌，可以考虑不造哲学直接造兵，通过时间差赚取收益是凯撒的一大特色。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;第二种则是早期事件压制转内政/跑分，这与凯撒本身的特性有关。一般情况第一个事件牌堆并不会那么快推出来，尤其是遇到对方一直不塞牌的，如果我方黑牌质量较差甚至可能到II时代才会推出，这时候双方玩家面板和军事情况都是难以预知的。但是凯撒则不同，凯撒是有不小概率在第四轮直接推出新牌堆的事件的，这时候往往对方还在建造奇迹，未必有精力甚至根本不存在兵种去进行军备竞赛。这是一种快攻思路，通过时间差享受事件红利，同时对于对手有一定威慑作用，比如令其不敢随便塞殖民地。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，凯撒的侧重点是控制事件，而非塞军事事件。一般来说第三轮的黑牌需要酌情塞入，因为后续局势未必明朗。比如对面2点抢了兵种并开始牺牲早期内政去造兵，那么如果我方事件牌堆中都不是军事事件对于对方也是一种损失，因为我方可以利用这个时间差提升内政。第二种打法的关键点在于转型的时间以及转型的意识，比如一边推事件一边造印刷，为后续跑分领袖诸如莎翁或者达尔文布局。因此这种打法也是不建议抓取I时代兵种，反而铁和印刷比较关键，因为凯撒的3红摸牌灵活性较高，等到对面真正想要进攻我方的时候一般已经到II时代了，凯撒能够让我方做内政或者跑分安全度过I时代已经是不错的收益了。如果对方一直躺平（即不造兵）试图和平对跑的话，那么事件牌堆的收益一般情况是比其他A领袖要高的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;总的来说凯撒是非常灵活的领袖，因为他的收益建立于信息不对称的黑牌。根据对手反应制定相应策略，伺机而发找寻转型机会，正谓兵者诡道。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
凯撒的胜率掉下去了不少，其实原因也很简单，凯撒上限高但是下限也挺低，三红三阵型懂得都懂。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;埃及艳后&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
艳后具体使用不如神医孔子这么顺手，因为她需要A时代奇迹的配合。一般艳后第二轮是造一个哲学，第三轮补铜+造一步奇迹。如果配合大道的话可以直接两铜秒大道，然后在合适时机再补出第三个铜或者直接造一个铁。在TTA中资源不是越多越好的，矿多了容易腐败，只要腐败一次那么这个艳后就等于白拿了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;艳后的主要意义是她可以早一轮出奇迹，所以不要为了贪多一次技能晚造奇迹。能快速建造出来奇迹可以更快享受收益，比如提前一回合造出金字塔那就是额外一白点的收益了。此外也不要为了艳后而用艳后，即拿一些用处不大的奇迹如巨人像或空中花园。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
现在来看，艳后的一个缺点就是太循规蹈矩了，己方操作较为局限且容易被对方预判，还可能被资源事件安排。在现今强调随机应变的版本可能不够灵性。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;摩西&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
摩西的技能普普通通，提前拉人未必有关键的1灯或者1矿重要。此外，如果选了摩西就要多拉人，不要总想着2白拿牌，因此对于牌列的控制会相对被动。可能以下两种情况摩西会比较厉害：
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.偏暴力，提前拉人做军力，可以配合空花、大道等降低笑脸需求的奇迹。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.偏跑分，把提前拉出的人用于造印刷戏剧，关键是要提前预估军事压力和人口压力（笑脸）。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
国内有些玩家对摩西颇为拿手，我可以去取取经。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;亚历山大大帝&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
其实大帝不能算弱，我认为属于A时代领袖的标杆。虽然拿到不会像孔子那样春风拂面感觉“这把稳了”，但是至少是个能用的领袖，2白换一个前期黄点一般来说都是值得的。
整体思路上和神医是相似的，依赖与持续发力的一时代领袖只是大帝更加看重前期的运营，即如何利用好这个前期黄点——是压粮（让人口停在二粮区）？还是造铜？造印刷？
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;阿育王&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
人称腐败王，也是一位比较需要操作的A时代领袖。他本身的强度是很高的，借用河马的一句话，“阿育王手上拿了I时代领袖本身就是I时代领袖的强度了”。与阿育王配合最好的领袖自然是哥伦布，可以在阿育王的最后一回合（甚至离场）再换上哥伦布触发一次性效果，这样整体节奏是非常顺滑的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;阿育王需要注意的几个细节：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.卡手问题。需要提前规划好后续回合的操作，要是为了凑颜色而拿牌，很可能会一手科技牌用不出去。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.腐败问题。一般缓解腐败压力就是提前多拉人。黄牌要考虑好再拿，因为阿育王本身粮矿充足黄牌未必很关键，要是腐败了一轮那么这个阿育王就等于白拿了。此外事件尽量少塞，尤其是在4p中，突然出现的几个2粮2矿事件就可能导致腐败了。防腐败的另一个办法就是造奇迹，看到多矿的奇迹可以提前拿下（如姬路教堂大学）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;总之玩阿育王提前规划很重要，考虑到1粮1矿的资源增益我个人感觉可以试着先造炼金造兵，中期再补基建。在4p中煤矿和选种都是相当不错的，I时代灌溉什么的可以不拿，这样也可以防止卡手（2p要是没有邮轮，灌溉一般还是要拿一张的）。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;荷马&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
荷马在多数时候都不是优先考虑的对象，因为实在是不太顺手。但是在一些特定的情况我觉得还是可以凑合下的，比如没领袖选的时候选个荷马卫城接印刷，可以弥补卫城印刷路线笑脸的短缺。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;几个可能被忽视的小细节：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.荷马的笑脸给教堂并不会触发笑脸双倍效果，因为教堂是使其它卡牌笑脸+1。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.岁月摧残（又名洪流，摧毁奇迹的那个事件）不会影响奇迹上荷马附带的笑脸。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;至于荷马的军事+1矿效果，不要刻意为了触发技能而造兵，之前的“主体思想流”（即全民皆兵）我个人是不太建议在混扩版本使用的。因为早期打人并不稳妥，造了兵易缺红摸牌，即便运气好摸到了侵略对面要是有诺查丹玛斯（骗子）或者姬路城也未必侵略成功，就算侵略成功了要是对面已经3铁了损失几个矿根本也不算什么。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
荷马胜率越来越感人了，但我觉得新手也不必被胜率左右，多多尝试总有益处的，比如这个笑脸对于事件也是有不少益处的，不论是叛乱还是移民都是荷马的好帮手。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AGE A 奇迹&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;奇迹&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;选取率&lt;/th&gt;
&lt;th&gt;胜率&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;strong&gt;雅典卫城&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24.5%&lt;/td&gt;
&lt;td&gt;54.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55.14&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;大图书馆&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;39.0%&lt;/td&gt;
&lt;td&gt;53.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;54.44&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;巨石阵&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.4%&lt;/td&gt;
&lt;td&gt;50.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.07&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;金字塔&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30.6%&lt;/td&gt;
&lt;td&gt;47.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.91&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;罗马大道&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;27.5%&lt;/td&gt;
&lt;td&gt;47.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.14&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;巨人像&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9.7%&lt;/td&gt;
&lt;td&gt;47.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46.02&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;斗兽场&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;21.4%&lt;/td&gt;
&lt;td&gt;45.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;45.05&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;空中花园&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.4%&lt;/td&gt;
&lt;td&gt;46.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;43.39&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
A时代的奇迹一般不是必选项，由于一时代奇迹强度也较为不错，适当等待也是可以的。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;雅典卫城&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
卫城是个跑分奇迹，不是暴力奇迹。主技能是城建数量+1以及演变省灯，至于1军只能说在特殊情况下有一定作用（例如凯撒卫城转跑分）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;因此，要最大化卫城的收益，主要是看后续领袖。最佳配合是II时代的巴赫、III时代的黛德丽或者顾拜旦，因为单一城建类型才最有机会达到城建数量上限。至于达尔文，由于没有巴赫直接建筑升级的技能，往往场上还会剩下几个哲学，想要达到II时代4印刷这种稍微有些难度，莎翁就更不现实了（当然，5套莎翁视觉效果很好，值得尝试）。说回I时代，一般的打法是3哲学演变君主/神权，以及配合古腾堡可以达到专制3炼金或者3印刷的效果，可以最大化古腾堡的收益，非常不错。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;由于和平局后期卫城可以打出个位数科技演变民主、5歌剧这些夸张的操作，一般来说是不太建议和卫城玩家和平跑分的，因为很可能会翻车。建议遇到卫城玩家在前期要多施压，如果II时代能吓得对手把哲学都拆了那就再好不过了。我个人的观点是，和平局后手+卫城在同等操作的情况下基本上必胜的。如果是后手+卫城+马丘还在互相发展内政，那么先手玩家不如直接II时代体退了。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但是话说回来，也正是因为卫城可以让对手被迫走军事路线，己方多数时候是可以占据跑分主动权的。当你感觉双方都是偏和平路线，或者游戏中跑分资源很多，那么卫城可以很好地限制对手操作。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
有几点新心得：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1. 卫城与一时代城建适配度最高，例如配合戏剧或者配合神权印刷都是非常高效的组合。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2. 卫城属于高手局奇迹，因为一般来说实力较高的局双方决策都会更加稳妥。但如果遇到比较冒进的军事流对手，卫城的下限可能会比较低。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;亚历山大图书馆&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
老牌强力奇迹，主要抉择点是第三轮是造哲学还是大图的问题：先哲学多1灯，先大图当前回合亏1灯但是第四回合额外获得1分、多1张军事牌上限，并可以节约一个人口之后视情况选择暴力（原本哲学的人口去兵）或者跑分（造印刷，相比于之前的哲学等于额外多了1分）。理论上是后者更好，但是实际情况差别没有那么大，因为牌列和领袖会影响路线的选择。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
不否认大图很强，但我感觉在印刷玩法被开发后其实大图没以前那么关键。原版大图的优点是可以让玩家在不出炼金的情况下补充科技，但如果玩家造印刷的话，大图其实本质也是个有特效的印刷，独特性有所降低。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;巨石阵&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
巨石的关键点在于抢节奏，因为它的6点科技是可以在早期提取出来的，这就和大图长线的10多点科技有一定差别。比如说，第4轮的铁，多数情况下玩家是缺1个灯打出的，那这时候巨石额外提供的一个灯就可以发挥作用。也是基于这个原因，我认为巨石主要是按需建造，没有必要像大图大道这样因为高收益越早建造越好。要是有其它事可以做，是完全不急于建造巨石的，因为巨石建造完成不过是一个寺庙，它的核心收益都是来自于建造的途中。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;理论上，如果有很早的法典、君主、铁，巨石的收益是非常高的，但问题是对面不一定会给我们，或者说法典这些牌未必每次都出现的很早。所以这奇迹是相对赌运气的，如果说我们没有用这些灯抢到节奏，那么巨石就表现平平。但不论如何，巨石兼顾笑脸、科技、分数，也算是符合均衡的标准，所以我还是比较喜欢这个奇迹的。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;金字塔&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
金字塔唯一的收益就是白点，如果这个白点能用于拿取关键牌，比如突破、沃土，那么I时代的金字塔就可以理解为是2点产能，自然非常不错的，但是万一没有合适的牌，比如拿个I时代储藏物资，或者拿了张以后估计都不会使用的科技牌，那就可能是毫无收益。总的来说，大金胜在灵活，但是因为收益不稳定所以对于操作要求比较高，这可能也就是大金胜率不高的主要原因。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;大金配合白点高要求的领袖（小亚、阿育王、达芬奇）都很不错。此外，大金配合神权也是个比较经典的组合，神权的多种属性可以很好的辅助大金单一的白点，同时大金的一个白点完美解决了神权4白的局限性，可谓相得益彰。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;罗马大道&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
我的观点是奇迹是平衡资源面板的，这也和TTA的基本思想是一致的，即前期注重均衡发展（矿多了会腐败，科技多了可能会吃黑暗时代，缺少笑脸可能使用不了人口…），因此前期奇迹最好是要兼顾多个属性，这样利于后续的转型以及路线的选择。比如大图，同时兼顾科技、文化、白点、红点，因此使用起来是非常顺手的。而大道也类似，同时兼顾矿物科技分数黄点蓝点，而其中黄点又是可以与粮食以及笑脸相挂钩的，因此可以说大道兼顾了TTA元素的方方面面。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;相应的，大道较为强力的属性所付出的代价就是4步的建造次数，但是由于大道只需要5个矿就可以造好所以也未必是一个较大的限制。5矿与6矿是有本质区别的，不论是配合建造天赋（工天）或者2矿事件都可以在第三轮正好花费4步秒出大道，而额外的黄点可以立刻提高粮食增速，节奏非常顺滑。一般来说第三回合的定式是哲学、拉人、二白拿牌，所以秒大道的优势就是不需要花费2白点拿牌，因为多数情况下第三轮适合2白拿的科技牌只有法典、君主、铁、印刷。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;由上面的分析可知，大道的优势是快速秒出来，因此与白点较多的领袖（拉比）或是奇迹小伙伴砖石建筑有着不错的配合。同理，大道不太适合与对白点有一定要求的领袖配合，比如小亚或者阿育王。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;顺便在这里简单分析下砖石，我心中的I时代蓝卡优先级是法典&amp;gt;砖石&amp;gt;战争机构&amp;gt;制图。一些新人当谈及砖石的时候都会认为“哪里够科技出砖石呀”，但我感觉4科技的制图大家经常出的眼都不眨一下（比如以前的我）。一般来说适合砖石的情况是，A奇迹没造完（大道、造了一步的大金）或者准备造下一个多步奇迹（丝路、紫禁城）。此外，如果II时代有轮船、铁路、运河，那么砖石的价值还会提升。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;巨人像&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
因为巨人像没有任何内政加成，所以如何把军事转换成内政与分数就是我们主要要思考的问题。主要用法有两种：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一是抗压，比如对方选择了凯撒孙子这种极力想要打死我们的，而I时代又以暴力领袖居多，那这时候选择巨人像可以让对方不敢肆无忌惮地塞暴力事件。但是这种思路的缺点是需要I时代兵种较晚出现，不然光靠巨人像的2军力是远远不够的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;二是进攻，属于比较激进的打法，主要也是配合凯撒或者孙子转I时代军事领袖如农场主、巴巴。通过控制事件牌堆干扰对手的内政，并通过军备竞赛建立优势。巨人像的一大优点就是，不怕对手躺平。在刚刚进入II时代这一轮，额外补充的3张黑牌是有概率摸到阵型、侵略甚至宣战的，比如II时代第0轮的谍战基本可以奠定游戏胜负。因此要是选择进攻打法我觉得应该孤注一掷，力求做到“II时代中期一定结束游戏”这个景象。此外考虑到阵型的问题，建议是要摸齐I时代兵种，这样不论是古典还是西班牙都有不错的发挥。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;还有几个小思路，比如转殖民路线、摸黑牌配合哥伦布、等III时代黑牌一拳……但是真正核心的进攻打法，我觉得对于手牌管理和局势把控要求是非常高的，因此我建议新手慎用这个奇迹。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
巨人像最核心的一点是可以降低军事路线的方差，和孙子性质是类似的。尽量在一时代进二时代那一轮提前拉高军力差等待三张II时代黑牌的到来，这个时间点非常关键。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;斗兽场&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
从模型考虑的话，除开关键的红点，斗兽同时兼顾笑脸、科技（一般来说4-6点）以及分数，属性也是可以接受的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;红点讲究凑够3个，2红对于事件及军事控制力太弱，3红不论是摸牌还是抗压都有着不错的效果，而4红以上防守绰绰有余，可以考虑打人的问题了。因此斗兽适合专制（配合法典或者萨拉丁）转II时代政体，尤其是配合共和。但要是我方选择了I时代政体或者与I时代的红点领袖配合，那么斗兽的这个红点就面临边际效用递减的问题了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;因此，斗兽的属性本身是可以接受的，但是究竟能不能最大化收益就需要领袖与卡牌的配合了，加上军事流的没落，侧重军事方面的奇迹可能适用性未必广。比较适合斗兽的领袖是，A时代孙子、孔子，I时代萨拉丁、巴巴罗萨、成吉思汗、埃莉诺、骗子。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;空中花园&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
空花在新增了2粮食的效果后，更像是个暴力奇迹了。因为一般来说奇迹能带来即时的收益是最好的，而空花的第二个笑脸要在步入II时代才能体现出优势。基于这个考虑，想要发挥出空花的收益就应该对人口做文章，和平方面的话空花印刷不错，多了2粮食有机会快速造出第二个印刷；暴力方面的话配合巴巴罗萨这种最好，完美利用笑脸和粮食，可以考虑全是A时代基建直接在II时代初打死对手。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;除了收益延后这个问题，空花的2粮食也未必是正收益，很可能腐败因而只是2矿换2粮罢了。总的来说空花属于食之无味型的奇迹，我建议尽量不拿，因为I时代厉害的奇迹可太多了，万一来个了很早的教堂岂不是比空花高到不知道哪里去了。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
看下来其实A时代奇迹除了卫城和大图其它都差不多，至于如何决策主要还是看发牌与配合。即便是空中花园，本身属性也是不错的，我还是建议新手多多尝试，不要被数据限制了思路。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AGE I 领袖&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;领袖&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;选取率&lt;/th&gt;
&lt;th&gt;胜率&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;strong&gt;古藤堡&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;17.9%&lt;/td&gt;
&lt;td&gt;56.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56.97&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;诺查丹玛斯&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;23.7%&lt;/td&gt;
&lt;td&gt;53.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;54.25&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;埃莉诺&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8.2%&lt;/td&gt;
&lt;td&gt;54.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.99&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;萨拉丁&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24.8%&lt;/td&gt;
&lt;td&gt;51.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.59&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;巴巴罗萨&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10.6%&lt;/td&gt;
&lt;td&gt;52.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.03&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;圣女贞德&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;21.9%&lt;/td&gt;
&lt;td&gt;51.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.71&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;达芬奇&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.3%&lt;/td&gt;
&lt;td&gt;51.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.65&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;哥伦布&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11.2%&lt;/td&gt;
&lt;td&gt;49.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;49.01&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;杰士卡&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;31.5%&lt;/td&gt;
&lt;td&gt;46.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.67&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;成吉思汗&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.3%&lt;/td&gt;
&lt;td&gt;44.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.55&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;伊莎贝拉&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.8%&lt;/td&gt;
&lt;td&gt;42.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.52&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;米开朗基罗&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8.6%&lt;/td&gt;
&lt;td&gt;43.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.03&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;无&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4.2%&lt;/td&gt;
&lt;td&gt;41.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37.54&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
I时代领袖承上启下，存在一定的路线分化，我更倾向于将A时代领袖与I时代领袖看做一个整体，一次性的A领袖配合持续收益时间较长的I时代领袖，或者本身强度尚可的A领袖配合短期快速收益的I领袖，都是不错的思路。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;古腾堡&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
在预览一局游戏的领袖奇迹时，要是感觉这局前期偏和平可以直接提前两白拿好古腾堡（按河马的意思可以三白，不过我感觉这领袖目前挺多人一白都不一定拿233）。古腾堡的上手难度偏高，需要提前规划好每一步的路线，可以实现内政和科技建筑两开花，或者军事和科技建筑两开花。至于技能方面我感觉还是贪一贪比较好，比如晚一回合造印刷，虽然亏了1灯1分，但是在下一回合可以节约1白1矿并提供2分。古腾堡的技能本质是提速内政，可以在相同的回合中做比对手多很多的事情，要是打出上限的话就是每回合1白2分以及1灯/1矿，收益极高。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;几种路线：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.炼金：最好是能够造出3炼金，因此I时代政体或者卫城都是古腾堡的好帮手。3炼金足够从I时代早期操作到II时代中期并换上强势领袖比如达尔文。造不造炼金主要看III时代科技系领袖比如盖茨席德梅尔或者居里乔布斯，要是科技系领袖多炼金的抓位会提高。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.印刷：一般2哲学2印刷或者1哲学2-3印刷。主要是为了II时代跑分领袖准备，即莎翁和达尔文。古腾堡省下的白可以很好地为提前抓戏剧（配合莎翁）或者面包（配合达尔文）做准备。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
我感觉新手多数不喜欢古腾堡，当然我自己以前也是看不上的，不过这个胜率完全值得大家多多尝试。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;当对局质量提高了，需要思考的就是如何反制古腾堡了。我个人观点是要从事件入手，因为古腾堡往往规划好了后续多个回合的操作，对于古腾堡玩家而言这叫做算无遗策，但是从对手的角度出发其实也更容易预测古腾堡每一轮的操作。如果古腾堡一边因为节约白点建造建筑而感到沾沾自喜，另一边人口因为事件溃败连连，那可能净收益就没那么高了。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;诺查丹玛斯&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
骗子完全不需要前置还有着高额的收益。军事局可以抗压，和平局可以偷偷塞一些军事事件安排对面，顺便每轮还能+3分。如果对方有军事的野心我方可以考虑2白拿骗子，这样可以安安稳稳地做内政。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;至于反制的办法，最常见的方式我觉得应该是选非军事领袖做内政或者跑分（比如米开教堂、达芬奇古腾堡印刷），甚至可以直接躺平。因为骗子自己对于内政是零加成的，你躺平的话对面的骗子就会陷入两难——要是安安心心塞事件内政跟不上，这个领袖就变成了单纯的跑3分；要是做军事准备压制的话一方面自己少红难摸黑牌，另一方面要是侵略亏了技能的3分。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，另一种反制的方式就是军力远高于骗子，让他忌惮于事件牌堆的负面事件，在绝对的力量面前，假军力又有什么用呢。并且假军力并不能防止战争，所以如果运气好来个早战那么骗子也就形同虚设了。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;埃莉诺&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
埃莉诺也是对游戏核心机制的一个体现，即事件与黑牌。我认为这是个上限不错的领袖，但是也有一些前置：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.需要军优或者是控事件能力强的A领袖转型。先说军优问题，这也是导致不少玩家不喜欢埃莉诺的直接原因。要是对方走军事埃莉诺就会相对难受，因为可能I时代的牌堆中有军力事件，而埃莉诺是没有军事加成的，很难与其他领袖在军事上抗衡。对于这个问题有两种解决方案：一是重骑兵，这个阵型是巴巴强大的原因之一，同理对于埃莉诺也是非常不错的，使其具有更大概率建立军事优势，同时埃莉诺与殖民的良好相性可以把之前没有用的小兵殖民掉提升节奏；二是靠控事件的领袖衔接，比如凯撒或者孔子。要是牌堆中殖民和双坑（或者只坑对方）事件比较多的话，换上埃莉诺也是个不错的选择。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.红点。埃莉诺3红是比较舒服的，因此斗兽和I时代政体都是可以考虑的。埃莉诺的主要发力时间是II时代，一方面摸阵型走军事路线，一方面摸殖民地和D卡走殖民路线。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;我觉得埃莉诺比较适合的是过渡使用，即用一个较为强力的A领袖换上埃莉诺，在塞出合适的阵型后跳车换上II时代领袖并可以顺手拿些关键牌。这个思路在哥伦布那一部分已经提过，这边不再赘述。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;最后说一个小tip:&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;埃莉诺可以靠II时代领袖与政体达成行动后革命的操作，因为换上领袖是可以返3白的。比方说手持政体可以拿个1点的领袖，拉个人，再换领袖革命，算是个比较灵活的combo。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;萨拉丁&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
萨拉丁的技能其实也算是TTA一个核心机制的缩影，即内政和军事的取舍——2军力的机会成本是1白点，但可能因为一个冲锋号角事件（军力最低的玩家失去2白点并抽取2黑卡）损失2白，因此这就涉及到了取舍问题了。对于我而言，我觉得除非实在是生死攸关之时，不然还是老老实实地获取一个白点算了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，白点也是有边际效应的，4白到5白是质变，8白到9白可能收益微乎其微。前期有一种打法就是6白或者7白打4白的白点压制，但是往往在前期获取大量白点是要舍弃很多东西的，比如文化增速和科技增速。你前期靠金字塔、紫禁城、法典萨拉丁等辛辛苦苦凑出的白点优势，可能会因为对面的早II时代政体而荡然无存。此外，白点需要与自身的经济相匹配：比如你是3计算机的盖茨，那么也许8白以上才可以大展身手；但是你要是在I时代，那么你的人口、矿物、科技都会是很大的制约因素，过量的白点只会让你没事干而被迫拿取2点位的卡牌，那么事实上这些白点的收益就更小了。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
欲戴皇冠必承其重，享受了白点丰裕的随心所欲，也就需要弥补白点所不能提供的额外需求，比如人口笑脸科技分数。因此如果在一时代白点就达到了7个甚至7个以上，那往往是需要警惕的，玩家很可能存在多个明显短板，而牌列中的卡牌可能并不能解决这些问题。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;巴巴罗萨&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
众所周知，巴巴是个内政领袖（手动狗头）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;巴巴的上下限差距也很大，因为他的技能不像萨拉丁贞德等领袖是稳定而持续的收益。想要最大化巴巴的技能收益需要提前布局好以下几个方面：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.粮食。这是巴巴技能的核心前置，粮食一定是必不可少的。但是灌溉并非是硬性需求，也可以选择3农或者靠A时代领袖囤积大量粮食（例如大帝压粮，即把粮食消耗卡在-0的位置；或者阿育王，在腐败压力较大的时候换上巴巴出兵）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.红点。这是巴巴技能收益的保证。选择巴巴无非有两种目的：一是节约出兵的白点同时缓解军事压力，二是出兵用于军事进攻或者殖民。但考虑到巴巴很容易拥有比对方更多的人口，后者用法是相对常见的。那么不论是造兵的红点，还是用于摸侵略和殖民的红点都是必不可少的。因此这种路线需要靠I时代政体或者斗兽场及战争机构来补充红点。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;3.笑脸。首先明确的一点是宗教并不能缓解笑脸压力，因为它仅仅是一人口换1笑脸而已，所以主要是靠奇迹或者I时代的面包马戏补充笑脸。这把顺便再提一下面包这张牌，它在有达尔文和顾拜旦的局收益是非常可观的，和巴巴的相性也非常不错。由此也可见斗兽场是最为契合巴巴的奇迹了，同时解决红点和笑脸问题，可以很好地打开节奏。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，最适合巴巴的阵型是3马的重骑兵。常言道：遇事不决，六马骑脸。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
巴巴人称军事版古腾堡，越需要操作的领袖上限越高，这是符合事物发展基本规律的。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;圣女贞德&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
贞德各项属性非常均衡，可谓暴力跑分的典范。其中一种打法是贞德寺庙转巴赫戏剧，可以全程保持一个高跑分状态。并且在和平环境中贞德相较于农场主的优势是不需要强制出兵种凑阵型，因此贞德可能只需要一个寺庙保持军优享受事件红利就行，观星效果隐性收益也是不可小觑的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但是要是一局游戏中有达尔文莎翁顾拜旦等领袖，宗教建筑的价值会相对降低，这时候选择贞德出宗教可能就与这几个领袖绝缘了，因此贞德的抓位会略有降低（当然就算不要宗教直接1红1分看事件的效果过渡一下已经不错了）。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
其实贞德就是我前文所说的白点巨兽的对立面，可能白点不多但是其它属性样样俱全，动不动一时代就是+5跑分。军事文化事件无一落下，内政再徐徐图之。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;达芬奇&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
一般的思路是先炼金/印刷，然后出政体、蓝色科技、灌溉，最后补铁/煤。最后出铁/煤是因为缓解腐败压力，如果先升铁甚至可能会出现本来不腐败，打出科技后腐败2的负面效果。以我个人使用而言，印刷是比炼金要舒服的，一是可以配合后续领袖，二是可以补充一下前期分数。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;这个版本是跑分的版本，纯内政打法由于缺少分数很容易被跑分流直接跑死。要是内政流III时代才开始发力的话，遇到白点多的对手（比如达尔文）很容易17回合就结束游戏了，可能根本不够时间做出可以一拳打死对面的军力。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，由于黑暗时代的出现，现在对于科技的计算更加重要。大量溢出的科技是一个非常危险的信号，尤其是你只有4白的时候。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
达芬奇上手难度比古腾堡要高，因为白点比较紧张。另外我一向不太喜欢灌溉铁矿炼金都造的面板，因为比较拘束不够灵活，对于达芬奇尤其如此。达芬奇完全可以放弃铁矿缓解白点压力，后续煤矿看情况抓取，如果对方3白卡煤矿，那就等于啥事不干让对方损失3白，效果拔群！
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;哥伦布&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
其实新扩加入后哥伦布是变厉害了的，因为一方面多了些较为强力的A时代领袖可以用到I时代晚期（孔子、阿育王），另一方面II时代还新增了一张非常厉害的红白地，提高了哥伦布殖民上手的概率。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;I时代三黄地无疑是首选，其它几个地除了笑脸地也都还可以接受，不过2军力地在II时代再吃收益会比较高（然后吃了后发现摸上II时代的强力殖民地而心态爆炸233）。I时代的地就是越早吃收益越高，因为可以尽快提升内政节奏。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;现在等II时代地也不失为一个选择，当然主要是需要强力A领袖度过前期。总而言之这就是个赌狗英雄，II时代第一轮吃II时代的红白或者拉人3黄地的快乐谁用谁知道。此外有一个小combo是哥伦布巨人像，有机会在II时代第0轮直接吃到殖民地，属于赌狗中的赌狗打法，可以一试。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;杰士卡&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
其实我觉得农场主的定位很像建造天赋（工天）：本身属性是非常强大的，但也正因为强于一般卡，玩家会被迫花更多的白点去卡这张牌。正如多数人2点卡工天一样，要是农场主不卡放给对面很可能就是3中古骑脸了。但是相应的问题就是，自己可能并不需要农场主（手上没有合适的阵或者兵种），这也是农场主胜率较低的一个原因。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;农场主常见的两种路线：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1. 军事流农场主，最合适的情况是3中古，可谓是军文双高的典范，I时代的13军很可能会让对手选择躺平，而农场主的1红更容易在II时代摸到侵略和战争，是对付躺平的一个有效手段。传说中的梦幻combo就是孙子中古农场主，能够凑齐的话高概率在II时代就带走对手了。但这个组合的弱点也很明显，万一农场主刷在对面回合，那可能之前的规划都是在帮对面布局了。此外，这个打法主要针对的是躺平路线，如果对面是巴巴这些那可能压制是不存在的。军事布局的农场主理论上可以收益到II时代末期，不过注意的是尽量不要为了贪额外的几点军力打出后续不好使用的II时代阵型。可能对面的布局比你更适合这个阵，一旦过完II时代，就可能是对面一转攻势了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2. 过渡流农场主，有点像贞德，最合适的是步兵阵型，可以考虑出3农不用额外造兵达到2军2分1红的效果，后续的转型也相对灵活，是我个人目前环境较为推荐的农场主打法。但是与贞德相比在事件方面没那么舒适，算是各有千秋。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
选取率依然很高，但胜率是真不行，版本陷阱了属于是。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;成吉思汗&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
大汗的打法思路可以参考巴巴那一篇，即抗压做内政或者重骑打人。大汗最厉害的时候是3步兵7军力那一波，要是能一直靠这几个兵3跑分还是可以接受的。但是他打人的问题就是没红点，这就是和农场主区别最大的地方了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;总的来说，这确实是个平平无奇的领袖，更难受的是选了他II时代都不方便转型，因为换了领袖军力会掉一大截。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
成吉思汗的技能特性让他更像一位事件-跑分型领袖，而非侵略-宣战的纯暴力型领袖。所以我觉得相较于暴兵打法，不如选择3兵7军轻跑3分。蜻蜓点水，大象无形。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;伊莎贝拉&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
这领袖属于初一看感觉还不错的类型，但不论是实际感受还是胜率统计这领袖都相对不强。我觉得主要原因有二：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.主技能每轮一矿实际上也就是A时代领袖的收益，在前期来看甚至还不如孔子的每轮一灯。虽说附带的2蓝可以缓解腐败，但像萨拉丁古腾堡巴巴这种直接提供或节省白点的领袖明显更能处理腐败问题。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.矿物同样也是有边际效应的。前期来说6矿足矣，在这个双方普遍升铁的环境下，伊莎贝拉很多时候所带来的仅仅是6矿到7矿的提升，甚至是可以忽略不计的。更何况7矿腐败的压力还挺大的，只要腐败一次又等于是零收益的领袖了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;基于以上两点，伊莎贝拉也比较适合灌溉开局，3铜变4矿还是一个比较大的提升，然后尽量靠殖民打出优势。至于花矿换殖民点数的效果，我觉得是个挺有意思的技能，例如3矿换2点数等于是出了一个剑士但是不用损失人口。综合来看，伊莎贝拉只能算是中规中矩，很难为自己的对局提供多少关键性帮助。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;这里顺便说一下矿物产量的问题。3矿是基本需求，适合多拿黄牌，不适合贪奇迹；3矿到6矿是个质变（即铜升铁），因为6矿具有建造后续奇迹的能力；6矿到8矿是个小突破（马丘3铁），因为可以每回合一个II时代的城市建筑；14矿也算是个爆发点（铁路单石油，一般靠瓦特达成），因为从此矿物几乎不再是个限制，可以为所欲为地造III时代城建或者做军事准备一拳。总之不同矿产有不同矿产的玩法，虽说3铁是目前主流，但并不意味着其他打法就有明显缺陷，根据自身矿产明确定位还是很关键的，比方说3铜造哈佛、A农3铁玩印刷戏剧莎翁这种就是资源失衡的体现。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
卡牌桌游第一定律：卡牌门槛越低，收益越低。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一般来说这是符合数学规律的，如果说门槛又低收益又高，那这张牌往往是不平衡的。轻则玩家无脑抢，重则怒喷设计师。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;米开朗基罗&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
最近看了几部关于米开的纪录片，对这位天才有了更深的认识。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;说回领袖本身，有些人有种误区就是看到对手选了米开就想打死他（包括后面会写到的莎翁也是如此）。这可能是打多了原版这种暴力环境导致的。事实上米开本质是个内政领袖，跑分属于是附带的效果，想要打死还是有些困难的。米开的几种路线：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.奇迹流。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;米开奇迹省白的效果相信大家都觉得挺有趣的，达成七大奇迹的时候想必也是挺有满足感的吧。这个路线一般来说是3铁甚至4铁，最好是佐以砖石建筑。砖石这张牌本身强度是非常高的，尤其是在I时代有丝路，II时代有轮船铁路这种多步奇迹的情况下。这个路线需要注意的是不要为了造奇迹而造奇迹，首先我们要明确奇迹的意义——节约人口（空花、铁塔）、弥补短缺（轮船、铁路）、提供一般卡牌所没有的特殊效果（教堂、姬路城）。因此要是遇到了自己未必需要的奇迹的话，我的观点是尽量不要贪米开的技能，除非矿特别多且实在没别的事干。要是拿了不想要的奇迹且造不完，过一轮正好刷出自己需要的，那明显是非常亏的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，拿取奇迹时需要考虑后续收益。如果刻意贪奇迹，那么一方面可能奇迹生效的时间太晚或者意义不大，比如III时代初造铁塔，最后可能只能换个十多分；另一方面会限制III时代奇迹发挥的空间，因为目前III时代奇迹是得分大头。且不论和平局40分+的好莱坞，就算是红十字快餐帝国大厦这些都能提供20分左右的收益，单从分数来看我觉得是远优于晚期的II时代奇迹的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;当然，有个传说中的combo是米开配联合国，奇迹本身的收益都可以提供24+分数，但是代价嘛……&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.跑分流。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;关键奇迹是教堂，这牌可以为米开在I时代就带来10+的跑分，让一个寺庙可以匹敌歌剧。另一种思路是米开面包，在III时代有顾拜旦的局效果更佳。但是同样，我也是不建议为了跑分而跑分，因为前期还是有很多其它事要干的，估计“见到米开一律打死”就是来自过于盲目的跑分流，即不要科技不要白点就是在快乐跑分。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;至于为什么把米开排在较为靠后的位置呢，因为我觉得奇迹和城建是偏对立的关系。比如我心中最纯粹的跑分流（卫城莎翁好莱坞），事实上是不需要什么奇迹就可以打出很高的上限的。从数学模型来说的话，肯定是同时调用人口、矿物、科技的城建路线性价比高于单纯用矿的奇迹路线。奇迹的优势是弥补短缺（比如邮轮补人口、2灯造哈佛）并把精力放于其他方面（比如造铁塔的暴力跑分打法），而城建是强化长处，即同种城建数量越多越厉害（比如3新闻）。再者，曾经米开引以为傲的文化增速，在II时代城建跑分领袖群雄割据的当前版本，未必有多突出。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AGE I 奇迹&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;奇迹&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;选取率&lt;/th&gt;
&lt;th&gt;胜率&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;strong&gt;丝绸之路&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10.4%&lt;/td&gt;
&lt;td&gt;54.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53.74&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;大教堂&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;23.6%&lt;/td&gt;
&lt;td&gt;52.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.81&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;泰姬陵&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.6%&lt;/td&gt;
&lt;td&gt;52.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.01&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;大学&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19.3%&lt;/td&gt;
&lt;td&gt;50.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.94&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;姬路城&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;32.6%&lt;/td&gt;
&lt;td&gt;50.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.86&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;马丘比丘&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20.9%&lt;/td&gt;
&lt;td&gt;50.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.47&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;紫禁城&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;17.6%&lt;/td&gt;
&lt;td&gt;47.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.48&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;长城&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.6%&lt;/td&gt;
&lt;td&gt;47.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46.69&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
I时代的奇迹其中几个都拥有2分以上文化增速，而早期奇迹跑分非常重要，也是高白点面板取得分数的重要途径，如果条件允许争取拿下。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;丝绸之路&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
我心中的I时代第一奇迹，但论适用性可能不如教堂高。丝路的完全体是每轮2分1灯1其它资源（视黄牌决定，一般是矿或者灯），论收益无疑是I时代的佼佼者。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，丝路特殊在5步7矿的建造方式，并且第一步建造后效果可以立刻生效。基于低廉的矿物消耗，丝路作为首发奇迹也是非常不错的选择，甚至可以说当第二/三轮看到丝路时可以直接放弃前面的奇迹等待丝路的到来。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但是相应的，丝路最大的缺点就是5步的建造步数，并且中间1矿的花费并不能解决腐败问题，因此玩家对于白点需要比较精算。基于这一点，丝路与砖石是绝配（我个人认为砖石丝路赢一半），领袖方面与拉比和萨拉丁相性比较好。如果只有4白是不建议强行选择丝路的，一方面建造压力大，另一方面丝路对于黄牌是有一定要求的，那其实对于白点管理又有更多的限制了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;顺便提一下以免有些新人不知道，丝路的技能对于并列的卡牌效果是可以同步触发的，比如爱国主义这张牌，那么除了额外的1矿还能提供一个额外的红点，在战争回合可能有着出乎意料的效果。文化遗产和A时代的储藏同理。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;圣彼得大教堂&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
之前看到有新手问怎么解决笑脸问题，首先靠不满工人卡笑脸也是可以的，如果要追求实质性的解决方案那么主要就是笑脸奇迹或者笑脸建筑，而其中教堂是最强的笑脸奇迹。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;原版的时候教堂的建造步数是44，因此是毋庸置疑的I时代第一奇迹；而在扩展中被小砍了一刀，变成了161的步数，6矿那步难造，而1矿的步数容易腐败，总的来说强度还是略微弱了一些的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但不论如何，几乎永久解决笑脸问题这个特效太诱人了，可以选择I时代配合贞德小庙，或者II时代戏剧巴赫。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;顺便一提，可以发现一I时代排位高的三个奇迹共性是都有2跑分，这也说明前期奇迹跑分是比较关键的。我个人认为比较普适的打法就是前期靠领袖和奇迹做内政并顺便跑分，中期视情况选择跑分/军事的转型。但不论如何，前期跑分还是必要的，因为要是纯内政的话分数可能被对面强力I时代奇迹拉开了几十分，并且这些奇迹对于内政也是有不少加成的，到III时代要是分数落后太多对于己方路线的选择就会比较被动。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;基于以上的分析，我个人认为姬路城和教堂都是值得看情况在二点位抢的，因为对于后续打法会有比较大的影响，并且建造步数较少，腐败压力不大。至于丝路，主要是考虑到白点的限制，要是是第二个奇迹，那么花费3个白点拿取意味着后几轮可能会疲于应对腐败，所以一般情况还是看情况在一白区拿取。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;泰姬陵&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
我还记得我刚开始看到这个奇迹时，心想“我有这些矿和白点，为什么不去提升内政而是浪费矿物跑分”，现在回头看我当时算是纯粹的内政思维，就想着提升资源增速、获取更多白点，对于前期分数一般都是不屑一顾。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;所以我认为理解泰姬陵算是学习跑分的开端，因为泰姬陵算是代价最小的跑分卡牌了。跑分一般是在牺牲部分内政的情况下获得分数，因此主要的抉择点就是“牺牲的这部分内政”，即对手领先我方的内政，能否转化为对方的胜势，在后期追回我方的文化优势。比如科方和新闻，前者占据了人口优势和轻微的科技优势，而后者有跑分优势，那么就是看科方玩家的科技和人口能不能找到突破口了（比如刷影响、宣战）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;那么为什么说泰姬陵代价最小呢，因为一般玩家都是0白点拿取泰姬陵，并且一般都是有铁才会选择的。对于铁开玩家，游戏中期很可能会出现腐败，那么这时候建造泰姬陵很可能正好解决了这个问题，等于3白6-8矿（可能解决腐败）换3增速，要是跑了7轮就有21分了，约等于一个III时代奇迹的收益。因此，我们需要抉择的是，这3白8矿用于做别的事能不能取得同等收益。以我观点来看多数时候是很难的，比如白点溢出拿了几张卡手的科技牌，或者拿了I时代储藏这种本身亏白点的牌，那么白点就浪费掉了；再者矿太多没事干腐败了2轮，那么矿物也浪费掉了。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;大学&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
大学也与建造时机有关，比如要是在I时代晚期拿取大学，那可能正在建造的时候II时代的哈佛都已经出来了，相比于大学凭空额外多出1灯和一个特效，因此拿取大学这一步相比于哈佛无形中就已经亏了不少了。TTA对局中看重的不是自己不亏，而是要比对方赚，因此作为常规模型的大学，我个人认为在许多时候表现可能都不算太理想。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;当然，来得很早的大学，或者是矿物溢出可以立刻造出来的情况下，我认为还是值得拿的。有一种思路是单哲学配合大学达到3灯增速，省下来另一个人口可以用于军事施压或者4铜，也是一种思路，可以试试。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;姬路城&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
姬路城可以理解为I时代的甘地，可以说是直接改变了游戏环境的奇迹。一般来说如果对手选择了姬路城那么就不建议己方走军事压制的路线了，因为中期根本打不死，分数还会因为姬路城的2增速慢慢被拉开。所以当己方准备选择II时代的跑分领袖，比如提前拿好了戏剧等待巴赫时，建造姬路城让对手陷入“跑又跑不过，打又打不死”的两难境地，可以说基本奠定了游戏的胜利。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;上面的讨论是对于防守方的，这也是姬路城的主要用法。对于进攻方而言姬路不是最好的选择，因为除了最后一轮的文战，前面多数情况的牺牲都是不划算的，选择教堂缓解笑脸压力或者泰姬陵暴力跑分或许都是更好的选择。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;因此，这也是我前文提到的“跑分潜力”的问题了，如果一方造了姬路城，另一方就可以考虑更为纯粹的跑分，因为姬路城要是不能发挥特效不过2军2分，自然不如丝路教堂等有额外内政收益的奇迹。让局势从自己是进攻方变为防守方，这种策略的转变还是挺值得深入研究的。防守方可以占据主动权，这也是卫城目前环境发挥较好地原因之一。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
与卫城相反，姬路城属于新手局（比如比赛初赛）优质奇迹，基本等于一份保险，因为一般来说新手不容易把握跑分时机，会倾向于造兵，这时候姬路城铜墙铁壁，万无一失。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;马丘比丘&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
大内政流的核心奇迹之一，打出上限可以做到人口和矿物不再是操作的限制因素，每轮正好1人口8矿造一个II时代城建，后期威慑力很强。但相应的对于科技和白点仍保持着较高的要求。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;我认为：先铁后马丘再灌溉&amp;gt;先马丘后铁再灌溉&amp;gt;铁马丘无灌溉≈先农铁双升后马丘&amp;gt;&amp;gt;农开马丘。其实马丘和铁的先后关系不是很重要，主要需要考虑的是科技和白点的问题。因为当完成马丘并升完铁后，游戏往往已经步入II时代了，那么此时科技大概率是要为政体准备的，同时农铁马丘可能会卡科技因而陷入拉人腐败空过的循环，导致马丘只算是负收益。至于灌溉，首先我觉得配合A农3增速基本前期也够用了，如果早期灌溉马丘的话一定是由于过量的粮食增速疲于拉人，因此灌溉优先级不高，在科技压力不大后打出是更好的选择。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;当然，以上讨论的主要是针对没有额外科技及白点获取手段的情况，比如要是达芬奇，科技根本不是问题，那么农铁印刷马丘全造了也行，II时代甚至还有余力演变政体。A时代卫城，I时代马丘，都是后期可以发挥很大作用，足以左右局势的奇迹。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
马丘属于重装奇迹，厚积薄发。对抗马丘玩家，要利用时间差干扰对手，抓住对方的薄弱点。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;紫禁城&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
影响紫禁城强度的主要因素是建造时机。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;要是是来得很早的首发奇迹，那多出来的伪2笑脸效果一般是完爆金字塔的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但是要是在I时代晚期，甚至II时代牌堆已经翻出才出现的话，那么实际收益是存疑的。建造时机的问题我在大学那一篇也会提及一下，因为我认为这对于奇迹的选择是至关重要的。一般来说奇迹越快造完越好，因为奇迹的收益是大于同时期其它卡牌的，越早造完越能享受收益，并且越后期白点价值会越高（1时代白点拿黄牌理想收益一般是2矿，到2时代就变成3矿了）。此外，相较于前3个奇迹，紫禁城是没有任何分数加成的，因此如何把白点和笑脸优势转化为分数也是需要思考的问题。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;至于配合的话，4步的建造是可以完美契合砖石的。此外，紫禁城神权，或者不拿去笑脸建筑过渡到II时代配合达尔文，我认为都是不错的选择。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;长城&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
我觉得长城的唯一打法就是爆剑士，配合巴巴大汗红点领袖更佳，有步兵阵型最好。虽说II时代存在炮炮阵，但是谁能保证拿取长城后又能摸到炮炮呢，因此一般情况就是堆剑士，到II时代如有步步炮的防御阵那就补炮，如果没有那就9剑士、12剑士。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;拿了长城就应该暴兵打死对面，不然这4步9矿的昂贵奇迹岂不就是大号的寺庙了。基于这点，红点和好运都是必要的，如果脸黑摸不到侵略和战争建议放弃这个套路。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AGE II 领袖&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;领袖&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;选取率&lt;/th&gt;
&lt;th&gt;胜率&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;strong&gt;莎士比亚&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24.4%&lt;/td&gt;
&lt;td&gt;56.4%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;57.66&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;巴赫&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30.8%&lt;/td&gt;
&lt;td&gt;56.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;57.65&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;特蕾莎&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9.3%&lt;/td&gt;
&lt;td&gt;53.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;罗伯斯比尔&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11.6%&lt;/td&gt;
&lt;td&gt;50.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.40&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;达尔文&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;23.2%&lt;/td&gt;
&lt;td&gt;49.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.33&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;拿破仑&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;18.5%&lt;/td&gt;
&lt;td&gt;49.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;49.21&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;叶卡捷琳娜女皇&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11.8%&lt;/td&gt;
&lt;td&gt;48.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.36&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;牛顿&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.0%&lt;/td&gt;
&lt;td&gt;48.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.32&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;诺贝尔&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24.0%&lt;/td&gt;
&lt;td&gt;46.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46.62&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;高迪&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;13.3%&lt;/td&gt;
&lt;td&gt;44.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;43.97&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;库克&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9.8%&lt;/td&gt;
&lt;td&gt;43.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.55&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;瓦特&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.1%&lt;/td&gt;
&lt;td&gt;42.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38.22&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;无&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.3%&lt;/td&gt;
&lt;td&gt;35.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;32.45&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
II时代领袖无疑是游戏的核心抉择点，强度普遍不错且有着较为稳定的发挥空间，跑分和军事都有发力点，一般从游戏开局就要为之做一定规划。但是由于圣像破坏这张事件牌的存在，抽到这张牌的玩家可以利用信息差预谋一些特殊布局。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;莎士比亚&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
跑分界的拿破仑。最强跑分领袖，但是相应的布局难度较大：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.提前拿好印刷戏剧。这属于奔着莎翁去的板子，一般可以在I时代造好2个左右印刷，然后II时代等待莎翁的到来。如果可以的话配合砖石建筑更佳，这牌配合莎翁可以使印刷0费用，戏剧1费用，因此限制这种莎翁的唯二因素就是人口和政体。早莎翁在军事压力不大的情况下可以快速摆出2套，这会让对手非常被动。当然，新闻或者歌剧也是备选项，不过主要抉择点是不拿印刷的话新闻非常容易被卡。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.人口与矿物的准备。莎翁的人口重要性远大于矿，因此要是这局确保可以拿到莎翁的话，3灌溉3铜这种侧重于人口的面板是优于升3个铁的。升铁的主要意义是造II时代奇迹，或者是做II时代兵种/城建。但是本身莎翁是不太需要奇迹的，关于城建和奇迹的选择我在之前篇章已经讨论过了，比如说莎翁就算拆2哲学造1套，也是花费4白3矿换取-1灯和5分增速，这收益是远高于所有奇迹的。而至于城建的话，莎翁配合建筑学可以4矿造出一个新闻，因此合理布局的话3铜也是足够的，事实上以我经验来看升铁的莎翁很有可能会腐败（造城建实在太便宜了）。所以对我而言，莎翁最适合的面板就是：在I时代政体（君主）或者II时代政体的情况下，不缺人口，然后通过拉人/拆哲学在II时代结束前视对方军事做出2套或者3套。并且不升铁的好处是可以省5个灯，配合印刷可以早1-2轮打出II时代政体，这样莎翁节奏会非常好，可以早几轮启动。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一般来说莎翁是整个游戏中跑分最快、上限最高的领袖，所以当一方摆出莎翁阵型后另一方是非常被动的。跑分肯定是跑不太过的，莎翁自带的省3灯省2矿效果也算是巨大的内政提升了。但是军事也是高风险的路线，因为阵型战争都不是想有就能有的。要是莎翁3白抢了战略/关键兵种，那可能根本连宣战的机会的没有了。再其次，莎翁造图书馆剧院根本都不花什么矿，甚至还有内政提升，到III时代摸上来III时代阵型再转型，可能军事都会被反杀。此外，目前普遍是18回合结束的（要是是达尔文可以17回合结束），所以像100军力这种情况是很难看见的。要是只能宣一发文战的话，估计抢的分不太够逆转局势。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;我认为对抗莎翁主要还是取决与对方是否给机会，比如完全不管军事强行3套，不卡兵种不卡红点，这样才有机会获胜。不然会布局的莎翁确实是基本无解的，我也被莎翁跑死过不少局，实在也是没啥很好的对策。要是有对抗莎翁很有经验的玩家欢迎分享下自己的打法。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
印刷+戏剧形态的莎翁是目前来说我个人感觉节奏最好的。虽然新闻很强，但是新闻更适合优质内政衔接使用，而莎翁并没有很高的矿物需求。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;现在回头来看的话，打莎翁比较合适的思路还是要注意时间差选择军事路线，比如拿破仑突然变速乱拳打死。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;巴赫&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
巴赫是内政转跑分最容易上手的领袖，很适合用于练习跑分打法。前置要求相比于莎翁达尔文几乎没有，II时代2张歌剧只要想拿是必然拿得到一张的，而I时代的戏剧如果能提前拿到也是非常不错的选择（我个人是推崇戏剧巴赫的，常见用法是拿了巴赫再出戏剧，然后宗教哲学移动至戏剧，这样对于科技矿物的影响都很小，很适合后续转型）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;巴赫之所以说是内政转跑分，因为他是相对在两边摇摆的：真要跑分是比不过莎翁的，而纯粹提升内政也自然不是牛顿等人的对手。但是相应的，虽然在某一领域做不到极致，巴赫却可以在内政文化军事三方面灵活转换，是比较均衡的领袖。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;巴赫可以直接将哲学移动到戏剧，但移动哲学后可能会陷入缺灯的情况，这时候有两种思路：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一是不靠计算机系补灯，即选择新闻或者配合科技系奇迹例如哈佛；&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;二是通过较为富余的白点抢革命理念，并准备转型暴力路线。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;顺便提一下我个人对于科技的看法：科技对于内政/跑分局是肯定越多越好的，可以转化成政体和蓝色科技扩大优势或缩小劣势；但是对于暴力局其实进入III时代后用处就不大了，因为要出的往往只有飞机。所以我觉得巴赫走暴力路线优势的原因是，拆家损耗低；3戏剧就可以在II时代中期做出高额的跑分，对手要是想要追上是需要付出更多人口和矿物的；并且巴赫的跑分非常容易到达上限（3戏剧5分），因此后续没事干的话不如给对手一定军事压力。当然，要是对手选择暴力那就更好，因为巴赫的抗压能力在跑分系领袖中自然是数一数二的。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
在我心中巴赫还是最强的二时代领袖，主要原因就是巴赫效率太高了，并且几乎可以适配任何面板，尤其是在缺少人口的情况下。虽然莎翁上限更高，但往往总是要拆掉几个无用的哲学或者宗教。相较之下巴赫迁移一个哲学，就是赚了1白3矿。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;特蕾莎&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
其实我个人还是很看好这个领袖的，丘吉尔人称小特蕾莎。最佳配合无疑是邮轮，其次选种或者3灌溉也都不错。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;特蕾莎适合在军备竞赛的对局中选择：若对方造兵，我方赚3矿继续跟军力；若不造兵则我方塞出军事事件。如此一来，我方可以获得较多的矿物或事件优势，方便后续的宣战和转型。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但是以上构想是有几个前提的：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.明确已知是军备竞赛局，双方的分数无法拉开。假设对方已经巴赫跑起来了，这时候虽然我方走军事，但是对面未必跟军力，每轮1矿的收益实在杯水车薪。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.我方有足够的人口、笑脸、红点和兵种。II时代兵种最佳，方便III时代转型走一拳的路线。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，特蕾莎走殖民路线也是不错的选择，殖民人口地甚至可以和技能联动获得灯和分。但是这个领袖潜在的问题和拿破仑类似，就是目前游戏环境并非军事主导的。选择这个领袖可能会限定自己未来的道路，所以在拿取时需要事先预测对手的路线选择。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
特蕾莎在尘埃落定后还是体现了她的价值，在军事局可以防守反击，而在对方跑分的情况下可以拉开矿差持续输出或者蓄力一拳。此外，她的科技收益强在是提前而非延后的，这就意味着可以极限凑出科技达成质变，比如突然打出11灯军理或者12灯飞机奇袭对手。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;罗伯斯庇尔&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
萝卜丝的用处主要是提升节奏，也就是所谓的一次性领袖。一般来说是在农铁双升但是较为缺灯的情况下使用，这种情况萝卜丝基本就是最优解，瞬间带来大量的白点收益。革命后的大体思路和诺贝尔是相似的，比如用省下来的科技快速出新闻，可以做出非常好看的内政面板。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;除了II时代共和的加强，III时代的共产也是一举成为了最强政体之一。III时代早期的萝卜丝共产不失为一个好选择，但是机会成本是3张黑牌，所以需要权衡利弊。我觉得在III时代中期之后3黑牌的潜在收益是不可小觑的（暴力流的文战或者内政翻盘的关键影响），所以要是没有太缺红点的话最好还是不要贪革命了。
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但是缺点就是他是要与其他II时代领袖竞争的，相较于莎翁达尔文等巨额内政和文化加成的领袖，萝卜丝节约科技的效果在相对和平的环境下就显得不太关键。假设说双方都是12科技等政体，那么看到萝卜丝也不应该拿，因为这6灯或者9灯可能并不能影响胜负。但是跑分领袖可以。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;达尔文&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
达尔文应该是我II时代使用率最高的领袖了，基本看到是必选的。曾经达尔文和莎翁都因为前置较高导致选取率不高，不过在大家熟悉了扩展环境了之后这两个领袖的强势一览无余。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;为了更好地帮助新人理解达尔文的用法，我先简述一下技能：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;第一部分宗教系建筑笑脸-1，这是全扩鲜有的负面效果，因此达尔文一般靠竞技场补充笑脸，或者靠笑脸奇迹过渡（教堂、紫禁城等）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;第二部分实验室及图书馆+1分。主流用法是配合2哲学+2-3印刷/新闻，这样可以达到4-5跑分。纯炼金或者科方的达尔文也不是不行，但是这种打法偏内政，在文化上会略有劣势。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;第三部分拿科技牌省白，这让达尔文具有了强大的内政能力。既可以凭借自己高额的科技增速打出类似牛顿的效果，也可以单纯的卡掉对手的关键牌（政体、战略）。二白区变成了一白区，因此二人对局中常见的“卡6”在达尔文眼中视若无物。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;然后介绍一下用法：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.印刷面包流。即在I时代便为达尔文做好布局，一般是在I时代结束前造好了2印刷1面包，这样一方面科技文化都有着不错的增长，另一方面包的两个笑脸可以解决前期的笑脸问题。这种布局俗称“达尔文阵型”，一般配合I时代的达芬奇/古腾堡可以较为容易的做出，在II时代有达尔文后可以立刻换上平滑转型，在III时代可以考虑印刷升多媒体。这个打法的缺点有三：一是意图太明显，很可能导致达尔文被同样可以使用的对手直接卡掉，这样我方只能退而求其次选择牛顿高迪之类的领袖；二是对于I时代的操作要求较高，在没有白点（萨拉丁、古腾堡、法典）或者科技领袖（达芬奇）的情况下想要摆出这个阵型还是相对限制其它行动的；三是达尔文印刷是跑分效率很高的combo，但非跑分绝对值最高的打法——3印刷2哲学达尔文的完全体也只有8跑分，因此想要和巴赫莎翁对跑还是有些难度的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.新闻竞技场流。即内政转跑分的思路，在农铁政体都相对完备的情况下一般2白抢新闻，佐以I时代的面包马戏或者II时代的团体竞技。这个打法的缺点是依赖新闻出现的时间，要是达尔文出得比新闻早的话还可能被对方卡掉。当然优点自然也很多：一是新闻是最强的内政转跑分城建，要是能在II时代结束前造出3新闻配合达尔文，那基本已经是必胜了；二是新闻达尔文的跑分能力也很强，完全可以和巴赫对跑（和印刷莎翁也可以一战）；三是没有太多的前置准备工作（即I时代的拿牌布局），可以视情况选取领袖，要是不合适拿达尔文大不了不拿，也未必会有很大影响。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;3.冷门流派。达尔文是非常灵活的，除了宗教不推荐造以外其它城建都可以自由排列组合。比如：莎翁流（和莎翁一样造图书馆和剧院），炼金戏剧流（内政跑分两不误），3科方（中期过渡，后期转盖茨）。当然除了印刷流派，其他打法对于内政的要求是比较高的，因此需要斟酌。此外，卫城也是达尔文的好帮手，5个印刷为所欲为。三个II时代科技领袖的前置需求大致如此：诺贝尔（完全不需要基建）&amp;lt;牛顿（一个科方/新闻足矣）&amp;lt;达尔文（科技城建/笑脸/政体全都要准备）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;达尔文强势到当你看到这局领袖中有达尔文时，从I时代一开始就可以布局。比如宗教的发展可以不造（要是提前准备好了面包的话）；面包值得2点拿取（要是这局同时有达尔文顾拜旦的话）。当然也具体情况具体分析，要是牌列不太适合的话可以不必刻意为达尔文准备。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;最后说一下顾拜旦。这是III时代的强力领袖之一，和达尔文的契合度也是非常之高，一般用法是达尔文即将离场的时候换上顾拜旦，然后享受额外的8分奥运会。不过需要注意的是，不要因为顾拜旦的跑分可能比达尔文低就不提前拿下，这牌强势到对方没有竞技场都可能会抢的，因为单纯8分也算是个很可观的收益了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
一代版本一代神，在达尔文打法刚刚被开发出来的时候达尔文选取率胜率双高，主要原因是达尔文的跑分和特效在面对内政流派是有着不错的压制力的，印刷新闻多元发展，面包体育能打能抗。但是当莎翁引领了大跑分时代后，达尔文的哲学劣质跑分就有点说不过去了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;单论达尔文技能收益依然还是很高的，但是还需考虑对手是谁。并且现在跑分路线趋于同质化，都是印刷与戏剧齐飞，这时候面包会显得有些尴尬，跑分不足，军事平平。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;拿破仑&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
曾经的战神，在扩展中被砍了一红。笔者认为拿哥的战略性有所降低，原因如下：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.缺少1红导致军事压制力降低。这一点相比大家都有直观感受，比如正好差1红做出1套新的阵型，或者差1红宣战甘地之类的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.目前跑分领袖的强势。基础版整体环境是偏暴力的，以至于玩家多数时候选择的是奇迹跑分，III时代转席德梅尔或者卓别林这样的路线，多数对局双方文化增速都在10点上下，所以文战（拿哥丘吉尔）以及III时代奇迹（盖茨）对于胜负是有着关键作用的。但是扩展新增了大量跑分元素，使得以往的暴力路线并不那么吃香，时常会出现跑也跑不过（文化增速劣势），打也打不过（对方手上囤积不少黄牌，军事潜力很高）的情况。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;基于以上2点，我个人的看法是，现在的暴力路线要么就I时代暴兵尽量II时代早早的打崩对手（比如巴巴重骑速换拿哥侵略宣战），要么就内政大幅度领先III时代一拳。当然后者的难度是比较高的——由于跑分领袖往往也具有内政提升的能力（比如达尔文的省白、莎翁的笑脸和省矿），跑分方的内政未必就会落后于军事方。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
军事领袖的核心还是抓住时间差，在这个层面上拿破仑还是具有极强威慑力的。突然提升的几点军力和一个红点可能就可以突破对手的阈值。军事讲究无形威慑，迫使对方疲于应对并且干扰操作。相较于传统战争路线，突然陡增的军力在面对共和这种少红点政体时威胁度极高。对方如果造兵则会损失抽取三时代影响牌的机会，如果选择让步则可能会因为关键的侵略而兵败如山倒。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;叶卡捷琳娜女皇&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
叶二的技能简单易懂，大体上理解为2分1红，还带有一个边境分歧的限定技。我个人认为限定技不是必须的，最合适的情况应该是吓一吓对方让对方造兵，或者走军备竞赛的路线。如果是军力压制较强的话那么侵略与战争的收益是远高于这个技能的，所以会显得有些矛盾。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;从定位上来说叶二和萝卜丝、诺贝尔属于同一风格的，即弥补短缺提速节奏。她的主要增益来自一红（配合共和或者打人）、2分（暴力跑分路线），所以既然选择了这个领袖就要发挥长处，不要太和平跑分了，不然她的两分就显得微不足道了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;顺便提下暴力跑分的打法，不过我不太擅长就是了嘤嘤嘤。核心思路是军文双高，在自己军事强势期让对方疲于应付军事压力而无法顾及内政与跑分。理想情况就是，当对手内政做起来的时候，要么他分数差了一大截难以追上，要么游戏快要结束了不能靠一拳翻盘。因此I时代的贞德、农场主，II时代的叶二，奇迹中的教堂、泰姬陵都是不错的选择。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;牛顿&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
巨大优势不胜男，尤其是科方牛顿，经常做出豪华的内政板子然后分数大幅度落后。究其原因还是跑分领袖的强势，加上时代数的缩短，让内政转化为分数的时间未必足够。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;不论是对手选牛顿还是我方选择，以下几点应该是共通的：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.对于双方来说新闻都是一张关键卡牌。能新闻就不要科方，再不济印刷过渡III时代升多媒体也行。当然要是科方牛顿一起早早的来，那该升还是要升的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.盯死III时代的蓝色科技，尽量不要放给对手。对于牛顿来说，只要科技够出科技基本是不用代价的，尤其是公共服务这种打出白赚白点的蓝色科技存在。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;3.对于牛顿玩家来说，在内政完备的情况下快速切换III时代的内政追分领袖，最为典型的应该是弗莱明和爱因斯坦。当今环境跑分至上，靠几张影响牌追个几十分的情况已经很少见了。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;诺贝尔&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
诺贝尔本质是加速内政雪中送炭，因此和牛顿对比是不太公平的。诺贝尔最强力的时候无疑是II时代的早期，尤其是只有2个哲学时，一方面2军力可以左右军事事件，另一方面2灯带来质变。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
在基建还没有建立完备的情况下（缺灯没政体），诺贝尔不需要前置的2灯产可以直接支撑你打出兵种/农铁，以及提速打出政体，这带来的收益是无与伦比的。从这个角度看，诺贝尔和罗伯斯庇尔的定位有些相似，都是使玩家更好地建立后续节奏，并且不需要付出什么代价。
当凭借诺贝尔顺滑做好内政后，就需要考虑转型的事情。对于跑分方面，新闻歌剧都是可以的，也可以为了III时代科技刷分路线布局（比如爱因斯坦或者乔布斯）。当然，走军事路线也行，两个哲学随时可以拆了备战。此外，我是不太建议偏和平环境诺贝尔出科方的，因为这样体现不出诺贝尔的优势，这也是部分玩家会拿牛顿和他对比的缘由。这一点在后文哈佛学院的介绍中也会有所体现，在玩家2哲学的时候诺贝尔/哈佛的收益可以直接让玩家科技增速翻倍，但是当建造了科技建筑之后诺贝尔的2灯就显得不太关键了。还有一点是，因为诺贝尔在内政及跑分方面并没有太大优势，所以如果说只是单纯和平建造城建那么是并没有发挥出他的全部收益的，对位一众跑分领袖可能会有天然的劣势。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
顺便提一句，因为II时代跑分领袖比较强势，所以在大多数情况下军事不是最优解。当你看到对手跑分跑起来了，我们应该同时思考是否有内政转跑分并靠影响和奇迹取胜的可能性，而非“这人居然敢莎翁？看我不打死他”这样头脑一热做军事。从大量对局数据来看，跑分都是目前环境的最强套路。军事一方面有大量前置（兵种、阵型、红点、战争），另一方面由于III时代过牌速度飞快有可能在17回合结束游戏，往往军事就是差那最后一回合达到质变。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
最后，诺贝尔还有个离场技能，配合爱因斯坦可以打出一轮10分的超高上限。不过在暴力局诺贝尔基本是用到死的，颁发不了诺贝尔奖。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;高迪&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
又一位跑分领袖，主要的用法是内政提速，过渡为主，比如花费低廉的科技打出多媒体转小爱，或者出计算机配合实验室系领袖如居里盖茨。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;对于这个领袖我有两个小想法，也算是对于跑分领袖的一个总结吧：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1. 对于单一城建分析，如何最大化城建效率。城建的特点是第一个最难（需要拿打），后面的只需要拉人建造即可。因此考虑效率的话最强的肯定是单一城建达到城建数量上限，这也是巴赫戏剧强势的原因。反观高迪，如果刻意达成5分高迪，我们是很可能打出一些用处不大的城建科技的，这些白点的浪费还是不可忽视的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.横向对比5种城建，城建本身效率不尽相同。比如戏剧（4矿2分）是大概率优于寺庙（3矿1分）的，而图书馆系要是有人口建造的话收益也是高于实验室的。因此即便同样是跑4分，高迪的净文化增速无疑是要低于巴赫和莎翁的，因为哲学就算配合高迪跑了1分也不过是人家印刷的水平而已。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
垫底三剑客之一，自从玩家进行数据统计以后，二时代倒数三位领袖就没有易位过，可谓表现稳定。但话说回来，二时代无领袖的胜率仅有35%，这也就意味着二时代是几乎不能没有领袖的，所以选择领袖时要权衡下利弊，一般来说有总比没有好。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;库克&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
我个人认为库克强度是足够的，甚至在扩展加入了殖民领袖（伊莎贝拉、埃莉诺）以及强力奇迹苏伊士运河之后殖民流应该是大幅增强了的。至于优势较大但是无法获胜的原因（比如7殖民地然后输了），我个人的想法如下：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.首先，殖民的目的。不少人拿了库克就喜欢各种抢殖民，抢到了多1文化增速感觉就很赚。但本质上殖民属于内政的分支，即内政薄弱处可以靠殖民弥补（缺粮食殖民人口地、缺矿殖民矿地…）。因此，殖民并非多多益善的，起不到关键作用的殖民就如同腐败一样，对自己的内政节奏是有所影响的。但是殖民并不如腐败那么直观，很多时候需要考虑的是机会成本的问题，因此选了库克后很可能会进入陷阱，陷入殖民、拉人、殖民的无尽循环。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.跑分的层级。跑分主要是城建跑分+领袖跑分+奇迹跑分，其中领袖起到关键作用（决定城建路线）而早期奇迹性价比很高（丝路、姬路城等高额跑分）。但是，占跑分比重最大的往往是城建。比如3套II时代莎翁，领袖本身的收益是6分（多数库克也可以做到这个分数），而城建是15跑分，是领袖收益的2.5倍。当然这种量化计算其实没啥意义，但我想探讨的是这么多看上去顺风顺水的库克最后失败的原因，即忽略了城建跑分的重要性。每当库克殖完民，往往就是白点拉人，要是腐败多就造几个兵，然后摸摸黄牌，结束回合。所以很多面板看似10跑分，但全都是领袖本身的分数，真正城建的跑分可能只有孤零零的一个寺庙。这种打法在基础版是没有问题的，因为之前也提过了大家都是10跑分左右。但是在具有30+跑分潜力的混扩模式，库克在殖民后需要寻找出路。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;因此，库克要是能理性殖民，注重城建跑分，强度肯定是不低的。比如4地库克和3戏剧巴赫，领袖同样都是5跑分，那凭什么跑不过嘛。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;瓦特&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
这个贴主要是分析2人模式，不过我还是事先提一句，瓦特在2人和4人是两个强度。因为4人游戏内政价值很高（游戏时代数长、决定军事潜力、影响牌刷分），同时II、III时代的农矿各有2张不太存在被卡的可能性，所以瓦特双A选择升煤矿，或者铁矿转石油，都是非常不错的选择。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;反观2p，相信不少人也靠3铜赢下了游戏，由此可见粮食和矿物产量并非决定胜负的关键性因素，因此瓦特本身就不是必需品。此外，除非已经拿好了煤等瓦特，不然要是对手3白卡掉煤瓦特就直接白板了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2p平均18回合，升煤和石油的时候需要考虑收益问题。尤其是碰到达尔文这种的，可能17甚至16回合结束游戏，基建的收益就更加存疑了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;当然，瓦特铁升石油只要2矿确实很快乐，配合铁路更是双倍快乐，甚至离场时还可以瞬间跑10分。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
截止至11月29号，我还是没有在2p中使用过瓦特，哭哭。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AGE II 奇迹&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;奇迹&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;选取率&lt;/th&gt;
&lt;th&gt;胜率&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;strong&gt;苏伊士运河&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;18.6%&lt;/td&gt;
&lt;td&gt;52.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53.14&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;远洋游轮&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;22.9%&lt;/td&gt;
&lt;td&gt;51.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.74&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;哈佛大学&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.6%&lt;/td&gt;
&lt;td&gt;51.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.90&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;卢浮宫&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.9%&lt;/td&gt;
&lt;td&gt;52.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;自由女神像&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.0%&lt;/td&gt;
&lt;td&gt;49.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;49.34&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;埃菲尔铁塔&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.0%&lt;/td&gt;
&lt;td&gt;49.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.83&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;跨洲铁路&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.3%&lt;/td&gt;
&lt;td&gt;47.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47.76&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;克里姆林宫&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9.5%&lt;/td&gt;
&lt;td&gt;46.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;45.66&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
III时代奇迹后接三时代得分奇迹，因此多数时候建造时机值得斟酌，除了前两位我认为强度都比较均衡，不存在什么无脑强的说法。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;苏伊士运河&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
运河的上限应该是所有奇迹之最，也是逆风翻盘的关键奇迹。如果说邮轮的出现让灌溉的价值相对降低，那么运河的到来就是削减了铁矿的收益。要是殖民路线成功了，那运河一般收益就是一个殖民地+2点以上矿产，部分极端的局可以达成6-8矿产，在这种情况下，就算不能赢，想必玩起来也是非常快乐的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但是前文的大前提是“殖民路线成功”，在日常对局中殖民的难度还是不小的。最重要的就是能摸到殖民地和D卡，这就需要一定运气的支撑了。如果说已知事件牌堆中殖民密度很高，同时自己又具有争抢殖民的能力，运河自然就是游戏中至关重要的一张牌，甚至对手需要被迫强行卡掉。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;因此，最适合运河的布局是，殖民领袖（巴巴、伊莎贝拉、埃莉诺、哥伦布、库克）、殖民奇迹或科技（巨人像、制图、航海）配上绝佳的运气（红白地、人口地）。此外，运河本质上还是在提升内政，所以一定需要寻找分数的突破口。考虑到运河带来的大量内政加成（人口、矿物），我认为走城建刷分是最佳的选择，一拳流可能反而不是那么稳妥。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;远洋邮轮&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
邮轮在扩展中意外的得到了加强，不但削减了费用还增加了一个黄点，简直不讲道理。本身免费拉人兼具了白点和粮食的功能，而人口作为游戏的一个核心元素重要性也是不言而喻，此外邮轮对于人口影响这个事件是有绝对优势的，可以说是普适性很高的奇迹。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一般来说，一局游戏中有邮轮的话，灌溉的抓位会大幅度降低，如果不顺手可以不拿。因为一方面灌溉的后期能力不如选种和邮轮，另一方面早期灌溉开局的直观收益我认为是不如铁开的，我个人觉得多数情况先铁后灌溉更强一些，或者说先出灌溉和出铁是有些冲突的，不如灌溉之后拉人去造印刷。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;顺便说说我的两个小心得：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.拿了邮轮就尽量不要拿其他农业科技了，如果粮食不足就造3A农、4A农…如果是先拿了灌溉后造邮轮那另当别论。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.在有红十字的局邮轮抓位会略微降低，因为邮轮和红十字一般来说是有点冲突的。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;哈佛大学&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
哈佛在2哲学的时候效果是最好的，可以节约科技建筑的人口，方便之后机动地调整到剧院（比如巴赫）或者造兵打人。这种情况下一般是矿多灯少，本身科技的负担较重，而科技建筑打出本身也是需要花费科技的，所以建造哈佛可以很好地弥补短缺，优化结构。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;相应的，与诺贝尔类似，哈佛在科技较为充裕的情况下收益自然是大打折扣的，这里也不赘述了；此外，哈佛适用于矿物较为充足的情况，因此3铜就尽量不要拿这个奇迹了，假设III时代中期才敲完一个哈佛，那显然不如不拿。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;最后说一句哈佛的额外效果，在白点较为充裕的时候有着不错的发挥，同时在军备方面可以提高上限，比如突然的爱国主义或者军事积累可能会打乱对手本来的宣战计划。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;卢浮宫&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
因为II时代奇迹后面就是III时代奇迹了，加上时代数的问题一般来说除非是特别强力或者救命的效果不然我个人认为都没有很大必要建造，之后完全可以等待III时代奇迹。因此卢浮宫的性质有点尴尬，可以理解为生于II时代的III时代奇迹。此外，损失3蓝并不能很好的解决腐败问题，一般还是需要司法、矿物殖民地等提供额外蓝点来解决腐败问题。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
但是，卢浮宫的一个亮点是额外的6个矿，如果在最后一回合配合奇迹那等于是损失3分但是多了6个矿，或者倒数第二轮提高军事上限，对于对手也是有一定威慑作用的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
总的来说，卢浮宫的跑分数值是很突出的，但是限制条件和弱点都不少，使用的时候需要权衡利弊。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
卢浮宫的胜率在二时代其实是第一位的，虽然选取率不高但考虑到对局样本数量也算可以接受了。这就说明卢浮宫在一定条件下强度是非常高的，我自己之前也使用不多，感觉可以多多尝试。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;自由女神像&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
女神像容易和铁塔做比较，不过综合来说的话我觉得二者定位有些不同。女神像的主要拿取情况应该就是矿物溢出没事干，2步的建造步数可以快速缓解腐败问题，同时提供一部分粮食与笑脸的解决方案。因为一般来说黄点不是必需品，它更像是替补品，所以很多时候我们也是难以刻意为之布局的，都是走一步看一步，发现时机合适了顺手一用就好。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;也是基于这个原因，女神像的收益不太好量化，只能说缺科技的专制政体尽量不要拿。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;埃菲尔铁塔&lt;/h3&gt;
&lt;h2&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
铁塔同时兼顾笑脸和分数，但实际上定位是偏向暴力路线节约人口的。因为根据前文的观点想要大幅度跑分需要依赖于跑分城建，而铁塔的孤立4分性质与哈佛有些相似——缺什么补什么。如果说自己已经开始建造歌剧，那么铁塔的收益是非常低的；但反之，自己是全民皆兵的暴力面板，并不打算建造多余的城建浪费人口，那么铁塔的1脸和4分在暴力跑分中都是非常不错的。&amp;lt;/p&amp;gt;&lt;/h2&gt;
&lt;h3&gt;跨洲铁路&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
于我个人而言铁路主要还是看重5军力，而它强也就强在提升军力的同时可以小程度地提升内政并减缓人口压力。最适合的情况是，对方的军力存在侵略的可能性而我方没有拿到合适兵种（没来或者没阵不想出）或不希望造兵减缓节奏（缺少人口或者缺少科技）。这时候铁路可以在II时代中期过渡，缓解军事压力并节省科技与人口。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外矿山额外生产矿物的技能，有铁矿的情况就已经可以接受了，如果有煤或者是后期升石油那实际收益还是非常可观的。至于4步的建造费用，和邮轮、运河一样，如果有砖石的配合节奏会非常舒服，这也是我日常推荐砖石的原因。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;那么不适合的情况也就是上述情况取反，比如白点较少、铁路运青铜、军备竞赛的大环境双方都侧重于人口的堆叠…因此总的来说铁路也算是张偏战略意义的卡牌，在特定局势下可以有着不错的发挥。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;克里姆林宫&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
克宫的哭脸限制了不少拿取的情况，适合选择的场合一是矿物太多缓解腐败的时候，这也是奇迹的常见用途；二是已经变了I时代政体，在II时代前中期卡牌不合适或者科技受限的情况下提升内政。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;至于克宫笑脸的负面问题，有一种较为极端的情况是整局游戏一个笑脸都不要，靠紫禁城、殖民地等缓解笑脸问题，这样配合克宫或者共产等于就没有负面收益了。但是这种情况既难以达成，吃了笑脸事件或者幸福影响还容易崩盘，因此建议谨慎考虑。那么正常情况下的话，这个笑脸是需要解决方案的，教堂、竞技场、宗教组织等都可以缓解。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;总的来说，克宫并不是一张具有战略意义的卡牌，多数时候就算不选择它玩家也可以选择其它的路线，甚至来的较晚的话（II时代晚期-III时代早期）建造的白点都可能比额外提供的白点要多，可能会得不偿失。此外，毕竟II时代奇迹之后就是III时代奇迹了，而III时代的得分奇迹我认为在现在环境下价值是非常高的，正常情况是双方一人一个，所以可以理解为拿取II时代奇迹是要潜在多花1个白点的，因此我的观点是II时代奇迹如果不是过于关键（缺粮食轮船、多殖民地运河），在拿取奇迹时可以多想想有没有什么替代方案，这样可以为III时代留下更多的操作空间。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AGE III 领袖&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;领袖&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;选取率&lt;/th&gt;
&lt;th&gt;胜率&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;strong&gt;黛德丽&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;31.1%&lt;/td&gt;
&lt;td&gt;52.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;54.15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;卓别林&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;17.9%&lt;/td&gt;
&lt;td&gt;53.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;54.04&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;丘吉尔&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;23.4%&lt;/td&gt;
&lt;td&gt;51.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.49&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;弗莱明&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;17.5%&lt;/td&gt;
&lt;td&gt;51.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.86&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;顾拜旦&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20.4%&lt;/td&gt;
&lt;td&gt;51.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;51.73&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;爱因斯坦&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16.0%&lt;/td&gt;
&lt;td&gt;50.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.13&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;曼德拉&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10.3%&lt;/td&gt;
&lt;td&gt;50.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;49.97&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;甘地&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.3%&lt;/td&gt;
&lt;td&gt;49.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.92&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;居里夫人&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.4%&lt;/td&gt;
&lt;td&gt;48.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46.85&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;乔布斯&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.3%&lt;/td&gt;
&lt;td&gt;48.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46.80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;比尔盖茨&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.5%&lt;/td&gt;
&lt;td&gt;44.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44.20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;席德梅尔&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8.1%&lt;/td&gt;
&lt;td&gt;45.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;43.89&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;无&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.0%&lt;/td&gt;
&lt;td&gt;42.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;41.40&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
III时代的领袖整体强度都还是不错的，大致思路是：要么II时代领袖强力，根本不在意III时代领袖；要么II时代领袖只能过渡或者是吃了偶破，那么III时代领袖哪个早选哪个，也没什么过多好想的。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;黛德丽&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
据我主观猜测，超过一半的黛德丽都没有剧院（手动滑稽）。黛德丽技能分为两部分，第一也是最常用的就是，可以节约一个兵种的人口。这个效果不论是在突然宣战还是被宣战准备反杀中都至关重要；第二部分剧院跑分+笑脸，非常适合作为戏剧巴赫的过渡。顺便一说，戏剧这张牌便宜好用，就算没有巴赫或者莎翁也是不错的选择。虽然可能感觉上前期舍不得花这几个灯和矿，但是带来的分数收益相应的也是非常可观，在高端局热度极高。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;卓别林&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
暴力领袖++;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;主技能2脸，在暴力局可以多两个人口用于造兵，效果很好。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;说回正题，一般是配合歌剧或者电影，正常跑分打法就是跑3-4分。但是因为一个剧院就可以达到上限，所以卓别林在暴力跑分局是有奇效的：一边造一个电影跑8分，一边用省下的人口造兵，达到军文双高的效果。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;什么，你问哪有科技打出电影？那当然是科技战争啦~&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
2笑脸在很多时候确实是很关键的，即便是跑分路线，由于一般都选择剧院系建筑，在三时代后期或者四时代都很容易出现笑脸短缺的情况，这时候2笑脸可能意味着节约了几个白点和矿物，或者在宣战时期避免拆家。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;丘吉尔&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
这领袖还是非常简单粗暴的，基本上选了后再差的内政都能让对手心怀忌惮。顺便一提，我觉得II时代新出的那个轻骑阵（马马）和丘吉尔真是绝配，遇事不决先升10个坦克再说。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;弗莱明&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
弗莱明是大内政流转跑分的一个好选择，上限只需要2个城建就可以跑6分，在所有领袖中都是首屈一指的。也可以用于莎翁或者达尔文领袖的IV时代过渡，刷刷奇迹比如好莱坞的分也是不错的选择。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;此外，对于看牌的技能，我感觉有两种思路：对于纯跑分局，肯定是早看比较好，有机会多使用出一次技能，可以更大程度的掌握对手的手牌；但是多数对局并非纯跑分，往往还需要顾及军事压力，这时候可能在对手最可能宣战的回合使用技能最好（或者自己宣战的回合看对手手牌），这样可以完美计算出对手的军事潜力。比如，要是倒数第二轮侵略和战争都没有，那么直接把家拆光都行（输一手和谐影响）。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
弗莱明现在的性质更像是二时代跑分领袖的备胎，在压榨完二时代领袖的剩余价值后最后当个一次性跑分使用，顺便看看牌开始收官操作。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;顾拜旦&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
由于II时代领袖的强势以及III时代领袖出现时机的不确定性，我个人认为刻意为III时代领袖布局是不太必要的，比如3科方等盖茨这种。多数时候用的顺手就好，往往III时代更看重面板本身，领袖只是辅助作用。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;综合来说顾拜旦的收益是非常可观的，正常情况每回合可以跑4-6分，在不少对局中甚至在自己没有竞技场建筑也可以卡掉，因为限定技8分已经难得可贵了。3种竞技场建筑都不错，其中面包与顾拜旦的相性最好，因为上限4面包正好可以解决所有的笑脸，让每一个人口都用在刀刃上。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一般来说看到III时代有顾拜旦，I时代面包的抓位是会大幅度增高的，配合II时代的达尔文是常见的套路，可以完美过渡打出较为舒适的跑分节奏。但与前文呼应，如果这局剧院价值较高的话竞技场的收益自然也就相对降低了。不过顾拜旦是偏暴力的跑分路线，也与莎翁这些纯和平跑分有些差异。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
双方越穷，顾拜旦越强，属于平民领袖。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;爱因斯坦&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
其实我最近小爱的使用率不算高，但记得以前还是挺喜欢的。一般来说我还是建议选择多媒体，原因在前文也叙述了不少了。期望文化增速大概是4分，算上额外的科技增益算是模型非常不错的领袖了。满配科技套装是民主工程学多媒体（公共服务），能打出这个面板大概率是可以赢的，就算不能赢看起来也是赏心悦目的。顺便一说影响得分的问题，技术、发展算是两张关键影响，一般靠单张影响领先20分的也就这两张可以做到。因此当选择小爱后最好要多关注III时代的蓝色科技，这些卡牌的抓位是非常高的。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
小爱在二时代跑分领袖面前也是有些尴尬的，虽然3增速很香，但可能会丢失+5文化增速。固然小爱自己可能也是3-4分，但是有时候也不能保证多出的科技可以弥补缺失的分数。因此这个领袖还是偏内政流使用，依赖多城建跑分的领袖一般也是把他藏在手里，在合适时机再替换。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;曼德拉&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
曼德拉是真正意义的草根领袖，有时候内政惨不忍睹但就是莫名其妙的赢了对手。因为我们是很难保证什么时候会有不满工人的，所以这领袖自然也非刻意布局的最佳选择。但可以明确的是，内政较差的玩家一般什么都缺，比如人口一片哭脸，这时候曼德拉正是救民于水火的不二选择。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;从收益的角度考虑的话，曼德拉适合多黄点的面板，比如殖民体系。而殖民体系又很可能是灌溉青铜的暴力面板，因此综合来说不论是殖民还是暴力都是很适合曼德拉的。1白2分的下限已经不错，而最大化这个白点收益可以配合白较少的政体，比如神权或者君主（没错，又是暴力路线）。此外，要最大化溢出笑脸跑分的技能，一般是选择宗教组织或者II、III时代的竞技场。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;我此前刚刚被4灌溉3铜的曼德拉+红十字教育了一局，也是深有感触。往往游戏会侧重矿物的积累，但这两张卡牌让人口+粮食导向的面板也具有不错的获胜潜力，也可见游戏多元化的一面。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;甘地&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
甘地算是可以影响整局游戏布局的领袖，一般来说看到甘地这局暴力的难度会相对增加。相较于原版技能（侵略宣战红点翻倍），在这次扩展中修改为了恒定3红点，算是一个关键性增强。相应的，黑牌中新增了一张2红的混合战争，算是给暴力路线多了一丝机会。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;适合选甘地的情况主要有两种，一是已经靠先前领袖跑了大量分数，换上甘地保分（比如米开、巴赫、莎翁），二是双方分数相近但是己方跑分潜力高，换上甘地可以安稳地跑分不用备战。（当然，三是自己想要暴力，然后3点卡了甘地233）。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;为什么要说明一下适合选甘地的情况呢，因为我发现不少对局中双方势均力敌，只要备战得当谁也打不了谁，这时候一方突然选了个甘地。这种情况下对面甚至可以把兵都拆了，换上个高收益跑分领袖比如弗莱明，这样反而选择甘地是作茧自缚。因此，我现在的观点是，尽量为自己留下一条后路：文战一拳很多时候就像是赌博，阵型文战回合轮次矿物红点（甘地）都是巨大的限制因素；不妨转念一想，比如II时代偷偷拿个歌剧，这样当对方被军事吓得换上甘地时，自己还有回旋的余地。当然，歌剧只是个举例，主要意思是要有一定的“跑分潜力”，这与以前大家常谈及的“军事潜力”异曲同工。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;居里夫人&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
我有种错觉是III时代领袖普遍偏暴力，常见的特点是上限不算高（与需要布局的II时代领袖比起来）但下限一般都不错（比如曼德拉至少1白2分）。居里也属于这个风格：要是单纯计算收益，上限3灯3军3分并且条件苛刻，是大概率不如爱因斯坦的。但是要是暴力局，一个煤矿顺手拿个居里当2灯2分就很不错，因为只是顺便跑下分。也是基于这个原因我的III时代评级中最低只有B这个等级，因为几乎所有领袖都可以用。比如吃了个圣像破坏，那正好有个居里刷在面前还是立刻抢了比较好，要是被对手拿了一来一回就可能是3-4跑分的差距了，再如果其他领袖沉底这个时间差可能就亏了10多分，足以影响胜负。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;居里一般有石油就是满配了，至于计算机我觉得意义非常小，不用刻意去升。因为有个石油后额外3科技增速已经足够了，我们当下主要的问题是怎么把石油足量的矿物使用掉，因此不论是走军事一拳或者造电影多媒体都是更好的选择。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
4位计算机系领袖位列倒数四位，令人唏嘘。不过就我个人体验来说，我也是很长一段时间没有碰过计算机了。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;乔布斯&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
乔布斯是相对冷门的领袖，但其实上限是不错的。一技能实验室提供笑脸，自然又是一个可以在暴力局发挥关键作用的效果，科方足矣，炼金也勉强能用，毕竟暴力领袖。不少人会拿乔布斯和爱因斯坦对比，但多数时候小爱是锦上添花，而乔布斯在关键回合可能是可以救命的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;二技能1矿换4分是主技能，和小爱的直接3分比起来各有千秋，虽然多数情况是没有限制的3分更强。但是要是我方有8矿以上（铁+铁路、3煤、石油），我觉得矿物的限制是非常小的，这时候多出来的一分可能就弥足珍贵。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;总的来说，乔布斯和居里都属于对矿与实验室有一定要求的领袖。当这两个领袖都有的时候煤矿、石油、科方、计算机强度是有一定提升的。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;比尔盖茨&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
我刚玩TTA的时候听得最多的就是“II时代拿哥，III时代盖茨”，对于盖茨本身的强度也是从不怀疑的。不过后来当扩展的胜率统计出来后，我依稀记得是44%的胜率，位列III时代领袖倒二（更新：目前荣登III时代胜率垫底榜榜首）。一方面可以猜测为盖茨的性质有点像金字塔，什么人都想着可以靠盖茨救命，其中不乏内政极度堪忧者；另一方面盖茨可能会被卡，比如0哲学抢盖茨不让对面内政起飞。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但以上两张猜想在我最近的对局中感觉出现的都不多，一个最明显的感觉是盖茨的出场率较低：在偏高端的对局中，图书馆和剧院系建筑出场率愈发增加，实验室建筑相对少见，尤其是计算机。我觉得也是可以理解的，当你花费白点拿取计算机并升级时，对手可能已经歌剧或者多媒体跑起来了，这个时间差往往就关乎胜负。类似席德梅尔配合科方，就是等于新闻的收益，那么对手要是选择新闻我方就等于白白亏了一个领袖。虽说不能那么机械地计算，比如席德梅尔在暴力局顺便跑分就相对不错，但是从模型来看就是这么一回事。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;因此，我觉得盖茨相对没落的真正原因是跑分的兴起和时代数的缩减。先说前者，比方说盖茨3计算机多了9个矿，那这多的矿可能也就是每轮造个电影，这么看似乎和直接领袖跑分差不多，盖茨是领袖-&amp;gt;矿-&amp;gt;分，而跑分领袖是直接领袖-&amp;gt;分。同时原版以前因为双方文化增速都不高，盖茨多造几个奇迹刷刷影响，分数是很容易追上的；而现在要是遇到个莎翁，可能盖茨3计算机的时候对方已经领先几十分了，想要追上就未必容易。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;至于后者，首先盖茨的出现时机是不一定的，要是早来自然好，但往往也是要3点抢；而要是中后期才来收益可能只是差强人意。游戏一般就是18回合结束，双方白点多的话可能17时代就结束了，我相信留给盖茨运营的时间并不算多。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;以上是我对于盖茨胜率及出场率相对不高的一些猜测，说回领袖本身自然是非常厉害的。额外的矿物收益以及终局的一次性跑分，一般来说看到这局有盖茨II时代科方的价值是增加了不少的。论上限盖茨应该是首屈一指，但基于平均期望因此放在了这个位置。盖茨最需要的是白点和工程学（或者I、II时代的对应蓝色科技），完美面板应该是机农3计算机，8白点以上并配合工程学。如果跑分有所障碍的话，不妨考虑走走军事，时机把握得当是有机会100军力一拳致胜的。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.17]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
卡牌桌游第一定律：卡牌门槛越低，收益越低。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一般来说这是符合数学规律的，如果说门槛又低收益又高，那这张牌往往是不平衡的。轻则玩家无脑抢，重则怒喷设计师。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;席德梅尔&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
还是和前文我的观点一样，席德梅尔的技能确实就是把实验室变成的图书馆。虽然我也不怎么喜欢这领袖，但是不能因为技能平平无奇就认为这个领袖太弱。比如此时我已经造好了3科方了，那当牌列刷出席德梅尔时这个领袖对我而言就是个6跑分领袖，是大于几乎所有其他III时代领袖的。因此II时代领袖重布局，III时代领袖看顺手。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AGE III 奇迹&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;奇迹&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;选取率&lt;/th&gt;
&lt;th&gt;胜率&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;strong&gt;好莱坞&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16.7%&lt;/td&gt;
&lt;td&gt;64.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.37&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;红十字会&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16.1%&lt;/td&gt;
&lt;td&gt;58.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;59.61&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;互联网&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.6%&lt;/td&gt;
&lt;td&gt;58.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;58.61&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;帝国大厦&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11.4%&lt;/td&gt;
&lt;td&gt;56.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56.91&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;首次太空飞行&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.5%&lt;/td&gt;
&lt;td&gt;55.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55.88&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;联合国&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.5%&lt;/td&gt;
&lt;td&gt;53.4%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53.32&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;快餐连锁店&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;17.0%&lt;/td&gt;
&lt;td&gt;51.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.28&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;曼哈顿计划&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16.8%&lt;/td&gt;
&lt;td&gt;48.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.88&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;综述&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
III时代奇迹胜率普遍过50%，重要性可见一斑。在双方内政和文化均势的情况下我还是建议先手方在三时代军事上多提供一些压力，不然倒数第二回合很可能会面临奇迹与军备必须舍弃一的情况。此外，也需要有适当放弃部分卡牌提前抓取三时代奇迹的意识。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;好莱坞&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
当今环境下存在双方和平对跑的情况，常见于一局游戏中有II时代领袖（莎翁达尔文巴赫）以及III时代（弗莱明黛德丽卓别林），其中以II时代为主要导向。如果说II时代这三个跑分领袖都在，那么这局其实是非常适合和平的，暴力和内政风险都会有点高。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;因此，在进III时代的时候，要是双方都摆开了跑分的架势（图书馆及剧院系建筑），那么与红十字类似的，好莱坞将是一张绝对意义的战略卡牌，可以说谁拿到了好莱坞谁就赢得了这场游戏。红十字的布局是存粮食，而好莱坞的布局也分为两步：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一是尽量不拿取II、III时代其它奇迹，为III时代抢好莱坞留下白点空间。这其实和城建跑分是相辅相成的：一般城建造的多奇迹自然就拿得少。而对于III时代奇迹，因为一般其它的奇迹只有20-30分，而好莱坞是可以达到40-70分的，所以说如果为了贪一个互联网将好莱坞1点区送给了对面，那无疑是买椟还珠了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;二是尽量减少过牌，这一点相对模糊，因为很多时候一些牌是不得不拿的。但是呢，我的意思就是心中需要有这个意识——比如牌库中只剩下10张牌了，好莱坞还没有出现，那么这时候恐怕没有什么卡牌的价值是可以高过好莱坞的，甚至如果1点区有高质量黄牌可能都要忍住不拿，因为要是我方拿到了好莱坞那么最后实际上也不差这么几矿。双方等好莱坞的情况有点像小时候玩过的一个积木游戏，两边各从积木塔中抽出一根积木，最后谁要是让积木塔倒塌了就算输了。因此玩家需要尽可能地减少扰动，即减少游戏的过牌速度。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但相信不少玩家都会认为，对跑局这种将一场多方面博弈的游戏解构成“抽积木”的游戏有点过于无趣了，所以与前面在【姬路城】等篇章提到的在军事局保留跑分潜力的观点相似，我们在跑分局一定要留有军事潜力。但需要注意的是，军事潜力不意味着是实打实的军力，更多的是一种威慑效果。比如我们是后手玩家，通过抓取飞机并亮出阵型，让对手认为我们有下回合宣战他的可能性，那么他的拿牌就会受到限制，比如被迫3点抢了一张军事积累。此外，也可以通过控制控制过时代的方式来迫使对方打出一些兵种科技牌，因为一般情况下对跑局双方都不会出兵种，只是留在手里。而如果过时代的话，对方的II时代兵种就会全部弃置，那要是面临宣战的话就可能只能造几个小兵了，所以对方迫于压力可能会打出炮或者马，那么这额外的科技花费就可能会阻碍对手抓牌或者是打出关键科技牌比如民主共产等政体。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;一个比较残酷的事实是，对跑局即使被宣战了，一般跑分方建造好莱坞的分数收益都是比军备要高的，比如文战抢20分，但是好莱坞50分这种。但不论如何，军事和文化的平衡是玩家每步行动时需要考虑的，太过单纯的选择某一路线很容易误入歧途并且容易被对手反制。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h4&gt;更新 [2021.11.29]&lt;/h4&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
胜率选取率双高，看来确实有些影响平衡。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;国际红十字会&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
最特殊的三时代奇迹，花费粮食并且可以合作建造。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;先说粮食，这就意味着在建造红十字的同时玩家还可以建造城建/军队，甚至留足矿物在完成红十字后再建造一个奇迹，因此是极具战略意义的，可以在游戏开始时就稍微布局。这个“稍微”的意思就是，可以在粮食方面做文章，比如3灌溉、2选种、或者时机合适灌溉升机农；同时分析对手面板，要是手中事件有丰收（根据粮食产量获得粮食）或者繁荣（根据笑脸获得粮食）可以选在在合适情况下塞入；此外邮轮的价值会略有降低，因为一般不会出现足量粮食。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;其次的合作建造，在4人局会体现的非常明显，在二人局则是单纯的此消彼长了，一般是尽可能不让对手蹭到步数，如果实在没有办法，让对手建造一步也是可以接受的。因为上文说了III时代奇迹的平均收益是20分，要是可以一人建造5步那就是30+殖民地数量的分数，收益非常可观；要是对手建造一步，那就是18+殖民地，同时对手会额外花费一个白点，而我方存在节约白点的可能性，也算是符合期望。为了达到这个效果，一方面可以提前准备好砖石或者建筑学（我个人比较喜欢砖石），另一方面粮食尽量精算，控到3的倍数，如果不是非常关键的情况下可以考虑不拉人，根据局势要是腐败一次也是可以接受的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;但是话说回来，布局终究是布局，具体奇迹在什么时候出、出的位置我们都是不得而知的。因此我的观点还是“稍微”布局，即心中一直会提防这个奇迹的出现，同时对于事件方面较为敏感，但还是尽量为自己留下其他选择的空间，不要为一个未知的奇迹打乱了自己的原本节奏。
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;互联网&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
曾被戏称破网，但事实上在城建崛起的今天，互联网上限是非常高的，加上笔者作为互联网相关专业学生，那必须得给互联网一个面子。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
互联网的两大特点为：5步建造，14矿花费。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
5步建造意味着这个奇迹一般需要与工程学等蓝科配合，并且23432的诡异步数和工天配合得不是很好。但另一方面14矿的花费对于对矿物较为敏感的3铁板子是一个福音，也与互联网主打的城建路线相挂钩。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
至于期望收益，如果双方都是图书馆剧院的板子，那么当一方选择好莱坞后另一方可以勉强用互联网作为替代品。此外，互联网与多媒体配合最佳，3个多媒体也有20分左右收益，至于上限比如席德梅尔计算机、顾拜旦体育，那就更不用说了。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;帝国大厦&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
帝国大厦的两个特点是：单向奇迹（往往一方是21-28分而另一方是7-14分）、一步建造。这个奇迹会提升内政的收益，尤其是粮矿。此外，III时代工天与之绝配，12矿一步建造节奏非常舒适。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;首次太空飞行&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
从我的对局实际体验来说，首飞上20分往往都还是有些门槛的，因为对跑局科技都用在刀刃上，其实并没有太多花里胡哨的科技要出。但如果是科技巨擘，26分似乎也是个比较常见的数字，因此上下限差别还是比较大的。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;联合国&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
2步建造是联合国的一大优点，一般来说奇迹本身收益是12-16分，配合的事件牌其实我认为期望差不多也就是2-4分。因为一般情况下玩家优势影响也不会太多，顶多就是同一张高分影响牌第一轮触发联合国第二轮再塞牌这样凑合凑合。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;快餐连锁店&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
稳定在18-23分的奇迹，朴实无华。不能说强，但是到手的分数能不要吗？&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;曼哈顿计划&lt;/h3&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
曼哈顿在III时代奇迹中属于比较特殊的存在，13军力容易联想到军事相关的对局。主要用法如下：&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;1.军事威慑。拿而不造，提高军事潜力阻碍对手的进攻计划。理论来说参与了一次III时代战争的曼哈顿就是至少26分了，但考虑机会成本的话一般是造兵的收益更好。所以更适合的是己方人口较为短缺、缺少合适兵种或阵型、红点受限等情况。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;2.科技刷分。配合盖茨或者席德梅尔的大内政流，可以做出15-20分的分数。如果顺便参与了战争那收益就超过40分了。&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;&amp;lt;p align=&quot;justify&quot;&amp;gt;
删删改改这也是第三版了，不少观点可能在对局中想到了但是真正提笔的时候又忘到九霄云外了，为了更好的整理思路并记录思路变化，我决定以网页的形式动态更新，并保留先前的一些观点以供对比。总的来说笔者也是在动态学习的过程之中，如有意见不同或者文章内容错误（错别字、语句歧义）欢迎与我联系。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
领袖奇迹的评价是为了让大家对于TTA游戏机制有一个较为浅显的认识，并不是让大家根据胜率或者排名较为机械地打牌，更重要的是知其所以然。我的希望是新手玩家可以通过我这篇文章大致了解TTA卡牌的决策，而至于真正的技术进步我认为认真对局和赛后复盘都是更有益处的。&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
Peace &amp;amp; love.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
2021年11月30日，第三版，by Ender。
&amp;lt;/p&amp;gt;&lt;/p&gt;
</content:encoded><category>桌游相关</category><author>Ender</author></item><item><title>影评 | 《英雄联盟：双城之战》</title><link>https://www.ender-wiggin.com/posts/review-arcane-season-1/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-arcane-season-1/</guid><description> </description><pubDate>Wed, 17 Nov 2021 22:00: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;而这部《双城之战》，又一次带给了我绝佳的视听体验。我个人认为《平行宇宙》《双城之战》这种动画风格是动画行业的一次革新，正如同当年《玩具总动员》对传统动画领域的冲击，虽然我不太清楚这种风格专有名词到底是什么。如果让我描述一下，大概就是在接近3D的框架下保留了笔刷的质感，用相对概括性的笔触勾勒人物与场景，同时注重场景的氛围渲染，有点像那些艺术画集的概念稿风格。这种处理方式在我看来优点有三：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在3D工业化的基础上增加了艺术性，尤其是对于绘画爱好者来说，看到笔刷的痕迹，都有点手痒想自己动动手了哈哈。比如这部剧集有一集议员绘画油画，鲜艳的色彩配上动态的液体质感，看得我仿佛嗅到了颜料的芳香，赶紧大口吃了几口饭。这一幕的处理，我认为和《平行宇宙》喷漆那幕异曲同工，但是由于油画风格本身与本作更吻合，所以看起来更加协调。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;轻易做到真实电影做不到的剪辑手法与场景调度。且不论作品中绝佳的转场设计，光是各种蒙太奇，都已经令人啧啧称奇了。再比如各种光怪陆离的打光风格和场景渲染，还有为所欲为的视角切换与画面调度，这些都是一般电影非常难做到的事情。如果说2D和3D动画让不可能的场景变为可能，那么这种风格则让承载一个个场景的场景调度从不可能变为可能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打通漫画与动画的边界。《平行宇宙》在这一点上做得更好，因为其漫画风格以及各种拟声词具现化简直就是将漫画搬上荧幕。但我认为这部作品则是在更偏向影视化层面做出努力，比如金克斯的各种画面闪回和涂鸦风格，我眼前几乎都可以编译出漫画的分镜，这种观影体验确实是非同凡响的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此外还有优质配乐，这点也就不提了。&lt;/p&gt;
&lt;p&gt;对于我来说英雄联盟虽然从没玩过，但也确实是渗透于我的生活，现在回想起来也有不少记忆片段与之有关，比如小时候学画画的老师聊天时说他觉得皮城女警很美，初中一个好朋友强烈建议我玩英雄联盟然后我鸽他鸽到现在，高中听刚认识的室友介绍新出的英雄艾克让我对这个IP的世界观第一次产生了兴趣，当然还有很多的赛事...不得不说这个剧集确实让我有种了解完整世界观的冲动，说不定还会因此第一次启动一局英雄联盟呢？&lt;/p&gt;
&lt;p&gt;谁知道呢。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>影评 | 《梅艳芳》：The Show Must Go On</title><link>https://www.ender-wiggin.com/posts/review-anita/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-anita/</guid><description> </description><pubDate>Thu, 04 Feb 2021 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;影片中两次出现 “show must go on&quot; 的时候我都突然泪目了，可能是因为在那一刹脑中响起了同名音乐——梅艳芳与弗雷迪，同样的负病演出，同样的空谷绝响。影片确实有不少动人点，笔者作为一个以前几乎不怎么了解梅艳芳其人的观众，在观影后收获还是不少的，也确实能将以前家中常播的歌与歌手对号入座了。&lt;/p&gt;
&lt;p&gt;但是作为一部传记电影，或者说是任何一部真实事件改变的电影，非常容易混淆的一点就是，我们究竟是感动于这部电影，还是传主或者事件本身。从观影时的体悟，加上观影前后对于梅艳芳本人的一些资料阅读，我感觉梅艳芳的生平不论是通过纪录片、传记还是传记小说的形式都可以带来很好的反馈，因为本身故事确实太精彩了，根本不需要什么额外的加工处理。而作为一部电影，需要做的是对诸多事件进行筛选剪辑的艺术加工，再通过合适的艺术形式作为输出。从这一点来看的话，其实电影确实是有一些不足的。&lt;/p&gt;
&lt;p&gt;首先从艺术形式上来说，《梅艳芳》混合了多种电影类型但却似乎都没有利用完全每一种类型的特点。首先作为演员翻拍的传记电影却穿插了大量真实影视资料，演员形象与真实形象的反复切换会带来观影上的一定违和感。当然，我个人并不排斥这种仿纪录片的处理，但是疑惑的是为什么在这种形式之上不更进一步比如引入画外音呢？纵观全篇的，其实不少镜头都是可以忽略，甚至不少场景都是可以直接用画外音提供背景描述的。这样形式上更贴近传统纪录片，但保留关键情节的演员演绎，感觉是可以做出《他们已不在变老》那种史料丰富、叙事简练且情感真挚的综合效果的。&lt;/p&gt;
&lt;p&gt;可能也就是由于艺术形式的定位不明确，导致电影中人物事件的选择与处理上显得有点繁杂而缺少中心，看似包罗万象却在诸多场景都感觉略欠火候。一方面从镜头编排上来说，比如最后那首《夕阳之歌》，歌曲内容、故事情节、影片结构都恰好来到”夕阳“这个时间点，最后呈现的效果本应该相得益彰，但是在镜头上却莫名穿插了梅艳芳个人的回忆镜头以及几个主要配角的对话闪回，仿佛给本来已经完美的演唱会视频画蛇添足。以上这种情况比比皆是，诸多突然插入的细节镜头在给电影做加法，会影响作品本身的连贯性与叙事简洁性。另一方面从叙事选材上来说，详实生平、音乐呈现、情感表达这三者受限于作品篇幅一般都是要至少舍弃一，或者说详略要有明确分配的。比如完全同题材的《波西米亚狂想曲》，这部作品在时间选择上切割了童年与最后1985年后的时光，在人物描写上很大程度的略写了其它配角，而着重呈现音乐的存粹美感，最后甚至浓墨重彩复原慈善演唱会的几乎每一首歌曲，力求在音乐上表现得淋漓尽致。再反观《梅艳芳》，情节确实有些太“满”了，比如童年到成名的处理，完全可以效仿《霸王别姬》在起点与终点这两个时间节点重笔描写，既能体现童年之艰也能为后续重要情节留足空间，“酒吧驻唱”、“声带问题”、“参加比赛”这些事件都是可以通过画外音几笔带过甚至只字不提；再比如清迈逃亡，整段情节其实大胆删去也未尝不可，如果说这段经历对于梅艳芳具有承上启下的作用那也没必要加上几段黑帮追杀戏，仿佛给抒情电影突然增加一点爆米花元素。&lt;/p&gt;
&lt;p&gt;最后提一句情节真实性这个传记电影永远无法避开的话题。我对于这种作品的倾向是，尽量忠实于真实事件，因为我观影的初衷就是来自传主本身，所以更希望我观影后的感动是来着真实之美而非工业制造的人工艺术品。如果一定要有改编或者虚构情节，那么尽量是在情理之中虚构，而非篡改真实性。比方这部作品中对于梅艳芳童年的简化处理，以及爱情的情节删改，还是稍微有些可惜。&lt;/p&gt;
&lt;p&gt;总的来说电影完成度还是不错的，作为了解梅艳芳生平的介绍作品可圈可点，后半段的整体观感相较于前部也是出色不少。只能说确实有些遗憾，因为梅艳芳的生平值得更好的处理方式。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item><item><title>漫评 | 《炎拳》</title><link>https://www.ender-wiggin.com/posts/review-fire-punch/</link><guid isPermaLink="true">https://www.ender-wiggin.com/posts/review-fire-punch/</guid><description> </description><pubDate>Mon, 26 Oct 2020 19:14:48 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;THIS REVIEW CONTAINS SPOILERS 剧透预警&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;《炎拳》是一部慕名已久的漫画作品，昨晚也是心血来潮花连续了几个小时看完了这个故事。开头的角色设定无疑是很有意思的：一位“祝福者”被永不熄灭的火焰所缠身，在极端痛苦之余也是获得了非比寻常的能力。除开外在所体现地“祝福”与“诅咒”并存的矛盾基调，主角随经过多年的肉体成长缺并未获得心智成长的“灵”与“肉”的矛盾对于我而言也是个很有趣的立足点，也使这个故事并非一般套路化的复仇故事。&lt;/p&gt;
&lt;p&gt;我个人认为故事一共有2个重要节点：&lt;/p&gt;
&lt;p&gt;1.遇到导演。让这个故事从一个常规复仇故事上升到了精神与艺术的层面。正如导演所说自己生活数百年不自杀是因为电影的存在，这个角色的出现一方面将叙述方式及画面呈现带有电影化的色彩，另一方面为这个冰川世纪附上了一层人文情怀。导演所装出的疯疯癫癫与他真实年龄及生活经验形成巨大反差，更让我觉得这个角色深不可测。&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;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/%E7%82%8E%E6%8B%B3-1-8d72ee4e20e84e63be593f03bc40795b.jpg&quot; alt=&quot;如同电影般的运镜&quot; /&gt;&lt;/p&gt;
&lt;p&gt;2.第二次遇到德玛。诚然，第一次遇到德玛已经让我对于这个故事充满惊讶，因为当年嘴角上扬的经典反派形象，如今变成了一个似乎精神有些失常的落寞老者。但作者叙述诡谲的地方在于，第一次在政治背景之下并未有过双方思想的交流，更像是一次被迫进行的政治会晤。此后，虽然读者们始终留有对于德玛转变原因的困惑，但是在大家齐心构建新的社会、新的意识形态的时候，读者们与阿格尼一样几乎忘记这个据说已死去的老者。当我们都认为德玛只是阿格尼为了让自己生命存在意义所刻意设定的目标时，一则德玛还活着的消息突然打破了这个故事的常规发展。正如之前阿格尼教所信奉的生存是第一要义，这次德玛的出现对生存这个命题做了更深的诠释，探讨的是生存的意义。&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;img src=&quot;https://ender-picgo.oss-cn-shenzhen.aliyuncs.com/img/%E7%82%8E%E6%8B%B3-2-588a2b29cba84e88af3ee63fd68a375c.jpg&quot; alt=&quot;微笑、平静、回忆...&quot; /&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;突然想起《三块广告牌》，也是一个披着复仇与拯救外衣的故事，最后随着《buckskin stallion blues》的悠扬歌声驶向远方。&lt;/p&gt;
&lt;p&gt;哪有那么多符号与立意，这不过是一段属于阿格尼自己的人生。&lt;/p&gt;
</content:encoded><category>文学影视</category><author>Ender</author></item></channel></rss>